#!/usr/bin/perl -w
use strict;
use Text::Wrap;

#
# Partimage Is Not Ghost / rc.ping
#
# Copyright(c) 2005-2006-2007-2008-2009-2010 EFFITEK, GNU Licence
# Home page: PING (Partimage Is Not Ghost) http://ping.windowsdream.com/
#
# This script is intended to run on a PING Linux-live image. As for PING,
# Linux, GNU/GPL tools, etc. refer to specific licences.
# This script (rc.ping) has been written by Natan Scemama (the Author),
# from Windowsdream.com. Email: natan@windowsdream.com .
#
# * Use it at your own risk. No warranty, no damage accountability.
# * Please, refer any bug / feature request to the Author.
#

$Text::Wrap::columns = 72;

my $MYSELF = "rc.ping";
my $VERSION = "3.00.04";
my $VERSION_DATE = "2010-04-16";
my $VERSION_LINUX = `uname -s -n -r -m -p -i -o`;
$VERSION_LINUX =~s/\s*$//;
my $PING_DIR = "/opt/PING";

my %P = ();
my %Target_Device = ();

my $TIP_SLEEP = 2;
my $PARTIMAGE_SLEEP = 2;
my $README_SLEEP = 7;
my $FAILED_SLEEP = 5;
my $DD_SPLIT = 660600000;
my $PARTIMAGE_SPLIT = 630;
my $ZSPLIT_SPLIT = 630;

my $NTFS_MNT = "mount -t ntfs-3g";
my $NTFS_MNT_TYPE = "ntfs-3g";

my $HELP_DELL = "";
my $GLOBAL_ACTION = "";


# Preliminaries
#
unless(-d "/tmp")
{
    mkdir("/tmp", 1777);
}
if(-e "/tmp/x.log.gz")
{
    unlink("/tmp/x.log.gz");
}
if(-e "/tmp/x.log")
{
    system("gzip -9 /tmp/x.log");
}

LOG("---------------------------------------------------\n");
LOG("* Starting [$MYSELF] [$VERSION] [$VERSION_DATE]\n");
LOG("* Linux version: [$VERSION_LINUX]\n");
LOG("*\n");


# Get info from a config file, if any.
#
LOG("* Looking for a possible [/etc/ping.conf] file...\n");
LOG("  or for arguments passed to the kernel at boot time\n");

my(@CONFIG_FIELDS) = ("IP", "Netmask", "Gateway", "DHCP_Timeout",
		      "Server", "Share", "User", "Passwd", "Directory",
		      "Image_To_Restore", "Minimize_Before_Storing",
		      "Repart", "After_Completion", "Replace_BIOS",
		      "CIFS_Preferred", "Zsplit_Preferred",
		      "Tarball_Preferred", "No_Shell",
		      "AUTO", "Cmd_1", "Cmd_2", "Cmd_3", "Restore_Only",
		      "Compression_Type", "NFS_Preferred", "FTP_Preferred",
		      "Its_HDA_Stupid", "Force_Dirty_NTFS_Mount",
		      "Extend_Parts_Whenever_Possible", "Dont_Warn_Me",
		      "Store_MD5", "New_Image_Name", "Parts_To_Backup",
		      "Already_Existing_Image", "Verbosity",
		      "Unique_Message");

{
    my $flag = 0;

    if(-e "/etc/ping.conf")
    {
	LOG("  Found a [/etc/ping.conf] file.\n");

	$flag = Get_Parameters("/etc/ping.conf", \@CONFIG_FIELDS, \%P);

	if(-e "/proc/cmdline")
	{
	    LOG("  Parsing the [/proc/cmdline] file for arguments possibly\n");
	    LOG("    passed to the kernel\n");

	    open(DB, "/proc/cmdline");
	    while(<DB>)
	    {
		s/^\s*//;
		s/\s*$//;
		while(m/\"([^\"]+)\"/)
		{
		    my $tmp = $1;
		    $tmp =~s/\s/£££/g;
		    s/\"([^\"]+)\"/$tmp/;
		}
		my(@Params) = split(/ /, $_);

		foreach my $Param (@Params)
		{
		    $Param =~s/£££/ /g;
		    my(@fields) = split(/=/, $Param);
		    if($#fields > 0)
		    {
			my $Key = $fields[0];
			my $Value = join("=", @fields[1..$#fields]);

			foreach(@CONFIG_FIELDS)
			{
			    if(lc($_) eq lc($Key))
			    {
				$P{$_} = $Value;
				$flag = 1;
			    }
			}
		    }
		}
	    }
	    close(DB);
	}

	foreach(@CONFIG_FIELDS)
	{
	    if(defined($P{$_}))
	    {
		LOG("  Param: [$_] = [".((m/^Passwd$/) ? "xxx":$P{$_})."]\n");
	    }
	}
    }

    unless($flag)
    {
	LOG("  No preconfiguration was found. Not a problem.\n\n");
	LOG("  TIP: by adding a ping.conf to the linux image,\n");
	LOG("       or by passing'key=value' arguments to the\n");
	LOG("       kernel ((iso|pxe)linux.cfg), you could avoid\n");
	LOG("       having to type again and again the same net/smb\n");
	LOG("       settings. Visit the forum for more information.\n\n");
	sleep($TIP_SLEEP);
    }
}


# Feed now all possible -- well, most -- /dev devices related to disk-storage.
# Source: http://www.lanana.org/docs/device-list/devices.txt
#
my(@ALPH) = (
    'a','b','c','d','e','f','g','h','i','j','k','l','m',
    'n','o','p','q','r','s','t','u','v','w','x','y','z');

my(%DEV_NODES) = ();

Feed_Dev_Nodes();


# The source.
#
my $SRC = "";


# Installs every available module (some drivers cannot be statically
# linked to the kernel).
#
LOG("* Scanning for modules to install...\n");
{
    my(@Modules) = `find /lib/modules/\`uname -r\` |grep \\.ko\$`;
    foreach(@Modules)
    {
	s/\s*$//;
	Exec_Log("insmod $_", 2, "", 0);
    }
}


# Try to mount the CDRom and find there an image.
#
LOG("* Looking for a possible CD/DVD...\n");

my $CD_Dev = '';
{
    my $nb = 0;
    foreach my $d ("hda", "hdb", "hdc", "hdd", "sda", "sdb", "sdc",
		   "sdd", "sde", "sdf", "sr0")
    {
	Umount("/mnt/cdrom");
        system("mount /dev/$d /mnt/cdrom >/dev/null 2>&1");
        my $nb = `df|grep cdrom|wc -l`;
        if($nb > 0)
	{
            LOG("  Found and mounted a CDRom\n");
            opendir(DIR, "/mnt/cdrom");
            my(@Files) = readdir(DIR);
            closedir(DIR);
            foreach my $F (@Files)
	    {
		if($F =~/[hs]d[abcdef]$/
		   || $F =~/(cciss|rd|ida|mapper)\/c\d{1}d\d{1,2}$/
		   || $F =~/askme_[abcdef]$/i)
		{
                    LOG("  Found a [/mnt/cdrom/$F] file\n");
		    $CD_Dev = $d;
                    $SRC = "/mnt/cdrom";
		    LOG("  Linking [/dev/cdrom] to [/dev/$d]\n");
		    if(-e "/dev/cdrom")
		    {
			LOG("    Deleting an old [/dev/cdrom]\n");
			unlink("/dev/cdrom");
		    }
		    Exec_Log("ln -sf /dev/$d /dev/cdrom", 4);
                }
            }
            sleep(3);   # Blink & start up.
            last;
        }
    }
    unless($nb)
    {
        LOG("  No CDRom device or media was found.\n");
    }
}


# If addon*.tar.gz/.zip files have been added to the cdrom,
# they can be untarred to the root of the system now.
#
LOG("* Looking for any addon in the CD/DVD...\n");
{
    opendir(DIR, "/mnt/cdrom");
    my(@files) = readdir(DIR);
    closedir(DIR);
    foreach(@files)
    {
	if(m/^addon(\-|_).*\.tar\.gz/i || m/^addon(\-|_).*\.zip/i)
	{
	    LOG("  Addon found! [$_]\n");
	    Exec_Log("cd /; ".Unzip("/mnt/cdrom/$_"), 4);
	}
    }
}


# Give a last tip if a CD/DVD with an image on it has been found.
#
if($SRC =~/cdrom/i)
{
    LOG("  TIP: by setting an AUTO parameter to Y,\n");
    LOG("       you can avoid have the restoration process go\n");
    LOG("       with strictly no user interaction. Go to the\n");
    LOG("       forum for more information or refer to the HOWTO.\n\n");
    sleep($TIP_SLEEP);
}


# Defaults some ping.conf options
#
unless(defined($P{Verbosity}) && $P{Verbosity} =~/^[012]$/)
{
    $P{Verbosity} = 2;
}
unless(defined($P{Unique_Message}))
{
    $P{Unique_Message} = "";
}

if(defined($P{No_Shell}))
{
    if($P{No_Shell} =~/^(y|1)$/i)
    {
	$P{No_Shell} = 1;
    }
    else
    {
	$P{No_Shell} = 0;
    }
}
else
{
    $P{No_Shell} = 0;
}

if(defined($P{Restore_Only}))
{
    if($P{Restore_Only} =~/^(y|1)$/i)
    {
	$P{Restore_Only} = 1;
    }
    else
    {
	$P{Restore_Only} = 0;
    }
}
else
{
    $P{Restore_Only} = 0;
}

if(defined($P{Dont_Warn_Me}))
{
    if($P{Dont_Warn_Me} =~/^(y|1)$/i)
    {
	$P{Dont_Warn_Me} = 1;
    }
    else
    {
	$P{Dont_Warn_Me} = 0;
    }
}
else
{
    $P{Dont_Warn_Me} = 0;
}

if(! defined($P{DHCP_Timeout}) || $P{DHCP_Timeout} =~/\D/)
{
    $P{DHCP_Timeout} = 0;
}

unless(defined($P{Image_To_Restore}))
{
    $P{Image_To_Restore} = "";
}


# Give the user a chance to avoid this script.
#
system("clear");
print "\n$VERSION_LINUX\n";
print "\n***       PING (Partimage Is Not Ghost) -- $VERSION $VERSION_DATE        ***\n";
print "***           Get doc and latest release on PING website             ***\n";
print "***                  http://ping.windowsdream.com/                   ***\n";
print "***                                                                  ***\n";
print "***    PING is brought to you by EFFITEK -- http://www.effitek.fr    ***\n";
unless($P{No_Shell})
{
    print "\n";
    print ">> Type [ENTER] to go on with the PING interface, or x to get a shell <<\n";
    print ">> (login as root, no passwd needed). Type h to get basic shell help. <<\n";
}
print "\n>> ";


# If a CD/DVD/USB device has been booted, raise no question if a file
# called 'AUTO' (case-sensitive) is found on the root of the CD/DVD/USB dev.
#
# This method is deprecated, and we ensure backward compatibility here.
#
if($SRC =~/cdrom/ && -e "/mnt/cdrom/AUTO")
{
    LOG("* /cdrom/AUTO file found! No exit possibility given to the user.\n");
    $P{AUTO} = 1;
}

unless(defined($P{AUTO}) && $P{AUTO})
{
    my $Grab = <STDIN>;
    if($Grab =~/^h/i && ! $P{No_Shell})
    {
	Print_Shell_Help();
	print "\n>> ";
	$Grab = <STDIN>;
    }
    if($Grab =~/^skip/i)
    {
	LOG("* Skip keyword! Erasing all data passed from kernel params/ping.conf\n");
	%P = ();
    }
    if($Grab =~/^x/i && ! $P{No_Shell})
    {
	exit;
    }
}


# Check for needed binaries in the OS.
#
LOG("* Check for needed binaries...\n");

foreach('fdisk', 'sfdisk', 'dialog', 'sleep', 'kill', 'cut', 'sed',
	'mount', 'grep', 'curlftpfs', 'partimage', 'zsplit', 'chntpw',
	'dd', 'echo', 'cmospwd', 'ntfsresize', 'swapoff', 'swapon',
	'df', 'parted', 'resize2fs', 'uname', 'find', 'wc', 'rm',
	'chmod', 'ps', 'tail', 'route', 'ifconfig', 'free', 'ntfslabel',
	'vol_id', 'star', 'tar', 'gzip', 'bzip2', '7za', 'unzip', 'md5sum',
	'tail')
{
    unless(-e $_ || Is_In_Path($_))
    {
	LOG("    !!! [$_] not found\n");
	LOG("        Your image is probably corrupt. Aborting now.\n");
	sleep($FAILED_SLEEP);
	Quit();
    }
}

LOG("  OK, everybody's here.\n");


# Try to find all HDD devices / will be useful or not, according
# to the user's choice, but it's (almost) costless to explore.
#
LOG("* Exploring local disk drives\n");

my @Dev = ();
{
    my @AllDevices = ('hda', 'hdb', 'sda', 'sdb', 'hdc', 'hdd', 'sdc',
		      'sdd', 'sde', 'sdf');
    #
    # Generate all possible cciss hard disk device names and push them into AllDevices
    # (Compaq HP SmartArray). Same for RAID devices (ida and rd).
    #
    for(my $i = 0; $i <= 7; $i++) 
    {
	for(my $j = 0; $j <= 15; $j++)
	{
	    push(@AllDevices, "cciss/c".$i."d".$j);
	    push(@AllDevices, "ida/c".$i."d".$j);
	    push(@AllDevices, "rd/c".$i."d".$j);
	}
    }

    foreach(@AllDevices)
    {
	# At least thanks to udev, we won't have too many tries!
	#
	if(-e "/dev/$_")
	{
#	    LOG("  - Checking [/dev/$_]...\n");
	    my $out = `echo |fdisk /dev/$_ 2>&1`;
	    $out =~s/^\s*//;
	    $out =~s/\s*$//;
#	    LOG("    [$out]\n");
	    #
	    # Either not a partition at all, either is a CDRom = bad cases
	    #
	    if($out !~/^unable to open/i && $out !~/you will not be able to write/i)
	    {
		push(@Dev, $_);
	    }
	} 
    }
}
foreach(@Dev)
{
    LOG("  => HDD device found: [$_]\n");
    sleep(1);
}
if($#Dev < 0)
{
    LOG("  !!! No HDD device could be found. Will do nothing.\n");
    LOG("      This usually means that the current kernel has been\n");
    LOG("      unable to load drivers for your local hard disks to be\n");
    LOG("      mounted. Go to PING Forum and describe your hardware and\n");
    LOG("      the PING release you're using, i.e. [$VERSION] [$VERSION_DATE].\n");
    LOG("      You can also consider recompiling the kernel yourself, in\n");
    LOG("      which case you'll find our .config file on the Download page.\n");
    sleep($FAILED_SLEEP);
    Quit();
}

# And now, exploring each device for parts and parts' types.
# In @Dev, we should have values like hda, hdb, sda, cciss/c0d0, rd/c0d1...
#
# Note concerning cciss/rd/ida: fdisk can send this:
#   Disk /dev/rd/c0d0: 128 heads, 32 sectors, 52069 cylinders
#   Units = cylinders of 4096 * 512 bytes
#
#           Device Boot    Start       End    Blocks   Id  System
#   /dev/rd/c0d0p1             1        26     53232   83  Linux
#   /dev/rd/c0d0p2            27       539   1050624   82  Linux swap
#
LOG("* Exploring each hdd for parts and types\n");

my(@Dev_Rich) = ();
HDD_Discover(\@Dev, \@Dev_Rich);


# Let's recapitulate all our device discovery results...
#
LOG("* Results of device discovery (Dev_Rich)\n");
HDD_Describe(\@Dev_Rich);


# If there's any command to execute before interactivity,
# do so now.
#
LOG("* Any command to execute before the welcome screen ?\n");

if(defined($P{Cmd_1}) && $P{Cmd_1})
{
    LOG("  Yes! [$P{Cmd_1}]");
    my $out = system($P{Cmd_1});
    LOG("  Output: [$out]\n");
}
else
{
    LOG("  No defined command.\n");
}


# Give a warm welcome to the user...
#
if(defined($P{AUTO}) && $P{AUTO})
{
    LOG("* AUTO param set! No warm welcome.\n");
}
else
{
    my $cmd = 'dialog --colors --msgbox "\Zb\Z7Welcome to PING (Partimage Is'
        .' Not Ghost)!\n\n\n'
        .'\ZnThis tool can be used both to backup a Ghost-like image of'
        .' your hard disk and to restore your hard disk from such an image.'
        .' Please, be aware that if you choose to restore your hard disk,'
        .' ALL the data contained on this computer might be '
        .'lost during the restoration. You may choose to abort now, by '
        .'stopping the computer now.\n\n\n" 22 73';
    system($cmd);
}


# Not everyone would like an automatic reboot to occur at the end of the
# operations... ask before, do later.
#
my $After_Completion = '';

if(defined($P{After_Completion}) && $P{After_Completion} =~/reboot/i)
{
    $After_Completion = "Reboot";
}
elsif(defined($P{After_Completion}) && $P{After_Completion} =~/shell/i)
{
    $After_Completion = "Shell";
}
elsif(defined($P{After_Completion}) && $P{After_Completion} =~/shutdown/i)
{
    $After_Completion = "Shutdown";
}

unless($After_Completion)
{
    if(defined($P{AUTO}) && $P{AUTO})
    {
	LOG("* AUTO param set! Won't offer not to reboot.\n");
    }
    else
    {
	LOG("* Ask whether we'll reboot or exit at the end of the job\n");

	my $SH = '';
	unless($P{No_Shell})
	{
	    $SH = '"Get a shell (root)" ""';
	}
	my $cmd = 'dialog --colors --menu "\Zb\Z7PING Partimage Is Not Ghost\n\n'
	    .'\ZnWhen the job is completed, do you want to...\n\n" 15 40 3 '
	    .' '.$SH.' "Reboot the system" ""'
	    .' "Shutdown" "" 2>/tmp/checklist.tmp';
	system($cmd);

	if(-z "/tmp/checklist.tmp")
	{
	    $After_Completion = "Shell";
	    Quit();
	}

	open(DB, "/tmp/checklist.tmp");
	while(<DB>)
	{
	    if(m/shell/i)
	    {
		$After_Completion = "Shell";
		LOG("  Shell!\n");
		last;
	    }
	    elsif(m/shutdown/i)
	    {
		$After_Completion = "Shutdown";
		LOG("  Shutdown!\n");
		last;
	    }
	    elsif(m/reboot/i)
	    {
		$After_Completion = "Reboot";
		LOG("  Reboot!\n");
		last;
	    }
	}
	close(DB);
	`rm -f /tmp/checklist.tmp`;
    }
}

LOG("* After Completion: [$After_Completion]\n");


# As no CD/DVD is involved, the user may choose whether he wants
# to save/restore to a local part or to the network.
#
unless($SRC)
{
    LOG("* Ask whether operations will be local or network\n");

    if(defined($P{Server}) && $P{Server} !~/^\/dev/)
    {
	LOG("  The user has provided a not-dev Server entry param =>Will be network\n");
    }
    elsif(defined($P{Server}) && $P{Server} =~/^\/dev/)
    {
	LOG("  The user has provided a /dev Server entry in params =>"
	    ."  Will be local\n");
	$P{Server} =~s/\s//g;
	LOG("  Server entry: [$P{Server}]\n");
	$SRC = "local";
    }
    else
    {
	LOG("  The user did not provide a Server entry in params => Ask.\n");

	my $cmd = 'dialog --colors --menu "\Zb\Z7PING Partimage Is Not Ghost\n\n'
	    .'\ZnWhere do you want to save/restore your images to/from ?\n\n" 15 40 2 '
	    .' "Network share" "" "Local disk/partition" "" 2>/tmp/checklist.tmp';
	system($cmd);

	if(-z "/tmp/checklist.tmp")
	{
	    Quit();
	}

	open(DB, "/tmp/checklist.tmp");
	while(<DB>)
	{
	    if(m/local/i || m/^l/i)
	    {
		$SRC = "local";
		LOG("  Local!\n");
	    }
	    else
	    {
		LOG("  Network!\n");
	    }
	}
	close(DB);
	`rm -f /tmp/checklist.tmp`;
    }
}


# If there's no CD or CD but no image within the CD and if this is not
# a local backup / restoration, then try the network.
#
unless($SRC)
{
    LOG("\n");
    LOG("* Handling the network connection issue...\n");

    # First, see whether we're already connected to the network.
    # If no, do so. Then, first try DHCP, unless some parameters
    # have been passed to us to fix an IP address.
    #
    LOG("  - Testing the current connection. Do we have an IP ?\n");
    if(! Is_Network_On())
    {
	LOG("    No IP yet.\n");
	if(defined($P{IP}) && $P{IP})
	{
	    LOG("    There are params about the network config. Don't try DHCP.\n");
	}
	else
	{
	    LOG("    No params about the network config. Try DHCP on eth0.\n");
	    Exec_Log("ifconfig eth0 down; ifconfig eth0 up; sleep 2; rm -f"
		     ." /var/run/dhcpcd-eth0.pid; dhcpcd "
		     .($P{DHCP_Timeout} ? "-t $P{DHCP_Timeout}":"")." eth0", 4);

	    if(! Is_Network_On())
	    {
		LOG("    ! Could not connect a eth0 NIC to DHCP.\n");
		LOG("    Trying DHCP on eth1.\n");
		Exec_Log("ifconfig eth1 down; ifconfig eth1 up; sleep 2; rm -f"
			 ." /var/run/dhcpcd-eth1.pid; dhcpcd "
			 .($P{DHCP_Timeout} ? "-t $P{DHCP_Timeout}":"")." eth1", 4);

		if(! Is_Network_On())
		{
		    LOG("    ! Could not connect a eth1 NIC to DHCP.\n");
		    LOG("    We'll have to ask the user.\n");
		}
	    }
	}
    }
    else
    {
	LOG("    We've already got an IP.\n");
    }

    if(! Is_Network_On())
    {
	LOG("    Still no IP yet. Trying to connect without DHCP.\n");

	my $Tries = 1;

	while($Tries)
	{
	    # Use preconfig parameters if defined.
	    # Set them to nothing if they have been tried unsuccessfully once.
	    #
	    unless($Tries == 1)
	    {
		foreach("IP", "Netmask", "Gateway")
		{
		    $P{$_} = "";
		}
	    }

	    unless(defined($P{IP}) && $P{IP})
	    {
		my $cmd = 'dialog --inputbox "Enter your IP address '
		    .'(eg. 192.168.0.10)" 8 51 2>/tmp/BLA';
		system($cmd);
		if(-e "/tmp/BLA")
		{
		    open(DB, "/tmp/BLA");
		    while(<DB>)
		    {
			$P{IP} .= $_;
		    }
		    close(DB);
		    unlink("/tmp/BLA");
		}
	    }

	    unless(defined($P{Netmask}) && $P{Netmask})
	    {
		my $cmd = 'dialog --inputbox "Enter your netmask '
		    .'(eg. 255.255.255.0)" 8 51 "255.255.255.0" 2>/tmp/BLA';
		system($cmd);
		if(-e "/tmp/BLA")
		{
		    open(DB, "/tmp/BLA");
		    while(<DB>)
		    {
			$P{Netmask} .= $_;
		    }
		    close(DB);
		    unlink("/tmp/BLA");
		}
	    }

	    unless(defined($P{Gateway}) && $P{Gateway})
	    {
		my $cmd = 'dialog --inputbox "Enter your gateway '
		    .'(eg. 192.168.0.1)" 8 51 2>/tmp/BLA';
		system($cmd);
		if(-e "/tmp/BLA")
		{
		    open(DB, "/tmp/BLA");
		    while(<DB>)
		    {
			$P{Gateway} .= $_;
		    }
		    close(DB);
		    unlink("/tmp/BLA");
		}
	    }

	    foreach("IP", "Netmask", "Gateway")
	    {
		$P{$_} =~s/^\s*//;
		$P{$_} =~s/\s*$//;
		$P{$_} =~s/[^0-9.]//g;
	    }

	    LOG("    Connecting to the network...\n");

	    LOG("      Trying eth0 first\n");

	    my $out =
		Exec_Log("ifconfig eth0 down;"
			 ." ifconfig eth0 $P{IP} netmask $P{Netmask} up;"
			 ." route add default gw $P{Gateway};"
			 ." ifconfig eth0", 6);

	    ++ $Tries;

	    if($out =~/$P{IP}/)
	    {
		$Tries = 0;
		LOG("      We're connected !\n");
	    }

	    unless($Tries == 0)
	    {
		LOG("      Trying eth1 now\n");

		my $out =
		    Exec_Log("ifconfig eth1 down;"
			     ." ifconfig eth1 $P{IP} netmask $P{Netmask} up;"
			     ." route add default gw $P{Gateway};"
			     ." ifconfig eth1", 6);

		++ $Tries;

		if($out =~/$P{IP}/)
		{
		    $Tries = 0;
		    LOG("      We're connected !\n");
		}
	    }

	    if($Tries)
	    {
		LOG("\n    Command output: [$out]\n");
		LOG("\n    BAD network parameters or device/driver pb. TRY AGAIN.\n");
		sleep(3);
	    }
	}
    }
    else
    {
	LOG("    We're connected !\n");
    }

    $SRC = "/mnt/smbfs";
}


# The user might have already mapped the /mnt/smbfs directory through
# addon scripts or a Cmd_1 parameter (for example, to use sshfs).
# We must detect this case and not interfere if the dir is mapped.
#
my $Is_Smbfs_Mounted = 0;

if($SRC =~/smbfs$/)
{
    LOG("* Make sure the [/mnt/smbfs] directory has not been already mounted\n");
    if(Is_Mounted("/mnt/smbfs"))
    {
	++ $Is_Smbfs_Mounted;
	LOG("  Seems mounted!\n");
    }
    else
    {
	LOG("  Doesn't seem to be mounted.\n");
    }
}


# If network will be used, we should know whether we'll prefer SMBFS,
# CIFS, or even forget samba and use NFS.
#
if($SRC =~/smbfs$/ && ! $Is_Smbfs_Mounted)
{
    LOG("* Will we rather use SMBFS, CIFS, NFS or FTP ?\n");

    if(defined($P{NFS_Preferred}))
    {
	LOG("  There's a preconfig NFS_Preferred entry; worth [$P{NFS_Preferred}]\n");
	if($P{NFS_Preferred} =~/^(y|1)$/i)
	{
	    $P{NFS_Preferred} = 1;
	}
	else
	{
	    $P{NFS_Preferred} = 0;
	}
    }
    elsif(defined($P{FTP_Preferred}))
    {
	LOG("  There's a preconfig FTP_Preferred entry; worth [$P{FTP_Preferred}]\n");
	if($P{FTP_Preferred} =~/^(y|1)$/i)
	{
	    $P{FTP_Preferred} = 1;
	}
	else
	{
	    $P{FTP_Preferred} = 0;
	}
    }
    elsif(defined($P{CIFS_Preferred}))
    {
	LOG("  There's a preconfig CIFS_Preferred entry; worth [$P{CIFS_Preferred}]\n");
	if($P{CIFS_Preferred} =~/^(y|1)$/i)
	{
	    $P{CIFS_Preferred} = 1;
	}
	else
	{
	    $P{CIFS_Preferred} = 0;
	}
    }
    else
    {
	LOG("  There's no preconfig predefined info. Asking.\n");
	
	my $cmd = 'dialog --colors --menu "\Zb\ZnWill we map a network share'
	    .' with Samba CIFS, NFS, or FTP (MS Share: use CIFS) ?'
	    .'\n\n" 12 52 4 "CIFS" "" "NFS" ""'
	    .' "FTP (NO image creation)" "" 2>/tmp/checklist.tmp';
	system($cmd);

	if(-z "/tmp/checklist.tmp")
	{
	    Quit();
	}

	foreach("CIFS", "NFS", "FTP")
	{
	    $P{$_."_Preferred"} = 0;
	}
	open(DB, "/tmp/checklist.tmp");
	while(<DB>)
	{
	    if(m/CIFS/i) { $P{CIFS_Preferred} = 1; last; }
	    if(m/NFS/i)  { $P{NFS_Preferred} = 1;  last; }
	    if(m/FTP/i)  { $P{FTP_Preferred} = 1;  last; }
	}
	close(DB);
	unlink("/tmp/checklist.tmp");

	foreach("CIFS", "NFS", "FTP")
	{
	    LOG("  =>".$_."_Preferred: [".$P{$_."_Preferred"}."]\n");
	}
    }
}


# Mount partimages' network share.
#
if($SRC =~/smbfs$/)
{
    if($Is_Smbfs_Mounted)
    {
	LOG("* /mnt/smbfs is mounted but we still need to know the Directory\n");

	my $Tries = 1;

	while($Tries)
	{
	    if(defined($P{Directory}) && $P{Directory})
	    {
		LOG("  Directory parameter: [$P{Directory}]\n");
	    }
	    else
	    {
		LOG("  No Directory parameter; let's ask for it\n");

		my $cmd = 'dialog --inputbox "Enter a possible subdirectory '
		    .'containing your data '
		    .'(eg. \mydir\Partimage)" 8 51 2>/tmp/BLA';
		system($cmd);
		$P{Directory} = '';
		if(-e "/tmp/BLA")
		{
		    open(DB, "/tmp/BLA");
		    while(<DB>)
		    {
			$P{Directory} .= $_;
		    }
		    close(DB);
		    unlink("/tmp/BLA");
		}
		$P{Directory} =~s/^\s*//;
		$P{Directory} =~s/\s*$//;
		$P{Directory} =~s/\\/\//g;
		$P{Directory} =~s/^\/*//;

		LOG("  Got: [$P{Directory}]\n");
	    }

	    if(Is_Mounted("/mnt/smbfs"))
	    {
		LOG("* Seems mounted.\n");
		$Tries = 0;

		# Many users forget to unzip Partimage.zip into their share.
		# We'll create the directories for them, and let them know.
		#
		unless(-d "/mnt/smbfs/$P{Directory}/Create_New_Image")
		{
		    LOG("/!\\ I see you have no"
			." [/mnt/smbfs/$P{Directory}/Create_New_Image]\n");
		    LOG("    directory. I'm only going to create it\n");
		    LOG("    for you, as you definitely want it. See the Howto for\n");
		    LOG("    more info. BTW, the Restore_Only parameter can be used\n");
		    LOG("    to prevent users from making images.\n");
		    sleep($README_SLEEP);
		    my $Done = 1;
		    mkdir("/mnt/smbfs/$P{Directory}/Create_New_Image", 0755)
			|| do { --$Done };
		    unless($Done)
		    {
			LOG("\n");
			LOG("!!! Unfortunately, I have no rights to write under"
			    ." the [/mnt/smbfs/$P{Directory}] dir.\n");
			LOG("    You should adjust this, refer to the Howto, etc.\n");
			sleep($FAILED_SLEEP);
		    }
		}
		unless(-e "/mnt/smbfs/$P{Directory}/Create_New_Image/hda")
		{
		    LOG("     We'll create a blank file"
			." [/mnt/smbfs/$P{Directory}/Create_New_Image/hda] for you\n");
		    open(H, ">/mnt/smbfs/$P{Directory}/Create_New_Image/hda");
		    close(H);
		}
	    }
	}
    }
    else
    {
	# Let's find this directory...
	#
	my $Tries = 1;

	while($Tries)
	{
	    unless($Tries == 1)
	    {
		foreach("Server", "Share", "User", "Passwd", "Directory")
		{
		    $P{$_} = "";
		}
	    }

	    unless(defined($P{Server}) && $P{Server})
	    {
		my $cmd = 'dialog --inputbox "Enter a valid ';
		if($P{NFS_Preferred})    { $cmd .= 'NFS'; }
		elsif($P{FTP_Preferred}) { $cmd .= 'FTP'; }
		else                     { $cmd .= 'SMB'; }
		$cmd .= ' Server IP (eg. 192.168.0.10)" 8 51 2>/tmp/BLA';
		system($cmd);
		if(-e "/tmp/BLA")
		{
		    open(DB, "/tmp/BLA");
		    while(<DB>)
		    {
			$P{Server} .= $_;
		    }
		    close(DB);
		    unlink("/tmp/BLA");
		}
		$P{Server} =~s/^\/*//;
		$P{Server} =~s/^\\*//;
	    }

	    unless((defined($P{Share}) && $P{Share}) || $P{FTP_Preferred})
	    {
		my $cmd = 'dialog --inputbox "Enter a valid '
		    .($P{NFS_Preferred} ? 'NFS':'SMB')
		    .' Share Name (eg. '
		    .($P{NFS_Preferred} ? '/home/share':'MyShare')
		    .')" 8 51 2>/tmp/BLA';
		system($cmd);
		if(-e "/tmp/BLA")
		{
		    open(DB, "/tmp/BLA");
		    while(<DB>)
		    {
			$P{Share} .= $_;
		    }
		    close(DB);
		    unlink("/tmp/BLA");
		}
		$P{Share} =~s/^\/*//;
		$P{Share} =~s/^\\*//;
		$P{Share} =~s/\$/\\\$/g;
	    }

	    unless((defined($P{User}) && $P{User}) || $P{NFS_Preferred})
	    {
		my $cmd = 'dialog --inputbox "Enter a valid username '
		    .'(eg. mydomain\johndoe)" 8 51 2>/tmp/BLA';
		system($cmd);
		if(-e "/tmp/BLA")
		{
		    open(DB, "/tmp/BLA");
		    while(<DB>)
		    {
			$P{User} .= $_;
		    }
		    close(DB);
		    unlink("/tmp/BLA");
		}
		$P{User} =~s/\//\\\\/g;
		$P{User} =~s/\\/\\\\/g;
	    }

	    unless((defined($P{Passwd}) && $P{Passwd}) || $P{NFS_Preferred})
	    {
		my $cmd = 'dialog --passwordbox "Enter a valid password '
		    .'(eg. secret)" 8 51 2>/tmp/BLA';
		system($cmd);
		if(-e "/tmp/BLA")
		{
		    open(DB, "/tmp/BLA");
		    while(<DB>)
		    {
			$P{Passwd} .= $_;
		    }
		    close(DB);
		    unlink("/tmp/BLA");
		}
		$P{Passwd} =~s/\\/\//;
	    }

	    foreach("Server", "Share", "User", "Passwd")
	    {
		$P{$_} = "" unless(defined($P{$_}));
		$P{$_} =~s/^\s*//;
		$P{$_} =~s/\s*$//;
	    }

	    LOG("* Unmounting [/mnt/smbfs]\n");
	    Umount("/mnt/smbfs");

	    LOG("* Trying to mount [//$P{Server}/$P{Share}] ("
		.($P{NFS_Preferred} ? "nfs":"cifs").")\n");

	    my $cmd = '';
	    if($P{NFS_Preferred})
	    {
		# Time it out... brutally.
		#
		$cmd = "((sleep 10; kill -9 `ps ax|grep mount|grep nfs|grep -v grep"
		    ." |sed -s \"s| *||\"|cut -d \" \" -f 1`) &);"
		    ." mount -t nfs $P{Server}:"
		    .($P{Share} =~/^\// ? '':'/')."$P{Share} /mnt/smbfs"
		    ." -o nolock >/tmp/out 2>&1";
	    }
	    elsif($P{FTP_Preferred})
	    {
		$cmd = "curlftpfs -o connect_timeout=10"
		  ." ftp://$P{User}:$P{Passwd}\@$P{Server}"
		    ." /mnt/smbfs >/tmp/out 2>&1";
	    }
	    else
	    {
		$cmd = "mount.cifs //$P{Server}/$P{Share} /mnt/smbfs";
		if($P{User})
		{
		    $cmd .= " -o username=\"$P{User}\"";
		}
		$cmd .= ",password=\"$P{Passwd}\" >/tmp/out 2>&1";
	    }

	    {
		my $tmp = $cmd;
		$tmp =~s/password=\"$P{Passwd}\"/password=xxx/g;
		$tmp =~s/:$P{Passwd}\@/:xxx\@/g;
		LOG("  Cmd: [$tmp]\n");
	    }
	    system($cmd);

	    my $out = "";
	    if(-e "/tmp/out")
	    {
		open(DB, "/tmp/out");
		while(<DB>)
		{
		    $out .= $_;
		}
		close(DB);
		unlink("/tmp/out");
		LOG("\n* SMB/NFS mount output: [$out]\n"); 

		if($P{NFS_Preferred})
		{
		    my $tmp = `df -P|grep smbfs |wc -l`;
		    $tmp =~s/\D//g;
		    unless($tmp)
		    {
			$Tries = 2;
			LOG("\n* No success... Trying again (nfs).\n");
			sleep($FAILED_SLEEP);
		    }
		    else
		    {
			$Tries = 1;
		    }
		}
		elsif($P{FTP_Preferred})
		{
		    # Do nothing
		}
		else
		{
		    if($out =~/failed/i || $out =~/cannot/i || $out =~/missing/i
		       || $out =~/error/i || $out =~/is not a valid/i)
		    {
			# Another chance
			my $tmp = `df -P|grep smbfs |wc -l`;
			$tmp =~s/\D//g;
			unless($tmp)
			{
			    $Tries = 2;
			    LOG("\n* No success... Trying again (cifs vs. smbfs).\n");
			    sleep($FAILED_SLEEP);
			}
		    }
		    else
		    {
			$Tries = 1;
		    }
		}
	    }

	    # The Samba syntax is sometimes hazardous. Several possibilities to try.
	    #
	    if(! $P{NFS_Preferred} && ! $P{FTP_Preferred})
	    {
		# It has been reported that sometimes, the syntax mount -t xxx //xxx/zzz
		# is not working, while mount -t xxx \\\\xxx\\zzz does. Trying.
		#
		if($Tries != 1)
		{
		    LOG("* Second chance! (cifs)\n");
		    LOG("* Unmounting [/mnt/smbfs]\n");
		    Umount("/mnt/smbfs");

		    LOG("* Trying to mount [\\\\$P{Server}\\$P{Share}] ("
			."cifs)\n");

		    my $cmd = "mount.cifs \\\\\\\\$P{Server}\\\\$P{Share} /mnt/smbfs";
		    if($P{User})
		    {
			$cmd .= " -o username=\"$P{User}\"";
		    }
		    $cmd .= ",password=\"$P{Passwd}\" >/tmp/out 2>&1";
		    {
			my $tmp = $cmd;
			$tmp =~s/password=\"$P{Passwd}\"/password=xxx/g;
			LOG("  Cmd: [$tmp]\n");
		    }
		    system($cmd);

		    $out = "";
		    if(-e "/tmp/out")
		    {
			open(DB, "/tmp/out");
			while(<DB>)
			{
			    $out .= $_;
			}
			close(DB);
			unlink("/tmp/out");
			LOG("\n* SMB mount output: [$out]\n"); 

			if($out =~/failed/i || $out =~/cannot/i || $out =~/missing/i
			   || $out =~/error/i || $out =~/is not a valid/i)
			{
			    # Another chance
			    my $tmp = `df -P|grep smbfs |wc -l`;
			    $tmp =~s/\D//g;
			    unless($tmp)
			    {
				$Tries = 2;
				LOG("\n* No success... Trying again (cifs vs."
				    ." smbfs).\n");
				sleep($FAILED_SLEEP);
			    }
			}
			else
			{
			    $Tries = 1;
			}
		    }
		}
            }

	    if($Tries == 1)
	    {
		my $cnt = 0;

		my $Mounted = 0;
		opendir(DIR, "/mnt/smbfs/$P{Directory}");
		my(@Files) = readdir(DIR);
		closedir(DIR);
		foreach my $F (@Files)
		{
		    next if($F =~/^\.{1,2}$/);
		    ++ $Mounted;
		    $Tries = 0;
		}
		if($Mounted)
		{
		    LOG("* Found subdirs in [/mnt/smbfs/$P{Directory}]."
			." Seems mounted.\n");
		}

		while(! $cnt && ! $Mounted)
		{
		    $P{Directory} = "";
		    my $cmd = 'dialog --inputbox "Enter a possible subdirectory '
			.'containing your data '
			.'(eg. \mydir\Partimage)" 8 51 2>/tmp/BLA';
		    system($cmd);
		    $P{Directory} = '';
		    if(-e "/tmp/BLA")
		    {
			open(DB, "/tmp/BLA");
			while(<DB>)
			{
			    $P{Directory} .= $_;
			}
			close(DB);
			unlink("/tmp/BLA");
		    }
		    $P{Directory} =~s/^\s*//;
		    $P{Directory} =~s/\s*$//;
		    $P{Directory} =~s/\\/\//g;
		    $P{Directory} =~s/^\/*//;
		    ++ $cnt;
		}
		$SRC = "/mnt/smbfs/$P{Directory}";
		LOG("* Found SRC: [$SRC]\n");
	    }
	}
    }
}


# If addon*.tar.gz/.zip files have been added to the SMB partimage dir,
# they can be untarred to the root of the system now.
#
if($SRC =~/smbfs/)
{
    LOG("* Looking for any addon in the SMB [$P{Directory}] dir...\n");
    {
	opendir(DIR, "/mnt/smbfs/$P{Directory}");
	my(@files) = readdir(DIR);
	closedir(DIR);
	foreach(@files)
	{
	    if(m/^addon(\-|_).*\.tar\.gz/i || m/^addon(\-|_).*\.zip/i)
	    {
		LOG("  Addon found! [$_]\n");
		Exec_Log("cd /; ".Unzip("/mnt/smbfs/$P{Directory}/$_"), 4);
	    }
	}
    }
}


# If a command has been scheduled to be executed after the
# mounting, do so now.
#
LOG("  * Any command to execute after the mounting ?\n");

if(defined($P{Cmd_2}) && $P{Cmd_2})
{
    LOG("    Yes! [$P{Cmd_2}]");
    my $out = system($P{Cmd_2});
    LOG("  Output: [$out]\n");
}
else
{
    LOG("    No defined command.\n");
}


# If the source is local, we've got to mount the partition where
# images are stored. The user might have several hdds... if so,
# give the possibility to choose which one to use.
#
my @Parts_To_Backup = ();
my $Part_For_Storage = '';

my $New_Second_Part = 0;

if($SRC eq "local")
{
    LOG("* Images are local. Find out what to backup.\n");

    # Only one HDD, only one part, no unpartitionned space
    #
    if($#Dev_Rich == 0 && $#{ $Dev_Rich[0]->{Parts} } == 0)
    {
	LOG("  Only one part. Obviously both src and dest.\n");
	LOG("    And obviously, will want to make an image.\n");

	LOG("  Only one partition. Let's investigate...\n");

	my $Dev_Size = $Dev_Rich[0]->{Heads} * $Dev_Rich[0]->{Sectors_Track} *
	    $Dev_Rich[0]->{Cylinders} * 512;

	my $Dev_Type = $Dev_Rich[0]->{Types}->[0];

	LOG("  - Size of the device: [$Dev_Size]\n");
	LOG("  - Type of the unique part: [$Dev_Type]\n");

	my $Used_Space = Used_Space($Dev_Rich[0]->{Parts}->[0]);
	LOG("  - Space used by [".$Dev_Rich[0]->{Parts}->[0]."]: [$Used_Space]\n");

	my $Avail_Space = $Dev_Size - $Used_Space;
	LOG("  - Available space on [".$Dev_Rich[0]->{Parts}->[0]."]:"
	    ." [$Avail_Space]\n");

	my $Useless_Space = 0;

	if($Avail_Space < .5 * $Dev_Size)
	{
	    LOG("  - Let's see if we can delete useless files...\n");

	    Umount("/mnt/dos");
	    Mount("/dev/".$Dev_Rich[0]->{Parts}->[0], "/mnt/dos", "");

	    foreach my $file ("pagefile.sys", "hiberfil.sys")
	    {
		if(-e "/mnt/dos/$file")
		{
		    $Useless_Space += (stat("/mnt/dos/$file"))[7];
		}
	    }
	    if($Useless_Space)
	    {
		$Avail_Space += $Useless_Space;
		$Used_Space -= $Useless_Space;
		LOG("    Yep. Can win [$Useless_Space] bytes more.\n");
		LOG("    After removing them, avail. space'd be: [$Avail_Space]\n");
		LOG("    and used space should be: [$Used_Space]\n");
	    }
	    else
	    {
		LOG("    Nope. No useless space.\n");
	    }

	    Umount("/mnt/dos");
	}

	if($Avail_Space >= .5 * $Dev_Size)
	{
	    LOG("  - Turns out we could resize the unique partition...\n");

	    my $Resize = 0;

	    if(defined($P{Repart}) && $P{Repart} =~/^(y|1)$/i)
	    {
		LOG("    Config file says we can repart!\n");
		$Resize = 1;
	    }
	    else
	    {
		my $cmd = 'dialog --colors --menu "\Zb\ZnYou only have one partition'
		    .' on your hard disk drive ('.$Dev_Rich[0]->{Dev}.'), and PING needs'
		    .' a second partition to write to or restore from saved images.'
		    .'\n\nNevertheless, it seems that there is enough free space for'
		    .' me to dynamically repart your HDD. Do you want me to do so ?'
		    .'\n\n(You definitely SHOULD have a backup of any valuable data'
		    .' before saying Yes here.)\n\nEven better, get an USB external'
		    .' drive and store your images in it.\n\n" 19 50 2 '
		    .' "Yes" "" "No" "" 2>/tmp/checklist.tmp';
		system($cmd);

		if(-z "/tmp/checklist.tmp")
		{
		    Quit();
		}

		open(DB, "/tmp/checklist.tmp");
		while(<DB>)
		{
		    if(m/Y/i)
		    {
			++ $Resize;
			last;
		    }
		}
		close(DB);
		unlink("/tmp/checklist.tmp");
	    }

	    unless($Resize)
	    {
		LOG("  Wisdom has been chosen... exit is the only choice.\n");
		sleep(5);
		Quit();
	    }

	    LOG("  So, we can repart the device [".$Dev_Rich[0]->{Dev}."]\n");

	    if($Useless_Space)
	    {
		LOG("  - Removing useless files on [/dev/"
		    .$Dev_Rich[0]->{Parts}->[0]."]\n");

		Umount("/mnt/dos");
		Mount("/dev/".$Dev_Rich[0]->{Parts}->[0], "/mnt/dos", "");

		foreach my $file ("pagefile.sys", "hiberfil.sys")
		{
		    if(-e "/mnt/dos/$file")
		    {
			Exec_Log("cd /mnt/dos; chmod 666 $file;"
				 ." rm -f $file; sync", 4);
			LOG("    Deleting [/mnt/dos/$file]\n");
		    }
		}

		Umount("/mnt/dos");
	    }

	    LOG("  - Estimating [/dev/".$Dev_Rich[0]->{Parts}->[0]."] future size\n");

	    my $New_Size = Part_Minimum_Size("/dev/".$Dev_Rich[0]->{Parts}->[0]);

	    unless($New_Size)
	    {
		LOG("    !!! We won't be able to resize. Aborting now.\n");
		sleep($FAILED_SLEEP);
		Quit();
	    }

	    # Not too much, yet.
	    #
	    if($New_Size < .5 * $Dev_Size)
	    {
		$New_Size = int($Dev_Size / 2);
		LOG("    Reducing to [$New_Size] will be enough.\n");
	    }

	    # mkfs.vfat won't operate if the volume is too small
	    # (who knows what data there is on the C: drive after all)
	    #
	    if(($Dev_Size - $New_Size) < 40000000)
	    {
		$New_Size = $Dev_Size - 40000000;
		LOG("    New part would be too small; reducing to [$New_Size]\n");
	    }

	    if($Used_Space > $New_Size || $New_Size == 0)
	    {
		LOG("    !!! Space problem. Aborting now.\n");
		sleep($FAILED_SLEEP);
		Quit();
	    }

	    LOG("  - Resizing to [$New_Size] bytes!!\n");

	    my $New_Size_Cyl =
		int($New_Size / ($Dev_Rich[0]->{Heads}
				 * $Dev_Rich[0]->{Sectors_Track} * 512));
	    LOG("  - well, to [$New_Size_Cyl] cylinders\n");

	    if(Part_Resize($Dev_Rich[0]->{Parts}->[0], $New_Size_Cyl, $New_Size))
	    {
		LOG("    !!! We could not resize that filesystem.\n");
		sleep($FAILED_SLEEP);
		Quit();
	    }
	    else
	    {
		LOG("    The resizing seems OK. Good.\n");
	    }

	    # Delete all parts...
	    #
	    Delete_All_Parts($Dev_Rich[0]->{Dev}, \@Dev_Rich);

	    # Adjust the partition table
	    #
	    $Dev_Rich[0]->{End}->[0] = $Dev_Rich[0]->{Start}->[0]
		+ $New_Size_Cyl;

	    # Now, we'll write images on the second part.
	    #
	    push(@{ $Dev_Rich[0]->{Parts} }, $Dev_Rich[0]->{Dev}."2");
	    push(@{ $Dev_Rich[0]->{Types} }, "c");
	    push(@{ $Dev_Rich[0]->{Dirs} }, "");
	    push(@{ $Dev_Rich[0]->{Labels} }, "");
	    push(@{ $Dev_Rich[0]->{Boot_Flags} }, 0);
	    push(@{ $Dev_Rich[0]->{Start} }, $Dev_Rich[0]->{Start}->[0]
		 + $New_Size_Cyl + 1);
	    push(@{ $Dev_Rich[0]->{End} }, $Dev_Rich[0]->{Cylinders});
	    push(@{ $Dev_Rich[0]->{Blocks} }, $Dev_Rich[0]->{Cylinders}
		 - $Dev_Rich[0]->{Start}->[0] - 1);

	    ++ $New_Second_Part;   # So we won't ask silly questions...

	    # Recreate partitions
	    #
	    Recreate_All_Parts($Dev_Rich[0]->{Dev}, \@Dev_Rich);

	    LOG("  - Formating the new partition [/dev/"
		.$Dev_Rich[0]->{Parts}->[1]."] (FAT32)\n");

	    Exec_Log("mkfs.vfat -F 32 /dev/".$Dev_Rich[0]->{Parts}->[1]);

	    LOG("  - Rediscover HDD's structure\n");
	    HDD_Discover(\@Dev, \@Dev_Rich);

	    @Parts_To_Backup = $Dev_Rich[0]->{Parts}->[0];
	    $Part_For_Storage = $Dev_Rich[0]->{Parts}->[1];

	    LOG("  - Evident deductions (are we clever):\n");
	    LOG("    Part to Backup: [$Parts_To_Backup[0]]\n");
	    LOG("    Part for Storage: [$Part_For_Storage]\n");
	}
	else
	{
	    LOG("  - Aie. The only present part occupies more than half disk space.\n");
	    LOG("    There's nothing we can do. Better exit now.\n");
	    sleep(5);
	    Quit();
	}
    }

    # Only one partition on one device, but unpartitionned space.
    # This case won't be handled here.
}

LOG("  Asking what parts to backup or restore\n");

if(defined($P{Image_To_Restore}) && $P{Image_To_Restore}
   && $P{Image_To_Restore} !=/^Create_New_Image$/i)
{
    LOG("    The user has passed a param for Image_To_Restore.\n");
    LOG("    => Be clever, assume he wants a restoration !\n");

    $GLOBAL_ACTION = "Restoration";
}
else
{
    if(defined($P{Image_To_Restore}) && $P{Image_To_Restore} =~/^Create_New_Image$/i)
    {
	LOG("    The user wants an automatic image creation.\n");
    }
    else
    {
	LOG("    No param passed for Image_To_Restore.\n");
    }

    if($P{Restore_Only})
    {
	LOG("    The user has set param Restore_Only to Y.\n");
    }
    else
    {
	LOG("    Param Restore_Only has not be set to Y; asking...\n");

	LOG("    Has the user set parameter Parts_To_Backup ?\n");
	if(defined($P{Parts_To_Backup}))
	{
	    LOG("      Yes! We've got: [$P{Parts_To_Backup}]\n");
	    @Parts_To_Backup = split(/\,/, $P{Parts_To_Backup});
	}
	else
	{
	    LOG("      No! Ask.\n");

	    while(1)
	    {
		my $EXIT = 0;

		my $cmd = 'dialog --colors --checklist "\Zb\Z7Choose the partitions'
		    .' to backup.\n\n'
		    .'\ZnNote that you cannot store a partition on itself. So,'
		    .' every partition but one (the destination) can be checked.\n\n'
		    .'****************************************************************\n'
		    .'*** Use SPACE to SELECT the entry(ies), only after hit ENTER ***\n'
		    .'****************************************************************\n\n"'
		    .' 20 73 7 ';

		if(! defined($P{Image_To_Restore})
		   || (defined($P{Image_To_Restore})
		       && $P{Image_To_Restore} !~/^Create_New_Image$/i))
		{
		    $cmd .= ' "    ### CHOOSE THIS if you want a'
			.' RESTORATION ###" "" off';
		}

		my $Nb_Parts = 0;
		foreach my $D (@Dev_Rich)
		{
		    for(my $i = 0; $i <= $#{ $D->{Parts} }; $i++)
		    {
			if($D->{Parts}->[$i] eq 'null')
			{
			    next;
			}

			my $Info = "";
			if($D->{Labels}->[$i])
			{
			    $Info = " (Label: ".$D->{Labels}->[$i].")";
			}
			else
			{
			    $Info = " (".substr($D->{Dirs}->[$i], 0, 35);
			    $Info .= "..." if(length($D->{Dirs}->[$i]) > 35);
			    $Info .= ")";
			    $Info =~s/ \(\)//;
			}

			$cmd .= ' "'.$D->{Parts}->[$i]
			    .' ('.ID_To_Type($D->{Types}->[$i]).')'.$Info.'" "" off';

			++ $Nb_Parts;
		    }
		}

		$cmd .= ' 2>/tmp/checklist.tmp';

		system($cmd);

		# What we get:
		# "hda1\ \(Linux\)\ \(lost+found,grub\)" "hda2\ \(Linux\ LVM\)" "mapper/VolGroup00-LogVol00\ \(\)\ \(mnt,root,tmp,usr,opt,bin,selinux,sr...\)" "mapper/VolGroup00-LogVol01\ \(Linux\)"

		my $tmp = '';
		open(DB, "/tmp/checklist.tmp");
		while(<DB>)
		{
		    s/^\s*//;
		    s/\s*$//;
		    $tmp .= $_;
		}
		close(DB);
		unlink("/tmp/checklist.tmp");

		if(! $tmp)
		{
		    LOG("\n");
		    LOG("Please, use the SPACE BAR to make a choice.\n");
		    sleep($README_SLEEP);
		}
		else
		{
		    @Parts_To_Backup = ();

		    my(@fields) = split(/\" \"/, $tmp);
		    foreach(@fields)
		    {
			s/\"//g;
			s/^\s*//;
			s/\s*$//;
			s/[^\/\-_0-9a-zA-Z].*$//;
			push(@Parts_To_Backup, $_);
			LOG("    To backup: [$_]\n");
		    }

		    if(! $Parts_To_Backup[0])
		    {
			$GLOBAL_ACTION = "Restoration";

			LOG("    A restoration has been asked.\n");
			++ $EXIT;
		    }
		    else
		    {
			$GLOBAL_ACTION = "Image Creation";

			LOG("    Nb parts to backup: [".($#Parts_To_Backup + 1)."]\n");
			LOG("    Nb not null seen parts: [$Nb_Parts]\n");
		
			if($SRC eq "local")
			{
			    LOG("    Local backup. Have to check if there's"
				." at least one spare part\n");

			    if($#Parts_To_Backup + 1 < $Nb_Parts)
			    {
				LOG("    Cool, the user did not check every part."
				    ." Go on!\n");
				++ $EXIT;
			    }
			    else
			    {
				LOG("    You must leave at least one part unchecked\n");
				LOG("    ...to store the backup somewhere :)\n");
				sleep(4);
			    }
			}
			else
			{
			    LOG("    Not a local backup. Can choose to backup"
				." all parts\n");
			    ++ $EXIT;
			}
		    }

		    last if($EXIT);
		}
	    }
	}
    }
}

LOG("  Global Action: [$GLOBAL_ACTION]\n");

if($SRC eq "local")
{
    LOG("  Asking where to store / where is stored, and mounting that part\n");
    {
	my $Mounted = 0;

	# Here, handle the case the user has provided a Server entry about
	# a local partition storing the image to restore automatically.
	# In this case, we can have:
	#   Server=/dev/hda1
	# or
	#   Server=/dev/hda1,/dev/sda1,/dev/hda2 ...
	#
	# We must mount the entry(ies) provided and see if in a possible
	# Directory folder, we can find the Image_To_Restore .
	#
	LOG("    Has the user provided a /dev/...-like Server entry\n");
	LOG("      and an Image_To_Restore entry ?\n");

	if($P{Server} =~/^\/dev/
	   && defined($P{Image_To_Restore}) && $P{Image_To_Restore})
	{
	    LOG("      Yes! Let's try to mount it and find what to restore.\n");
	    my(@ldev) = split(/\,/, $P{Server});
	    foreach my $ldev (@ldev)
	    {
		LOG("      Trying [$ldev]...\n");
		Umount("/mnt/dos");
		Mount($ldev, "/mnt/dos", "");

		if(Is_Mounted($ldev))
		{
		    LOG("      [$ldev] is mounted !\n");
		    my $tmp = "/mnt/dos".($P{Directory} ? $P{Directory}:"/");
		    $tmp .= ($tmp =~/\/$/ ? "":"/");
		    unless($P{Image_To_Restore} =~/^Create_New_Image$/i)
		    {
			# As it makes no sense to force users to have an empty
			# /Create_New_Image on their target device since they
			# have defined that target...

			$tmp .= $P{Image_To_Restore};
		    }
		    LOG("      Any [$tmp] found ?\n");
		    if(-d $tmp)
		    {
			LOG("        Yes! Good!\n");
			$Part_For_Storage = $P{Server};
			$Part_For_Storage =~s/^\/dev\///;
			LOG("        Part for Storage: [$Part_For_Storage]\n");
			$Mounted = 1;
		    }
		    else
		    {
			LOG("        No... Next device to try, if any\n");
		    }
		}
		else
		{
		    LOG("      [$ldev] couldn't be mounted. Next, if any.\n");
		}
	    }
	}
	else
	{
	    LOG("      Nope. Go on the interactive way.\n");
	}

	while(! $Mounted)
	{
	    my $cmd = 'dialog --colors --menu "\ZnChoose the partition where ';

	    if(! $Parts_To_Backup[0] || $P{Restore_Only})
	    {
		$cmd .= 'the images are stored';
	    }
	    else
	    {
		$cmd .= 'to store the image';
	    }
	    $cmd .= '.\n\n" 20 73 7 ';

	    foreach my $D (@Dev_Rich)
	    {
		for(my $i = 0; $i <= $#{ $D->{Parts} }; $i++)
		{
		    if($D->{Parts}->[$i] eq 'null')
		    {
			next;
		    }
		    my $flag = 0;
		    foreach my $Have (@Parts_To_Backup)
		    {
			if($Have eq $D->{Parts}->[$i])
			{
			    ++ $flag;
			}
		    }
		    unless($flag)
		    {
			my $Info = "";
			if($D->{Labels}->[$i])
			{
			    $Info = " (Label: ".$D->{Labels}->[$i].")";
			}
			else
			{
			    $Info = " (".substr($D->{Dirs}->[$i], 0, 35);
			    $Info .= "..." if(length($D->{Dirs}->[$i]) > 35);
			    $Info .= ")";
			    $Info =~s/ \(\)//;
			}

			$cmd .= ' "'.$D->{Parts}->[$i].' ('
			    .ID_To_Type($D->{Types}->[$i]).')'.$Info.'" ""';
		    }
		}
	    }

	    $cmd .= ' 2>/tmp/checklist.tmp';

	    system($cmd);

	    my $tmp = "";
	    open(DB, "/tmp/checklist.tmp");
	    while(<DB>)
	    {
		$tmp .= $_;
	    }
	    close(DB);
	    unlink("/tmp/checklist.tmp");

	    $Part_For_Storage = $tmp;
	    $Part_For_Storage =~s/^\s*//;
	    $Part_For_Storage =~s/\s*$//;
	    $Part_For_Storage =~s/[^0-9a-zA-Z].*$//;
	    LOG("    Part for Storage: [$Part_For_Storage]\n");

	    LOG("    Mounting [/dev/$Part_For_Storage]...\n");

	    Umount("/mnt/dos");
	    Mount("/dev/$Part_For_Storage", "/mnt/dos", "");

	    $tmp = `df -P|grep dos |wc -l`;
	    $tmp =~s/\D//g;
	    if($tmp)
	    {
		LOG("  Mounted !\n");
		++ $Mounted;
	    }
	    else
	    {
		LOG("    ! Could not mount the device at all. TRY AGAIN.\n");
		sleep(5);
	    }
	}
    }
}

if($SRC eq "local")
{
    $SRC = "/mnt/dos";
}

my $Found = 0;

if($SRC =~/cdrom/)
{
    $P{Directory} = '';
}
else
{
    if($New_Second_Part)
    {
	LOG("  The dest part is new. Creating a /Partimage directory on it.\n");

	Umount($SRC);
	Mount("/dev/$Part_For_Storage", $SRC, "");
	Exec_Log("mkdir $SRC/Partimage", 4);

	$P{Directory} = "/Partimage";
    }
    else
    {
	if($P{Directory} && -d "$SRC/$P{Directory}")
	{
	    # from the preconfig
	    LOG("  Found !\n");
	    ++ $Found;
	}
	else
	{
	    while(! $Found)
	    {
		$P{Directory} = "";
		my $cmd = 'dialog --inputbox "Enter a root directory '
		    .'containing your data '
		    .'(eg. \mydir\Partimage)" 8 51 2>/tmp/BLA';
		system($cmd);
		$P{Directory} = '';
		if(-e "/tmp/BLA")
		{
		    open(DB, "/tmp/BLA");
		    while(<DB>)
		    {
			$P{Directory} .= $_;
		    }
		    close(DB);
		    unlink("/tmp/BLA");
		}
		$P{Directory} =~s/^\s*//;
		$P{Directory} =~s/\s*$//;
		$P{Directory} =~s/\\/\//g;
		$P{Directory} =~s/^\/*//;

		LOG("  Chosen root subdir: [$P{Directory}]\n");

		if(-d "$SRC/$P{Directory}")
		{
		    LOG("  Found !\n");
		    ++ $Found;
		}
		else
		{
		    LOG("  ! The [$SRC/$P{Directory}] could not be found."
			." Try again.\n");
		}
	    }
	}
    }
}

$SRC = "$SRC/$P{Directory}";
LOG("* Found SRC: [$SRC]\n");


# Get the list of all partimaged Entries.
# Listing the share's directories gives it. And so there's no need
# to modify this script every time a new entry is added...
#
# Note: a directory name starting with ___ won't be listed, so to
#   give admins an easy way to hide some images not to be deployed.
#
my @Entities = ();

if($SRC =~/smbfs/ || $SRC =~/dos/)
{
    opendir(DIR, $SRC);
    my @F = readdir(DIR);
    closedir(DIR);

    foreach(@F)
    {
        next if m/^\.{1,2}$/;
        next unless(-d "$SRC/$_");
	if(m/^___/)
	{
	    next;
	}
        push(@Entities, $_);
    }

    if($#Entities < 0 && $SRC =~/smbfs/)
    {
        LOG("!!! No directory could be found in [$SRC].\n");
	LOG("    No image directory, and no Partimage.zip-made directory. Nothin'\n");
	LOG("    Maybe you've provided the wrong directory, or forgotten to\n");
	LOG("    prepare it properly. We'll abort now, sorry.\n");
	sleep($FAILED_SLEEP);
        Quit();
    }
}


# If the user has chosen a local copy, he obviously wants to be
# able to create a new image etc. So, let's add the special directories
# if he has forgotten to create them.
#
if($SRC =~/dos/)
{
    foreach my $S ("Create_New_Image", "Blank_Local_Admin_Passwd")
    {
	# Don't propose to create an image if the user has checked that
	# he doesn't want to backup anything.
	#
	next if($S eq "Create_New_Image" && defined($Parts_To_Backup[0])
		&& $Parts_To_Backup[0] =~/Choose/i);
	next if($S eq "Create_New_Image" && $P{Restore_Only});

	my $flag = 0;
	foreach(@Entities)
	{
	    if(m/^$S$/i)
	    {
		++ $flag;
		last;
	    }
	}
	unless($flag)
	{
	    push(@Entities, $S);
	}
    }
}


# Display special images first, or not at all if this is a restoration.
# Don't display any restorable image if this is an image creation, so
# to avoid disasters (people not choosing Create_New_Image and overwriting
# their own OS).
#
{
    my(@New) = ();
    foreach(@Entities)
    {
	if(m/^Create_New_Image$/i || m/^Blank_Local_Admin_Passwd$/i)
	{
	    unless($GLOBAL_ACTION eq "Restoration" && m/^Create_New_Image$/i)
	    {
		push(@New, $_);
	    }
	}
    }
    if($GLOBAL_ACTION eq "Restoration" || $P{Image_To_Restore})
    {
	foreach(@Entities)
	{
	    unless(m/^Create_New_Image$/i || m/^Blank_Local_Admin_Passwd$/i)
	    {
		push(@New, $_);
	    }
	}
    }
    @Entities = @New;

    LOG("  Found...:\n");
    foreach(@Entities)
    {
	LOG("    - [$_]\n");
    }
}


# Which entry will we install ?
#
LOG("* Which entry will we install ?\n");

my $Entity = '/';

{
    my $browse = 1;

    if(defined($P{Image_To_Restore}) && $P{Image_To_Restore})
    {
	LOG("  The user has passed a param for this: [$P{Image_To_Restore}]\n");
        foreach(@Entities)
	{
	    if(m/^$P{Image_To_Restore}$/i)
	    {
		LOG("  Found among the directories: [$_]\n");
		$Entity = $_;
		$browse = 0;
		last;
	    }
	}
	if($browse)
	{
	    LOG("  ! This image cannot be found among the directories\n");
	    LOG("    We had only:\n");
	    foreach(@Entities)
	    {
		LOG("    - [$_]\n");
	    }
	    sleep($TIP_SLEEP);
	    LOG("    We'll have to browse and ask you what to restore\n");
	    sleep($TIP_SLEEP);
	    $P{Image_To_Restore} = '';
	}
    }

    if($browse && ($SRC =~/smbfs/ || $SRC =~/dos/))
    {
	my $cmd = 'dialog --colors --menu "\Zb\Z7Actions and Available images for'
	    .' restoration...\n\n'
	    .($P{Restore_Only} ? '':'\ZnChoose Create_New_Image if'
	      .' you want a Ghost-like image of your partition(s)')
	    .'" 20 60 8 ';
	foreach(@Entities)
	{
	    # Don't propose to create an image if the user has checked that
	    # he doesn't want to backup anything.
	    #
	    next if(($_ eq "Create_New_Image" || $_ eq "Blank_Local_Admin_Passwd")
		    && $P{Restore_Only});

	    $cmd .= '"'.$_.'" "" ';
	}
	if($#Entities < 0)
	{
	    # Our menu wouldn't be ok anyway.
	    #
	    LOG("    !!! You've asked for a restoration, but there's no\n");
	    LOG("        image in the directory you've provided.\n");
	    LOG("        Quitting...\n");
	    sleep($FAILED_SLEEP);
	    Quit();
	}
	$cmd .= ' 2>/tmp/BLA';
	LOG("    Cmd: [$cmd]\n");
	system($cmd);
	if(-e "/tmp/BLA")
	{
	    open(DB, "/tmp/BLA");
	    while(<DB>)
	    {
		$Entity .= $_;
	    }
	    close(DB);
	    unlink("/tmp/BLA");
	}
	$Entity =~s/^\s+//g;
	$Entity =~s/\s+$//g;
	$Entity =~s/^\///g;
    }
}

LOG("  To install: [$Entity]\n");


# Restore partitions.
# Naming convention: files sda1.000 sda1.001 etc. => /dev/sda1.
#                    files askme_a1.000 askme_a1.001 etc. => Ask me!
# File 'sda' contains partitionning information and must be dumped first.
# (or file 'askme_a').
#
my $Have_Restored = 0;
my $Have_Minimized_Before_Storing = 0;
my(@Minimized_Devices) = ();

if($Entity =~/Blank_Local_Admin_Passwd/i
   || $Entity =~/Create_New_Image/i)
{
    LOG("* Either image creation, either local admin passwd to blank\n");

    if($Entity =~/Create_New_Image/i)
    {
	LOG("* Image creation\n");

	# Do something for the case there's a Dell Utility Partition
	#
	LOG("  Save user's time if there's a Dell Utility part\n");

	foreach my $D (@Dev_Rich)
	{
	    for(my $i = 0; $i <= $#{ $D->{Parts} }; $i++)
	    {
		if(defined($D->{Types}->[$i])
		   && Is_Dell_Type($D->{Types}->[$i]) && $HELP_DELL eq "")
		{
		    LOG("\n");
		    my(@W) = ();
		    $W[0] = "It appears that you're about to clone a Dell box,"
			." as there's a so-called Dell Utility partition."
			." Please, be aware that several users have reported"
			." problems with the cloning of systems containing this"
			." Dell Utility partition. You might choose...\n";
		    $W[1] = "1. Go on anyway! (not recommended)\n";
		    $W[2] = "2. Prepare this system yourself, this way\n"
			."   => - Stop this image creation\n"
			."      - Remove the Dell Utility partition\n"
			."      - Extend the other partition so to use all space\n"
			."      - Fix your boot.ini file so it will boot the 1st part\n"
			."      - Launch PING again to create your image.\n";
		    $W[3] = "3. Let us store only the second partition (C:\\).\n";
		    $W[4] = "We recommend you to choose the 3rd option, because"
			." we have scripted the whole thing so to have your future"
			." restorations use all available space on the drive, with"
			." a boot.ini file modified for you on the fly, and a fixed"
			." MBR and Boot Sector.";

		    LOG("  Issuing this warning:\n");
		    foreach(@W)
		    {
			LOG("    [$_]\n");
		    }

		    open(W, ">/tmp/warning");
		    print W wrap('', '', join("\n", @W));
		    close(W);

		    system("reset; clear; more /tmp/warning");
		    unlink("/tmp/warning");

		    my $Grab = '';
		    my $cnt = 0;
		    while($Grab !~/^(1|2|3)/)
		    {
			if($cnt)
			{
			    print "\nPlease, type 1, 2 or 3.\n\n>> ";
			}
			print "\n>> ";
			$Grab = <STDIN>;
			if($Grab =~/^2/)
			{
			    LOG("      Has chosen to quit.\n");
			    Quit();
			}
			elsif($Grab =~/^3/)
			{
			    LOG("      Has chosen option 3. Hurra!\n");
			    ++ $HELP_DELL;
			}
			++ $cnt;
		    }
		}
	    }
	}

	# Name the future image
	#	
	my $New_Image = '';

	LOG("* Getting the name of the future image\n");
	LOG("  Has the user set it on a parameter ?\n");

	if(defined($P{New_Image_Name}) && $P{New_Image_Name})
	{
	    LOG("    Yes! will be [$P{New_Image_Name}]\n");
	    $New_Image = $P{New_Image_Name};
	}
	else
	{
	    LOG("    No; let's ask\n");

	    while(length($New_Image) < 1)
	    {
		my $cmd = 'dialog --inputbox "Enter the name of the '
		    .'new image :" 8 51 2>/tmp/BLA';
		system($cmd);
		if(-e "/tmp/BLA")
		{
		    open(DB, "/tmp/BLA");
		    while(<DB>)
		    {
			$New_Image .= $_;
		    }
		    close(DB);
		    unlink("/tmp/BLA");
		}
	    }
	}
	$New_Image =~s/^\s*//;
	$New_Image =~s/\s*$//;
	$New_Image =~s/\s/_/g;
	$New_Image =~s/\(/_/g;
	$New_Image =~s/\)/_/g;
	$New_Image =~s/\[/_/g;
	$New_Image =~s/\]/_/g;

	LOG("  Name of future image: [$New_Image]\n");

	# Give the user the possibility to store recorded files details,
	# in order to make future incremential backup possible.
	#
	LOG("  Ask if we should store file details\n");

	if(defined($P{Store_MD5}))
	{
	    LOG("  There's a preconfig entry; worth [$P{Store_MD5}]\n");
	    if($P{Store_MD5} =~/^(y|1)$/i)
	    {
		$P{Store_MD5} = 1;
	    }
	    else
	    {
		$P{Store_MD5} = 0;
	    }
	}
	else
	{
	    LOG("  There's no preconfig predefined info\n");

	    my $cmd = 'dialog --colors --menu "\Zb\ZnWe can store details about'
		.' each recorded file, in order to make future incremential'
		.' image creation possible. It will take some more time first,'
		.' but will allow small updates rather than new images to be'
		.' written from scratch. Note also that unfortunately,'
		.' NTFS ACL permissions are NOT handled. So, do you want us'
		.' to record the info ?\n\nNote: ACLs *not* handled.\n\n" 17 65 2 '
		.' "No" "" "Yes" "" 2>/tmp/checklist.tmp';
	    system($cmd);

	    if(-z "/tmp/checklist.tmp")
	    {
		Quit();
	    }

	    open(DB, "/tmp/checklist.tmp");
	    while(<DB>)
	    {
		if(m/Y/i)
		{
		    $P{Store_MD5} = 1;
		    last;
		}
	    }
	    close(DB);
	    unlink("/tmp/checklist.tmp");

	    LOG("    ".($P{Store_MD5} ? "Yes!":"No")."\n");
	}

	# Give the user the possibility to choose between gzip and bzip2
	#
	LOG("* What compressor will be used ?\n");
	my $Compression_Type = 'gzip';
	if(defined($P{Compression_Type}) && $P{Compression_Type})
	{
	    LOG("  The user has provided an entry =>[$P{Compression_Type}]\n");
	    if($P{Compression_Type} =~/gzip/i)
	    {
		$Compression_Type = 'gzip';
	    }
	    elsif($P{Compression_Type} =~/bzip2/i)
	    {
		$Compression_Type = 'bzip2';
	    }
	    elsif($P{Compression_Type} =~/no\s*compression/i)
	    {
		$Compression_Type = 'no compression';
	    }
	    LOG("  Compression type to be used: [$Compression_Type]\n");
	}
	else
	{
	    LOG("  No param entry; let's ask the user\n");
	    my $cmd = 'dialog --colors --menu "\Zb\ZnDo you prefer gzip (faster),'
		.' bzip2 (less used space), or no compression ?\n\n" 12 50 3 '
		.' "gzip" "" "bzip2" "" "no compression" "" 2>/tmp/checklist.tmp';
	    system($cmd);

	    if(-z "/tmp/checklist.tmp")
	    {
		Quit();
	    }

	    open(DB, "/tmp/checklist.tmp");
	    while(<DB>)
	    {
		if(m/bzip2/i)
		{
		    $Compression_Type = 'bzip2';
		    last;
		}
		elsif(m/no/i)
		{
		    $Compression_Type = 'no compression';
		    last;
		}
	    }
	    close(DB);
	    unlink("/tmp/checklist.tmp");
	}
	LOG("* Chosen compression type: [$Compression_Type]\n");

	# Give the user the possibility to choose zsplit or a tarball
	# over partimage
	#
	LOG("  Ask if zsplit or a tarball must be preferred to partimage\n");

	if(defined($P{Zsplit_Preferred}))
	{
	    LOG("    There's a preconfig entry; worth [$P{Zsplit_Preferred}]\n");
	    if($P{Zsplit_Preferred} =~/^(y|1)$/i)
	    {
		$P{Zsplit_Preferred} = 1;
	    }
	    else
	    {
		$P{Zsplit_Preferred} = 0;
	    }
	}
	if(defined($P{Tarball_Preferred}))
	{
	    LOG("    There's a preconfig entry; worth [$P{Tarball_Preferred}]\n");
	    if($P{Tarball_Preferred} =~/^(y|1)$/i)
	    {
		$P{Tarball_Preferred} = 1;
	    }
	    else
	    {
		$P{Tarball_Preferred} = 0;
	    }
	}
	if(! defined($P{Zsplit_Preferred}) && ! defined($P{Tarball_Preferred}))
	{
	    LOG("    The user did not provide a Zsplit_Preferred entry\n");
	    LOG("    nor a Tarball_Preferred entry in a ping.conf file => Ask.\n");

	    my $cmd = 'dialog --colors --menu "\Zb\Z7PING Partimage Is Not Ghost\n\n'
		.'\ZnDo you want zsplit or tar+gzip to be used instead of partimage ?'
		.' (Most users should prefer Partimage.)\n\n" 15 50 3 '
		.' "Partimage" "" "Zsplit" "" "Tarball" "" 2>/tmp/checklist.tmp';
	    system($cmd);

	    if(-z "/tmp/checklist.tmp")
	    {
		Quit();
	    }

	    open(DB, "/tmp/checklist.tmp");
	    while(<DB>)
	    {
		if(m/zsplit/i)
		{
		    $P{Zsplit_Preferred} = 1;
		    $P{Tarball_Preferred} = 0;
		    LOG("    Zsplit!\n");
		}
		elsif(m/tarball/i)
		{
		    $P{Zsplit_Preferred} = 0;
		    $P{Tarball_Preferred} = 1;
		    LOG("    Tarball!\n");
		}
		else
		{
		    $P{Zsplit_Preferred} = 0;
		    $P{Tarball_Preferred} = 0;
		    LOG("    Partimage!\n");
		}
	    }
	    close(DB);
	    `rm -f /tmp/checklist.tmp`;
	}

	# It can be interesting to reduce the size of the partition
	# before storing it. Thus, it's possible to restore it later
	# into a smaller partition. Note that even if we decide later
	# to restore into a equally-big or bigger partition, PING will
	# always try to maximize the size of the filesystem at the end.
	#
	LOG("  Ask if we should minimize NTFS filesystems before storing them\n");

	if(defined($P{Minimize_Before_Storing}))
	{
	    LOG("  There's a preconfig entry; worth [$P{Minimize_Before_Storing}]\n");
	    if($P{Minimize_Before_Storing} =~/^(y|1)$/i)
	    {
		$P{Minimize_Before_Storing} = 1;
	    }
	    else
	    {
		$P{Minimize_Before_Storing} = 0;
	    }
	}
	else
	{
	    LOG("  There's no preconfig predefined info\n");

	    if(defined($P{AUTO}) && $P{AUTO})
	    {
		LOG("  AUTO param set! don't ask, minimize\n");
		$P{Minimize_Before_Storing} = 1;
	    }
	    else
	    {
		LOG("  AUTO param not set... let's ask the user\n");

		my $cmd = 'dialog --colors --menu "\Zb\ZnMost filesystems can be'
		    .' reduced before being stored. It permits restoring them to'
		    .' smaller partitions. Do you want us to minimize ? (Choose'
		    .' NO unless everything is backuped).\nNote: *useless* if'
		    .' tar+gzip has been chosen.\n\n" 13 50 2 '
		    .' "No" "" "Yes" "" 2>/tmp/checklist.tmp';
		system($cmd);

		if(-z "/tmp/checklist.tmp")
		{
		    Quit();
		}

		open(DB, "/tmp/checklist.tmp");
		while(<DB>)
		{
		    if(m/Y/i)
		    {
			$P{Minimize_Before_Storing} = 1;
			last;
		    }
		}
		close(DB);
		unlink("/tmp/checklist.tmp");

		LOG("    ".($P{Minimize_Before_Storing} ? "Yes!":"No")."\n");
	    }
	}


	# If the user has asked for a local image to be done, then we
	# might have to mount the dest part with NTFS-3G, for it
	# to be writable. By default, we did not, as reading is faster
	# with native kernel NTFS read-only drivers.
	#
	if($SRC =~/dos/i)
	{
	    LOG("* A local image was asked. Checking [/dev/$Part_For_Storage] FS...\n");

	    my $FS = Part_Type($Part_For_Storage);
	    LOG("  Filesystem ID found: [$FS]\n");

	    if(Is_NTFS($FS))
	    {
		LOG("  This means NTFS. Remounting it R/W.\n");
		Umount("/mnt/dos");
		Mount("/dev/$Part_For_Storage", "/mnt/dos", "RW");

		LOG("  Checking the mounting...\n");
		if(Is_Mounted("/dev/$Part_For_Storage"))
		{
		    LOG("  Mounted !\n");
		}
		else
		{
		    LOG("  ! Could not mount the device at all. Exit.\n");
		    sleep(5);
		    Quit();
		}
	    }
	}

	# If there's already a directory called after the name of the image
	# the user wants to create, there can be three interesting choices :
	# - Either replace it
	# - Either rename the old dir to xxx.OLD.$$
	# - Either try to update it, which means detecting the files that
	#   have changed and store them only, to .tar.gz/.bz2 files.
	#   This can only be proposed if a sda1.RecFiles.txt has been found.
	#
	if(-d "$SRC/$New_Image")
	{
	    LOG("* There's already a [$SRC/$New_Image] directory...\n");
	    LOG("  Has the user provided a Already_Existing_Image param ?\n");

	    my $Found_RecFiles = 0;
	    {
		opendir(DIR, "$SRC/$New_Image");
		my(@files) = readdir(DIR);
		closedir(DIR);
		foreach(@files)
		{
		    if(m/\.RecFiles\.txt$/i)
		    {
			++ $Found_RecFiles;
			last;
		    }
		}
	    }

	    if(defined($P{Already_Existing_Image})
	       && ($P{Already_Existing_Image} =~/Update/i
		   || $P{Already_Existing_Image} =~/Replace/i
		   || $P{Already_Existing_Image} =~/Rename/i))
	    {
		LOG("  Yes! Worth [$P{Already_Existing_Image}]\n");

		if($P{Already_Existing_Image} =~/Update/i)
		{
		    if(! $Found_RecFiles)
		    {
			LOG("  An Update is impossible, as there's no RecFiles.txt\n");

			if($P{Already_Existing_Image} =~/Replace/i)
			{
			    LOG("  The user said Replace should be OK too... good\n");
			    $P{Already_Existing_Image} = 'Replace';
			}
			elsif($P{Already_Existing_Image} =~/Rename/i)
			{
			    LOG("  The user said Rename should be OK too... good\n");
			    $P{Already_Existing_Image} = 'Rename';
			}
			else
			{
			    LOG("  We'll have to ask, as no 2nd choice was provided\n");
			    $P{Already_Existing_Image} = '';
			}
		    }
		    else
		    {
			LOG("  The Update should be feasible.\n");
			$P{Already_Existing_Image} = 'Update';
		    }
		}
		else
		{
		    if($P{Already_Existing_Image} =~/Replace/i)
		    {
			$P{Already_Existing_Image} = 'Replace';
		    }
		    elsif($P{Already_Existing_Image} =~/Rename/i)
		    {
			$P{Already_Existing_Image} = 'Rename';
		    }
		    else
		    {
			$P{Already_Existing_Image} = '';
		    }
		}
	    }
	    else
	    {
		$P{Already_Existing_Image} = '';
	    }

	    unless($P{Already_Existing_Image})
	    {
		LOG("  No; let's ask what to do\n");

		my $cmd = 'dialog --colors --menu "\Zb\ZnAn image called ['
		    .$New_Image.'] has been found in your directory. What should'
		    .' PING do ?\n\n" 12 50 3 '
		    .' "Rename and keep the old image" ""'
		    .' "Replace the old one" ""';
		if($Found_RecFiles)
		{
		    $cmd .= ' "Update the image that has been found" ""';
		}
		$cmd .= ' 2>/tmp/checklist.tmp';
		system($cmd);

		if(-z "/tmp/checklist.tmp")
		{
		    Quit();
		}

		open(DB, "/tmp/checklist.tmp");
		while(<DB>)
		{
		    if(m/^Replace/i)
		    {
			$P{Already_Existing_Image} = 'Replace';
			last;
		    }
		    elsif(m/^Rename/i)
		    {
			$P{Already_Existing_Image} = 'Rename';
			last;
		    }
		    elsif(m/^Update/i)
		    {
			$P{Already_Existing_Image} = 'Update';
			last;
		    }
		    else
		    {
			Quit();
		    }
		}
		close(DB);
		unlink("/tmp/checklist.tmp");

		LOG("  Chosen action: [$P{Already_Existing_Image}]\n");
	    }

	    if($P{Already_Existing_Image} eq 'Rename')
	    {
		my $Date = Date();
		$Date =~s/\D//g;
		LOG("  * Renaming old [$SRC/$New_Image] to [(...).OLD.$Date]\n");
		rename("$SRC/$New_Image", "$SRC/$New_Image.OLD.$Date");
	    }
	    elsif($P{Already_Existing_Image} eq 'Replace')
	    {
		LOG("  * Deleting old [$SRC/$New_Image] directory\n");
		Exec_Log("rm -fr \"$SRC/$New_Image\"", 4);
	    }
	}

	unless(-d "$SRC/$New_Image")
	{
	    LOG("* Creating dir [$SRC/$New_Image]\n");
	    mkdir("$SRC/$New_Image", 0755);
	}

	unless(-d "$SRC/$New_Image")
	{
	    LOG("  ! I've been unable to create dir [$SRC/$New_Image]\n");
	    LOG("    There's probably a problem of space or permissions here.\n");
	    LOG("    No way to make a backup... quitting.\n");
	    sleep($FAILED_SLEEP);
	    Quit();
	}

	# Handling the case of a $Dev being worth cciss/c0d0 (HP SmartArray)...
	# + rd + ida + mapper. We need a $SRC/$New_Image/cciss/ directory.
	#
	foreach(@Parts_To_Backup)
	{
	    if(m/\//)
	    {
		my $tmp = $_;
		$tmp =~s/\/[^\/]+$//;
		if(! -d "$SRC/$New_Image/$tmp")
		{
		    LOG("* Creating dir [$SRC/$New_Image/$tmp]\n");
		    mkdir("$SRC/$New_Image/$tmp", 0755);
		}
	    }
	}

	# BIOS first
	#
	LOG("* Backuping bios\n");
	Exec_Log("cmospwd -w \"$SRC/$New_Image/bios\"", 2);

	# Keep a file with the drives(s) partition(s) list
	#
	LOG("* Keep the drive(s) partition(s) list\n");
	LOG("  => File \"$SRC/$New_Image/HDD_Look.txt\"\n");
	open(OUT, ">$SRC/$New_Image/HDD_Look.txt");
	print OUT HDD_Exact_Look(\@Dev_Rich);
	close(OUT);

	# Doing the backup of the partitions
	#
	foreach my $P (@Parts_To_Backup)
	{
	    LOG("* Backuping partition [/dev/$P]\n");

	    # Dell parts... don't allow them for storage if the user
	    # asked for help
	    #
	    # Disk /dev/hda: 85.8 GB, 85899345920 bytes
	    # 255 heads, 63 sectors/track, 10443 cylinders
	    # Units = cylinders of 16065 * 512 = 8225280 bytes
	    #
	    #    Device Boot    Start     End     Blocks   Id  System
	    # /dev/hda1             1       5      40131   de  Dell Utility
	    # /dev/hda2   *         6    9268   74405047+   7  HPFS/NTFS
	    # /dev/hda3          9269    9725    3670852+  db  CP/M / CTOS / ...
	    #
	    if($HELP_DELL && Is_Dell_Type(Part_Type("/dev/$P")))
	    {
		LOG("  Don't backup this Dell-made part [/dev/$P] (Help Dell)\n");
		next;
	    }

	    # If the device is classical, part to backup is like sda1
	    # => just keep the letters.
	    # If cciss/rd/ida, part to backup is like cciss/c0d0p1, and
	    # device is cciss/c0d0.
	    #
	    LOG("  * Backuping first sectors of [/dev/$P]'s device\n");

	    my $HDD = HDD_Name($P);
	    LOG("    HDD: [$HDD]\n");

	    if(! -e "$SRC/$New_Image/$HDD")
	    {
		Exec_Log("dd if=/dev/$HDD of=/tmp/aaa count=64 bs=512;"
			 ." mv -f /tmp/aaa \"$SRC/$New_Image/$HDD\"", 4);
	    }


	    # Now storing the partition itself
	    #
	    # HP SmartArray => there will be a /dev/cciss/c0d0 device,
	    # and /dev/cciss/c0d0p1, p2, ... filesystems. The last digit must
	    # be removed for the parsing of the fdisk command to succeed.
	    # Disk /dev/cciss/c0d0: 73.3 GB, 73372631040 bytes
	    # Idem for ida and rd (RAID).
	    #
	    LOG("  * Now storing the [/dev/$P] partition\n");

	    # Before storing anything, remove any unwanted space-consuming file
	    #
	    LOG("    Checking [/dev/$P] for pagefile.sys and hiberfil.sys to remove\n");
	    {
		my $Type = Part_Type($P);
		LOG("      [/dev/$P]'s type: [$Type]\n");

		Umount("/mnt/win");
		Mount("/dev/$P", "/mnt/win", "RW");

		foreach my $file ("pagefile.sys", "hiberfil.sys")
		{
		    if(-e "/mnt/win/$file")
		    {
			LOG("      Removing [$file]\n");
			Exec_Log("chmod 0666 /mnt/win/$file; rm -f /mnt/win/$file", 6);
		    }
		    else
		    {
			LOG("      No [$file] to delete\n");
		    }
		}

		Umount("/mnt/win");
	    }


	    # It can be interesting to reduce the size of the partition
	    # before storing it. Thus, it's possible to restore it later
	    # into a smaller partition. Note that even if we decide later
	    # to restore into a equally-big or bigger partition, PING will
	    # always try to maximize the size of the filesystem at the end.
	    #
	    LOG("  * Minimizing the filesystem of [/dev/$P]\n");

	    if($P{Minimize_Before_Storing})
	    {
		LOG("    Estimating [/dev/$P] future size\n");
		my $New_Size = Part_Minimum_Size("/dev/$P");
		LOG("    We can reduce to [$New_Size]\n");

		if($New_Size)
		{
		    LOG("    Resizing to [$New_Size] !!\n");

		    my $New_Size_Cyl =
			int($New_Size / ($Dev_Rich[0]->{Heads}
					 * $Dev_Rich[0]->{Sectors_Track} * 512));
		    LOG("    - well, to [$New_Size_Cyl] cylinders\n");

		    if(Part_Resize($P, $New_Size_Cyl, $New_Size))
		    {
			LOG("    ! We could not resize that filesystem.\n");
			LOG("      Maybe not a drama.\n");
		    }
		    else
		    {
			LOG("    The resizing seems OK. Good. Go on.\n");

			# We now have to use fdisk to modify the size of
			# the partition accordingly to what ntfsresize has done.
			# fdisk is so hard to use in batch-mode that the best
			# way is to destroy all parts, and re-create them.
			#
			# We've got to set /dev/$P to $New_Size bytes.
			#
			# $New_Size bytes means $New_Size / (nb heads x
			#   nb sectors by track x 512) blocks.
			#
			my $tmp_End = '';
			my $tmp_Blocks = '';
			foreach my $D (@Dev_Rich)
			{
			    next unless($D->{Dev} eq $HDD);

			    for(my $i = 0; $i <= $#{ $D->{Parts} }; $i++)
			    {
				next unless($D->{Parts}->[$i] eq $P);

				LOG("    Old fdisk start for [$P]: ["
				    .$D->{Start}->[$i]."]\n");
				LOG("    Old fdisk end for [$P]: ["
				    .$D->{End}->[$i]."]\n");
				LOG("    Old fdisk nb of blocks for [$P]: ["
				    .$D->{Blocks}->[$i]."]\n");

				$tmp_Blocks = $D->{Blocks}->[$i];
				$tmp_End = $D->{End}->[$i];

				$D->{End}->[$i] = $D->{Start}->[$i]
				    + int($New_Size
					  / ($D->{Heads}
					     * $D->{Sectors_Track} * 512));
				$D->{Blocks}->[$i] =
				    int(($D->{End}->[$i] - $D->{Start}->[$i] + 1)
					* $D->{Heads} * $D->{Sectors_Track}
					* 512 / 1024);

				LOG("    New fdisk end for [$P]: ["
				    .$D->{End}->[$i]."]\n");
				LOG("    New fdisk nb of blocks for [$P]: ["
				    .$D->{Blocks}->[$i]."]\n");
			    }
			}

			Delete_All_Parts($HDD, \@Dev_Rich);
			Recreate_All_Parts($HDD, \@Dev_Rich);

			LOG("    HDD Description after minimizing (Dev_Rich)\n");
			HDD_Describe(\@Dev_Rich);

			if($tmp_Blocks && $tmp_End)
			{
			    foreach my $D (@Dev_Rich)
			    {
				next unless($D->{Dev} eq $HDD);

				for(my $i = 0; $i <= $#{ $D->{Parts} }; $i++)
				{
				    next unless($D->{Parts}->[$i] eq $P);

				    LOG("    Old fdisk start for [$P]: ["
					.$D->{Start}->[$i]."]\n");
				    LOG("    Old fdisk end for [$P]: ["
					.$D->{End}->[$i]."]\n");
				    LOG("    Old fdisk nb of blocks for [$P]: ["
					.$D->{Blocks}->[$i]."]\n");

				    $D->{Blocks}->[$i] = $tmp_Blocks;
				    $D->{End}->[$i] = $tmp_End;

				    LOG("    New fdisk end for [$P]: ["
					.$D->{End}->[$i]."]\n");
				    LOG("    New fdisk nb of blocks for [$P]: ["
					.$D->{Blocks}->[$i]."]\n");
				}
			    }
			}

			LOG("    HDD Description before minimizing (Dev_Rich)\n");
			HDD_Describe(\@Dev_Rich);

			++ $Have_Minimized_Before_Storing;
			push(@Minimized_Devices, $HDD);
		    }
		}
		else
		{
		    LOG("    Part FS not handled => no minimizing.\n");
		}
	    }
	    else
	    {
		LOG("    Not wanted.\n");
	    }


	    # Storing first sectors of the part (they may include the boot
	    # sector sometimes, and the definition of the volume if LVM).
	    #
	    LOG("  * Storing first sectors of part [/dev/$P]\n");

	    if($P{Zsplit_Preferred})
	    {
		LOG("    Zsplit will be preferred to partimage. So, skip this step.\n");
	    }
	    else
	    {
		Exec_Log("dd if=/dev/$P of=/tmp/aaa count=20 bs=512;"
			 ." mv -f /tmp/aaa \"$SRC/$New_Image/$P.first_sectors\"", 4);

		# If dd didn't even record 20x512 bytes, no need to go on,
		# we took everything (probably a ext'd).
		#
		# This is not true as far as curlftpfs is concerned, because
		# the program acts as a daemon, and leaves false 0-byte-long files.
		#
		my $Size = (stat("$SRC/$New_Image/$P.first_sectors"))[7];
		LOG("    dd has recorded [$Size] octets\n");
		if($Size < 20*512)
		{
		    LOG("    This is less than asked; no more data to record"
			." on this part, next\n");
		    if($P{FTP_Preferred})
		    {
			LOG("    Well, ftp is used... don't take it into account\n");
		    }
		    else
		    {
			next;
		    }
		}
	    }


	    # Storing the partition now
	    #
	    LOG("  * Storing the part [/dev/$P] itself\n");

	    # Modify the boot.ini file if this is a Dell system, so restored
	    # images will boot the right partition. We'll set it back afterwards.
	    #
	    # [boot loader]
	    # timeout=30
	    # default=multi(0)disk(0)rdisk(0)partition(2)\WINDOWS
	    # [operating systems]
	    # multi(0)disk(0)rdisk(0)partition(2)\WINDOWS="Microsoft Windows XP Home Edition" /fastdetect
	    #
	    if($HELP_DELL)
	    {
		LOG("    First, helping Dell. Modifying boot.ini ...\n");
		Umount("/mnt/win");
		Mount("/dev/$P", "/mnt/win", "RW");
		my $Bootini = Find_File_Whatever_Case("/mnt/win", "boot.ini");
		if($Bootini)
		{
		    Exec_Log("cp -f /mnt/win/$Bootini /tmp/.");
		    my $New = "";
		    open(S, "/tmp/$Bootini");
		    open(T, ">/tmp/$Bootini.new");
		    while(<S>)
		    {
			s/partition\(2\)/partition\(1\)/i;
			print T $_;
		    }
		    close(S);
		    close(T);
		    Exec_Log("cp -fv /tmp/$Bootini.new /mnt/win/$Bootini");
		}
		Umount("/mnt/win");
	    }

	    if(Part_Type($P) eq "8e" || Part_Type($P) eq "82")
	    {
		LOG("    Well, no we won't (Part type: ["
		    .ID_To_Type(Part_Type($P))."])\n");
	    }
	    else
	    {
		# Two methods: either update a preexisting image, then just
		# tar the new files, either create partimage/zsplit/targz
		# global files.
		#
		if(-e "$SRC/$New_Image/$P.RecFiles.txt"
		   && $P{Already_Existing_Image} =~/Update/i)
		{
		    LOG("    There's a [$SRC/$New_Image/$P.RecFiles.txt] file\n");
		    LOG("      and the user wants an Update. Let's do it.\n");

		    unless($P{Store_MD5})
		    {
			LOG("      Rectifying param Store_MD5 to 0 (not so obvious)\n");
			$P{Store_MD5} = 0;
		    }

		    # Find out what has changed
		    #
		    my(%Sizes) = ();
		    my(%MD5) = ();
		    open(R, "$SRC/$New_Image/$P.RecFiles.txt");
		    while(<R>)
		    {
			s/^\s*//;
			s/\s*$//;
			my(@F) = split(/\;/, $_);
			$Sizes{$F[0]} = (defined($F[1]) ? $F[1]:-1);
			$MD5{$F[0]} = (defined($F[2]) ? $F[2]:-1);
		    }
		    close(R);

		    my(@To_Delete) = ();
		    my(@To_Update) = ();
		    my(@To_Add) = ();

		    Mount("/dev/$P", "/mnt/win", "");

		    my $cnt = 0;

		    sub rec_Update_MD5
		    {
			my($dir) = shift || '';
			return() unless($dir && -e $dir);

			opendir(DIR, $dir);
			my(@files) = readdir(DIR);
			closedir(DIR);

			foreach my $file (@files)
			{
			    next if($file =~/^\.{1,2}$/);

			    ++ $cnt;
			    if(($cnt / 1000) == int($cnt / 1000))
			    {
				LOG("      [$cnt] file details checked\n");
			    }

			    my $path = "$dir/$file";
			    $path =~s/^\/mnt\/win//;

			    if(-d "$dir/$file")
			    {
				unless(defined($Sizes{$path}))
				{
				    push(@To_Add, $path);
				}
				rec_Update_MD5("$dir/$file");
			    }
			    else
			    {
				if(defined($Sizes{$path}))
				{
				    if((stat("$dir/$file"))[7] eq $Sizes{$path})
				    {
					if(MD5("$dir/$file") ne $MD5{$path})
					{
					    push(@To_Update, $path);
					}
				    }
				    else
				    {
					push(@To_Update, $path);
				    }
				}
				else
				{
				    push(@To_Add, $path);
				}
			    }
			}
		    }

                    rec_Update_MD5("/mnt/win");

                    foreach(keys(%Sizes))
                    {
			unless(-e "/mnt/win/$_")
			{
			    push(@To_Delete, $_);
			}
		    }

		    LOG("      Nb of removed files: [".($#To_Delete + +1)."]\n");
		    LOG("      Nb of added files: [".($#To_Add + +1)."]\n");
		    LOG("      Nb of updated files: [".($#To_Update + +1)."]\n");

                    # Update the xxx.RecFiles.txt file
                    #
                    open(D, ">$SRC/$New_Image/$P.RecFiles.txt.new");
		    open(S, "$SRC/$New_Image/$P.RecFiles.txt");
		    while(<S>)
		    {
		        s/^\s*//;
		        s/\s*$//;
		        my(@fields) = split(/\;/, $_);
		        my $flag = 0;
		        foreach my $T (@To_Delete)
		        {
			    if($T eq $fields[0])
			    {
				++ $flag;
				last;
			    }
		        }
		        unless($flag)
		        {
			    my $flag2 = 0;
			    foreach my $U (@To_Update)
			    {
				if($U eq $fields[0])
				{
				    ++ $flag2;
				    last;
				}
			    }
			    if($flag2)
			    {
				print D "$fields[0];".(stat("/mnt/win/$fields[0]"))[7]
				    .";".MD5("/mnt/win/$fields[0]")."\n";
			    }
			    else
			    {
				print D "$_\n";
			    }
		        }
		    }
		    close(S);
		    foreach(@To_Add)
		    {
		        if(-d "/mnt/win/$_")
		        {
			    print D "$_\n";
		        }
		        else
		        {
			    print D "$_;".(stat("/mnt/win/$_"))[7].";"
				.MD5("/mnt/win/$_")."\n";
		        }
		    }
                    close(D);

                    if(-e "$SRC/$New_Image/$P.RecFiles.txt.old")
                    {
                        unlink("$SRC/$New_Image/$P.RecFiles.txt.old");
                    }
                    rename("$SRC/$New_Image/$P.RecFiles.txt",
			   "$SRC/$New_Image/$P.RecFiles.txt.old");
                    rename("$SRC/$New_Image/$P.RecFiles.txt.new",
			   "$SRC/$New_Image/$P.RecFiles.txt");

                    # Tar the added / updated files
                    #
                    my $inc = 0;
                    my $inc2 = $inc;
                    while(length($inc2) < 3)
                    {
                        $inc2 = "0".$inc2;
                    }
                    while(-e "$SRC/$New_Image/$P.$inc2.tar.gz"
                          || -e "$SRC/$New_Image/$P.$inc2.tar.bz2")
                    {
                        ++ $inc;
                        $inc2 = $inc;
                        while(length($inc2) < 3)
                        {
			    $inc2 = "0".$inc2;
                        }
                    }
                    foreach(@To_Add, @To_Update)
                    {
			my $X = $_;
			$X =~s/^\///;
                        system("cd /mnt/win; "
			       ."tar rvf \"$SRC/$New_Image/$P.$inc2.tar\" \"$X\"");
                    }
                    Exec_Log((($Compression_Type eq "gzip") ? "gzip -4":"bzip2 -9")
			     ." \"$SRC/$New_Image/$P.$inc2.tar\"");

		    Umount("/mnt/win");
		}
		else
		{
		    LOG("    No Update, but an image creation for part [/dev/$P]\n");

		    if($SRC =~/dos/i)
		    {
			Umount("/mnt/dos");
			Mount("/dev/$Part_For_Storage", "/mnt/dos", "RW");
		    }

		    # Trying partimage first
		    # ...except if we've got a "Dell utility" partition. For this
		    #    one, use zsplit, even though they MIGHT be standard FAT16
		    #    parts which ID might have been renamed to 'de' (they are
		    #    always small anyway, so don't bother too much).
		    #
		    if(Part_Type($P) eq "de")
		    {
			LOG("    This is a Dell Utility part. Don't use partimage!\n");
		    }
		    elsif($P{Zsplit_Preferred})
		    {
			LOG("    The user wants zsplit to be preferred."
			    ." Don't use partimage!\n");
		    }
		    elsif($P{Tarball_Preferred})
		    {
			LOG("    The user wants tar+gz to be preferred."
			    ." Don't use partimage!\n");
		    }
		    else
		    {
			my $cmd = "partimage -f3 -z";
			if($Compression_Type eq 'bzip2')
			{
			    $cmd .= "2";
			}
			elsif($Compression_Type eq 'no compression')
			{
			    $cmd .= "0";
			}
			else
			{
			    $cmd .= "1";    # defaulted gzip anyway
			}
			$cmd .= " -b -c -d -V$PARTIMAGE_SPLIT -o"
			    ." save /dev/$P \"$SRC/$New_Image/$P\";"
			    ." sleep $PARTIMAGE_SLEEP; reset";
			LOG("    Cmd: [$cmd]\n");
			system($cmd);

			my $out = `tail --lines=15 /var/log/partimage-debug.log 2>&1`;
			LOG("    Output: (...) [$out]\n");

			# Clean temp files (due to *** glibc detected *** failures...)
			# (Careful with the rd/ida/cciss/mapper subdirs)
			#
			{
			    my $Path = "$SRC/$New_Image";
			    if($P =~/\//)
			    {
				my $tmp = $P;
				$tmp =~s/\/[^\/]+$//;
				$Path .= "/$tmp";
			    }
			    opendir(DIR, $Path);
			    my(@Files) = readdir(DIR);
			    closedir(DIR);
			    foreach(@Files)
			    {
				if(m/^pi.*\.tmp$/i)
				{
				    LOG("    Deleting a remaining tmp file: [$_]\n");
				    unlink("$Path/$_");
				}
			    }
			}
		    }

		    # If partimage has failed (LVM parts, for example, or just any
		    # reason), or if the user has set Zsplit_Preferred to Y, use
		    # zsplit. Then, of course, the dd-ed 20 first sectors are
		    # redundant and must be deleted.
		    #
		    if(! -e "$SRC/$New_Image/$P.000" && ! $P{Tarball_Preferred})
		    {
			LOG("    ! Partimage has not written any partition.\n");
			LOG("      We'll use zsplit.\n");
			my $cmd = "zsplit -s ".$ZSPLIT_SPLIT."M -N $P -o"
			    ." \"$SRC/$New_Image\" -d /dev/$P";
			LOG("    Cmd: [$cmd]\n");
			system($cmd);
			if(-e "./debug.log")
			{
			    my $out = "";
			    open(DB, "./debug.log");
			    while(<DB>)
			    {
				$out .= $_;
			    }
			    close(DB);
			    unlink("./debug.log");
			    LOG("    Output: [$out]\n");
			}
			if(-e "$SRC/$New_Image/$P.first_sectors")
			{
			    LOG("    Deleting [$P.first_sectors], now useless\n");
			    unlink("$SRC/$New_Image/$P.first_sectors");
			}
		    }

		    # Finally, use tar+gzip.
		    #
		    if($P{Tarball_Preferred})
		    {
			LOG("    Using tar+gzip to store the filesystem's contents.\n");
			Umount("/mnt/win");
			Mount("/dev/$P", "/mnt/win", "");

			my $cnt = 0;
			open(TC, ">$SRC/$New_Image/$P.Tarballs.txt");

			my(@Dirs) = ();
			my(@Root_Files) = ();
			{
			    opendir(DIR, "/mnt/win");
			    my(@Files) = readdir(DIR);
			    closedir(DIR);
			    foreach(@Files)
			    {
				next if(m/\.{1,2}$/);
				if(-d "/mnt/win/$_")
				{
				    push(@Dirs, $_);
				}
				else
				{
				    push(@Root_Files, $_);
				}
			    }
			}

			my $A = "";
			my $B = "";
			if($Compression_Type eq "gzip")
			{
			    $A = ".gz";
			    $B = "|gzip -1 -";
			}
			elsif($Compression_Type eq "bzip2")
			{
			    $A = ".bz2";
			    $B = "|bzip2 -9 -";
			}

			foreach my $Dir (@Dirs)
			{
			    my $Cnt = $cnt;
			    while(length($Cnt) < 3)
			    {
				$Cnt = "0$Cnt";
			    }

			    LOG("      - Storing dir [$Dir] to [$P.$Cnt.tar$A]\n");

			    my $cmd = "cd /mnt/win; tar cf -"
				." \"$Dir\" $B >$SRC/$New_Image/$P.$Cnt.tar$A";

			    Exec_Log($cmd, 8);

			    print TC "$P.$Cnt.tar$A --- $Dir\n";
			    ++ $cnt;
			}

			# And the files in the root
			#
			my $Cnt = $cnt;
			while(length($Cnt) < 3)
			{
			    $Cnt = "0$Cnt";
			}
			my $cmd = "cd /mnt/win; tar cf - ";
			foreach(@Root_Files)
			{
			    $cmd .= "\"$_\" ";
			}
			$cmd .= " >$SRC/$New_Image/$P.$Cnt.tar$A";
			Exec_Log($cmd, 8);

			print TC "$P.$Cnt.tar$A --- /\n";
			close(TC);

			Umount("/mnt/win");
		    }
		}

		# Now the partition has been recorded, it might be
		# interesting to store the MD5 checksum of all imaged
		# files, in order to be able to make incremental
		# backuping next time (updating an existing image).
		#
		LOG("    Will we store details about each included file ?\n");

		if($P{Store_MD5})
		{
		    LOG("      Yes!\n");
		    Mount("/dev/$P", "/mnt/dos", "");

		    my $cnt = 0;
		    open(TC, ">$SRC/$New_Image/$P.RecFiles.txt");

		    sub rec_Store_MD5
		    {
			my($dir) = shift || '';
			return() unless($dir && -e $dir);

			opendir(DIR, $dir);
			my(@files) = readdir(DIR);
			closedir(DIR);

			foreach my $file (@files)
			{
			    next if($file =~/^\.{1,2}$/);

			    ++ $cnt;
			    if(($cnt / 1000) == int($cnt / 1000))
			    {
				LOG("      [$cnt] file details stored\n");
			    }

			    my $path = "$dir/$file";
			    $path =~s/^\/mnt\/dos//;

			    if(-d "$dir/$file")
			    {
				print TC "$path\n";
				rec_Store_MD5("$dir/$file");
			    }
			    else
			    {
				print TC "$path;".(stat("$dir/$file"))[7].";"
				    .MD5("$dir/$file")."\n";
			    }
			}
		    }

                    rec_Store_MD5("/mnt/dos");

		    close(TC);

		    Umount("/mnt/dos");
		}
		else
		{
		    LOG("      No!\n");
		}
	    }

	    if($HELP_DELL)
	    {
		LOG("    Finally, helping Dell. Restore original boot.ini ...\n");
		Mount("/dev/$P", "/mnt/dos", "RW");
		my $Bootini = Find_File_Whatever_Case("/mnt/dos", "boot.ini");
		if($Bootini && -e "/tmp/$Bootini")
		{
		    Exec_Log("cp -fv /tmp/$Bootini /mnt/dos/.");
		}
		Umount("/mnt/dos");
	    }
	}

	# Finally, save a Dell user. We have saved only the part
	# containing the data. Now, why not tell PING that when it
	# restores the disk, it uses all available disk space ?
	#
	# Also, make sure saved files will be hda1 and not hda2.
	#
	if($HELP_DELL)
	{
	    LOG("* Helping Dell for a better restore of this image...\n");
	    LOG("  => Preparing file \"$SRC/$New_Image/HDD_Target.txt\"\n");

	    if($SRC =~/dos/i)
	    {
		Umount("/mnt/dos");
                Mount("/dev/$Part_For_Storage", "/mnt/dos", "RW");
            }

	    my(@S_Dev_Rich) = ();
	    HDD_Get_From_Exact_Look("$SRC/$New_Image/HDD_Look.txt", \@S_Dev_Rich);

	    foreach my $D (@S_Dev_Rich)
	    {
		for(my $i = 0; $i <= $#{ $D->{Parts} }; $i++)
		{
		    if($D->{Parts}->[$i] eq 'null')
		    {
			next;
		    }
		    if(Is_Dell_Type($D->{Types}->[$i]))
		    {
			$D->{Parts}->[$i] = 'null';
			$D->{Start}->[$i] = 'null';
			$D->{End}->[$i] = 'null';
			next;
		    }
		    else
		    {
			$D->{Start}->[$i] = 1;
			$D->{Parts}->[$i] = $D->{Dev}."1";
			$D->{Boot_Flags}->[$i] = 1;
		    }
		}
	    }

	    open(T, ">$SRC/$New_Image/HDD_Target.txt");
	    print T HDD_Exact_Look(\@S_Dev_Rich);
	    close(T);

	    opendir(DIR, "$SRC/$New_Image");
	    my(@files) = readdir(DIR);
	    closedir(DIR);
	    foreach(@files)
	    {
		if(m/^(h|s)da2/)
		{
		    my $N = $_;
		    $N =~s/da2/da1/;
		    LOG("     Renaming [$_] to [$N]\n");
		    rename("$SRC/$New_Image/$_", "$SRC/$New_Image/$N");
		}
	    }

	    LOG("   Creating a blank [$SRC/$New_Image/Help_Dell] file,\n");
	    LOG("     so PING will know when restoring that the BS is KO\n");
	    open(D, ">$SRC/$New_Image/Help_Dell");
	    close(D);
	}
    }

    elsif($Entity =~/Blank_Local_Admin_Passwd/i)
    {
	LOG("* Blanking local admin's password!\n");

	foreach my $D (@Dev_Rich)
	{
	    foreach my $P (@{ $D->{Parts} })
	    {
		LOG("  * Trying to blank local admin's password on [/dev/$P]\n");

		LOG("    Mounting [/dev/$P]\n");
		Umount("/mnt/dos");
		Mount("/dev/$P", "/mnt/dos", "RW");

		unless(Is_Mounted("/dev/$P"))
		{
		    LOG("      This part could not be mounted. Next.\n");
		    next;
		}

		my $SAM = "/mnt/dos";

		my $found = 0;
		opendir(DIR, $SAM);
		my @files = readdir(DIR);
		closedir(DIR);
		foreach(@files)
		{
		    if(m/^windows$/i)
		    {
			$SAM .= "/$_";
			$found = 1;
			last;
		    }
		}
		unless($found)
		{
		    LOG("      No [windows] dir could be found. Next.\n");
		    next;
		}

		$found = 0;
		opendir(DIR, $SAM);
		@files = readdir(DIR);
		closedir(DIR);
		foreach(@files)
		{
		    if(m/^system32$/i)
		    {
			$SAM .= "/$_";
			$found = 1;
			last;
		    }
		}
		unless($found)
		{
		    LOG("      No [system32] dir could be found. Next.\n");
		    next;
		}

		$found = 0;
		opendir(DIR, $SAM);
		@files = readdir(DIR);
		closedir(DIR);
		foreach(@files)
		{
		    if(m/^config$/i)
		    {
			$SAM .= "/$_";
			$found = 1;
			last;
		    }
		}
		unless($found)
		{
		    LOG("      No [config] dir cound be found. Next.\n");
		    next;
		}

		$found = 0;
		opendir(DIR, $SAM);
		@files = readdir(DIR);
		closedir(DIR);
		foreach(@files)
		{
		    if(m/^SAM$/i)
		    {
			$SAM .= "/$_";
			$found = 1;
			last;
		    }
		}
		unless($found)
		{
		    LOG("      No [SAM] file could be found. Next.\n");
		    next;
		}

		LOG("    Addressing SAM file [$SAM]\n");

		LOG("    Is local admin locked ?\n");
		my $cmd = "chntpw -l \"$SAM\" >/tmp/BLA";
		LOG("    Cmd: [$cmd]\n");
		system($cmd);

		my $Status = 0;
		if(-e "/tmp/BLA")
		{
		    open(DB, "/tmp/BLA");
		    while(<DB>)
		    {
			if(m/\<Administrator\>/i && m/locked/i)
			{
			    ++ $Status;
			    last;
			}
		    }
		    close(DB);
		    unlink("/tmp/BLA");
		}
		my $Unlock = "";
		if($Status)
		{
		    $Unlock = "y\n";
		    LOG("      It was locked.\n");
		}
		else
		{
		    LOG("      Was not.\n");
		}

		$cmd = "echo -e \"$Unlock*\ny\ny\n\"|chntpw -u Administrator $SAM";
		LOG("    Cmd: [$cmd]\n");
		system($cmd);

		LOG("    Unmounting [/mnt/dos]\n");
		Umount("/mnt/dos");
	    }
	}
    }
}
else
{
    # This is an image that is going to be restored.

    LOG("* Image restoration\n");

    my @Label_CMD = ();

    my(@F) = ();
    my(@FF) = ();
    my $Multi = 0;
    Read_Dir(\@F, \@FF, \$Multi);       # Will feed @F, @FF and $Multi

    # Only for logging purpose, get more info on files
    #
    LOG("* Get details about each file to restore (debug purpose)\n");
    foreach my $FF (@FF)
    {
	my $short = ".../".(split(/\//, $FF))[-1];
	LOG("  [$short] => size [".(stat($FF))[7]."]\n");
    }

    # If there's an HDD_Look.txt file, let's see what is inside...
    #
    my(@Image_Dev_Rich) = ();
    foreach my $FF (@FF)
    {
	if($FF =~/\/HDD_Look\.txt$/i)
	{
	    LOG("\n");
	    LOG("* There's an HDD_Look.txt file... get the info\n");
	    HDD_Get_From_Exact_Look($FF, \@Image_Dev_Rich);

	    LOG("\n");
	    LOG("* Info from the HDD_Look.txt file: (Image_Dev_Rich)\n");
	    HDD_Describe(\@Image_Dev_Rich);
	}
    }

    # Some users may not want to have the BIOS settings restored.
    # It's time to ask the user about this point.
    #
    foreach my $CurrFF (@FF)
    {
	if($CurrFF =~/\/bios$/i)
	{
	    LOG("* There's a [bios] file; what will we do with it ?\n");

	    if(defined($P{Replace_BIOS}))
	    {
		LOG("  There's a preconfig entry; worth [$P{Replace_BIOS}]\n");
		if($P{Replace_BIOS} =~/^(y|1)$/i)
		{
		    $P{Replace_BIOS} = 1;
		}
		else
		{
		    $P{Replace_BIOS} = 0;
		}
	    }
	    else
	    {
		LOG("  There's no preconfig predefined info\n");

		if(defined($P{AUTO}) && $P{AUTO})
		{
		    LOG("  AUTO param set! don't ask, replace\n");
		    $P{Replace_BIOS} = 1;
		}
		else
		{
		    LOG("  AUTO param not set... let's ask the user\n");

		    my $cmd = 'dialog --colors --menu "\Zb\ZnBIOS settings have been'
			.' recorded on your image. Do you want them to be restored ?'
			.'\n\n" 12 50 2 '
			.' "Yes" "" "No" "" 2>/tmp/checklist.tmp';
		    system($cmd);

		    if(-z "/tmp/checklist.tmp")
		    {
			Quit();
		    }

		    open(DB, "/tmp/checklist.tmp");
		    while(<DB>)
		    {
			if(m/Y/i)
			{
			    $P{Replace_BIOS} = 1;
			    last;
			}
		    }
		    close(DB);
		    unlink("/tmp/checklist.tmp");
		}
	    }

	    last;
	}
    }
    unless($P{Replace_BIOS})
    {
	$P{Replace_BIOS} = 0;
    }

    LOG("  Replace BIOS =>[$P{Replace_BIOS}]\n");

    # If there's another addon-*.tar.gz/zip file, apply it now.
    #
    foreach my $CurrFF (@FF)
    {
	if($CurrFF =~/\/addon(\-|_).*\.tar\.gz$/i || $CurrFF =~/\/addon(\-|_).*\.zip$/i)
	{
	    LOG("* There's a [addon-*.tar.gz] file; unzip to /\n");
	    Exec_Log("cd /; ".Unzip($CurrFF), 2);
	}
    }

    # If there are new parameters specific to the chosen image, get them
    # and overwrite those passed either by the APPEND line of the kernel,
    # either by the system /etc/ping.conf file.
    #
    foreach my $CurrFF (@FF)
    {
	if($CurrFF =~/\/ping\.conf$/i)
	{
	    LOG("* There's a [ping.conf] file; get parameters\n");

	    Get_Parameters($CurrFF, \@CONFIG_FIELDS, \%P);

	    foreach(@CONFIG_FIELDS)
	    {
		if(defined($P{$_}))
		{
		    LOG("  Param: [$_] = [".((m/^Passwd$/) ? "xxx":$P{$_})."]\n");
		}
	    }
	}
    }

    # LAST Warning. If some data has been detected on the local drive, and
    # if the chosen image is definitely going to overwrite it, ask for a
    # last confirmation. Except if AUTO is on.
    #
    LOG("* Maybe a last R-U-Sure screen ?...\n");

    if((defined($P{AUTO}) && $P{AUTO}) || $P{Dont_Warn_Me})
    {
	LOG("  AUTO or Dont_Warn_Me param set! No R-U-Sure screen.\n");
    }
    else
    {
	LOG("  Let's see if some local data will be overwritten.\n");

	my(%Parts_Dirs) = ();
	foreach my $D (@Dev_Rich)
	{
	    for(my $i = 0; $i <= $#{ $D->{Parts} }; $i++)
	    {
		if($D->{Dirs}->[$i])
		{
		    $Parts_Dirs{$D->{Parts}->[$i]} = $D->{Dirs}->[$i];
		}
	    }
	}
	my $N = keys(%Parts_Dirs);

	my $Warning = "This is the LAST WARNING\n"
	    ."------------------------\n\n";

	if($N > 0)
	{
	    $Warning .= "On the LOCAL hard disk drive of this computer, we have"
		." found some data. Here are a list of partitions which filesystems"
		." we have been able to read, and the directories we have found"
		." in them.\n\n";

	    foreach(keys(%Parts_Dirs))
	    {
		if($_ eq 'null')
		{
		    -- $N;
		}
		else
		{
		    $Warning .= "- Partition [".ADT($_, \@Dev_Rich)
			."] => Directories: [$Parts_Dirs{$_}]\n\n";
		}
	    }
	}

	$Warning .= "On the REMOTE side, the image you've asked to restore"
	    ." contains the following files. Please, make sure you understand"
	    ." what their restoration by PING will do before going on. Read"
	    ." the Howto or visit the PING Forum if you need more information.\n\n";

	my $flag = 0;
	foreach my $FF (@FF)
	{
	    my @fields = split(/\//, $FF);
	    my $F = $fields[-1];
	    #
	    # Section added for cciss compatibility (HP SmartArray), + rd + ida.
	    #
	    foreach('cciss', 'rd', 'ida', 'mapper')
	    {
		if($fields[$#fields - 1] =~/^$_$/)
		{
		    $F = "$_/$F";
		}
	    }

	    if($F =~/[hs]d[abcdef]$/
	       || $F =~/(cciss|ida|rd|mapper)\/c\d{1}d\d{1,2}$/
	       || $F =~/mapper\/VolGroup\d{2}\-LogVol\d{2}$/
	       || $F =~/askme_[abcdef]$/i)
	    {
		$Warning .= "- [$F] => will overwrite the master boot record and"
		    ." partition table of device [/dev/".ADT($F, \@Dev_Rich)."]\n\n";
		++ $flag;
	    }
	    elsif($F =~/^bios$/i)
	    {
		$Warning .= "- [$F] => Will overwrite your BIOS settings, unless"
		    ." explicitely declined. Be aware that if you apply the BIOS"
		    ." settings of one specific hardware model to another one,"
		    ." you might be forced to restore factory settings to fix"
		    ." your box.\n\n";
		++ $flag;
	    }
	    elsif($F =~/\.first_sectors$/ || $F =~/\.fir$/)
	    {
		my $Dev = $F;
		$Dev =~s/\.fir.*$//i;
		$Warning .= "- [$F] => Will overwrite the 20 first sectors of"
		    ." partition [/dev/".ADT($Dev, \@Dev_Rich)."]. If mistakenly"
		    ." applied to a partition, data might become inaccessible"
		    ." (note: if this happens accidentally, use testdisk).\n\n";
		++ $flag;
	    }
	    elsif($F =~/_0\.spl\.zp$/ || $F =~/\.000$/)
	    {
		my $Dev = $F;
		$Dev =~s/_0\.spl\.zp$//;
		$Dev =~s/\.000$//;
		$Warning .= "- [$F] => Will overwrite the contents of partition"
		    ." [/dev/".ADT($Dev, \@Dev_Rich)."]. If mistakenly applied"
		    ." to a partition, data will be lost.\n\n";
		++ $flag;
	    }
	}

	$Warning .= "Please, type YES and [Enter] to go on with the restoration"
	    ." process. If you are not sure about the result, or feel you need"
	    ." a backup, just hit Ctrl-Alt-Del to reboot your computer or type"
	    ." NO and [Enter] to exit PING.\n";

	if($N > 0 && $flag)
	{
	    LOG("  Issuing this warning: [$Warning]\n");

	    open(W, ">/tmp/warning");
	    print W wrap('', '', $Warning);
	    close(W);

	    system("reset; clear; more /tmp/warning");
	    unlink("/tmp/warning");
	    print "\n>> ";

	    my $Grab = '';
	    my $cnt = 0;
	    while($Grab !~/yes/i)
	    {
		if($cnt)
		{
		    print "\nPlease, type YES or NO.\n\n>> ";
		}

		$Grab = <STDIN>;

		if($Grab =~/no/i)
		{
		    Quit();
		}

		++ $cnt;
	    }
	}
	else
	{
	    LOG("  No warning to issue.\n");
	}
    }

    # Then, apply boot sectors and bios settings
    #
    foreach my $FF (@FF)
    {
	my @fields = split(/\//, $FF);
	my $F = $fields[-1];
	#
	# Section added for cciss compatibility (HP SmartArray), + rd + ida.
	#
	foreach('cciss', 'rd', 'ida', 'mapper')
	{
	    if($fields[$#fields - 1] =~/^$_$/)
	    {
		$F = "$_/$F";
	    }
	}
	#
	# Search for both standard linux devices and cciss/rd/ida/mapper
	# devices to restore
	#
	if($F =~/[hs]d[abcdef]$/
	   || $F =~/(cciss|ida|rd|mapper)\/c\d{1}d\d{1,2}$/
	   || $F =~/mapper\/VolGroup\d{2}\-LogVol\d{2}$/
	   || $F =~/askme_[abcdef]$/i)
	{
	    LOG("  * Restore [/dev/".ADT($F, \@Dev_Rich)."]\n");

	    # Well, unless PING is about to overwrite the source it is to
	    # get the images from !
	    #
	    if($SRC =~/dos/
	       && ((Is_Mounted("/mnt/dos")
		    && HDD_Name(Dev_Mount_Point("/mnt/dos")) eq ADT($F, \@Dev_Rich))
		   || ($Part_For_Storage
		       && HDD_Name($Part_For_Storage) eq ADT($F, \@Dev_Rich))))
	    {
		LOG("    Won't overwrite the bootsector of HDD ["
		    .ADT($F, \@Dev_Rich)."]\n");
		LOG("      because it contains the image to restore!\n");
		sleep($README_SLEEP);
	    }
	    else
	    {
		my $cmd = "dd if=$FF of=/dev/".ADT($F, \@Dev_Rich)." 2>&1";
		LOG("    Cmd: [$cmd]\n");
		my $out = `$cmd`;
		LOG("    Out: [$out]\n");
		LOG("    Synchronizing [/dev/".ADT($F, \@Dev_Rich)."]\n");
		Synchronize_Device(ADT($F, \@Dev_Rich));
		++ $Have_Restored;
	    }
	}
	elsif($F =~/^bios$/i)
	{
	    LOG("  * Found [bios] file\n");
	    if($P{Replace_BIOS})
	    {
		my $cmd = "echo 2|cmospwd -r \"$FF\" >/dev/null 2>&1";
		LOG("    Cmd: [$cmd]\n");
		system($cmd);
	    }
	    else
	    {
		LOG("    BIOS settings must not be replaced. No action.\n");
	    }
	}
    }

    # Now, partitioning instructions, if any
    #
    foreach my $FF (@FF)
    {
	my @fields = split(/\//, $FF);
	my $F = $fields[-1];
	foreach('cciss', 'rd', 'ida', 'mapper')
	{
	    if($fields[$#fields - 1] =~/^$_$/)
	    {
		$F = "$_/$F";
	    }
	}
	#
	# Search for both standard linux devices and cciss/rd/ida devices to restore
	#
	if($F =~/[hs]d[abcdef]\.part$/i
	   || $F =~/(cciss|rd|ida|mapper)\/c\d{1}d\d{1,2}\.part$/i
	   || $F =~/mapper\/VolGroup\d{2}\-LogVol\d{2}\.part$/)
	{
	    LOG("  * Found [$F] => partitioning instructions.\n");
	    LOG("    Sorry! these old .part files are not used any more.\n");
	    LOG("    Use a HDD_Target.txt file instead. Please, refer to\n");
	    LOG("    the Howto for more details.\n\n");
	    LOG("    !!! Aborting.\n");
	    sleep($FAILED_SLEEP);
	    Quit();
	}
	elsif($F =~/^HDD_Target\.txt$/i)
	{
	    LOG("  * Found [$F] => partitioning instructions.\n");

	    my(@Target_Dev_Rich) = ();
	    HDD_Get_From_Exact_Look($FF, \@Target_Dev_Rich);

	    LOG("\n");
	    LOG("    Info from the HDD_Target.txt file: (Target_Dev_Rich)\n");
	    HDD_Describe(\@Target_Dev_Rich);

	    HDD_Discover(\@Dev, \@Dev_Rich);
	    foreach my $D (@Dev)
	    {
		# Delete all parts...
		#
		Delete_All_Parts($D, \@Dev_Rich);

		# Recreate partitions
		#
		Recreate_All_Parts($D, \@Target_Dev_Rich);
	    }

	    HDD_Discover(\@Dev, \@Dev_Rich);
	    LOG("    New HDD Description (Dev_Rich)\n");
	    HDD_Describe(\@Dev_Rich);
	}
    }

    # We have just applied a new boot sector in the restoration
    # process, or recreated partitions according to instructions.
    # We've got the possibility to detect NOW that there will or
    # will not be segments of unused space in the partition table.
    # Detecting it now brings 2 advantages:
    # - raise a possible question before long operations start
    # - let the script maximize the filesystems as usually, after
    #   the contents have been restored.
    #
    LOG("  * Inspecting the new restored partition table, so\n");
    LOG("    to see if empty segments can be used.\n");

    my(@Restored_Dev_Rich) = ();
    HDD_Discover(\@Dev, \@Restored_Dev_Rich);

    LOG("    * Results of device discovery (Restored_Dev_Rich)\n");
    HDD_Describe(\@Restored_Dev_Rich);

    LOG("    * Has the user passed a param telling what to do ?\n");
    if(defined($P{Extend_Parts_Whenever_Possible}))
    {
	LOG("      Extend_Parts_Whenever_Possible:"
	    ." [$P{Extend_Parts_Whenever_Possible}]\n");
    }
    else
    {
	LOG("      No.\n");
    }

    if(! defined($P{Extend_Parts_Whenever_Possible})
       || $P{Extend_Parts_Whenever_Possible} =~/1|Y/i)
    {
	LOG("    * Let's see if there's at least one segment to extend\n");

	# Here, we'll prepare to extend only partitions, not filesystems.
	# We can *only* extend to the right direction (->).
	#
	# Whatever the partition, if it's followed by a null segment...:
	# - if the partition is an ext'd or a primary, just extend
	# - if it's a logical, then extend no further than the end of
	#   the ext'd part containing the partition followed by a null seg'.
	#
	my $Could_Extend = 0;

	foreach my $D (@Restored_Dev_Rich)
	{
	    LOG("      Device: [".$D->{Dev}."]\n");

	    for(my $i = 0; $i <= $#{ $D->{Parts} }; $i++)
	    {
		LOG("        Partition [$i]\n");
		LOG("          Name: [".$D->{Parts}->[$i]."]\n");
		LOG("          Start: [".$D->{Start}->[$i]."]\n");
		LOG("          End: [".$D->{End}->[$i]."]\n");

		my $Loop = 1;

		while($Loop)
		{
		    $Loop = 0;

		    for(my $j = 0; $j <= $#{ $D->{Parts} }; $j++)
		    {
			if($D->{Start}->[$j] == ($D->{End}->[$i] + 1)
			   && $D->{Parts}->[$j] eq 'null')
			{
			    LOG("          Null segment [".$D->{Parts}->[$j]
				."] after\n");
			    LOG("            Start: [".$D->{Start}->[$j]."]\n");
			    LOG("            End: [".$D->{End}->[$j]."]\n");

			    my $Not_Logical = 0;

			    if(Is_Part_Extended(Part_Type($D->{Parts}->[$i])))
			    {
				LOG("           This part was an ext'd one;"
				    ." extend more\n");
				++ $Not_Logical;

				# Extend the ext'd; the last part contained in the ext'd
				# will need being extended too.
				#
				$D->{End}->[$i] = $D->{End}->[$j];
				LOG("             New End for part [$i]: ["
				    .$D->{End}->[$i]."]\n");
			    }

			    my $d = Is_Part_Logical($i, $D);

			    if($d == -1)
			    {
				LOG("           This part was primary; extend more\n");
				++ $Not_Logical;

				# Extend the primary
				#
				$D->{End}->[$i] = $D->{End}->[$j];
				LOG("            New End for part [$i]: ["
				    .$D->{End}->[$i]."]\n");
			    }

			    if(! $Not_Logical)
			    {
				LOG("         This part was logical, contained"
				    ." in [$d]\n");

				# Extend the logical (same thing as extending a primary,
				# because HDD_Discover() will enumerate two null
				# segments if they are contiguous while first one
				# is contained in an ext'd and the second one is not.
				#
				$D->{End}->[$i] = $D->{End}->[$j];
				LOG("            New End for part [$i]: ["
				    .$D->{End}->[$i]."]\n");
			    }

			    # Suppress the null segment
			    #
			    $D->{Start}->[$j] = 'null';
			    $D->{End}->[$j] = 'null';

			    ++ $Loop;
			    ++ $Could_Extend;
			}
		    }
		}
	    }
	}

	if($Could_Extend)
	{
	    if(! defined($P{Extend_Parts_Whenever_Possible}))
	    {
		LOG("    * At least one segment to extend. Ask the user.\n");

		my $cmd = 'dialog --colors --menu "\Zb\ZnWe have detected that'
		    .' there will be extra space on your drive after restoring'
		    .' the image. We can extend partitions for you, so this'
		    .' space will be available on your system. Do you want'
		    .' us to do so ?\n\n" 13 50 2 '
		    .' "Yes" "" "No" "" 2>/tmp/checklist.tmp';
		system($cmd);

		if(-z "/tmp/checklist.tmp")
		{
		    Quit();
		}

		open(DB, "/tmp/checklist.tmp");
		while(<DB>)
		{
		    if(m/Y/i)
		    {
			$P{Extend_Parts_Whenever_Possible} = 1;
			last;
		    }
		}
		close(DB);
		unlink("/tmp/checklist.tmp");

		LOG("      =>[".($P{Extend_Parts_Whenever_Possible} ? "Yes!":"No")
		    ."]\n");
	    }

	    if($P{Extend_Parts_Whenever_Possible} =~/1|Y/i)
	    {
		LOG("    * Let's extend.\n");

		foreach my $D (@Restored_Dev_Rich)
		{
		    Delete_All_Parts($D->{Dev}, \@Restored_Dev_Rich);
		    Recreate_All_Parts($D->{Dev}, \@Restored_Dev_Rich);
		    LOG("    New HDD Description (Restored_Dev_Rich)\n");
		    HDD_Describe(\@Restored_Dev_Rich);
		}

		# Re-discover, as we might have extended.
		#
		LOG("      Re-discover, as we might have extended.\n");
		HDD_Discover(\@Dev, \@Dev_Rich);
	    }
	}
	else
	{
	    LOG("    * No segment to extend. Go on.\n");
	}
    }

    # When playing with segments, we umount many things...
    #
    if($Part_For_Storage)
    {
	LOG("  * Remount [/dev/$Part_For_Storage] on [$SRC]\n");
	Mount("/dev/$Part_For_Storage", "/mnt/dos", "");
    }

    # Now, scans the image directory for any other action
    #
    {
	my $Go_On = 1;    # Rescan the dir after each cd span
                          # When no more work is done, break.
	my @Applied = (); # Tracks all applied files. To break.

	while($Go_On)
	{
	    $Go_On = 0;
	    if($SRC =~/cdrom/i)
	    {
		Umount("/dev/$CD_Dev");
		Exec_Log("mount /dev/$CD_Dev /mnt/cdrom", 4);
	    }
	    Read_Dir(\@F, \@FF, \$Multi);       # Will feed @F, @FF and $Multi

	    foreach my $FF (@FF)
	    {
		my @fields = split(/\//, $FF);
		my $F = $fields[-1];
		foreach('cciss', 'rd', 'ida', 'mapper')
		{
		    if($fields[$#fields - 1] =~/^$_$/)
		    {
			$F = "$_/$F";
		    }
		}

		unless($F =~/\.000$/ || $F =~/\.zip$/i || $F =~/\.first_sectors$/
		       || $F =~/\.fir$/ || $F =~/_0\.spl\.zp$/ || $F =~/MULTI$/i
		       || Is_Archive($F) || $F =~/Help_Dell$/i
		       || $F =~/\.RecFiles\.txt$/i)
		{
		    next;
		}

		LOG("  * Restore [/dev/... from $F] with [$FF]\n");

		my $Already_Applied = 0;
		foreach my $A (@Applied)
		{
		    if($A eq $FF)
		    {
			++ $Already_Applied;
			last;
		    }
		}
		if($Already_Applied)
		{
		    LOG("    Already applied. Next, if any.\n");
		    next;
		}

		if($FF =~/\.first_sectors$/ || $FF =~/\.fir$/)
		{
		    my $Dev = $F;
		    $Dev =~s/\.first_sectors$//;
		    $Dev =~s/\.fir$//;

		    # Well, unless PING is about to overwrite the source it is to
		    # get the images from !
		    #
		    if($SRC =~/dos/
		       && ((Is_Mounted("/mnt/dos")
			    && HDD_Name(Dev_Mount_Point("/mnt/dos"))
			    eq ADT($Dev, \@Dev_Rich))
			   || ($Part_For_Storage
			       && HDD_Name($Part_For_Storage) eq ADT($Dev,\@Dev_Rich))))
		    {
			LOG("    Won't overwrite the first sectors of ["
			    .ADT($Dev, \@Dev_Rich)."]\n");
			LOG("      because it contains the image to restore!\n");
			sleep($README_SLEEP);
		    }
		    else
		    {
			Exec_Log("dd if=\"$FF\" of=/dev/".ADT($Dev, \@Dev_Rich), 4);
			LOG("    Synchronizing [/dev/".ADT($Dev, \@Dev_Rich)."]\n");
			Synchronize_Device(ADT($Dev, \@Dev_Rich));

			# Give LVM a chance to succeed
			# Note: when it comes to LVM, it's crucial that
			#   .first_sectors/.fir files should be restored before
			#   the parts themselves, for the vgchange command
			#   to succeed. This is the case, thanks to the
			#   Read_Dir() function.
			#
			Exec_Log("vgscan; vgchange -ay; dmraid -ay", 4);
		    }

		    push(@Applied, $FF);
		    $Go_On = 1;
		}

		# When zsplitted, filename is hda5_0.spl.zp (and _1.spl.zp etc.)
		#
		# We can send the multivol argument to partimage even if all
		# files are accessible, as partimage can detect that it has
		# finished its job. Well, unzsplit cannot. So, the rule will
		# be arbitrary and utterly unfair :) if we've got to
		# unzsplit hda1* files and there's an empty MULTI file in
		# the directory and there's at least one hdaX* file with
		# X > 1 (the 1 of hda1), then no multivol argument.

		if($FF =~/_0\.spl\.zp$/)
		{
		    my $Dev = $F;
		    $Dev =~s/_0\.spl\.zp$//;

		    my $Multi_Concerned = 0;

		    # Well, unless PING is about to overwrite the source it is to
		    # get the images from !
		    #
		    if($SRC =~/dos/
		       && ((Is_Mounted("/mnt/dos")
			    && HDD_Name(Dev_Mount_Point("/mnt/dos"))
			    eq ADT($F, \@Dev_Rich))
			   || ($Part_For_Storage
			       && HDD_Name($Part_For_Storage) eq ADT($F, \@Dev_Rich))))
		    {
			LOG("    Won't overwrite the partition ["
			    .ADT($Dev, \@Dev_Rich)."]\n");
			LOG("      because it contains the image to restore!\n");
			sleep($README_SLEEP);
		    }
		    else
		    {
			Umount("/dev/".ADT($Dev, \@Dev_Rich));
			#
			# unzsplit dislikes SMB mounts, but stops complaining
			# if given a symlink... (or cat | unzsplit -c). Well...
			# Better create now a huge number of symlinks, as there
			# may be a multivol, and we won't know how many are needed.
			# BTW, unzsplit does not tolerate paths like /tmp///toto.
			#
			# Now, unzsplit is happy again.
			#
			my $Base = $F;
			$Base =~s/\_0.spl.zp$//;
			Exec_Log("rm -f /tmp/$Base.*.spl.zp", 4);
			my $A = "$SRC/$Entity/$Base"."_*.spl.zp";
			$A =~s/\/+/\//g;
			Exec_Log("cd /tmp; ln -sf $A .", 4);

			LOG("    Is [".ADT($Dev, \@Dev_Rich)
			    ."] concerned by the general MULTI flag ?\n");
			$Multi_Concerned = 1;
			{
			    my $Number = $F;
			    $Number =~s/^(\D)*//;
			    $Number =~s/(\D).*$//;
			    LOG("      Current part's number: [$Number]\n");
			    opendir(DIR, "$SRC/$Entity");
			    my(@Files) = readdir(DIR);
			    closedir(DIR);
			    my $Found = 0;
			    foreach my $File (@Files)
			    {
				next if($File =~/^\.{1,2}$/);
				if($File eq $F)
				{
				    ++ $Found;
				    next;
				}
				if($Found)
				{
				    my $Number2 = $File;
				    $Number2 =~s/^(\D)*//;
				    $Number2 =~s/(\D).*$//;
				    next if($Number2 !~/^\d+$/);
				    LOG("      Number of [$File]: [$Number2]\n");
				    if($Number2 > $Number)
				    {
					$Multi_Concerned = 0;
					last;
				    }
				}
			    }
			}
			LOG("      ".($Multi_Concerned ? "Yes":"No")."!\n");
		
			if($Multi && $Multi_Concerned)
			{
			    # Don't call Umount() here, obviously enough :)
			    #
			    my $Todo = "umount /mnt/cdrom >/dev/null 2>&1;"
				." eject /dev/cdrom >/dev/null 2>&1;"
				." sleep 20;"
				." mount /dev/cdrom /mnt/cdrom >/dev/null 2>&1;"
				." cd /tmp; ln -sf $A .";
			    my $cmd = "/etc/rc.d/rc.multi unzsplit \"$Todo\" &";
			    LOG("    * Multivol! Cmd: [$cmd]\n");
			    system($cmd);
			    $Go_On = 1;
			}
			#
			# -d brings the debug log into a file. If multivol,
			# you'll prefer see it on the screen.
			#
			Exec_Log("cd /tmp; unzsplit "
				 .($Multi && $Multi_Concerned ? "-m":"")
				 ." -d -D /dev/".ADT($Dev, \@Dev_Rich)." $Dev", 4);
		    }

		    push(@Applied, $FF);
		    $Go_On = 1;
		    if(-e "/tmp/debug.log")
		    {
			my $out = "";
			open(DB, "/tmp/debug.log");
			while(<DB>)
			{
			    $out .= $_;
			}
			close(DB);
			unlink("/tmp/debug.log");
			LOG("      Output: [$out]\n");
		    }
		    Exec_Log("rm -f /tmp/*.spl.zp", 4);

		    # Re-read the source directory (must have changed)
		    #
		    if($Multi && $Multi_Concerned)
		    {
			Read_Dir(\@F, \@FF, \$Multi);  # Will feed @F, @FF & $Multi
		    }
		}
		elsif($FF =~/\.000$/)
		{
		    my $Dev = $F;
		    $Dev =~s/\.\d{3}$//;

		    # Well, unless PING is about to overwrite the source it is to
		    # get the images from !
		    #
		    if($SRC =~/dos/
		       && ((Is_Mounted("/mnt/dos")
			    && HDD_Name(Dev_Mount_Point("/mnt/dos"))
			    eq ADT($Dev, \@Dev_Rich))
			   || ($Part_For_Storage
			       && HDD_Name($Part_For_Storage) eq ADT($Dev,\@Dev_Rich))))
		    {
			LOG("    Won't overwrite the partition ["
			    .ADT($Dev, \@Dev_Rich)."]\n");
			LOG("      because it contains the image to restore!\n");
			sleep($README_SLEEP);
		    }
		    else
		    {
			Umount("/dev/".ADT($Dev, \@Dev_Rich));
			if($Multi)
			{
			    # Don't use Umount() here, obviously enough :)
			    #
			    my $Todo = "umount /mnt/cdrom >/dev/null 2>&1;"
				." eject /dev/cdrom >/dev/null 2>&1;"
				." sleep 20;"
				." mount /dev/cdrom /mnt/cdrom >/dev/null 2>&1";
			    my $cmd = "/etc/rc.d/rc.multi partimage \"$Todo\" &";
			    LOG("    * Multivol! Cmd: [$cmd]\n");
			    system($cmd);
			    $Go_On = 1;
			}
			my $cmd = "sleep $PARTIMAGE_SLEEP; partimage "
			    .($Multi ? "-w":"")
			    ." -f3 -b -c -d -o restore /dev/".ADT($Dev, \@Dev_Rich)
			    ." \"$FF\"; sleep $PARTIMAGE_SLEEP; reset";
			LOG("    Cmd: [$cmd]\n");
			system($cmd);
			my $out = `tail --lines=15 /var/log/partimage-debug.log 2>&1`;
			LOG("    Output: (...) [$out]\n");
		    }

		    push(@Applied, $FF);
		    $Go_On = 1;
		}
		elsif(Is_Archive($FF)
		      && ($FF !~/addon(\-|_).*\.tar\.gz/i
			  || $FF !~/addon(\-|_).*\.zip/i))
		{
		    my $Dev = "";
		    {
			my @f1 = split(/\./, $F);
			$Dev = $f1[0];
		    }

		    my $TType = Is_NTFS(Part_Type(ADT($Dev, \@Dev_Rich)))
			? "-t $NTFS_MNT_TYPE":"";

		    # If a partition where the user wants data to be
		    # untarred can't be mounted, it should mean that it
		    # is a new partition, with no data. Not an update, but
		    # a 100%-tar-archived image (probably a linux system).
		    #
		    unless(Is_Mountable(ADT($Dev, \@Dev_Rich)))
		    {
			LOG("    Partition [".ADT($Dev, \@Dev_Rich)."] can't be"
			    ." mounted. Format!\n");
			Umount(ADT($Dev, \@Dev_Rich));
			Format(ADT($Dev, \@Dev_Rich), \@Dev_Rich);
		    }

		    # The tarball might be empty. Then, save time.
		    unless(-z $FF)
		    {
			Umount("/mnt/win");
			Mount("/dev/".ADT($Dev, \@Dev_Rich), "/mnt/win", "RW");

			if(! Is_Mounted(ADT($Dev, \@Dev_Rich))
			   && Is_NTFS(Part_Type(ADT($Dev, \@Dev_Rich))))
			{
			    LOG("      Not mounted... ntfsfix & 2nd try\n");
			    Exec_Log("ntfsfix /dev/".ADT($Dev, \@Dev_Rich), 6);
			    Mount("/dev/".ADT($Dev, \@Dev_Rich), "/mnt/win", "RW");
			}

			if(Is_Mounted(ADT($Dev, \@Dev_Rich)))
			{
			    Exec_Log("cd /mnt/win; ".Unzip($FF)."; sleep 2", 6);
			}
			else
			{
			    LOG("      ! Can't mount the part, won't unzip [$FF]\n");
			    sleep($README_SLEEP);
			}

			Umount("/mnt/win");
			sleep(2);
			push(@Applied, $FF);
			$Go_On = 1;
		    }
		}
                elsif($FF =~/\.RecFiles\.txt$/i)
                {
		    my $Dev = $F;
		    $Dev =~s/\.RecFiles\.txt$//i;

		    LOG("    Will we use [$FF] to find out files to delete ?\n");
                    if(! -e "$SRC/$Entity/$FF.old")
                    {
                        LOG("      Well, a [$FF.old] file could not be found...\n");
                        LOG("      => Seems useless.\n");
                    }
                    else
                    {
                        Umount("/mnt/win");
		        Mount("/dev/$Dev", "/mnt/win", "RW");

		        my $cnt = 0;
                        my $cnt2 = 0;

		        my(%Keep) = ();
		        open(R, $FF);
		        while(<R>)
		        {
                            s/^\s*//;
                            s/\s*$//;
			    my(@fields) = split(/\;/, $_);
			    $Keep{$fields[0]} = 1;
		        }
		        close(R);

    		        sub rec_Delete
		        {
			    my($dir) = shift || '';
			    return() unless($dir && -e $dir);

			    opendir(DIR, $dir);
			    my(@files) = readdir(DIR);
			    closedir(DIR);

			    foreach my $file (@files)
			    {
			        next if($file =~/^\.{1,2}$/);

				++ $cnt;
				if(($cnt / 1000) == int($cnt / 1000))
				{
				    LOG("      [$cnt] files checked for removal\n");
				}

				my $path = "$dir/$file";
				$path =~s/^\/mnt\/win//;

				if(-d "$dir/$file")
				{
				    if(! defined($Keep{$path}) && -e "$dir/file")
				    {
					system("rm -fr \"$dir/$file\"");
					++ $cnt2;
				    }
				    rec_Delete("$dir/$file");
				}
				else
				{
				    if(! defined($Keep{$path}) && -e "$dir/$file")
				    {
					unlink("$dir/$file");
					++ $cnt2;
				    }
				}
			    }
			}

                        rec_Delete("/mnt/win");
                        Umount("/mnt/win");

                        LOG("      [$cnt2] files/dirs have been removed\n");
                    }

		    push(@Applied, $FF);
		    $Go_On = 1;
                }
		elsif($FF =~/MULTI$/i)
		{
		    LOG("    It's a multi. New image files (at least one .000)\n");
		    LOG("      to be found on a new CD/DVD. Spanning now...\n");
		    KILL("rc.multi");
		    Umount("/mnt/cdrom");
		    sleep(1);
		    Exec_Log("eject /dev/cdrom", 4);
		    print "\nINSERT the next CD/DVD\n";
		    <STDIN>;
		    Exec_Log("mount /dev/cdrom /mnt/cdrom", 4);
		    $Go_On = 1;
		}
		elsif($FF =~/help_dell$/i)
		{
		    LOG("  * Found [Help_Dell] file. We must fix the boot sector.\n");
                    Write_Std_MBR($Dev_Rich[0]->{Dev});
	            for(my $i = 0; $i <= $#{ $Dev_Rich[0]->{Parts} }; $i++)
	            {
                        if($Dev_Rich[0]->{Boot_Flags}
                            && $Dev_Rich[0]->{Parts}->[$i] ne "null")
                        {
                            Fix_Dell_Boot($Dev_Rich[0]->{Parts}->[$i]);
                            last;
                        }
                    }
		    push(@Applied, $FF);
		    $Go_On = 1;
		}
	    }
	}
    }
}


# Unmount the stuff.
#
LOG("* Unmounting [$SRC]\n");
Umount($SRC);


# If we had minimized one partition before storing it, it's now
# time to restore the partition table as it was before.
# That way, we'll be able to call ntfsresize so to remaximize.
#
if($Have_Minimized_Before_Storing)
{
    LOG("* There has been a partition minimization...\n");

    foreach(@Minimized_Devices)
    {
	Delete_All_Parts($_, \@Dev_Rich);
	Recreate_All_Parts($_, \@Dev_Rich);
    }
}


# One last sync, just in case
#
if($Have_Restored)
{
    LOG("* One last sync, just in case\n");
    foreach(@Dev)
    {
	Synchronize_Device($_);
    }
}


# Sometimes, users might create images with a small hdd-equipped station,
# and restore it on stations with bigger hdds. They might use a HDD_Target.txt
# file to specify bigger parts. As a result, we can end up with 10-GB
# filesystems in a 40-GB partition... We can handle this easily. Yet,
# we'll help only as far as NTFS filesystems are involved.
#
# Note: after testing, not so sure NTFS needs it... doing it anyway.
#
if($Have_Restored || $Have_Minimized_Before_Storing)
{
    LOG("* There has been a restoration or a Minimization...\n");
    LOG("  Let's make sure no partition is bigger than the\n");
    LOG("  filesystem it contains (not all FS supported)\n");

    foreach my $D (@Dev_Rich)
    {
	LOG("  Device: [".$D->{Dev}."]\n");

	for(my $i = 0; $i <= $#{ $D->{Parts} }; $i++)
	{
	    if($D->{Parts}->[$i] eq 'null')
	    {
		next;
	    }
	    LOG("    Partition [$i]\n");
	    LOG("      Name: [".$D->{Parts}->[$i]."]\n");

	    my $Part_Size = ($D->{End}->[$i] - $D->{Start}->[$i])
		* $D->{Heads} * $D->{Sectors_Track} * 512;
	    LOG("      Part Size: [$Part_Size] bytes\n");

	    my $FS_Size = FS_Size($D->{Parts}->[$i]);
	    LOG("      Filesystem Size: [$FS_Size] bytes\n");

	    if($FS_Size < $Part_Size - 1000000)
	    {
		LOG("      It's interesting to augment the volume size\n");

		my $Max_Size = FS_Maximum_Size($D->{Parts}->[$i]);
		LOG("      Let's resize to [$Max_Size] bytes\n");

		my $Max_Size_Cyl =
		    int($Max_Size / ($D->{Heads}
				     * $D->{Sectors_Track} * 512));
		LOG("      - well, to [$Max_Size_Cyl] cylinders\n");

		if($Max_Size && $FS_Size)
		{
		    if(Part_Resize($D->{Parts}->[$i], $Max_Size_Cyl, $Max_Size))
		    {
			LOG("      ! We could not resize that filesystem.\n");
			LOG("        Maybe you will have to do it yourself.\n");
			sleep($FAILED_SLEEP);
		    }
		    else
		    {
			LOG("      The resizing seems OK. Good.\n");
		    }
		}
		else
		{
		    LOG("      ! The filesystem could not be evaluated.\n");
		    LOG("        Maybe you will have to do it yourself.\n");
		    sleep($FAILED_SLEEP);
		}
	    }
	    else
	    {
		LOG("      No interest to augment the volume size.\n");
	    }
	}
    }
}



# The End
#
Quit();



sub LOG
{
    my $Say = shift;
    my $Now = Time();
    unless(defined($P{Verbosity}) && $P{Verbosity} == 0)
    {
	print "$$ $MYSELF $Now> $Say";
    }
    else
    {
	system("clear");
	if($P{Unique_Message})
	{
	    print $P{Unique_Message};
	}
    }
    open(LOG, ">>/tmp/x.log");
    print LOG "$$ $MYSELF $Now> $Say";
    close(LOG);
}



sub Print_Shell_Help
{
    print "To have several terms, get a shell and launch /etc/rc.d/rc.ping.\n\n";
    print "The shell has every tool you need to modify or format partitions,\n";
    print "or to access data on a local hard disk. Most filesystems are\n";
    print "currently supported, NTFS included ($NTFS_MNT_TYPE).\n";
    print "\n";
    print "Basic Shell Reminder:\n";
    print "To mount a NTFS volume: $NTFS_MNT /dev/sda1 /mnt/dos\n";
    print "To get an IP address through DHCP: dhcpcd eth0 (or: dhcpcd eth1)\n";
    print "To manually set up your network config (adjust):\n";
    print "  ifconfig eth0 192.168.0.123 netmask 255.255.255.0 up\n";
    print "  route add default gw 192.168.0.1\n";
    print "To mount a SMB (MS network share) partition:\n";
    print "  mount.cifs //192.168.0.1/myshare /mnt/smbfs -o";
    print " username=xxx,password=yyy\n";
    print "To call fdisk on local parts: fdisk /dev/hda (or hdb, hdc, sda, sdb)\n";
}



sub ID_To_Type
{
    my($ID) = shift;
    return unless($ID);

    my %Corr = (
	"0" => "Empty",
	"1e" => "Hidden W95 FAT1",
	"80" => "Old Minix",
	"be" => "Solaris boot",
	"1" => "FAT12",
	"24" => "NEC DOS",
	"81" => "Minix / old Lin",
	"bf" => "Solaris",
	"2" => "XENIX root",
	"39" => "Plan 9",
	"82" => "Linux swap / So",
	"c1" => "DRDOS/sec (FAT-",
	"3" => "XENIX usr",
	"3c" => "PartitionMagic",
	"83" => "Linux",
	"c4" => "DRDOS/sec (FAT-",
	"4" => "FAT16 <32M",
	"40" => "Venix 80286",
	"84" => "OS/2 hidden C:",
	"c6" => "DRDOS/sec (FAT-",
	"5" => "Extended",
	"41" => "PPC PReP Boot",
	"85" => "Linux extended c7 Syrinx",
	"6" => "FAT16",
	"42" => "SFS",
	"86" => "NTFS volume set",
	"da" => "Non-FS data",
	"7" => "HPFS/NTFS",
	"4d" => "QNX4.x",
	"87" => "NTFS volume set",
	"db" => "CP/M / CTOS / .",
	"8" => "AIX",
	"4e" => "QNX4.x 2nd part",
	"88" => "Linux plaintext",
	"de" => "Dell Utility",
	"9" => "AIX bootable",
	"4f" => "QNX4.x 3rd part",
	"8e" => "Linux LVM",
	"df" => "BootIt",
	"a" => "OS/2 Boot Manag",
	"50" => "OnTrack DM",
	"93" => "Amoeba",
	"e1" => "DOS access",
	"b" => "W95 FAT32",
	"51" => "OnTrack DM6 Aux",
	"94" => "Amoeba BBT",
	"e3" => "DOS R/O",
	"c" => "W95 FAT32 (LBA)",
	"52" => "CP/M",
	"9f" => "BSD/OS",
	"e4" => "SpeedStor",
	"e" => "W95 FAT16 (LBA)",
	"53" => "OnTrack DM6 Aux",
	"a0" => "IBM Thinkpad hi",
	"eb" => "BeOS fs",
	"f" => "W95 Ext'd (LBA)",
	"54" => "OnTrackDM6",
	"a5" => "FreeBSD",
	"ee" => "EFI GPT",
	"10" => "OPUS",
	"55" => "EZ-Drive",
	"a6" => "OpenBSD",
	"ef" => "EFI (FAT-12/16/",
	"11" => "Hidden FAT12",
	"56" => "Golden Bow",
	"a7" => "NeXTSTEP",
	"f0" => "Linux/PA-RISC b",
	"12" => "Compaq diagnost",
	"5c" => "Priam Edisk",
	"a8" => "Darwin UFS",
	"f1" => "SpeedStor",
	"14" => "Hidden FAT16 <3",
	"61" => "SpeedStor",
	"a9" => "NetBSD",
	"f4" => "SpeedStor",
	"16" => "Hidden FAT16",
	"63" => "GNU HURD or Sys",
	"ab" => "Darwin boot",
	"f2" => "DOS secondary",
	"17" => "Hidden HPFS/NTF",
	"64" => "Novell Netware",
	"b7" => "BSDI fs",
	"fd" => "Linux raid auto",
	"18" => "AST SmartSleep",
	"65" => "Novell Netware",
	"b8" => "BSDI swap",
	"fe" => "LANstep",
	"1b" => "Hidden W95 FAT3",
	"70" => "DiskSecure Mult",
	"bb" => "Boot Wizard hid",
	"ff" => "BBT",
	"1c" => "Hidden W95 FAT3",
	"75" => "PC/IX"
    );
    if(defined($Corr{$ID}))
    {
	return($Corr{$ID});
    }
    else
    {
	return($ID);
    }
}



sub Date
{
    my($sec, $min, $hour, $mday, $mon, $year, $wday, $yday, $isdst);
    ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime(time);

    ++ $mon;

    if($sec < 10){ $sec = "0$sec"; }
    if($min < 10){ $min = "0$min"; }
    if($hour < 10){ $hour = "0$hour"; }
    if($mday < 10){ $mday = "0$mday"; }
    if($mon < 10){ $mon = "0$mon"; }
    if($yday < 10){ $yday = "0$yday"; }

    $year += 1900;
    return("$year-$mon-$mday $hour:$min:$sec");
}



sub Time
{
    my($sec, $min, $hour, $mday, $mon, $year, $wday, $yday, $isdst);
    ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime(time);

    if($sec < 10){ $sec = "0$sec"; }
    if($min < 10){ $min = "0$min"; }
    if($hour < 10){ $hour = "0$hour"; }

    return("$hour:$min:$sec");
}



# Add partimage's log to x.log. Might help to debug.
#
sub Merge_Partimage_Log
{
    my $PLOG = "/var/log/partimage-debug.log";
    my $i = 0;
    if(-e $PLOG)
    {
	LOG("  Adding [$PLOG] to [/tmp/x.log]\n");
	LOG("    ------------------------------------------\n");
	open(L, $PLOG);
	while(<L>)
	{
	    # Many, many, quite useless log lines. Eg.:
	    # [Main] image_disk.cpp->read#254: cid: 14336 ; m_qwTotal = 344064
	    # => just keep a few of them.
	    #
	    if(m/m_qwTotal/)
	    {
		++ $i;
		if($i > 6) { next; }
		if($i > 5) { LOG("(...)\n"); }
	    }
	    $i = 0;
	    s/^\s*//;
	    s/\s*$//;
	    LOG("    $_\n");
	}
	close(L);
	LOG("    ------------------------------------------\n");
    }
}



sub Quit
{
    LOG("* Quitting...\n");

    # If a command has been scheduled to be executed before
    # exitting, do so now.
    #
    LOG("  * Any command to execute before exitting ?\n");

    if(defined($P{Cmd_3}) && $P{Cmd_3})
    {
	LOG("    Yes! [$P{Cmd_3}]\n");
	my $out = system($P{Cmd_3});
	LOG("    Output: [$out]\n");
    }
    else
    {
	LOG("    No defined command.\n");
    }

    # Reboot.
    #
    if($After_Completion =~/reboot/i)
    {
	LOG("  Reboot within 10 seconds !!\a\a\a\n\n");
    }
    elsif($After_Completion =~/shutdown/i)
    {
	LOG("  Shutdown within 10 seconds !!\a\a\a\n\n");
    }
    else
    {
	if($P{No_Shell})
	{
	    LOG("  No shell allowed. Shutdown within 60 seconds !!\a\a\a\n\n");
	}
	else
	{
	    LOG("  Give the user a shell.\a\a\a\n\n");
	}
    }

    LOG("---------------------------------------------------\n");

    if($SRC =~/cdrom/i)
    {
	LOG("  Hey! Don't forget thy DVD!\a\a\a\n\n");
	system("eject /dev/$CD_Dev >/dev/null 2>&1");
	system("eject /mnt/cdrom >/dev/null 2>&1");
    }
    if($After_Completion =~/reboot/i)
    {
	sleep(10);
	system("shutdown -r now");
    }
    elsif($After_Completion =~/shutdown/i)
    {
	sleep(10);
	system("shutdown -h now");
    }
    else
    {
	if($P{No_Shell})
	{
	    sleep(60);
	    system("shutdown -h now");
	}
	else
	{
#	    Merge_Partimage_Log();

	    system("reset; clear");
	    print "\nYou are now given the possibility to login to the shell.\n";
	    print "Please, be aware that PING log is stored in /tmp/x.log.\n\n\a\a\a";
	    Print_Shell_Help();
	    if($P{Image_To_Restore} && $P{Image_To_Restore} !~/^Create_New_Image$/i
		&& $P{Image_To_Restore} !~/^Blank_Local_Admin$/i)
	    {
		print "Last restored image: [$P{Image_To_Restore}]\n";
	    }
	    print "\n";
	    exit;
	}
    }
}



# Get parameters from a ping.conf file.
# Returns 1 if at least one parameter has been found.
#
# Syntax: Get_Parameters("/etc/ping.conf", \@CONFIG_FIELDS, \%P);
#
sub Get_Parameters
{
    my $Conf_File = shift || '';
    my $pCONFIG_FIELDS = shift || '';
    my $pP = shift || '';

    return unless($Conf_File && -e $Conf_File && $pCONFIG_FIELDS && $pP);

    my $flag = 0;

    open(DB, $Conf_File) || return;
    while(<DB>)
    {
	s/^\s*//;
	s/\s*$//;
	next unless($_);
	next if(m/^\#/);
	my(@f) = split(/\=/, $_);
	my $V = "";
	if(defined($f[1]))
	{
	    $V = $f[1];
	}
	foreach(2..($#f - 1))
	{
	    if(defined($f[$_]))
	    {
		$V .= "=$f[$_]";
	    }
	}
	foreach($f[0], $V)
	{
	    s/^\s*//;
	    s/\s*$//;
	    s/^\"//;
	    s/\"$//;
	}
	foreach(@{ $pCONFIG_FIELDS })
	{
	    if(lc($_) eq lc($f[0]))
	    {
		$pP->{$_} = $V;
		$flag = 1;
	    }
	}
    }
    close(DB);

    return($flag);
}



# Umount a device.
# Syntax: Umount("/dev/hda1")
#         Umount("/mnt/dos");
#
sub Umount
{
    my($D) = shift || '';
    return unless($D);

    LOG("Umount> Umounting [$D]\n");

    if(Is_Mounted($D))
    {
	Exec_Log("umount $D", 0, "Umount> ");
    }
    else
    {
	LOG("Umount> Not mounted.\n");
    }
}



# Mount a *local* device somewhere. Don't use for a CDRom.
#
# Syntax: Mount("/dev/hda1", "/mnt/dos", "RW");
#         Mount("/dev/hda1", "/mnt/dos", "");
#
sub Mount
{
    my($Dev) = shift || '';
    my($Mnt) = shift || '';
    my($RW) = shift || '';
    return unless($Dev && $Mnt);

    LOG("Mount> Trying to umount [$Mnt]\n");
    Umount($Mnt);

    LOG("Mount> Trying to mount [$Dev] on [$Mnt]\n");

    Exec_Log((Is_NTFS(Part_Type($Dev)) ? $NTFS_MNT:"mount")
	     ." $Dev $Mnt", 2, "Mount> ");

    if(! Is_Mounted($Dev) && Is_NTFS(Part_Type($Dev)))
    {
	LOG("Mount> This part could not be mounted.\n");
	LOG("Mount>   Well, it's NTFS... let's fix it, just in case.\n");
	Exec_Log("ntfsfix $Dev", 2, "Mount> ");
	Exec_Log("$NTFS_MNT $Dev $Mnt", 2, "Mount> ");
    }

    if(! Is_Mounted($Dev))
    {
	LOG("Mount> This part could not be mounted.\n");
	if(Is_NTFS(Part_Type($Dev)))
	{
	    LOG("Mount> Well, it's NTFS...\n");

	    # Sometimes, a NTFS partition cannot be mounted
	    # because Windows has not been shut down properly,
	    # or because it's in hibernation mode. We can detect
	    # this by trying to mount the partition read-only.
	    #
	    LOG("Mount> Maybe we can mount it Read Only ?\n");
	    Umount($Mnt);
	    Exec_Log("mount -o ro -t $NTFS_MNT_TYPE $Dev $Mnt", 2, "Mount> ");

	    if(! Is_Mounted($Dev))
	    {
		LOG("Mount> Still not mounted... trying another way\n");
		Umount($Mnt);
		Exec_Log("mount $Dev $Mnt", 0, "Mount> ");
	    }
	    if(Is_Mounted($Dev))
	    {
		LOG("Mount> Mounted. Let's tell the user.\n");
		LOG("Mount> \n");
		LOG("Mount> !!! It seems that your partition [$Dev]\n");
		LOG("Mount>     can only be mounted in Read Only. It generally\n");
		LOG("Mount>     means that Windows has not been shut down properly\n");
		LOG("Mount>     or is in hibernation mode. We're going to proceed,\n");
		LOG("Mount>     but we won't be able to write data on this part.\n");
		LOG("Mount>     You may reboot now and run chkdsk if necessary.\n");
		LOG("Mount>     Sleeping $README_SLEEP seconds.\n");

		if($RW eq "RW")
		{
		    sleep($README_SLEEP);

		    # Now, we can mount this part R/W if the user really
		    # wants it.
		    #
		    LOG("Mount> Has the user provided a parameter to force R/W"
			." mount ?\n");
		    if(defined($P{Force_Dirty_NTFS_Mount}))
		    {
			LOG("Mount>   Yes! =>[$P{Force_Dirty_NTFS_Mount}]\n");
		    }
		    else
		    {
			LOG("Mount>   No. Let's ask.\n");
			my $cmd = 'dialog --colors --menu "\Zb\ZnYour'
			    .' NTFS partition was not clean. The safe'
			    .' choice is to boot Windows and run chkdsk.'
			    .' However, we can mount it read-write and'
			    .' proceed if you want to. Do we ?" 13 50 2 '
			    .' "Yes" "" "No" "" 2>/tmp/checklist.tmp';
			system($cmd);

			if(-z "/tmp/checklist.tmp")
			{
			    Quit();
			}

			$P{Force_Dirty_NTFS_Mount} = 0;
			open(DB, "/tmp/checklist.tmp");
			while(<DB>)
			{
			    if(m/Y/i)
			    {
				$P{Force_Dirty_NTFS_Mount} = 1;
				last;
			    }
			}
			close(DB);
			unlink("/tmp/checklist.tmp");

			LOG("Mount>   Force_Dirty_NTFS_Mount:"
			    ." [$P{Force_Dirty_NTFS_Mount}]\n");
		    }

		    # Correct the way we'll mount NTFS parts if they are dirty and
		    # the user does not want to care about it.
		    #
		    if($P{Force_Dirty_NTFS_Mount} =~/1|Y/i)
		    {
			$NTFS_MNT = "ntfsmount -o force";
			LOG("Mount>   Will now mount NTFS with [$NTFS_MNT]\n");
		    }
		}
		else
		{
		    LOG("Mount>   R/W mode not asked for; dont warn the user"
			." too much :)\n");
		}
	    }
	}
    }
}



# Tells if a part is mounted.
# Syntax: Is_Mounted("/dev/sda1");
#         Is_Mounted("sda1");
#         Is_Mounted("/mnt/smbfs");
#
sub Is_Mounted
{
    my($Part) = shift || '';
    return(0) unless($Part);

    $Part =~s/^\/dev\///;

    LOG("Is_Mounted> Is part [$Part] mounted ?\n");

    my $out = `df -P`;
    my(@Lines) = split(/\n/, $out);
    foreach my $L (@Lines)
    {
	my $M = $L;
	$L =~s/ +.*$//;            # /dev/xxx (start of line)
	$M =~s/^.*\s(\S+)$/$1/;    # /mnt/xxx (end of line)
	if($L eq "/dev/$Part" or $M eq $Part)
	{
	    LOG("Is_Mounted> Yes\n");
	    return(1);
	}
    }

    LOG("Is_Mounted> No\n");
    return(0);
}



# Tells if a part can be mounted.
# Syntax: Is_Mountable("/dev/sda1");
#         Is_Mountable("sda1");
#
sub Is_Mountable
{
    my($Part) = shift || '';
    return(0) unless($Part);

    LOG("Is_Mountable> Is [$Part] mountable ?\n");

    $Part =~s/^\/dev\///;
    my $MNT = "/mnt/Is_Mountable_$$";
    if(-d $MNT)
    {
	system("umount $MNT >/dev/null 2>&1");
	system("rm -fr $MNT");
    }
    mkdir($MNT, 1775);
    system("mount /dev/$Part $MNT >/dev/null 2>&1");
    my $Mounted = Is_Mounted($MNT);
    system("umount $MNT >/dev/null 2>&1");

    if(! $Mounted && Is_NTFS(Part_Type($Part)))
    {
	system("ntfsmount -o force /dev/$Part $MNT >/dev/null 2>&1");
	$Mounted = Is_Mounted($MNT);
	system("umount $MNT >/dev/null 2>&1");
    }

    LOG("Is_Mountable> ".($Mounted ? "Yes":"No")."\n");
    return($Mounted);
}



# Browses a win32/smb directory and find the name of a file,
# whatever the case.
#
sub Find_File_Whatever_Case
{
    my $Dir = shift || '';
    my $File = shift || '';
    return unless($Dir && $File && -d $Dir);

    opendir(DIR, $Dir) || return;
    my(@files) = readdir(DIR);
    closedir(DIR);
    foreach(@files)
    {
	if(m/^$File$/i)
	{
	    return($_);
	}
    }
}



# Tells if a partition is Dell-made
#
sub Is_Dell_Type
{
    my($Type) = shift || '';
    return(($Type eq "db" || $Type eq "de") ? 1:0);
}



# Tells if a partition type is NTFS
#
sub Is_NTFS
{
    my($Type) = shift || '';
    return(($Type eq "7" || $Type eq "42" || $Type eq "86"
	    || $Type eq "87") ? 1:0);
}



# Tells if a partition type is EXT2 or EXT3
#
sub Is_EXT23
{
    my($Type) = shift || '';
    return(($Type eq "83") ? 1:0);
}



# Tells if a partition type is FAT
#
sub Is_FAT
{
    my($Type) = shift || '';
    return(($Type eq "c" || $Type eq "1e" || $Type eq "1"
	    || $Type eq "4" || $Type eq "6" || $Type eq "b"
	    || $Type eq "c" || $Type eq "e" || $Type eq "ef"
	    || $Type eq "11" || $Type eq "14" || $Type eq "16"
	    || $Type eq "1b" || $Type eq "1c") ? 1:0);
}



# Tells if a partition type is FAT32
#
sub Is_FAT32
{
    my($Type) = shift || '';
    return(($Type eq "b" || $Type eq "c" || $Type eq "1b"
	    || $Type eq "1c") ? 1:0);
}



# Tells if a partition type is FAT16
#
sub Is_FAT16
{
    my($Type) = shift || '';
    return(($Type eq "1e" || $Type eq "4" || $Type eq "6"
	    || $Type eq "e" || $Type eq "14" || $Type eq "16") ? 1:0);
}



# Tells if a partition type is HFS (Apple)
#
sub Is_HFS
{
    my($Type) = shift || '';
    return(($Type eq "a8") ? 1:0);
}



# Tells if Parted can accept this partition type
# (and we don't trust any more parted when they say ext2-3 is
# supported, for it just fails for obscure reasons, while
# resize2fs does not).
#
sub Is_Type_Parted_Compliant
{
    my($Type) = shift || '';
    return((Is_FAT16($Type) || Is_FAT32($Type) || Is_HFS($Type)) ? 1:0);
}



# Tells wether a partition can be resized. We can resize...:
# - NTFS
# - Ext2/3
# - what parted can do else, i.e. hsf, fat16 and fat32.
#
# Ex.: if(Is_Resizable("/dev/hda1")) ...
#      if(Is_Resizable("hda1")) ...
#
sub Is_Resizable
{
    my($Part) = shift || '';
    return(0) unless($Part);

    $Part =~s/^\/dev\///;

    return(Is_NTFS(Part_Type($Part))
	   || Is_EXT23(Part_Type($Part))
	   || Is_Type_Parted_Compliant(Part_Type($Part)));
}



# Return the type of a filesystem.
# Type can be: reiserfs, ext2, ext3, ntfs, vfat, ?
#
# Ex.: my $FS = FS_Type('/dev/hda1');
#
sub FS_Type
{
    my($Part) = shift || '';
    return(0) unless($Part);

    $Part =~s/^\/dev\///;

    my $MNT = "/mnt/FS_Type_$$";
    my $flag = 0;

    unless(Is_Mounted($Part))
    {
	if(-e $MNT)
	{
	    system("rm -fr $MNT");
	}
	mkdir($MNT, 0755);
	Mount("/dev/$Part", $MNT, "");
	++ $flag;
    }

    my $cmd = "df -P -T|grep -i ^\/dev\/$Part|grep -v grep";
    LOG("FS_Type> Cmd: [$cmd]\n");
    my $out = `$cmd`;
    LOG("FS_Type> Out: [$out]\n");
    my $fst = '';
    if($out)
    {
	$out =~s/\s+/ /g;
	my(@f) = split(/\s/, $out);
	$fst = defined($f[1]) ? $f[1]:'';
    }
    else
    {
	$fst = '';
    }
    LOG("FS_Type> Found FS type: [$fst]]\n");

    # Many things can become fuse blocks these days, ntfs-3g included.
    # Fortunately, type-7 parts can only containe a NTFS filesystem.
    #
    if($fst eq "fuseblk" && Is_NTFS(Part_Type($Part)))
    {
	$fst = "ntfs";
	LOG("FS_Type> Rectify to FS type: [$fst]]\n");
    }

    if($flag)
    {
	Umount($MNT);
	system("rm -fr $MNT");
    }

    return($fst);
}



# Tells the device of a mount point.
# Syntax: Dev_Mount_Point("/mnt/dos");   # Returns hda1
#
sub Dev_Mount_Point
{
    my($MPoint) = shift || '';
    return(0) unless($MPoint);

    LOG("Dev_Mount_Point> Device of mount point [$MPoint] ?\n");

    my $ret = '';

    my $out = `df -P`;
    my(@lines) = split(/\n/, $out);
    foreach my $L (@lines)
    {
	$L =~s/^\s*$//;
	$L =~s/\s*$//;
	my(@fields) = split(/\s+/, $L);
	if(defined($fields[5]) && $fields[5] eq $MPoint)
	{
	    $ret = $fields[0];
	    $ret =~s/^\/dev\///;
	}
    }

    LOG("Dev_Mount_Point>   Found: [$ret]\n");
    return($ret);
}



# Tells if a file has an archive extension (one of these that sub
# Unzip() can handle).
#
sub Is_Archive
{
    my($File) = shift || '';

    if($File =~/\.zip$/i || $File =~/\.tar$/i || $File =~/\.tar\.gz$/i
       || $File =~/\.tar\.bz2$/ || $File =~/\.tar\.7z$/i || $File =~/\.7z$/i)
    {
	return(1);
    }

    return(0);
}



# Tells if we are connected to the network.
#
sub Is_Network_On
{
    my($Eth) = shift || 'eth';
    my $out = `route -n |grep $Eth |wc -l 2>&1`;
    $out =~s/\D//g;
    if($out == 0)
    {
	return(0);
    }
    else
    {
	if($Eth eq 'eth')
	{
	    my(@Eths) = ();
	    $out = `ifconfig |grep ^eth`;
	    my(@lines) = split(/\n/, $out);
	    foreach(@lines)
	    {
		s/^(eth\d+)\D.*$/$1/;
		push(@Eths, $_);
	    }
	    foreach(@Eths)
	    {
		$out = `ifconfig $_`;
		my $IP = '';
		if($out =~/inet add?r\:(\S+)/)
		{
		    $IP = $1;
		}
		if($IP !~/^169\.254\./)
		{
		    return(1);
		}
	    }
	    return(0);
	}
	else
	{
	    $out = `ifconfig $Eth`;
            my $IP = '';
            if($out =~/inet add?r\:(\S+)/)
	    {
		$IP = $1;
	    }
	    if($IP =~/^169\.254\./)
	    {
		return(0);
	    }
         }
    }
    return(1);
}



# Tells if a part is a swap or not.
# Syntax: Is_Swap("/dev/hda1")
#         Is_Swap("hda1")
#
sub Is_Swap
{
    my($Part) = shift || '';
    return(0) unless($Part);

    $Part =~s/^\/dev\///;

    LOG("Is_Swap> Is part [$Part] a swap ?\n");

    my $cmd = "swapoff /dev/$Part >/dev/null 2>&1;"
	." swapon /dev/$Part >/dev/null 2>&1";
    system($cmd);

    my $Out = `free`;
    my @L = split(/\n/, $Out);
    foreach my $L (@L)
    {
	$L =~s/^\s*//;
	next unless($L =~/^Swap:/i);
	while($L =~/  /)
	{
	    $L =~s/  / /g;
	}
	my(@F) = split(/ /, $L);
	if(defined($F[1]) && $F[1] =~/^\d+$/ && $F[1] > 0)
	{
	    system("swapoff /dev/$Part >/dev/null 2>&1");
	    LOG("Is_Swap> Yes\n");
	    return(1);
	}
    }

    LOG("Is_Swap> No\n");
    return(0);
}



# Ex.: my $FS = Part_Type("hda1");
#      LOG("  Filesystem ID found: [$FS]\n");  <= 7 for NTFS
#
sub Part_Type
{
    my($Part) = shift || '';
    return(0) unless($Part);

    $Part =~s/^\/dev\///;

    # If we've already have it, get id.
    #
    foreach my $D (@Dev_Rich)
    {
	for(my $i = 0; $i <= $#{ $D->{Parts} }; $i++)
	{
	    if($D->{Parts}->[$i] eq $Part)
	    {
		return($D->{Types}->[$i]);
	    }
	}
    }

    # Else, retrieve it.
    #
    my $FS = '';

    my $HDD = HDD_Name($Part);

    system("fdisk -l /dev/$HDD | grep -i $Part >/tmp/bla 2>&1");
    if(-e "/tmp/bla")
    {
	open(DB, "/tmp/bla");
	while(<DB>)
	{
	    s/^\s*//;
	    s/\s*$//;
	    s/\*//g;    # Boot flag
	    s/\+//g;    # Blocks addon
	    while(m/\s\s/)
	    {
		s/\s\s/ /g;
	    }
	    $FS = (split(/ /, $_))[4];
	}
	close(DB);
	unlink("/tmp/bla");
    }
    return($FS);
}



# Ex.: my $HDD = HDD_Name('hda1');         # returns 'hda'
#      my $HDD = HDD_Name('cciss/c0d0p1'); # returns 'c0d0'
#      my $HDD = HDD_Name('/dev/hda1');    # returns 'hda'
#      my $HDD = HDD_Name('/dev/mapper/VolGroup00-LogVol00');  # returns 'VolGroup00'
#
sub HDD_Name
{
    my $HDD = shift || '';
    $HDD =~s/^\/dev\///;
    if($HDD =~/^(cciss|rd|ida|mapper)/)
    {
	$HDD =~s/p\d+$//;
	$HDD =~s/\-LogVol\d+$//;
    }
    else
    {
	$HDD =~s/\d+$//;
    }
    return($HDD);
}



# Ex.: Part_Number('/dev/hda1');                  # returns 1
#      Part_Number('cciss/c0d0p2');               # returns 2
#      Part_Number('mapper/VolGroup00-LogVol00'); # returns 00
#
sub Part_Number
{
    my($pnum) = shift || '';
    $pnum =~s/^.*\D(\d+)$/$1/;
    $pnum = '' unless($pnum =~/^\d+$/);
    return($pnum);
}



# Ex.: assuming hda contains 3 parts
#      Nb_Parts('/dev/hda');  # returns 3
#      Nb_Parts('hda');       # returns 3
#
sub Nb_Parts
{
    my($Dev) = shift || '';
    $Dev =~s/^\/dev\///;
    my $cnt = `fdisk -l /dev/$Dev |grep ^\/dev\/$Dev |wc -l`;
    $cnt =~s/\D//g;
    return($cnt);
}



# Ex.: Is_Part_Extended(5);   # returns 1
#
# Extended types are:
#   "5" => "Extended",
#   "85" => "Linux extended c7 Syrinx",
#   "f" => "W95 Ext'd (LBA)",
#
sub Is_Part_Extended
{
    my($Type) = shift || '';
    if($Type eq '5' || $Type eq 'f' || $Type eq '85')
    {
	return(1);
    }
    else
    {
	return(0);
    }
}



# Tells if a partition is contained in an ext'd part, and returns
# the number of the containing ext'd part. Returns -1 if primary.
#
# Ex.: $i = Is_Part_Logical(2, $D);  # $D from @Dev_Rich
#
sub Is_Part_Logical
{
    my $p = shift || '';
    my $D = shift || '';

    return unless($p ne '' && $D);

    $D =~s/^\/dev\///;

    LOG("IPLogic> Is part [$p] logical ? Dev [".$D->{Dev}."]\n");
    LOG("IPLogic> Identifying ext'd parts\n");

    my(@Extd) = ();
    for(my $i = 0; $i <= $#{ $D->{Parts} }; $i++)
    {
	if(Is_Part_Extended($D->{Types}->[$i]))
	{
	    push(@Extd, $i);
	}
    }

    LOG("IPLogic>   Extd parts:\n");
    foreach(@Extd)
    {
	LOG("IPLogic>     [$_] => [".$D->{Parts}->[$_]."]\n");
    }

    for(my $i = 0; $i <= $#{ $D->{Parts} }; $i++)
    {
	my $In_Extd = 0;
	foreach my $d (@Extd)
	{
	    if($D->{Start}->[$i] >= $D->{Start}->[$d]
	       && $D->{End}->[$i] <= $D->{End}->[$d]
	       && $i != $d && $i == $p)
	    {
		LOG("IPLogic>       Part [$i] ["
		    .$D->{Parts}->[$i]."] in extd part [$d]\n");
		return($d);
	    }
	}
    }

    LOG("IPLogic>   It's a primary part.\n");
    return(-1);
}



# Apply modifications to the partition table.
# Just like calling fdisk /dev/hda and w.
#
sub Synchronize_Device
{
    my($Dev) = shift || '';
    $Dev =~s/^\/dev\///;
    `sfdisk -R /dev/$Dev`;
    if(Is_In_Path("udevadm"))
    {
	`udevadm trigger`;
    }
    elsif(Is_In_Path("udevtrigger"))
    {
	`udevtrigger`;
    }
}



# Tells how much space is used in a mountable partition.
# Returns a number of bytes.
#
# Ex.: Used_Space("/dev/hda1");
#
sub Used_Space
{
    my($D) = shift || '';
    return(0) unless($D);

    LOG("Used_Space> Called on dev [$D]\n");

    $D =~s/^\/dev\///;

    my $Already_Mounted = Is_Mounted("/dev/$D");
    my $MNT = "/mnt/Used_Space_$$";

    unless($Already_Mounted)
    {
	Umount($MNT);
	if(-d $MNT)
	{
	    system("rm -fr $MNT");
	}
	mkdir($MNT, 0755);
	Mount("/dev/$D", $MNT, "");
    }

    my $Used_Space = 0;
    my $cmd = "df -P 2>&1 | grep -i $D";
    LOG("Used_Space> Cmd: [$cmd]\n");
    my $out = `$cmd`;
    LOG("Used_Space> Out: [$out]\n");
    $out =~s/\s+/ /g;
    $Used_Space = (split(/ /, $out))[2] * 1024;

    unless($Already_Mounted)
    {
	Umount($MNT);
	system("rm -fr $MNT");
    }

    LOG("Used_Space> Used space found [$Used_Space]\n");

    return($Used_Space);
}



# Find out the minimum size a partition can be reduced to.
# Will use ntfsresize if NTFS, parted else.
# Returns a number of bytes.
#
# Syntax: my $Min_Size = Part_Minimum_Size("/dev/hda1");
#
sub Part_Minimum_Size
{
    my($D) = shift || '';
    return(0) unless($D);

    LOG("Part_Minimum_Size> Called on dev [$D]\n");

    $D =~s/^\/dev\///;

    unless(Is_Resizable($D))
    {
	LOG("Part_Minimum_Size> Filesystem of [$D] is not handled.\n");
	return(0);
    }

    my $Min_Size = 0;

    if(Is_NTFS(Part_Type($D)))
    {
	my $cmd = "(echo y|ntfsresize -i -f /dev/$D) 2>&1";
	LOG("Part_Minimum_Size> Cmd: [$cmd]\n");
	system($cmd);
	my $out = `$cmd`;
	LOG("Part_Minimum_Size> Out: [$out]\n");

	my(@lines) = split(/\n/, $out);
	foreach(@lines)
	{
	    if(m/^You might resize at/i)
	    {
		$Min_Size = $_;
		$Min_Size =~s/^\D+([0-9]+)\sbytes.*$/$1/;
		$Min_Size =~s/\D//g;
		LOG("Part_Minimum_Size> NTFS / Can reduce to [$Min_Size] bytes\n");
		last;
	    }
	}
    }
    else
    {
	my $cmd = 'echo -e "unit cyl\nprint '.Part_Number($D)
	    .'\nq\n"| parted /dev/'.HDD_Name($D).' 2>&1';
	LOG("Part_Minimum_Size> Cmd: [$cmd]\n");
	my $out = `$cmd`;
	LOG("Part_Minimum_Size> Out: [$out]\n");

	my(@lines) = split(/\n/, $out);
	foreach(@lines)
	{
	    if(m/^Minimum size/i)
	    {
		s/^\s*//;
		s/\s*$//;
		$Min_Size = $_;
		$Min_Size =~s/^Minimum size:\s*(\S+).*$/$1/i;
		last;
	    }
	}
	if($Min_Size =~/KB$/)
	{
	    $Min_Size =~s/[^0-9\.]//g;
	    $Min_Size *= 1024;
	}
	elsif($Min_Size =~/MB$/)
	{
	    $Min_Size =~s/[^0-9\.]//g;
	    $Min_Size *= 1024 * 1024;
	}
	elsif($Min_Size =~/GB$/)
	{
	    $Min_Size =~s/[^0-9\.]//g;
	    $Min_Size *= 1024 * 1024 * 1024;
	}

	LOG("Part_Minimum_Size> not NTFS / Can reduce to [$Min_Size] bytes\n");
    }

    return($Min_Size);
}



# Will resize the *filesystem* contained in a partition.
# Will use ntfsresize if NTFS, parted else.
# Returns 1 in case of failure, and 2 if the FS is not supported.
# Succes => returns 0.
#
# Note: parted also adjusts the partition table, and ntfsresize does not.
#       This sub won't bother with fdisk.
#
# Note: The number of cylinders could be calculated through the number
#       of bytes, but parted and fdisk will blow up fat partitions if
#       bytes are given to parted, and cylinders to fdisk, because of
#       a small size difference. So, give cyls to everyone.
#
# Syntax: Part_Resize("/dev/hda1", 5222, 800000000);
#                     dev, number of cylinders, number of bytes
#
sub Part_Resize
{
    my($D) = shift || '';
    my($New_Size_Cyl) = shift || '';
    my($New_Size_Bytes) = shift || '';
    return(0) unless($D && $New_Size_Cyl ne '' && $New_Size_Bytes ne '');

    LOG("Part_Resize> Called on dev [$D] / Resize to [$New_Size_Cyl] cyls\n");
    LOG("Part_Resize>   or [$New_Size_Bytes] bytes\n");

    $D =~s/^\/dev\///;

    unless(Is_Resizable($D))
    {
	LOG("Part_Resize> The filesystem of [$D] is not handled. Won't resize.\n");
	return(2);
    }

    if(Is_NTFS(Part_Type($D)))
    {
	my $cmd = "(echo y|ntfsresize -f -s $New_Size_Bytes /dev/$D) 2>&1";
	LOG("Part_Resize> Cmd: [$cmd]\n");
	my $out = `$cmd`;
	LOG("Part_Resize> Out: [$out]\n");
	if($out =~/error/i)
	{
	    return(1);
	}
    }
    elsif(Is_EXT23(Part_Type($D)))
    {
	my $cmd = "resize2fs -f /dev/$D "
	    .int($New_Size_Bytes / 512)."s 2>&1";
	LOG("Part_Resize> Cmd: [$cmd]\n");
	my $out = `$cmd`;
	LOG("Part_Resize> Out: [$out]\n");
	if($out =~/error/i)
	{
	    return(1);
	}
    }
    else
    {
	# GNU Parted 1.8.8
	# Using /dev/hda
	# Welcome to GNU Parted! Type 'help' to view a list of commands.
	# (parted) Model: VMware Virtual IDE Hard Drive (ide)
	# Disk /dev/hda: 85.9GB
	# Sector size (logical/physical): 512B/512B
	# Partition Table: msdos
	#
	# Number  Start   End       Size      Type     File system  Flags
	#  1      0cyl    10442cyl  10442cyl  primary  fat32        lba  
	#
	my $cmd = 'echo -e "unit cyl\nprint\nq"| parted /dev/'
	    .HDD_Name($D).' 2>&1';
	LOG("Part_Resize> Cmd: [$cmd]\n");
	my $out = `$cmd`;
	LOG("Part_Resize> Out: [$out]\n");

	my $Start = 0;
	my(@lines) = split(/\n/, $out);
	my $flag = 0;
	foreach(@lines)
	{
	    s/^\s*//;
	    s/\s*$//;
	    if(m/^Number/i)
	    {
		++ $flag;
		next;
	    }
	    if($flag)
	    {
		my(@F) = split(/\s+/, $_);
		if(defined($F[0]) && $F[0] eq Part_Number($D))
		{
		    $Start = $F[1];
		}
	    }
	}
	LOG("Part_Resize> Partition start: [$Start]\n");

	$Start =~s/\D//g;
	my $End = $Start + $New_Size_Cyl;

	LOG("Part_Resize> Partition start: [$Start] bytes\n");
	LOG("Part_Resize> Partition future end: [$End] bytes\n");

	$cmd = 'echo -e "resize '.Part_Number($D)
	    .' '.$Start.'cyl '.$End.'cyl\nq"| parted /dev/'.HDD_Name($D);
	LOG("Part_Resize> Cmd: [$cmd]\n");
	$out = `$cmd`;
	LOG("Part_Resize> Out: [$out]\n");
	if($out =~/error/i)
	{
	    return(1);
	}
    }

    return(0);
}



# Find out the maximum size a filesystem can be raised to.
# Will use ntfsresize if NTFS, parted else.
# Returns a number of bytes.
#
# Syntax: my $Max_Size = FS_Maximum_Size("/dev/hda1");
#
sub FS_Maximum_Size
{
    my($D) = shift || '';
    return(0) unless($D);

    LOG("FS_Maximum_Size> Called on dev [$D]\n");

    $D =~s/^\/dev\///;

    unless(Is_Resizable($D))
    {
	LOG("FS_Maximum_Size> Filesystem of [$D] is not handled.\n");
	return(0);
    }

    my $Max_Size = 0;

    if(Is_NTFS(Part_Type($D)))
    {
	my $cmd = "ntfsfix /dev/$D 2>&1";
	LOG("FS_Maximum_Size> Cmd: [$cmd]\n");

	$cmd = "(echo y|ntfsresize -i -f /dev/$D) 2>&1";
	LOG("FS_Maximum_Size> Cmd: [$cmd]\n");
	system($cmd);
	my $out = `$cmd`;
	LOG("FS_Maximum_Size> Out: [$out]\n");

	my(@Lines) = split(/\n/, $out);
	my $CurVol = 0;
	my $CurDev = 0;
	foreach(@Lines)
	{
	    if(m/Current volume size/i)
	    {
		$CurVol = $_;
	    }
	    elsif(m/Current device size/i)
	    {
		$CurDev = $_;
	    }
	}
	foreach($CurVol, $CurDev)
	{
	    s/^.* ([0-9]+) bytes.*$/$1/;
	}
	LOG("FS_Maximum_Size> Current volume size: [$CurVol]\n");
	LOG("FS_Maximum_Size> Current device size: [$CurDev]\n");

	$Max_Size = $CurDev;

        LOG("FS_Maximum_Size> We can raise the volume size to [$Max_Size]!\n");
    }
    else
    {
	my $cmd = 'echo -e "unit cyl\nprint '.Part_Number($D)
	    .'\nq\n"| parted /dev/'.HDD_Name($D).' 2>&1';
	LOG("FS_Maximum_Size> Cmd: [$cmd]\n");
	my $out = `$cmd`;
	LOG("FS_Maximum_Size> Out: [$out]\n");

	my(@lines) = split(/\n/, $out);
	foreach(@lines)
	{
	    if(m/^Maximum size/i)
	    {
		s/^\s*//;
		s/\s*$//;
		$Max_Size = $_;
		$Max_Size =~s/^Maximum size:\s*(\S+).*$/$1/i;
		last;
	    }
	}
	if($Max_Size =~/KB$/)
	{
	    $Max_Size =~s/[^0-9\.]//g;
	    $Max_Size *= 1024;
	}
	elsif($Max_Size =~/MB$/)
	{
	    $Max_Size =~s/[^0-9\.]//g;
	    $Max_Size *= 1024 * 1024;
	}
	elsif($Max_Size =~/GB$/)
	{
	    $Max_Size =~s/[^0-9\.]//g;
	    $Max_Size *= 1024 * 1024 * 1024;
	}

	LOG("FS_Maximum_Size> not NTFS / Can raise to [$Max_Size] bytes\n");
    }

    return($Max_Size);
}



# Find out the current size of a filesystem.
# Will use ntfsresize if NTFS, parted else.
# Returns a number of bytes.
#
# Syntax: my $FS_Size = FS_Size("/dev/hda1");
#
sub FS_Size
{
    my($D) = shift || '';
    return(0) unless($D);

    LOG("FS_Size> Called on dev [$D]\n");

    $D =~s/^\/dev\///;

    unless(Is_Resizable($D))
    {
	LOG("FS_Size> Filesystem of [$D] is not handled.\n");
	return(0);
    }

    my $Cur_Size = 0;

    if(Is_NTFS(Part_Type($D)))
    {
	my $cmd = "ntfsfix /dev/$D 2>&1";
	LOG("FS_Size> Cmd: [$cmd]\n");

	$cmd = "echo y|ntfsresize -i -f /dev/$D 2>&1";
	LOG("FS_Size> Cmd: [$cmd]\n");
	system($cmd);
	my $out = `$cmd`;
	LOG("FS_Size> Out: [$out]\n");

	my(@Lines) = split(/\n/, $out);
	foreach(@Lines)
	{
	    if(m/Current volume size/i)
	    {
		$Cur_Size = $_;
	    }
	}
	$Cur_Size =~s/^.* ([0-9]+) bytes.*$/$1/;

	LOG("FS_Size> NTFS / Size: [$Cur_Size] bytes\n");
    }
    else
    {
	my $cmd = 'echo -e "unit cyl\nprint '.Part_Number($D)
	    .'\nq\n"| parted /dev/'.HDD_Name($D).' 2>&1';
	LOG("FS_Size> Cmd: [$cmd]\n");
	my $out = `$cmd`;
	LOG("FS_Size> Out: [$out]\n");

	my(@lines) = split(/\n/, $out);
	foreach(@lines)
	{
	    if(m/^Size/i)
	    {
		s/^\s*//;
		s/\s*$//;
		$Cur_Size = $_;
		$Cur_Size =~s/^Size:\s*(\S+).*$/$1/i;
		last;
	    }
	}
	if($Cur_Size =~/KB$/)
	{
	    $Cur_Size =~s/[^0-9\.]//g;
	    $Cur_Size *= 1024;
	}
	elsif($Cur_Size =~/MB$/)
	{
	    $Cur_Size =~s/[^0-9\.]//g;
	    $Cur_Size *= 1024 * 1024;
	}
	elsif($Cur_Size =~/GB$/)
	{
	    $Cur_Size =~s/[^0-9\.]//g;
	    $Cur_Size *= 1024 * 1024 * 1024;
	}

	LOG("FS_Size> not NTFS / Size: [$Cur_Size] bytes\n");
    }

    return($Cur_Size);
}



# Looks for a file somewhere in the system path.
#
sub Is_In_Path
{
    my $Bin = shift || '';
    return(0) unless($Bin);
    foreach(split(/:/, $ENV{PATH}))
    {
	if(-e "$_/$Bin")
	{
	    return(1);
	}
    }
    return(0);
}



# Returns the correct way to unarchive a file.
# Ex.: system(Unzip($Path));
#      If $Path is "file.zip", returns "unzip \"file.zip\"".
#
sub Unzip
{
    my $Path = shift || '';
    return("# No Path") unless($Path);

    # Prefer star to tar, as it's faster and should handle
    # ACLs better (except for NTFS).

    my $STar = Is_In_Path("star");

    if($Path =~/\.zip$/i)
    {
	return("unzip \"$Path\"");
    }
    elsif($Path =~/\.tar$/i)
    {
	return(($STar ? "s":"")."tar xvf \"$Path\"");
    }
    elsif($Path =~/\.tar\.gz$/i)
    {
	return(($STar ? "star xvf":"tar xvfz")." \"$Path\"");
    }
    elsif($Path =~/\.tar\.bz2$/i)
    {
	return(($STar ? "star xvf":"bzip2 -d -c")
	       ." \"$Path\"".($STar ? "":"|tar xvf -"));
    }
    elsif($Path =~/\.tar\.7z$/i)
    {
	return("7za x -y -so \"$Path\"|".($STar ? "s":"")."tar xvf -");
    }
    elsif($Path =~/\.7z$/i)
    {
	return("7za x -y \"$Path\"");
    }

    return("# No idea");
}



# Display the results of the HDD discovery.
#
sub HDD_Describe
{
    my $pD = shift;
    foreach my $D (@{ $pD })
    {
	LOG("HDD_Describe> Device: [".$D->{Dev}."]\n");
	if(defined($D->{Heads}))
	{
	    LOG("HDD_Describe>   P.Table-read Heads: [".$D->{Heads}."]\n");
	}
	if(defined($D->{Sectors_Track}))
	{
	    LOG("HDD_Describe>   P.Table-read Sectors/track: ["
		.$D->{Sectors_Track}."]\n");
	}
	if(defined($D->{Cylinders}))
	{
	    LOG("HDD_Describe>   P.Table-read Cylinders: [".$D->{Cylinders}."]\n");
	}
	if(defined($D->{BIOS_Heads}))
	{
	    LOG("HDD_Describe>   BIOS-read Heads: [".$D->{BIOS_Heads}."]\n");
	}
	if(defined($D->{BIOS_Sectors_Track}))
	{
	    LOG("HDD_Describe>   BIOS-read Sectors/track: ["
		.$D->{BIOS_Sectors_Track}."]\n");
	}
	if(defined($D->{BIOS_Cylinders}))
	{
	    LOG("HDD_Describe>   BIOS-read Cylinders: [".$D->{BIOS_Cylinders}."]\n");
	}

	for(my $i = 0; $i <= $#{ $D->{Parts} }; $i++)
	{
	    foreach("Parts", "Types", "FS_Types", "Dirs", "Labels",
		    "Boot_Flags", "Start", "End", "Blocks")
	    {
		$D->{$_}->[$i] = "" unless(defined($D->{$_}->[$i]));
	    }
	    LOG("HDD_Describe>   Partition [$i]\n");
	    LOG("HDD_Describe>     Name: [".$D->{Parts}->[$i]."]\n");
	    LOG("HDD_Describe>     Type: [".$D->{Types}->[$i]."]\n");
	    LOG("HDD_Describe>     FS_Type: [".$D->{FS_Types}->[$i]."]\n");
	    LOG("HDD_Describe>     Dirs: [".$D->{Dirs}->[$i]."]\n");
	    LOG("HDD_Describe>     Label: [".$D->{Labels}->[$i]."]\n");
	    LOG("HDD_Describe>     Boot flag: [".$D->{Boot_Flags}->[$i]."]\n");
	    LOG("HDD_Describe>     Start: [".$D->{Start}->[$i]."]\n");
	    LOG("HDD_Describe>     End: [".$D->{End}->[$i]."]\n");
	    LOG("HDD_Describe>     Blocks: [".$D->{Blocks}->[$i]."]\n");
	}
    }
}



# Sends back the exact look of the drive(s)
#
sub HDD_Exact_Look
{
    my $pD = shift;

    my $Out = '';
    foreach my $D (@{ $pD })
    {
	$Out .= "--------------------fdisk start Device [".$D->{Dev}."]\n";
	$Out .= `fdisk -l /dev/$D->{Dev}`;
	$Out .= "--------------------fdisk end Device [".$D->{Dev}."]\n";
	$Out .= "--------------------ping start Device [".$D->{Dev}."]\n";
	$Out .= "P.Table-read Heads: [".$D->{Heads}."]\n";
	$Out .= "P.Table-read Sectors/track: [".$D->{Sectors_Track}."]\n";
	$Out .= "P.Table-read Cylinders: [".$D->{Cylinders}."]\n";
	$Out .= "BIOS-read Heads: [".$D->{BIOS_Heads}."]\n";
	$Out .= "BIOS-read Sectors/track: [".$D->{BIOS_Sectors_Track}."]\n";
	$Out .= "BIOS-read Cylinders: [".$D->{BIOS_Cylinders}."]\n";

	for(my $i = 0; $i <= $#{ $D->{Parts} }; $i++)
	{
	    $Out .= "--------------------ping start Partition [$i]\n";
	    $Out .= "Name: [".$D->{Parts}->[$i]."]\n";
	    $Out .= "Type: [".$D->{Types}->[$i]."]\n";
	    $Out .= "FS_Type: [".$D->{FS_Types}->[$i]."]\n";
	    $Out .= "Dirs: [".$D->{Dirs}->[$i]."]\n";
	    $Out .= "Label: [".$D->{Labels}->[$i]."]\n";
	    $Out .= "Boot flag: [".$D->{Boot_Flags}->[$i]."]\n";
	    $Out .= "Start: [".$D->{Start}->[$i]."]\n";
	    $Out .= "End: [".$D->{End}->[$i]."]\n";
	    $Out .= "Blocks: [".$D->{Blocks}->[$i]."]\n";
	    $Out .= "--------------------ping end Partition [$i]\n";
	}

	$Out .= "--------------------ping end Device [".$D->{Dev}."]\n";
    }

    return($Out);
}



# Sends back the exact look of the drive(s)
# not too proud about the quality of the code... :p
#
sub HDD_Get_From_Exact_Look
{
    my $Path = shift || '';
    my $pD = shift;

    LOG("HDD_GFEL> Path to HDD_Look: [$Path]\n");

    unless(defined($Path) && $Path && defined($pD) && -e $Path)
    {
	LOG("HDD_GFEL> ! Bad arguments sent to the sub.\n");
	unless(defined($pD))
	{
	    LOG("HDD_GFEL>   \$pD not defined\n");
	}
	return();
    }

    @{ $pD } = ();

    my $Loc = '';

    my $Dev = '';
    my $Dev_Heads = 0;
    my $Dev_Sectors_Track = 0;
    my $Dev_Cylinders = 0;
    my $BIOS_Heads = 0;
    my $BIOS_Sectors_Track = 0;
    my $BIOS_Cylinders = 0;
    my $Parts = '';
    my $Types = '';
    my $FS_Types = '';
    my $Dirs = '';
    my $Labels = '';
    my $Boot_Flags = '';
    my $Start = '';
    my $End = '';
    my $Blocks = '';

    open(D, $Path);
    while(<D>)
    {
	s/^\s*//;
	s/\s*$//;
	my $Line = $_;

	if($Line =~/--------------------ping start Device \[/)
	{
	    LOG("HDD_GFEL>   Line [$Line]\n");

	    $Dev = $Line;
	    $Dev =~s/--------------------ping start Device \[//;
	    $Dev =~s/\].*//;
	    $Dev =~s/\s//g;

	    $Dev_Heads = 0;
	    $Dev_Sectors_Track = 0;
	    $Dev_Cylinders = 0;
	    $BIOS_Heads = 0;
	    $BIOS_Sectors_Track = 0;
	    $BIOS_Cylinders = 0;
	    $Parts = '';
	    $Types = '';
	    $FS_Types = '';
	    $Dirs = '';
	    $Labels = '';
	    $Boot_Flags = '';
	    $Start = '';
	    $End = '';
	    $Blocks = '';

	    LOG("HDD_GFEL>   Device [$Dev]\n");
	}
	elsif($Line =~/--------------------ping start Partition \[/)
	{
	    my $Part = $Line;
	    $Part =~s/--------------------ping start Partition \[//;
	    $Part =~s/\].*//;
	    $Part =~s/\s//g;

	    LOG("HDD_GFEL>     Partition [$Part]\n");
	}
	elsif($Line =~/--------------------ping end Device \[/)
	{
	    LOG("HDD_GFEL>   End of info about Device [$Dev]\n");

	    my(%Rich) = ();
	    $Rich{Dev} = $Dev;
	    $Rich{Heads} = $Dev_Heads;
	    $Rich{Sectors_Track} = $Dev_Sectors_Track;
	    $Rich{Cylinders} = $Dev_Cylinders;
	    $Rich{BIOS_Heads} = $BIOS_Heads;
	    $Rich{BIOS_Sectors_Track} = $BIOS_Sectors_Track;
	    $Rich{BIOS_Cylinders} = $BIOS_Cylinders;
	    $Rich{Parts} = $Parts;
	    $Rich{Types} = $Types;
	    $Rich{FS_Types} = $FS_Types;
	    $Rich{Dirs} = $Dirs;
	    $Rich{Labels} = $Labels;
	    $Rich{Boot_Flags} = $Boot_Flags;
	    $Rich{Start} = $Start;
	    $Rich{End} = $End;
	    $Rich{Blocks} = $Blocks;

	    push(@{ $pD }, \%Rich);
	}
	else
	{
	    if($Line =~/P\.Table\-read Heads:/)
	    {
		$Dev_Heads = $Line;
		$Dev_Heads =~s/P\.Table\-read Heads: \[//;
		$Dev_Heads =~s/\].*//;
		$Dev_Heads =~s/\s//g;

		LOG("HDD_GFEL>     P.Table-read Heads: [$Dev_Heads]\n");
	    }
	    elsif($Line =~/P\.Table\-read Sectors\/track:/)
	    {
		$Dev_Sectors_Track = $Line;
		$Dev_Sectors_Track =~s/P\.Table\-read Sectors\/track: \[//;
		$Dev_Sectors_Track =~s/\].*//;
		$Dev_Sectors_Track =~s/\s//g;

		LOG("HDD_GFEL>     P.Table-read Sectors/track: [$Dev_Sectors_Track]\n");
	    }
	    elsif($Line =~/P\.Table\-read Cylinders:/)
	    {
		$Dev_Cylinders = $Line;
		$Dev_Cylinders =~s/P\.Table\-read Cylinders: \[//;
		$Dev_Cylinders =~s/\].*//;
		$Dev_Cylinders =~s/\s//;

		LOG("HDD_GFEL>     P.Table-read Cylinders: [$Dev_Cylinders]\n");
	    }
	    if($Line =~/BIOS-read Heads:/)
	    {
		$BIOS_Heads = $Line;
		$BIOS_Heads =~s/BIOS-read Heads: \[//;
		$BIOS_Heads =~s/\].*//;
		$BIOS_Heads =~s/\s//g;

		LOG("HDD_GFEL>     BIOS-read Heads: [$BIOS_Heads]\n");
	    }
	    elsif($Line =~/BIOS-read Sectors\/track:/)
	    {
		$BIOS_Sectors_Track = $Line;
		$BIOS_Sectors_Track =~s/BIOS-read Sectors\/track: \[//;
		$BIOS_Sectors_Track =~s/\].*//;
		$BIOS_Sectors_Track =~s/\s//g;

		LOG("HDD_GFEL>     BIOS-read Sectors/track: [$BIOS_Sectors_Track]\n");
	    }
	    elsif($Line =~/BIOS-read Cylinders:/)
	    {
		$BIOS_Cylinders = $Line;
		$BIOS_Cylinders =~s/BIOS-read Cylinders: \[//;
		$BIOS_Cylinders =~s/\].*//;
		$BIOS_Cylinders =~s/\s//;

		LOG("HDD_GFEL>     BIOS-read Cylinders: [$BIOS_Cylinders]\n");
	    }

	    if($Line =~/^Name:/)
	    {
		my $Name = $Line;
		$Name =~s/^Name: \[//;
		$Name =~s/\].*//;
		$Name =~s/\s//g;

		$Parts .= ':::' if($Parts);
		$Parts .= $Name;

		LOG("HDD_GFEL>       Name: [$Name]\n");
	    }
	    elsif($Line =~/^Type:/)
	    {
		my $Type = $Line;
		$Type =~s/^Type: \[//;
		$Type =~s/\].*//;
		$Type =~s/\s//g;

		$Types .= ':::' if($Types);
		$Types .= $Type;

		LOG("HDD_GFEL>       Type: [$Type]\n");
	    }
	    elsif($Line =~/^FS_Type:/)
	    {
		my $FS_Type = $Line;
		$FS_Type =~s/^FS_Type: \[//;
		$FS_Type =~s/\].*//;
		$FS_Type =~s/\s//g;

		$FS_Types .= ':::' if($FS_Types);
		$FS_Types .= $FS_Type;

		LOG("HDD_GFEL>       FS_Type: [$FS_Type]\n");
	    }
	    elsif($Line =~/^Dirs:/)
	    {
		my $Dir_s = $Line;
		$Dir_s =~s/^Dirs: \[//;
		$Dir_s =~s/\].*//;

		$Dirs .= ':::' if($Dirs);
		$Dirs .= $Dir_s;

		LOG("HDD_GFEL>       Dirs: [$Dir_s]\n");
	    }
	    elsif($Line =~/^Label:/)
	    {
		my $Label = $Line;
		$Label =~s/^Label: \[//;
		$Label =~s/\].*//;
		$Label =~s/\s//g;

		$Labels .= ':::' if($Labels);
		$Labels .= $Label;

		LOG("HDD_GFEL>       Label: [$Label]\n");
	    }
	    elsif($Line =~/^Boot flag:/)
	    {
		my $Boot_Flag = $Line;
		$Boot_Flag =~s/^Boot flag: \[//;
		$Boot_Flag =~s/\].*//;
		$Boot_Flag =~s/\s//g;

		$Boot_Flags .= ':::' if($Boot_Flags);
		$Boot_Flags .= $Boot_Flag;

		LOG("HDD_GFEL>       Boot flag: [$Boot_Flag]\n");
	    }
	    elsif($Line =~/^Start:/)
	    {
		my $Star_t = $Line;
		$Star_t =~s/^Start: \[//;
		$Star_t =~s/\].*//;
		$Star_t =~s/\s//g;

		$Start .= ':::' if($Start);
		$Start .= $Star_t;

		LOG("HDD_GFEL>       Start: [$Star_t]\n");
	    }
	    elsif($Line =~/^End:/)
	    {
		my $En_d = $Line;
		$En_d =~s/^End: \[//;
		$En_d =~s/\].*//;
		$En_d =~s/\s//g;

		$End .= ':::' if($End);
		$End .= $En_d;

		LOG("HDD_GFEL>       End: [$En_d]\n");
	    }
	    elsif($Line =~/^Blocks:/)
	    {
		my $Block_s = $Line;
		$Block_s =~s/^Blocks: \[//;
		$Block_s =~s/\].*//;
		$Block_s =~s/\s//g;

		$Blocks .= ':::' if($Blocks);
		$Blocks .= $Block_s;

		LOG("HDD_GFEL>       Blocks: [$Block_s]\n");
	    }
	}
    }
    close(D);

    # We couldn't use references any more because of the loop parsing.
    # Therefore, no use of arrays before, as references would overwrite values.
    # Time to clean up.
    #
    my(@New_D) = ();
    foreach my $D (@{ $pD })
    {	
	my(%Rich) = ();

	$Rich{Dev} = $D->{Dev};
	$Rich{Heads} = $D->{Dev_Heads};
	$Rich{Sectors_Track} = $D->{Dev_Sectors_Track};
	$Rich{Cylinders} = $D->{Dev_Cylinders};
	$Rich{BIOS_Heads} = $D->{BIOS_Heads};
	$Rich{BIOS_Sectors_Track} = $D->{BIOS_Sectors_Track};
	$Rich{BIOS_Cylinders} = $D->{BIOS_Cylinders};

	my(@Parts) = split(/:::/, $D->{Parts});
	$Rich{Parts} = \@Parts;

	my(@Types) = split(/:::/, $D->{Types});
	$Rich{Types} = \@Types;

	my(@FS_Types) = split(/:::/, $D->{FS_Types});
	$Rich{FS_Types} = \@FS_Types;

	my(@Dirs) = split(/:::/, $D->{Dirs});
	$Rich{Dirs} = \@Dirs;

	my(@Labels) = split(/:::/, $D->{Labels});
	$Rich{Labels} = \@Labels;

	my(@Boot_Flags) = split(/:::/, $D->{Boot_Flags});
	$Rich{Boot_Flags} = \@Boot_Flags;

	my(@Start) = split(/:::/, $D->{Start});
	$Rich{Start} = \@Start;

	my(@End) = split(/:::/, $D->{End});
	$Rich{End} = \@End;

	my(@Blocks) = split(/:::/, $D->{Blocks});
	$Rich{Blocks} = \@Blocks;

	push(@New_D, \%Rich);
    }
    @{ $pD } = @New_D;
}



# Tells wether a device exists or not on the current system.
# Ex.: if(Does_Dev_Exist("hda", \@Dev_Rich)) ...
# Ex.: if(Does_Dev_Exist("hda1", \@Dev_Rich)) ...
# Ex.: if(Does_Dev_Exist("hda", \@Dev_Rich)) ...
# Ex.: if(Does_Dev_Exist("rd/c0d0p1", \@Dev_Rich)) ...
# Ex.: if(Does_Dev_Exist("rd/c0d0", \@Dev_Rich)) ...
# Ex.: if(Does_Dev_Exist("mapper/VolGroup00-LogVol00", \@Dev_Rich)) ...
#
sub Does_Dev_Exist
{
    my $To_Check = shift || '';
    my $pD = shift || '';
    return(0) unless($To_Check && $pD);

    $To_Check =~s/^\/dev\/?//;

    foreach my $D (@{ $pD })
    {
	if($D->{Dev} eq $To_Check)
	{
	    return(1);
	}

	for(my $i = 0; $i <= $#{ $D->{Parts} }; $i++)
	{
	    if($D->{Parts}->[$i] eq $To_Check)
	    {
		return(1);
	    }
	}
    }

    return(0);
}



# ChkDev / Check device existence in /dev
#
# All conventional /dev files are enumerated here:
# http://www.lanana.org/docs/device-list/devices.txt
#
sub ChkDev
{
    my $Part = shift || '';
    return(0) unless($Part);

    $Part =~s/^\/dev\/?//;

    LOG("ChkDev> Will check the existence of [/dev/$Part]\n");

    if(-e "/dev/$Part")
    {
	LOG("ChkDev> Is ok.\n");
    }
    else
    {
	LOG("ChkDev> Must be created.\n");
	my $cmd = "mknod ";
#:::




    }
}




# ADT / Adjust Device Target
#
# Sometimes, you store an image called after device /dev/hda,
# and you want to restore it to a SATA-enabled system => /dev/sda.
# Ex.: ADT("hda", \@Dev_Rich) => may return "sda"
#      ADT("hda1", \@Dev_Rich) => may return "cciss/c0d0p1"
#      ADT("askme_a1", \@Dev_Rich) => may return "sda1"
#      ADT("mapper/VolGroup00-LogVol00", \@Dev_Rich) => may return "sda1"
#
sub ADT
{
    my $To_Check = shift || '';
    my $pD = shift || '';
    return($To_Check) unless($To_Check && $pD);

    $To_Check =~s/^\/dev\/?//;

    LOG("ADT> Check for [$To_Check]\n");

    # Find the device after the thing to check.
    #
    my $Device_To_Check = HDD_Name($To_Check);   # => hda or cciss/c0d0
    my $Part_To_Check = Part_Number($To_Check);  # => \d+ or nothing

    LOG("ADT>   Device to check: [$Device_To_Check]\n");
    LOG("ADT>   Part to check: [$Part_To_Check]\n");

    # If the device exists, leave...
    #
    if(Does_Dev_Exist($Device_To_Check, $pD))
    {
	LOG("ADT> The device [$Device_To_Check] exists\n");
	LOG("ADT> Return [$To_Check] (1)\n");
	return($To_Check);
    }

    # If we've already asked for the target of this device/part,
    # send it back.
    #
    if(defined($Target_Device{$Device_To_Check}))
    {
	LOG("ADT> Found target device: [$Target_Device{$Device_To_Check}] (2)\n");

	LOG("ADT>   To check: [$To_Check]\n");
	my $Defined = $To_Check;
	$Defined =~s/$Device_To_Check/$Target_Device{$Device_To_Check}/;
	$Defined =~s/\-LogVol\d*//g;
	LOG("ADT>   Defined: [$Defined]\n");

	if($To_Check =~/mapper\/VolGroup\d+/)
	{
	    if($Defined =~/mapper\/VolGroup\d+/)
	    {
		$Defined .= "-LogVol".Part_Number($To_Check);
	    }
	    elsif($Defined =~/(cciss|rd|ida|mapper)/)
	    {
		$Defined .= "p".(int(Part_Number($To_Check)) + 1);
	    }
	    else
	    {
		$Defined .= (int(Part_Number($To_Check)) + 1);
	    }
	}
	else
	{
	    if($Defined =~/mapper\/VolGroup\d+/)
	    {
		$Defined .= "-LogVol0".(int(Part_Number($To_Check)) - 1);
		$Defined =~s/LogVol00/LogVol0/;
	    }
	    elsif($Defined =~/(cciss|rd|ida|mapper)/)
	    {
		$Defined .= "p".Part_Number($To_Check);
	    }
	    else
	    {
		$Defined .= Part_Number($To_Check);
	    }
	}

	LOG("ADT> Return [$Defined] (2)\n");
	return($Defined);
    }

    # If not, we should ask... unless
    # 1. There's only one possible target device OR it's a LVM/RAID device
    # 2. And the user has passed a parameter to the kernel to prevent interactivity.
    #
    if($#Dev_Rich == 0
       && defined($P{Its_HDA_Stupid}) && $P{Its_HDA_Stupid} =~/Y|1/i)
    {
	my $Defined = $Dev_Rich[0]->{Dev};
	if($To_Check =~/mapper\/VolGroup\d+/)
	{
	    if($Defined =~/mapper\/VolGroup\d+/)
	    {
		$Defined .= "-LogVol".Part_Number($To_Check);
	    }
	    elsif($Defined =~/(cciss|rd|ida|mapper)/)
	    {
		$Defined .= "p".(int(Part_Number($To_Check)) + 1);
	    }
	    else
	    {
		$Defined .= (int(Part_Number($To_Check)) + 1);
	    }
	}
	else
	{
	    if($Defined =~/mapper\/VolGroup\d+/)
	    {
		$Defined .= "-LogVol0".(int(Part_Number($To_Check)) - 1);
		$Defined =~s/LogVol00/LogVol0/;
	    }
	    elsif($Defined =~/(cciss|rd|ida|mapper)/)
	    {
		$Defined .= "p".Part_Number($To_Check);
	    }
	    else
	    {
		$Defined .= Part_Number($To_Check);
	    }
	}

	LOG("ADT> Return [$Defined] (3)\n");
	$Target_Device{$Device_To_Check} = $Dev_Rich[0]->{Dev};
	return($Defined);
    }

    if($To_Check =~/(cciss|rd|ida|mapper)/
       && defined($P{Its_HDA_Stupid}) && $P{Its_HDA_Stupid} =~/Y|1/i)
    {
	LOG("ADT> Return [$To_Check] (3)\n");
	return($To_Check);
    }

    # Now, ask.
    #
    LOG("ADT> Ask what target device to use\n");

    my $cmd = 'dialog --colors --menu "\Zb\ZnThe device that has been'
	.' recorded initially is /dev/'.$Device_To_Check.' but it cannot'
	.' be found in your system. Maybe you would want us to apply'
	.' this image to one of the devices found on your system ?'
	.' For info, on your HDD...\n\n';

    foreach my $D (@Dev_Rich)
    {
	$cmd .= 'Device '.$D->{Dev}.' has '.($#{ $D->{Parts} } + 1).' part'
	    .($#{ $D->{Parts} } > 0 ? 's':'').'; ';

	for(my $i = 0; $i <= $#{ $D->{Parts} }; $i++)
	{
	    $cmd .= $D->{Parts}->[$i].' => '
		.($D->{Dirs}->[$i] ? substr($D->{Dirs}->[$i], 0, 50):'unknown')
		.(length($D->{Dirs}->[$i]) > 50 ? '...':'').'; ';
	}

	$cmd =~s/; $//;
	$cmd .= ')\n';
    }

    $cmd .= '\n\n" 20 73 5 "Cancel the process" "" ';

    # Sometimes, saved files refer to LVM or RAID volumes, and
    # they will appear on the target box only after first devices
    # have been restored (sda before mapper/VolGroup00). Give the
    # possibility not to map now.
    #
    if($To_Check =~/(cciss|rd|ida|mapper)/)
    {
	$cmd .= '"There is a RAID or LVM, it will appear, dont map!" "" ';
    }

    foreach my $D (@Dev_Rich)
    {
	$cmd .= '"'.$D->{Dev}.'" "" ';
    }
    $cmd .= ' 2>/tmp/checklist.tmp';
    system($cmd);

    if(-z "/tmp/checklist.tmp")
    {
	Quit();
    }

    my $Choice = '';
    open(DB, "/tmp/checklist.tmp");
    while(<DB>)
    {
	$Choice .= $_;
    }
    close(DB);
    `rm -f /tmp/checklist.tmp`;

    $Choice =~s/^\s*//;
    $Choice =~s/\s+.*$//;
    LOG("ADT> Choice: [$Choice]\n");

    if($Choice =~/cancel/i)
    {
	Quit();
    }

    if($Choice =~/there/i)
    {
	LOG("ADT> Part of a RAID/LVM to be built. Don't map now.\n");
	LOG("ADT> Return [$To_Check] (5)\n");
	$Target_Device{$Device_To_Check} = $To_Check;
	return($To_Check);
    }

    my $Defined = $Choice;
    if($To_Check =~/mapper\/VolGroup\d+/)
    {
	if($Defined =~/mapper\/VolGroup\d+/)
	{
	    $Defined .= "-LogVol".Part_Number($To_Check);
	}
	elsif($Defined =~/(cciss|rd|ida|mapper)/)
	{
	    $Defined .= "p".(int(Part_Number($To_Check)) + 1);
	}
	else
	{
	    $Defined .= (int(Part_Number($To_Check)) + 1);
	}
    }
    else
    {
	if($Defined =~/mapper\/VolGroup\d+/)
	{
	    $Defined .= "-LogVol0".(int(Part_Number($To_Check)) - 1);
	    $Defined =~s/LogVol00/LogVol0/;
	}
	elsif($Defined =~/(cciss|rd|ida|mapper)/)
	{
	    $Defined .= "p".Part_Number($To_Check);
	}
	else
	{
	    $Defined .= Part_Number($To_Check);
	}
    }

    LOG("ADT> Return [$Defined] (4)\n");
    $Target_Device{$Device_To_Check} = $Choice;
    return($Defined);
}



# Formats the filesystem of a partition.
# If FAT, will only format FAT32.
# If EXT, will only format EXT3.
#
# Ex.: Format("hda1", \@Dev_Rich);
# Ex.: Format("/dev/rd/c0d0p1", \@Dev_Rich);
#
sub Format
{
    my $Part = shift || '';
    my $pD = shift || '';
    return(0) unless($Part && $pD);

    $Part =~s/^\/dev\/?//;

    LOG("Format> Will format [/dev/$Part]\n");

    my $Done = 0;
    foreach my $D (@{ $pD })
    {
	for(my $i = 0; $i <= $#{ $D->{Parts} }; $i++)
	{
	    if($D->{Parts} eq $Part)
	    {
		LOG("Format> Partition known\n");
		LOG("Format>   Partition type: [".$D->{Types}->[$i]."]\n");
		LOG("Format>   FS type: [".$D->{FS_Types}->[$i]."]\n");

		my $cmd = '';
		if(Is_NTFS($D->{Types}->[$i]))
		{
		    $cmd = "mkntfs -Q";
		}
		elsif($D->{FS_Types}->[$i] =~/^fat/i)
		{
		    $cmd = "mkfs.vfat -F 32";
		}
		elsif($D->{FS_Types}->[$i] =~/^ext(2|3)$/i)
		{
		    $cmd = "mkfs.ext3";
		}

		if($cmd)
		{
		    $cmd .= " /dev/$Part";
		    Exec_Log($cmd, 0, "Format>   ");
		}
		else
		{
		    LOG("Format>   Don't know how to format this FS.\n");
		}

		++ $Done;
	    }
	}
    }

    unless($Done)
    {
	LOG("Format> Part [/dev/$Part] unknown; didn't format.\n");
    }
}



# Delete all partitions of a device.
# Ex.: Delete_All_Parts("hda", \@Dev_Rich);
#
sub Delete_All_Parts
{
    my $DEVICE = shift || '';
    my $pD = shift;

    $DEVICE =~s/^\/dev\///;

    LOG("Delete_All_Parts> * Suppressing all parts from device [$DEVICE]\n");

    # Well, PING is not going to touch the source it is to
    # get the images from !
    #
    if($SRC =~/dos/
       && ((Is_Mounted("/mnt/dos")
	    && HDD_Name(Dev_Mount_Point("/mnt/dos")) eq $DEVICE)
	   || ($Part_For_Storage
	       && HDD_Name($Part_For_Storage) eq $DEVICE)))
    {
	LOG("Delete_All_Parts>   Won't touch HDD [$DEVICE] because\n");
	LOG("Delete_All_Parts>     it contains the image to restore!\n");
	sleep($README_SLEEP);
	return();
    }

    # Empty segments must be removed
    #
    my(@New_D) = ();

    foreach my $D (@{ $pD })
    {
	if(! $D->{Dev} eq $DEVICE)
	{
	    push(@New_D, $D);
	}
	else
	{
	    my(@Parts) = ();
	    my(@Types) = ();
	    my(@FS_Types) = ();
	    my(@Dirs) = ();
	    my(@Labels) = ();
	    my(@Boot_Flags) = ();
	    my(@Start) = ();
	    my(@End) = ();
	    my(@Blocks) = ();

	    for(my $i = 0; $i <= $#{ $D->{Parts} }; $i++)
	    {
		if($D->{Parts}->[$i] ne 'null'
		   && $D->{Parts}->[$i] !~/(cciss|ida|rd|mapper)/i)
		{
		    push(@Parts, $D->{Parts}->[$i]);
		    push(@Types, $D->{Types}->[$i]);
		    push(@FS_Types, $D->{FS_Types}->[$i]);
		    push(@Dirs, $D->{Dirs}->[$i]);
		    push(@Labels, $D->{Labels}->[$i]);
		    push(@Boot_Flags, $D->{Boot_Flags}->[$i]);
		    push(@Start, $D->{Start}->[$i]);
		    push(@End, $D->{End}->[$i]);
		    push(@Blocks, $D->{Blocks}->[$i]);
		}
	    }

	    my(%Rich) = ();
	    $Rich{Dev} = $D->{Dev};
	    $Rich{Heads} = $D->{Heads};
	    $Rich{Sectors_Track} = $D->{Sectors_Track};
	    $Rich{Cylinders} = $D->{Cylinders};
	    $Rich{BIOS_Heads} = $D->{BIOS_Heads};
	    $Rich{BIOS_Sectors_Track} = $D->{BIOS_Sectors_Track};
	    $Rich{BIOS_Cylinders} = $D->{BIOS_Cylinders};
	    $Rich{Parts} = \@Parts;
	    $Rich{Types} = \@Types;
	    $Rich{FS_Types} = \@FS_Types;
	    $Rich{Dirs} = \@Dirs;
	    $Rich{Labels} = \@Labels;
	    $Rich{Boot_Flags} = \@Boot_Flags;
	    $Rich{Start} = \@Start;
	    $Rich{End} = \@End;
	    $Rich{Blocks} = \@Blocks;

	    push(@New_D, \%Rich);
	}
    }

    # Suppresssing all parts from the device $DEVICE.
    # When an extended partition is deleted, the contained parts
    # are automatically deleted at the same time.
    #
    foreach my $D (@New_D)
    {
	LOG("Delete_All_Parts>   Device [".$D->{Dev}."]\n");

	if($D->{Dev} eq $DEVICE)
	{
	    my $Last_Type = '';
	    my(@tmp) = ('p');
	    if($#{ $D->{Parts} } == -1)
	    {
		LOG("Delete_All_Parts>     No partition to delete.\n");
	    }
	    elsif($#{ $D->{Parts} } == 0)
	    {
		LOG("Delete_All_Parts>     Only one partition to delete.\n");
		push(@tmp, 'd');
	    }
	    else
	    {
		my(@Del_Start) = ();
		my(@Del_End) = ();
		for(my $i = 0; $i <= $#{ $D->{Parts} }; $i++)
		{
		    if($D->{Parts}->[$i] eq 'null')
		    {
			next;
		    }

		    # Not going to recreate these parts with fdisk !
		    #
		    if($D->{Parts}->[$i] =~/(cciss|ida|rd|mapper)/i)
		    {
			next;
		    }

		    my $flag = 0;
		    for(my $j = 0; $j <= $#Del_Start; $j++)
		    {
			if($D->{Start}->[$i] >= $Del_Start[$j]
			   && $D->{End}->[$i] <= $Del_End[$j])
			{
			    ++ $flag;
			    last;
			}
		    }
		    unless($flag)
		    {
			push(@tmp, 'd', Part_Number($D->{Parts}->[$i]));
			push(@Del_Start, $D->{Start}->[$i]);
			push(@Del_End, $D->{End}->[$i]);
			$Last_Type = $D->{Types}->[$i];
		    }
		}
	    }
	    if($#tmp > -1)
	    {
		# When there's only one partition remaining, fdisk does
		# not need the digit to be precised. So, we should remove
		# it. But, if the last part we wanted to remove was an
		# extended one, there can be other parts afterwards, and
		# then the digit should be written.
		#
		if(! Is_Part_Extended($Last_Type) && $tmp[-1] =~/^\d+$/)
		{
		    pop(@tmp);
		}
		push(@tmp, 'p');
		push(@tmp, 'w');
		my $cmd = 'echo -e "';
		foreach(@tmp)
		{
		    $cmd .= $_.'\n';
		}
		$cmd .= '" | fdisk /dev/'.$D->{Dev}.' 2>&1';
		LOG("Delete_All_Parts>     Cmd: [$cmd]\n");
		my $out = `$cmd`;
		$out =~s/^\s*//;
		$out =~s/\s*$//;
		LOG("Delete_All_Parts>     Output: [$out]\n");

		# Finally, if all partitions have been deleted, fdisk
		# now returns the number of heads/sectors by track/cylinders
		# by getting it from the kernel (= BIOS), and this can differ
		# from what it was before. We've got to re-set these settings
		# as they were. (Only possible on IDE devices. Not SCSI/SATA.)
		#
		if($D->{Heads} ne $D->{BIOS_Heads}
		   || $D->{Sectors_Track} ne $D->{BIOS_Sectors_Track}
		   || $D->{Cylinders} ne $D->{BIOS_Cylinders})
		{
		    LOG("Delete_All_Parts>     P.Table device geometry differs"
			." from BIOS's.\n");
		    if(-e "/proc/ide/$DEVICE/settings")
		    {
			LOG("Delete_All_Parts>       It's a IDE drive. Rectifying.\n");
			my $cmd = "echo bios_cyl:".$D->{Cylinders}
			." bios_head:".$D->{Heads}
			." bios_sect:".$D->{Sectors_Track}
			." >/proc/ide/$DEVICE/settings";
			LOG("Delete_All_Parts>       Cmd: [$cmd]\n");
			my $out = `$cmd 2>&1`;
			$out =~s/^\s*//;
			$out =~s/\s*$//;
			LOG("Delete_All_Parts>       Output: [$out]\n");
			LOG("Delete_All_Parts>       Synchronizing device [$DEVICE]\n");
			Synchronize_Device($DEVICE);
		    }
		    else
		    {
			LOG("Delete_All_Parts>       It's not a IDE drive."
			    ." Odd!! No action.\n");
		    }
		}
		else
		{
		    LOG("Delete_All_Parts>     P.Table device geometry is same"
			." as BIOS's.\n");
		}
	    }
	}
	else
	{
	    LOG("Delete_All_Parts>     Not [$DEVICE].\n");
	}
    }
}



# Kill a process after its name.
#
sub KILL
{
    my $Proc = shift || '';
    return unless($Proc);

    my $out = `ps -ef|grep "$Proc"|grep -v grep|grep -v "$MYSELF"`;
    my(@lines) = split(/\n/, $out);
    #LOG("PPID() Out: [$out]\n");

    my(@PIDs) = ();
    foreach my $L (@lines)
    {
	my @f = split(/ +/, $L);
	if(defined($f[1]))
	{
	    $f[1] =~s/^\s*//;
	    $f[1] =~s/\s*$//;
	    push(@PIDs, $f[1]);
	}
    }

    foreach(@PIDs)
    {
	system("kill -9 $_");
    }
}



# Re-create all partitions of a device.
# Ex.: Recreate_All_Parts("hda", \@Dev_Rich);
#
sub Recreate_All_Parts
{
    my $DEVICE = shift || '';
    my $pD = shift;

    $DEVICE =~s/^\/dev\///;

    LOG("Recreate_All_Parts> * Re-creating all parts of device [$DEVICE]\n");

    # Well, PING is not going to touch the source it is to
    # get the images from !
    #
    if($SRC =~/dos/
       && ((Is_Mounted("/mnt/dos")
	    && HDD_Name(Dev_Mount_Point("/mnt/dos")) eq $DEVICE)
	   || ($Part_For_Storage
	       && HDD_Name($Part_For_Storage) eq $DEVICE)))
    {
	LOG("Recreate_All_Parts>   Won't touch HDD [$DEVICE] because\n");
	LOG("Recreate_All_Parts>     it contains the image to restore!\n");
	sleep($README_SLEEP);
	return();
    }

    foreach my $D (@{ $pD })
    {
	LOG("Recreate_All_Parts>   Device [".$D->{Dev}."]\n");

	if($D->{Dev} eq $DEVICE)
	{
	    my(@tmp) = ('p');
	    my $cnt = 0;
	    my $First_T = 0;

	    my $Nb_Not_Null_Parts = 0;
	    for(my $i = 0; $i <= $#{ $D->{Parts} }; $i++)
	    {	
		if($D->{Parts}->[$i] eq 'null')
		{
		    next;
		}

		# Not going to recreate these parts with fdisk !
		#
		if($D->{Parts}->[$i] =~/(cciss|ida|rd|mapper)/i)
		{
		    next;
		}

		++ $Nb_Not_Null_Parts;
	    }

	    for(my $i = 0; $i <= $#{ $D->{Parts} }; $i++)
	    {	
		if($D->{Parts}->[$i] eq 'null')
		{
		    next;
		}

		# Not going to recreate these parts with fdisk !
		#
		if($D->{Parts}->[$i] =~/(cciss|ida|rd|mapper)/i)
		{
		    next;
		}

		my $L_Flag = 0;
		push(@tmp, 'n');
		#
		# The choice between primary vs. extended/logical part
		# is not given by fdisk if the choice is irrelevant. Case:
		# 1 extended part has been created, + 3 primary =>l is assumed.
		#
		if(Is_Part_Extended($D->{Types}->[$i]))
		{
		    push(@tmp, 'e');
		    ++ $cnt;
		}
		else
		{
		    if(Part_Number($D->{Parts}->[$i]) > 4)
		    {
			if($cnt < 4)
			{
			    push(@tmp, 'l');
			}
			++ $L_Flag;
		    }
		    else
		    {
			push(@tmp, 'p');
			++ $cnt;
		    }
		}
		#
		# Logical part's number is never asked.
		# Primary part's number is not asked when it's the last available
		# (means 2 primary parts and 1 ext'd part, or 3 primary parts, have
		# already been created).
		#
		unless($L_Flag || $cnt == 4)
		{
		    push(@tmp, Part_Number($D->{Parts}->[$i]));
		}
		push(@tmp, $D->{Start}->[$i]);
		push(@tmp, $D->{End}->[$i]);
		push(@tmp, 't');
		if($First_T)
		{
		    push(@tmp, Part_Number($D->{Parts}->[$i]));
		}
		else
		{
		    ++ $First_T;
		}
		push(@tmp, $D->{Types}->[$i]);
		if($D->{Boot_Flags}->[$i])
		{
		    push(@tmp, 'a');
		    #
		    # For boot flag, always need to tell which part,
		    # even if there's only one.
		    #
		    push(@tmp, Part_Number($D->{Parts}->[$i]));
		}
	    }
	    push(@tmp, 'p');
	    push(@tmp, 'w');
	    my $cmd = 'echo -e "';
	    foreach(@tmp)
	    {
		$cmd .= $_.'\n';
	    }
	    $cmd .= '" | fdisk /dev/'.$D->{Dev}.' 2>&1';
	    LOG("Recreate_All_Parts>     Cmd: [$cmd]\n");
	    my $out = `$cmd 2>&1`;
	    $out =~s/^\s*//;
	    $out =~s/\s*$//;
	    LOG("Recreate_All_Parts>     Output: [$out]\n");
	}
	else
	{
	    LOG("Recreate_All_Parts>     Not [$DEVICE].\n");
	}
    }
}



# Ex.: Fdisk_List('/dev/hda');
#
sub Fdisk_List
{
    my $D = shift || '';
    return unless($D);
    my $out = `fdisk -l $D`;
    return($out);
}



# Simple execution of a command, with log.
#
sub Exec_Log
{
    my $cmd = shift || '';
    my $spaces = shift || 0;
    my $prefix = shift || '';
    my $logit = shift || "nc";
    if($logit eq "nc")
    {
	if(defined($P{Verbosity}) && $P{Verbosity} == 0)
	{
	    $logit = 0;
	}
	else
	{
	    $logit = 1;
	}
    }
    if($logit)
    {
	$cmd = "($cmd) 2>&1";
    }
    else
    {
	$cmd = "($cmd) >/dev/null 2>&1";
    }
    LOG($prefix.(" "x$spaces)."Cmd: [$cmd]\n");
    if($logit)
    {
	my $out = '';
	$out = `$cmd` || '';
	$out =~s/^\s*//;
	$out =~s/\s*$//;
	LOG($prefix.(" "x$spaces)."Output: [$out]\n");
	return($out);
    }
    else
    {
	`$cmd`;
    }
}



# Discovering all HDD Devices and partitions.
#
# Syntax: HDD_Discover(\@Dev, \@Dev_Rich);
#
sub HDD_Discover
{
    # Pointers
    my($pDev) = shift;
    my($pD) = shift;

    @{ $pD } = ();

    # Will have to handle LVM devices...
    #
    {
	LOG("Disco> Preparing to handle possibly LVM devices\n");
	Exec_Log("vgscan; vgchange -ay; dmraid -ay", 2, "Disco> ");
    }

    foreach my $D (@{ $pDev })
    {
	LOG("Disco> Device [$D]\n");

	# Device geometry according to fdisk
	#
	my $Dev_Heads = 0;
	my $Dev_Sectors_Track = 0;
	my $Dev_Cylinders = 0;
	{
	    my $tmp = `sfdisk -G /dev/$D`;
	    $tmp =~s/^\s*//;
	    $tmp =~s/\s*$//;
	    if($tmp =~/(\d+)\s+cylinders/i)
	    {
		$Dev_Cylinders = $1;
	    }
	    if($tmp =~/(\d+)\s+heads/i)
	    {
		$Dev_Heads = $1;
	    }
	    if($tmp =~/(\d+)\s+sectors/i)
	    {
		$Dev_Sectors_Track = $1;
	    }
	}

	# Device geometry according to the kernel's reading
	# of BIOS data (and it can differ from real device
	# geometry). I store both data because people send their
	# log sometimes, and I'd like to know if someday, the
	# geometry given by the kernel becomes finally safe :)
	#
	my $BIOS_Heads = 0;
	my $BIOS_Sectors_Track = 0;
	my $BIOS_Cylinders = 0;
	{
	    my $tmp = `sfdisk -g /dev/$D`;
	    $tmp =~s/^\s*//;
	    $tmp =~s/\s*$//;
	    if($tmp =~/(\d+)\s+cylinders/i)
	    {
		$BIOS_Cylinders = $1;
	    }
	    if($tmp =~/(\d+)\s+heads/i)
	    {
		$BIOS_Heads = $1;
	    }
	    if($tmp =~/(\d+)\s+sectors/i)
	    {
		$BIOS_Sectors_Track = $1;
	    }
	}

	my(@Parts) = ();
	my(@Types) = ();
	my(@FS_Types) = ();
	my(@Dirs) = ();
	my(@Labels) = ();
	my(@Boot_Flags) = ();
	my(@Start) = ();
	my(@End) = ();
	my(@Blocks) = ();

	`fdisk -l /dev/$D >/tmp/bla`;
	if(-e "/tmp/bla")
	{
	    open(DB, "/tmp/bla");
	    while(<DB>)
	    {
		s/^\s*//;
		s/\s*$//;
		if(m/^\/dev\/$D/i)
		{
		    if(m/\*/)
		    {
			s/\*//g;    # Boot flag
			push(@Boot_Flags, 1);
		    }
		    else
		    {
			push(@Boot_Flags, 0);
		    }
		    s/  / /g while(m/  /);
		    s/^\s*\/dev\///i;
		    my(@F) = split(/ /, $_);
		    push(@Parts, $F[0]);
		    for(1..3)
		    {
			$F[$_] =~s/\D//g;
		    }
		    push(@Start, $F[1]);
		    push(@End, $F[2]);
		    push(@Blocks, $F[3]);
		    push(@Types, $F[4]);
		}
	    }
	    close(DB);
	    unlink("/tmp/bla");
	}

	# If LVM, add the volumes -- will be treated as parts.
	#
	# Warning, /dev/VolGroup00/LogVol00 should be transcribed to
	#   /dev/mapper/VolGroup00-LogVol00, as this is what df will show
	#   (function IsMounted() wouldn't see).
	#
	{
	    my $out = Exec_Log("lvscan", 2, "Disco> ");
	    my @L = split(/\n/, $out);
	    foreach my $L (@L)
	    {
		# [  ACTIVE       '/dev/VolGroup00/LogVol00' [15.34 GB] inherit]
		#
		$L =~s/^\s*//;
		if($L =~/^ACTIVE/)
		{
		    my $Part = $L;
		    $Part =~s/^[^\']+\'//;
		    $Part =~s/\'.*$//;
		    $Part =~s/^\/dev\///;
		    my(@f) = split(/\//, $Part);
		    push(@Parts, "mapper/$f[0]-$f[1]");
		    LOG("Disco>   Found LVM: [/dev/$Part] =>[/dev/$Parts[-1]]\n");

		    # I don't know how to retrieve the type of a logical volume,
		    # but I can at least detect if this is a linux swap.
		    #
		    LOG("Disco>   Is [/dev/$Parts[-1]] a linux swap ?\n");
		    if(Is_Swap("/dev/$Parts[-1]"))
		    {
			LOG("Disco>     It's a linux swap.\n");
			push(@Types, "82");
		    }
		    else
		    {
			LOG("Disco>     It's not.\n");
			push(@Types, '');
		    }
		}
	    }
	}

	for(my $i = 0; $i <= $#Parts; $i++)
	{
	    LOG("Disco>   Local part: [$Parts[$i]]; type: [$Types[$i]]\n");

	    # Keep label (if ntfs)
	    #
	    if(Is_NTFS($Types[$i]))
	    {
		my $Label = `ntfslabel /dev/$Parts[$i]`;
		$Label =~s/^\s*//;
		$Label =~s/\s*$//;
		push(@Labels, $Label);
	    }
	    else
	    {
		push(@Labels, '');
	    }

	    # Keep first directory names, to help users identify their
	    # partitions (think USB mass storage). Get also filesystem type.
	    #
	    Umount("/mnt/dos");
	    Mount("/dev/$Parts[$i]", "/mnt/dos", "");

	    if(Is_Mounted("/dev/$Parts[$i]"))
	    {
		my $fst = FS_Type($Parts[$i]);
		push(@FS_Types, $fst);
		LOG("Disco>     Found FS type: [$fst]]\n");

		opendir(DIR, "/mnt/dos");
		my(@Files) = readdir(DIR);
		closedir(DIR);
		my(@R) = ();
		foreach(@Files)
		{
		    next if(m/^\.{1,2}$/);
		    next unless(-d "/mnt/dos/$_");
		    next if(m/^System Volume Information$/i);   # Not an info
		    push(@R, $_);
		}
		push(@Dirs, join(',', @R));
		Exec_Log("umount /mnt/dos", 4, "Disco> ");

		LOG("Disco>     Found directories: [".join(', ', @R)."]\n");
	    }
	    else
	    {
		LOG("Disco>     No info about directories / FS type.\n");
		push(@Dirs, '');
		push(@FS_Types, '');
	    }
	}

	# Storage!
	#
	my(%Rich) = ();
	$Rich{Dev} = $D;
	$Rich{Heads} = $Dev_Heads;
	$Rich{Sectors_Track} = $Dev_Sectors_Track;
	$Rich{Cylinders} = $Dev_Cylinders;
	$Rich{BIOS_Heads} = $BIOS_Heads;
	$Rich{BIOS_Sectors_Track} = $BIOS_Sectors_Track;
	$Rich{BIOS_Cylinders} = $BIOS_Cylinders;
	$Rich{Parts} = \@Parts;
	$Rich{Types} = \@Types;
	$Rich{FS_Types} = \@FS_Types;
	$Rich{Dirs} = \@Dirs;
	$Rich{Labels} = \@Labels;
	$Rich{Boot_Flags} = \@Boot_Flags;
	$Rich{Start} = \@Start;
	$Rich{End} = \@End;
	$Rich{Blocks} = \@Blocks;

	push(@{ $pD }, \%Rich);
    }

    # Now, handle the issue of possible unused segments. Complex.
    #
    # Ex.: Disk /dev/hda: 85.8 GB, 85899345920 bytes
    #      255 heads, 63 sectors/track, 10443 cylinders
    #      Units = cylinders of 16065 * 512 = 8225280 bytes
    #
    #         Device Boot      Start         End      Blocks   Id  System
    #      /dev/hda1               1           5       40131   de  Dell Utility
    #      /dev/hda2   *           6        9268    74405047+   7  HPFS/NTFS
    #      /dev/hda3            9290       10443     9269505   83  Linux
    #
    # Ex.: Disk /dev/hda: 85.8 GB, 85899345920 bytes
    #      255 heads, 63 sectors/track, 10443 cylinders
    #      Units = cylinders of 16065 * 512 = 8225280 bytes
    #
    #         Device Boot      Start         End      Blocks   Id  System
    #      /dev/hda1   *           1        1000     8032468+   7  HPFS/NTFS
    #      /dev/hda2            1001        8000    56227500    5  Extended
    #      /dev/hda3            8001        9000     8032500   83  Linux
    #      /dev/hda5            1001        3000    16064968+  83  Linux
    #      /dev/hda6            3001        7000    32129968+  83  Linux
    #
    LOG("Disco> * Handle the issue of empty segments\n");

    my(@New) = ();

    foreach my $D (@{ $pD })
    {
	LOG("Disco>   Device [".$D->{Dev}."]\n");

	my(@Parts) = ();
	my(@Types) = ();
	my(@FS_Types) = ();
	my(@Dirs) = ();
	my(@Labels) = ();
	my(@Boot_Flags) = ();
	my(@Start) = ();
	my(@End) = ();
	my(@Blocks) = ();

	# No partitions in the device
	#
	if($#{ $D->{Parts} } < 0)
	{
	    LOG("Disco>     No partition. No problem.\n");
	    push(@New, $D);
	    next;
	}

	# Storage...
	#
	my(%Rich) = ();

	# Keep device general info
	#
	foreach('Dev', 'Heads', 'Sectors_Track', 'Cylinders',
		'BIOS_Heads', 'BIOS_Sectors_Track', 'BIOS_Cylinders')
	{
	    $Rich{$_} = $D->{$_};
	}

	# First, make sure at least one part starts at block 1.
	#
	LOG("Disco>     Check wether one part starts at block 1\n");

	my $First_Start = $D->{Cylinders};
	for(my $i = 0; $i <= $#{ $D->{Parts} }; $i++)
	{
	    if($D->{Start}->[$i] < $First_Start)
	    {
		$First_Start = $D->{Start}->[$i];
	    }
	}
	LOG("Disco>       First start: [$First_Start]\n");

	if($First_Start > 1)
	{
	    LOG("Disco>       (1) Found null segment [1]-["
		.($First_Start - 1)."]\n");

	    push(@Parts, 'null');
	    push(@Types, 'null');
	    push(@FS_Types, 'null');
	    push(@Dirs, 'null');
	    push(@Labels, 'null');
	    push(@Boot_Flags, 'null');
	    push(@Start, 1);
	    push(@End, $First_Start - 1);
	    push(@Blocks, 'null');
	}
	else
	{
	    LOG("Disco>       No null segment here\n");
	}

	# Next, identify ext'd parts, so to find out its (damned) children
	#
	LOG("Disco>     Identifying ext'd parts\n");

	my(@Extd) = ();
	for(my $i = 0; $i <= $#{ $D->{Parts} }; $i++)
	{
	    if(Is_Part_Extended($D->{Types}->[$i]))
	    {
		push(@Extd, $i);
	    }
	}

	LOG("Disco>       Extd parts:\n");
	foreach(@Extd)
	{
	    LOG("Disco>         [$_] => [".$D->{Parts}->[$_]."]\n");
	}

	# Worth knowing which parts are located in an ext'd
	#
	LOG("Disco>     Identifying logical and not logical parts\n");

	my(@Not_Logical) = ();
	my(@Logical) = ();
	for(my $i = 0; $i <= $#{ $D->{Parts} }; $i++)
	{
	    my $In_Extd = 0;
	    foreach my $d (@Extd)
	    {
		if($D->{Start}->[$i] >= $D->{Start}->[$d]
		   && $D->{End}->[$i] <= $D->{End}->[$d]
		   && $i != $d)
		{
		    ++ $In_Extd;
		    LOG("Disco>       Part [$i] ["
			.$D->{Parts}->[$i]."] in extd part [$d]\n");
		}
	    }
	    if($In_Extd)
	    {
		push(@Logical, $i);
	    }
	    else
	    {
		push(@Not_Logical, $i);
	    }
	}

	LOG("Disco>       Logical parts:\n");
	foreach(@Logical)
	{
	    LOG("Disco>         [$_] => [".$D->{Parts}->[$_]."]\n");
	}

	LOG("Disco>       Not Logical parts:\n");
	foreach(@Not_Logical)
	{
	    LOG("Disco>         [$_] => [".$D->{Parts}->[$_]."]\n");
	}

	# Now, find empty segments concerning only parts that are not
	# located in an extd part.
	#
	LOG("Disco>     Find empty segments outside not logical parts\n");

	foreach my $i (@Not_Logical)
	{
	    LOG("Disco>       Part [$i] / [".$D->{Parts}->[$i]."]\n");

	    # Must be one part finishing just before the start of current.
	    #
	    my $Closest_End = 0;
	    foreach my $j (@Not_Logical)
	    {
		if($D->{End}->[$j] < $D->{Start}->[$i]
		   && $D->{End}->[$j] > $Closest_End)
		{
		    $Closest_End = $D->{End}->[$j];
		}
	    }

	    LOG("Disco>         Closest part ends at block [$Closest_End]\n");

	    if($Closest_End < ($D->{Start}->[$i] - 1) && $Closest_End > 0)
	    {
		LOG("Disco>         (2) Found null segment ["
		    .($Closest_End + 1)."]-[".($D->{Start}->[$i] - 1)."]\n");

		push(@Parts, 'null');
		push(@Types, 'null');
		push(@FS_Types, 'null');
		push(@Dirs, 'null');
		push(@Labels, 'null');
		push(@Boot_Flags, 'null');
		push(@Start, $Closest_End + 1);
		push(@End, $D->{Start}->[$i] - 1);
		push(@Blocks, 'null');
	    }
	    else
	    {
		LOG("Disco>           No space found.\n");
	    }
	}

	# And don't forget the last segment. Have to look after.
	# No possible problem with an extd part here.
	#
	LOG("Disco>     Find an empty segment after farest logical part\n");

	my $Farest_End = 0;
	for(my $j = 0; $j <= $#{ $D->{Parts} }; $j++)
	{
	    if($D->{Parts}->[$j] ne 'null'
	       && $D->{End}->[$j] > $Farest_End)
	    {
		$Farest_End = $D->{End}->[$j];
	    }
	}

	LOG("Disco>       Farest not logical part ends at block ["
	    ."$Farest_End]\n");

	if($Farest_End < $D->{Cylinders})
	{
	    LOG("Disco>       (3) Found null segment ["
		.($Farest_End + 1)."]-[".$D->{Cylinders}."]\n");

	    push(@Parts, 'null');
	    push(@Types, 'null');
	    push(@FS_Types, 'null');
	    push(@Dirs, 'null');
	    push(@Labels, 'null');
	    push(@Boot_Flags, 'null');
	    push(@Start, $Farest_End + 1);
	    push(@End, $D->{Cylinders});
	    push(@Blocks, 'null');
	}
	else
	{
	    LOG("Disco>        No empty space found\n");
	}

	# Now, checks segments concerning only parts that are
	# located in an extd part.
	#
	LOG("Disco>     Find empty segments in ext'd parts\n");

	foreach my $d (@Extd)
	{
	    LOG("Disco>       Search ext'd part [$d] / [".$D->{Parts}->[$d]."]\n");

	    # Make sure there's one part at the beginning of the extd part
	    #
	    LOG("Disco>         Make sure it begins with one part\n");

	    my $First_Start = $D->{End}->[$d];
	    foreach my $i (@Logical)
	    {
		if($D->{Start}->[$i] >= $D->{Start}->[$d]
		   && $D->{End}->[$i] <= $D->{End}->[$d]
		   && $D->{Start}->[$i] < $First_Start
		   && $d != $i)
		{
		    $First_Start = $D->{Start}->[$i];
		}
	    }
	    LOG("Disco>           First Start: [$First_Start]\n");

	    if($First_Start > $D->{Start}->[$d])
	    {
		LOG("Disco>           (4) Found null segment ["
		    .($D->{Start}->[$d])."]-[".($First_Start - 1)."]\n");

		push(@Parts, 'null');
		push(@Types, 'null');
		push(@FS_Types, 'null');
		push(@Dirs, 'null');
		push(@Labels, 'null');
		push(@Boot_Flags, 'null');
		push(@Start, $D->{Start}->[$d]);
		push(@End, $First_Start - 1);
		push(@Blocks, 'null');
	    }
	    else
	    {
		LOG("Disco>           No empty block at the beginning of this ext'd\n");
	    }

	    # Must be one part finishing just before the start of current.
	    #
	    LOG("Disco>         Check each contained part for space before it\n");

	    foreach my $i (@Logical)
	    {
		LOG("Disco>           Check part [$i] [".$D->{Parts}->[$i]."]\n");

		if($D->{Start}->[$i] < $D->{Start}->[$d]
		   || $D->{End}->[$i] > $D->{End}->[$d]
		   || $d == $i)
		{
		    LOG("Disco>             Out of scope. Next.\n");
		    next;
		}

		my $Closest_End = $D->{Start}->[$d];
		for(my $j = 0; $j <= $#{ $D->{Parts} }; $j++)
		{
		    if($D->{End}->[$j] > $Closest_End
		       && $D->{End}->[$j] < $D->{Start}->[$i])
		    {
			$Closest_End = $D->{End}->[$j];
		    }
		}

		LOG("Disco>             Closest part ends at block [$Closest_End]\n");

		if($Closest_End < ($D->{Start}->[$i] - 1))
		{
		    LOG("Disco>             (5) Found null segment ["
			.($Closest_End + 1)."]-[".($D->{Start}->[$i] - 1)."]\n");

		    if($Closest_End == $D->{Start}->[$d])
		    {
			LOG("Disco>               Ignoring it; 1st segment of ext'd\n");
			LOG("Disco>               already done.\n");
		    }
		    else
		    {
			push(@Parts, 'null');
			push(@Types, 'null');
			push(@FS_Types, 'null');
			push(@Dirs, 'null');
			push(@Labels, 'null');
			push(@Boot_Flags, 'null');
			push(@Start, $Closest_End + 1);
			push(@End, $D->{Start}->[$i] - 1);
			push(@Blocks, 'null');
		    }
		}
		else
		{
		    LOG("Disco>             No space found.\n");
		}
	    }

	    # Check remaining space at the end of the ext'd
	    #
	    LOG("Disco>         Check for a segment at the end of the ext'd\n");

	    my $Farest_End = $D->{Start}->[$d];
	    foreach my $i (@Logical)
	    {
		if($D->{Start}->[$i] < $D->{Start}->[$d]
		   || $D->{End}->[$i] > $D->{End}->[$d]
		   || $d == $i)
		{
		    LOG("Disco>             Out of scope. Next.\n");
		    next;
		}
		if($D->{End}->[$i] > $Farest_End)
		{
		    $Farest_End = $D->{End}->[$i];
		}
	    }

	    LOG("Disco>           Farest part ends at block [$Farest_End]\n");

	    if($Farest_End < $D->{End}->[$d])
	    {
		LOG("Disco>             (6) Found null segment ["
		    .($Farest_End + 1)."]-[".$D->{End}->[$d]."]\n");

		push(@Parts, 'null');
		push(@Types, 'null');
		push(@FS_Types, 'null');
		push(@Dirs, 'null');
		push(@Labels, 'null');
		push(@Boot_Flags, 'null');
		push(@Start, $Farest_End + 1);
		push(@End, $D->{End}->[$d]);
		push(@Blocks, 'null');
	    }
	    else
	    {
		LOG("Disco>             No space found.\n");
	    }
	}

	# Keep non-null partitions as well... :)
	#
	for(my $i = 0; $i <= $#{ $D->{Parts} }; $i++)
	{
	    push(@Parts, $D->{Parts}->[$i]);
	    push(@Types, $D->{Types}->[$i]);
	    push(@FS_Types, $D->{FS_Types}->[$i]);
	    push(@Dirs, $D->{Dirs}->[$i]);
	    push(@Labels, $D->{Labels}->[$i]);
	    push(@Boot_Flags, $D->{Boot_Flags}->[$i]);
	    push(@Start, $D->{Start}->[$i]);
	    push(@End, $D->{End}->[$i]);
	    push(@Blocks, $D->{Blocks}->[$i]);
	}

	# Storage!
	#
	$Rich{Parts} = \@Parts;
	$Rich{Types} = \@Types;
	$Rich{FS_Types} = \@FS_Types;
	$Rich{Dirs} = \@Dirs;
	$Rich{Labels} = \@Labels;
	$Rich{Boot_Flags} = \@Boot_Flags;
	$Rich{Start} = \@Start;
	$Rich{End} = \@End;
	$Rich{Blocks} = \@Blocks;

	push(@New, \%Rich);
    }

    LOG("Disco> * After handling the issue of possible unused segments,\n");
    LOG("Disco> * we found this:\n");
    HDD_Describe(\@New);

    @{ $pD } = @New;
}



# Retrieves the MD5 checksum of a file.
# If the file is too big, will truncate it and calculate over
# small, merged parts.
#
# Syntax: my $MD5 = MD5("/tmp/myfile");
#
sub MD5
{
    my $Path = shift || '';
    return('') unless($Path && -e $Path);

    my $To_Calc = "";
    my $Size = (stat($Path))[7];
    if($Size < 1000000)
    {
	$To_Calc = $Path;
    }
    else
    {
	my $P = $Path;
	$P =~s/\$/\\\$/g;
#	$P =~s/\@/\\\@/g;
#	$P =~s/\%/\\\%/g;
#	$P =~s/\[/\\\[/g;
#	$P =~s/\]/\\\]/g;
	system("dd if=\"$P\" of=/tmp/MD5.$$ count=1 bs=50000 >/dev/null 2>&1");
	system("tail --bytes=50000 \"$P\" >>/tmp/MD5.$$");
	$To_Calc = "/tmp/MD5.$$";
    }

    my $P = $To_Calc;
    $P =~s/\$/\\\$/g;
#    $P =~s/\@/\\\@/g;
#    $P =~s/\%/\\\%/g;
#    $P =~s/\[/\\\[/g;
#    $P =~s/\]/\\\]/g;

#   LOG("!!! [md5sum \"$P\"]\n");
    my $out = `md5sum "$P"`;
    $out = substr($out, 0, 32);

    if(-e "/tmp/MD5.$$")
    {
	unlink("/tmp/MD5.$$");
    }

    return($out);
}



# Convert each two-digit hex number back to an ASCII character.
#
sub hex_to_ascii($)
{
    (my $str = shift) =~ s/([a-fA-F0-9]{2})/chr(hex $1)/eg;
    return $str;
}



# Convert each ASCII character to a two-digit hex number.
#
sub ascii_to_hex($)
{
    (my $str = shift) =~ s/(.|\n)/sprintf("%02lx", ord $1)/eg;
    return $str;
}



# Writes a standard MBR to a device.
#
# To restore a NTFS MBR, dd ntfs.mbr to /dev/hda. Then, rebuild the p.table.
# Or, dd from 0000 to 01BD, and last two bytes (from 01FE to 01FF, the usual 55 AA)
# (the MBR can be taken from any Windows OS, or from public standard code).
#
sub Write_Std_MBR
{
    my $DEVICE = shift || '';

    $DEVICE =~s/^\/dev\///;

    LOG("Write_Std_MBR> * Writing a new MBR for device [$DEVICE]\n");

    my(@Bytes) = ("c033", "d08e", "00bc", "fb7c", "0750", "1f50", "befc", "7c1b",
		  "1bbf", "5006", "b957", "01e5", "a4f3", "bdcb", "07be", "04b1",
		  "6e38", "7c00", "7509", "8313", "10c5", "f4e2", "18cd", "f58b",
		  "c683", "4910", "1974", "2c38", "f674", "b5a0", "b407", "8b07",
		  "acf0", "003c", "fc74", "07bb", "b400", "cd0e", "eb10", "88f2",
		  "104e", "46e8", "7300", "fe2a", "1046", "7e80", "0b04", "0b74",
		  "7e80", "0c04", "0574", "b6a0", "7507", "80d2", "0246", "8306",
		  "0846", "8306", "0a56", "e800", "0021", "0573", "b6a0", "eb07",
		  "81bc", "fe3e", "557d", "74aa", "800b", "107e", "7400", "a0c8",
		  "07b7", "a9eb", "fc8b", "571e", "f58b", "bfcb", "0005", "568a",
		  "b400", "cd08", "7213", "8a23", "24c1", "983f", "de8a", "fc8a",
		  "f743", "8be3", "86d1", "b1d6", "d206", "42ee", "e2f7", "5639",
		  "770a", "7223", "3905", "0846", "1c73", "01b8", "bb02", "7c00",
		  "4e8b", "8b02", "0056", "13cd", "5173", "744f", "324e", "8ae4",
		  "0056", "13cd", "e4eb", "568a", "6000", "aabb", "b455", "cd41",
		  "7213", "8136", "55fb", "75aa", "f630", "01c1", "2b74", "6061",
		  "006a", "006a", "76ff", "ff0a", "0876", "006a", "0068", "6a7c",
		  "6a01", "b410", "8b42", "cdf4", "6113", "7361", "4f0e", "0b74",
		  "e432", "568a", "cd00", "eb13", "61d6", "c3f9", "6e49", "6176",
		  "696c", "2064", "6170", "7472", "7469", "6f69", "206e", "6174",
		  "6c62", "0065", "7245", "6f72", "2072", "6f6c", "6461", "6e69",
		  "2067", "706f", "7265", "7461", "6e69", "2067", "7973", "7473",
		  "6d65", "4d00", "7369", "6973", "676e", "6f20", "6570", "6172",
		  "6974", "676e", "7320", "7379", "6574", "006d", "0000", "0000",
		  "0000", "0000", "0000", "0000", "0000", "0000", "0000", "0000",
		  "0000", "0000", "0000", "0000", "0000", "0000", "0000", "0000",
		  "0000", "0000", "0000", "0000", "0000", "0000", "0000", "0000",
		  "0000", "0000", "2c00", "6344", "0000", "0000", "0000");

    open(OUT, ">/tmp/mbr.1.$$");
    binmode(OUT);
    foreach my $Curr (@Bytes)
    {
	my $Rev = substr($Curr, 2, 2).substr($Curr, 0, 2);
	print OUT hex_to_ascii($Rev);
    }
    close(OUT);
    LOG("Write_Std_MBR>   Wrote [/tmp/mbr.1.$$]\n");
    my $Hex = `hexdump /tmp/mbr.1.$$`;
    LOG("Write_Std_MBR>     Contents: [$Hex]\n");

    open(OUT, ">/tmp/mbr.2.$$");
    binmode(OUT);
    my $A = "55aa";
    print OUT hex_to_ascii($A);
    close(OUT);
    LOG("Write_Std_MBR>   Wrote [/tmp/mbr.2.$$]\n");
    $Hex = `hexdump /tmp/mbr.2.$$`;
    LOG("Write_Std_MBR>     Contents: [$Hex]\n");

    Exec_Log("dd if=/tmp/mbr.1.$$ of=/dev/$DEVICE", 0, "Write_Std_MBR>   ");
    Exec_Log("dd if=/dev/$DEVICE of=/tmp/$DEVICE.$$ count=1 bs=510", 0,
	     "Write_Std_MBR>   ");
    Exec_Log("cat /tmp/mbr.2.$$ >>/tmp/$DEVICE.$$", 0, "Write_Std_MBR>   ");
    Exec_Log("dd if=/tmp/$DEVICE.$$ of=/dev/$DEVICE", 0, "Write_Std_MBR>   ");

    foreach("/tmp/mbr.1.$$", "/tmp/mbr.2.$$", "/tmp/$DEVICE.$$")
    {
	if(-e $_)
	{
	    unlink($_);
	}
    }
}



# Replaces Dell's proprietary MBR with a standard one.
#
# Fixes the DELL NTFS boot sector. The one shipped with Dell boxes
# has a number of hidden sectors that was OK as long as we had a
# /dev/hda1 Dell Utility partition. Once removed, 1Ch must be fixed.
#
sub Fix_Dell_Boot
{
    my $DEVICE = shift || '';

    $DEVICE =~s/^\/dev\///;

    LOG("Fix_Dell_Boot> * Fixing the number of hidden sectors for [$DEVICE]\n");

    Exec_Log("dd if=/dev/$DEVICE of=/tmp/$DEVICE.$$ bs=512 count=16", 0,
	     "Fix_Dell_Boot>   ");

    my $byteCount = 0;

    open(IN, "< /tmp/$DEVICE.$$");
    binmode(IN);
    open(OUT, "> /tmp/$DEVICE.$$.new");

    my $Value = 63;    # 63 hidden sectors, for the MBR + 62 empty.

    binmode(OUT);
    while(read(IN, $b, 1))
    {
	if($byteCount == 28)
	{
	    my $tmp = sprintf("%x", $Value);
	    while(length($tmp) < 8)
	    {
		$tmp = "0$tmp";
	    }
	    print(OUT substr(pack("i", sprintf("%d", hex(substr($tmp, 6, 2)))), 0, 1));
	}
	elsif($byteCount == 29)
	{
	    my $tmp = sprintf("%x", $Value);
	    while(length($tmp) < 8)
	    {
		$tmp = "0$tmp";
	    }
	    print(OUT substr(pack("i", sprintf("%d", hex(substr($tmp, 4, 2)))), 0, 1));
	}
	elsif($byteCount == 30)
	{
	    my $tmp = sprintf("%x", $Value);
	    while(length($tmp) < 8)
	    {
		$tmp = "0$tmp";
	    }
	    print(OUT substr(pack("i", sprintf("%d", hex(substr($tmp, 2, 2)))), 0, 1));
	}
	elsif($byteCount == 31)
	{
	    my $tmp = sprintf("%x", $Value);
	    while(length($tmp) < 8)
	    {
		$tmp = "0$tmp";
	    }
	    print(OUT substr(pack("i", sprintf("%d", hex(substr($tmp, 0, 2)))), 0, 1));
	}
	else
	{
	    print(OUT $b);
	}

	$byteCount++;
    }

    close(IN);
    close(OUT);

    LOG("Fix_Dell_Boot>   Wrote [$byteCount] bytes.\n");

    my $Hex = `hexdump /tmp/$DEVICE.$$`;
    LOG("Fix_Dell_Boot>     Contents of [/tmp/$DEVICE.$$]: [$Hex]\n");
    $Hex = `hexdump /tmp/$DEVICE.$$.new`;
    LOG("Fix_Dell_Boot>     Contents of [/tmp/$DEVICE.$$.new]: [$Hex]\n");

    Exec_Log("dd if=/tmp/$DEVICE.$$.new of=/dev/$DEVICE", 0, "Fix_Dell_Boot>   ");

    foreach("/tmp/$DEVICE.$$", "/tmp/$DEVICE.$$.new")
    {
	if(-e $_)
	{
	    unlink($_);
	}
    }
}



sub Read_Dir
{
    # Pointers.
    my($pF) = shift;
    my($pFF) = shift;
    my($pMulti) = shift;

    # $SRC and $Entity need no pointer as they are global vars...

    return unless($pF && $pFF && $pMulti);

    LOG("* READ DIR\n");

    opendir(DIR, "$SRC/$Entity");
    @{ $pF } = readdir(DIR);
    closedir(DIR);

    # Resolving symlinks
    #
    LOG("  * Resolving possible symlinks\n");
    LOG("    * Before:\n");
    foreach(@{ $pF })
    {
        LOG("    * [$_]\n");
    }
    @{ $pFF } = ();
    foreach my $F (@{ $pF })
    {
        next if($F =~/^\.{1,2}/);
        if($F =~/\.symlink/i)
	{
            my $Link = "";
            open(DB, "$SRC/$Entity/$F");
            while(<DB>)
	    {
                $Link .= $_;
            }
            close(DB);
            $Link =~s/^\s*//;
            $Link =~s/\s*$//;
            $Link =~s/\\/\//g;
            LOG("    * Symlink. Points to [$Link]\n");
            if(-e "$SRC/$Entity/$Link")
            {
                push(@{ $pFF }, "$SRC/$Entity/$Link");
            }
            else
            {
                LOG("      * [$SRC/$Entity/$Link] can't be found!!!\n");
                sleep(5);
                next;
            }
        }
	else
	{
	    # Added for cciss compatabililty (HP SmartArray). Idem for ida and rd.
	    #
            if($F =~/(cciss|ida|rd|mapper)/i)
	    {
		opendir(DIR, "$SRC/$Entity/$F");
		my(@tmp) = readdir(DIR);
		closedir(DIR);
		foreach(@tmp)
		{
		    next if(m/^\.{1,2}$/);
		    push(@{ $pFF }, "$SRC/$Entity/$F/$_");
		}
		closedir(DIR);
	    }
	    #
	    # All non-CCISS/rd/ida/mapper cases
	    #
	    else
	    {
		push(@{ $pFF }, "$SRC/$Entity/$F");
            }
	}
    }
    foreach(@{ $pFF })
    {
	s/\/+/\//g;
    }
    LOG("    * After:\n");
    foreach(@{ $pFF })
    {
        LOG("      * [$_]\n");
    }

    # Sorting data.
    #
    LOG("  * Re-ordering\n");
    my @files = @{ $pFF };
    @{ $pFF } = ();
    foreach(@files)
    {
	if(m/^addon(\-|_).*\.tar\.gz$/i || m/^addon(\-|_).*\.zip$/i)
	{
	    push(@{ $pFF }, $_);
	}
    }
    foreach(@files)
    {
	if(m/^ping\.conf$/i)
	{
	    push(@{ $pFF }, $_);
	}
    }
    foreach(@files)
    {
	if(m/\/bios$/)
	{
	    push(@{ $pFF }, $_);
	}
    }
    foreach(@files)
    {
	if(m/\/[hs]d[abcdef]$/
	   || m/askme_[abcdef]$/i)
	{
	    push(@{ $pFF }, $_);
	}
    }
    foreach(@files)
    {
	if(m/(cciss|ida|rd|mapper)\/c\dd\d$/
	   || m/mapper\/VolGroup\d{2}\-LogVol\d{2}$/)
	{
	    push(@{ $pFF }, $_);
	}
    }
    foreach(@files)
    {
	if(m/\.first_sectors$/ || m/\.fir$/)
	{
	    push(@{ $pFF }, $_);
	}
    }
    foreach(@files)
    {
	if(! m/\/bios$/
	   && ! m/\/[hs]d[abcdef]$/ && ! m/(cciss|ida|rd|mapper)\/c\dd\d$/
	   && ! m/mapper\/VolGroup\d{2}\-LogVol\d{2}$/
	   && ! m/\.first_sectors$/ && ! m/\.fir$/ && ! m/MULTI$/i
	   && ! m/askme_[abcdef]$/i)
	{
	    push(@{ $pFF }, $_);
	}
    }
    foreach(@files)
    {
	if(m/\.RecFiles\.txt$/i)
	{
	    push(@{ $pFF }, $_);
	}
    }
    foreach(@files)
    {
	if(m/MULTI$/i)
	{
	    push(@{ $pFF }, $_);
	    ${ $pMulti } = 1;
	}
    }
    foreach(@files)
    {
	if(m/^Help_Dell$/i)
	{
	    push(@{ $pFF }, $_);
	}
    }
    LOG("    * Re-ordered:\n");
    foreach(@{ $pFF })
    {
        LOG("      * [$_]\n");
    }
}



sub Feed_Dev_Nodes
{
    (%DEV_NODES) = (
	"hda" => {minor => 3, major => 0},
	"hdb" => {minor => 3, major => 64},
	"hdc" => {minor => 22, major => 0},
	"hdd" => {minor => 22, major => 64},
	"hde" => {minor => 33, major => 0},
	"hdf" => {minor => 33, major => 64},
	"hdg" => {minor => 34, major => 0},
	"hdh" => {minor => 34, major => 64},
	"eda" => {minor => 36, major => 0},
	"edb" => {minor => 36, major => 64},
	"pda" => {minor => 45, major => 0},
	"pdb" => {minor => 45, major => 16},
	"pdc" => {minor => 45, major => 32},
	"pdd" => {minor => 45, major => 48},
	"hdi" => {minor => 56, major => 0},
	"hdj" => {minor => 56, major => 64},
	"hdk" => {minor => 57, major => 0},
	"hdl" => {minor => 57, major => 64},
	);

    for(my $i = 1; $i <= 63; $i++)
    {
	$DEV_NODES{"hda$i"} = {minor => 3, major => $i};
	$DEV_NODES{"hdb$i"} = {minor => 3, major => 64 + $i};
	$DEV_NODES{"hdc$i"} = {minor => 22, major => $i};
	$DEV_NODES{"hdd$i"} = {minor => 22, major => 64 + $i};
	$DEV_NODES{"hde$i"} = {minor => 33, major => $i};
	$DEV_NODES{"hdf$i"} = {minor => 33, major => 64 + $i};
	$DEV_NODES{"hdg$i"} = {minor => 34, major => $i};
	$DEV_NODES{"hdh$i"} = {minor => 34, major => 64 + $i};
	$DEV_NODES{"eda$i"} = {minor => 36, major => $i};
	$DEV_NODES{"edb$i"} = {minor => 36, major => 64 + $i};
	$DEV_NODES{"hdi$i"} = {minor => 56, major => $i};
	$DEV_NODES{"hdj$i"} = {minor => 56, major => 64 + $i};
	$DEV_NODES{"hdk$i"} = {minor => 57, major => $i};
	$DEV_NODES{"hdl$i"} = {minor => 57, major => 64 + $i};
    }

    # sda - sdp
    for(my $i = 0; $i <= 15; $i++)
    {
	$DEV_NODES{"sd".$ALPH[$i]} = {minor => 8, major => 16 * $i};
	for(my $j = 1; $j <= 15; $j++)
	{
	    $DEV_NODES{"sd".$ALPH[$i].$j} = {minor => 8, major => 16 * $i + $j};
	}
    }

    # sdq - sdz
    for(my $i = 16; $i <= 25; $i++)
    {
	$DEV_NODES{"sd".$ALPH[$i]} = {minor => 65, major => 16 * ($i - 16)};
	for(my $j = 1; $j <= 15; $j++)
	{
	    $DEV_NODES{"sd".$ALPH[$i].$j} = {minor => 65, major => 16 * ($i - 16) + $j};
	}
    }

    # sdaa - sdaf
    for(my $i = 0; $i <= 5; $i++)
    {
	$DEV_NODES{"sda".$ALPH[$i]} = {minor => 65, major => 240-5*16 + 16 * $i};
	for(my $j = 1; $j <= 15; $j++)
	{
	    $DEV_NODES{"sda".$ALPH[$i].$j} = {minor => 65, major => 240-5*16 + 16 * $i + $j};
	}
    }

    # sdag - sdav
    for(my $i = 7; $i <= 20; $i++)
    {
	$DEV_NODES{"sda".$ALPH[$i]} = {minor => 66, major => 16 * ($i - 6)};
	for(my $j = 1; $j <= 15; $j++)
	{
	    $DEV_NODES{"sda".$ALPH[$i].$j} = {minor => 66, major => 16 * ($i - 6) + $j};
	}
    }

    # sdaw - sdaz
    for(my $i = 21; $i <= 25; $i++)
    {
	$DEV_NODES{"sda".$ALPH[$i]} = {minor => 67, major => 16 * ($i - 21)};
	for(my $j = 1; $j <= 15; $j++)
	{
	    $DEV_NODES{"sda".$ALPH[$i].$j} = {minor => 67, major => 16 * ($i - 21) + $j};
	}
    }

    # sdba - sdbl
    for(my $i = 0; $i <= 11; $i++)
    {
	$DEV_NODES{"sdb".$ALPH[$i]} = {minor => 67, major => 240-11*16 + 16 * $i};
	for(my $j = 1; $j <= 15; $j++)
	{
	    $DEV_NODES{"sdb".$ALPH[$i].$j} = {minor => 67, major => 240-11*16 + 16 * $i + $j};
	}
    }

    # sdbm - sdbz
    for(my $i = 12; $i <= 25; $i++)
    {
	$DEV_NODES{"sdb".$ALPH[$i]} = {minor => 68, major => 16 * ($i - 12)};
	for(my $j = 1; $j <= 15; $j++)
	{
	    $DEV_NODES{"sdb".$ALPH[$i].$j} = {minor => 68, major => 16 * ($i - 12) + $j};
	}
    }

    # sdca - sdcb
    for(my $i = 0; $i <= 1; $i++)
    {
	$DEV_NODES{"sdc".$ALPH[$i]} = {minor => 68, major => 240-1*16 + 16 * $i};
	for(my $j = 1; $j <= 15; $j++)
	{
	    $DEV_NODES{"sdc".$ALPH[$i].$j} = {minor => 68, major => 240-1*16 + 16 * $i + $j};
	}
    }

    # sdcc - sdcr
    for(my $i = 2; $i <= 17; $i++)
    {
	$DEV_NODES{"sdc".$ALPH[$i]} = {minor => 69, major => 16 * ($i - 2)};
	for(my $j = 1; $j <= 15; $j++)
	{
	    $DEV_NODES{"sdc".$ALPH[$i].$j} = {minor => 69, major => 16 * ($i - 2) + $j};
	}
    }

    # sdcs - sdcz
    for(my $i = 18; $i <= 25; $i++)
    {
	$DEV_NODES{"sdc".$ALPH[$i]} = {minor => 70, major => 16 * ($i - 18)};
	for(my $j = 1; $j <= 15; $j++)
	{
	    $DEV_NODES{"sdc".$ALPH[$i].$j} = {minor => 70, major => 16 * ($i - 18) + $j};
	}
    }

    # sdda - sddh
    for(my $i = 0; $i <= 7; $i++)
    {
	$DEV_NODES{"sdd".$ALPH[$i]} = {minor => 70, major => 240-7*16 + 16 * $i};
	for(my $j = 1; $j <= 15; $j++)
	{
	    $DEV_NODES{"sdd".$ALPH[$i].$j} = {minor => 70, major => 240-7*16 + 16 * $i + $j};
	}
    }

    # sddi - sddx
    for(my $i = 8; $i <= 23; $i++)
    {
	$DEV_NODES{"sdd".$ALPH[$i]} = {minor => 71, major => 16 * ($i - 8)};
	for(my $j = 1; $j <= 15; $j++)
	{
	    $DEV_NODES{"sdd".$ALPH[$i].$j} = {minor => 71, major => 16 * ($i - 8) + $j};
	}
    }

    # sddy - sddz
    for(my $i = 24; $i <= 25; $i++)
    {
	$DEV_NODES{"sdd".$ALPH[$i]} = {minor => 128, major => 16 * ($i - 24)};
	for(my $j = 1; $j <= 15; $j++)
	{
	    $DEV_NODES{"sdd".$ALPH[$i].$j} = {minor => 128, major => 16 * ($i - 24) + $j};
	}
    }

    # sdea - sden
    for(my $i = 0; $i <= 13; $i++)
    {
	$DEV_NODES{"sde".$ALPH[$i]} = {minor => 128, major => 32 + 16 * $i};
	for(my $j = 1; $j <= 15; $j++)
	{
	    $DEV_NODES{"sde".$ALPH[$i].$j} = {minor => 128, major => 32 + 16 * $i + $j};
	}
    }

    # sdeo - sdez
    for(my $i = 14; $i <= 25; $i++)
    {
	$DEV_NODES{"sde".$ALPH[$i]} = {minor => 129, major => 16 * ($i - 14)};
	for(my $j = 1; $j <= 15; $j++)
	{
	    $DEV_NODES{"sde".$ALPH[$i].$j} = {minor => 129, major => 16 * ($i - 14) + $j};
	}
    }

    # sdfa - sdfd
    for(my $i = 0; $i <= 3; $i++)
    {
	$DEV_NODES{"sdf".$ALPH[$i]} = {minor => 129, major => 240-3*16 + 16 * $i};
	for(my $j = 1; $j <= 15; $j++)
	{
	    $DEV_NODES{"sdf".$ALPH[$i].$j} = {minor => 129, major => 240-3*16 + 16 * $i + $j};
	}
    }

    # sdfe - sdft
    for(my $i = 4; $i <= 19; $i++)
    {
	$DEV_NODES{"sdf".$ALPH[$i]} = {minor => 130, major => 16 * ($i - 4)};
	for(my $j = 1; $j <= 15; $j++)
	{
	    $DEV_NODES{"sdf".$ALPH[$i].$j} = {minor => 130, major => 16 * ($i - 4) + $j};
	}
    }

    # sdfu - sdfz
    for(my $i = 20; $i <= 25; $i++)
    {
	$DEV_NODES{"sdf".$ALPH[$i]} = {minor => 131, major => 16 * ($i - 20)};
	for(my $j = 1; $j <= 15; $j++)
	{
	    $DEV_NODES{"sdf".$ALPH[$i].$j} = {minor => 131, major => 16 * ($i - 20) + $j};
	}
    }

    # sdga - sdgj
    for(my $i = 0; $i <= 9; $i++)
    {
	$DEV_NODES{"sdg".$ALPH[$i]} = {minor => 131, major => 240-9*16 + 16 * $i};
	for(my $j = 1; $j <= 15; $j++)
	{
	    $DEV_NODES{"sdg".$ALPH[$i].$j} = {minor => 131, major => 240-9*16 + 16 * $i + $j};
	}
    }

    # sdgk - sdgz
    for(my $i = 10; $i <= 25; $i++)
    {
	$DEV_NODES{"sdg".$ALPH[$i]} = {minor => 132, major => 16 * ($i - 10)};
	for(my $j = 1; $j <= 15; $j++)
	{
	    $DEV_NODES{"sdg".$ALPH[$i].$j} = {minor => 132, major => 16 * ($i - 10) + $j};
	}
    }

    # pda - pdd
    for(my $i = 0; $i <= 15; $i++)
    {
	$DEV_NODES{"pda$i"} = {minor => 45, major => $i};
	$DEV_NODES{"pdb$i"} = {minor => 45, major => $i};
	$DEV_NODES{"pdc$i"} = {minor => 45, major => $i};
	$DEV_NODES{"pdd$i"} = {minor => 45, major => $i};
    }

    # rd/c*d* and partitions rd/c*d*p*
    for(my $i = 0; $i <= 31; $i++)
    {
	for(my $k = 0; $k <= 7; $k++)
	{
	    $DEV_NODES{"rd/c".$k."d$i"} = {minor => 48 + $k, major => 8 * $i};
	}
	for(my $j = 1; $j <= 7; $j++)
	{
	    for(my $k = 0; $k <= 7; $k++)
	    {
		$DEV_NODES{"rd/c".$k."d".$i."p$j"} = {minor => 48 + $k, major => 8 * $i + $j};
	    }
	}
	for(my $k = 8; $k <= 15; $k++)
	{
	    $DEV_NODES{"rd/c".$k."d$i"} = {minor => 136 - 8 + $k, major => 8 * $i};
	}
	for(my $j = 1; $j <= 7; $j++)
	{
	    for(my $k = 8; $k <= 15; $k++)
	    {
		$DEV_NODES{"rd/c".$k."d".$i."p$j"} = {minor => 136 - 8 + $k, major => 8 * $i + $j};
	    }
	}
    }

    # ida/c*d* and partitions ida/c*d*p*
    for(my $i = 0; $i <= 15; $i++)
    {
	for(my $k = 0; $k <= 7; $k++)
	{
	    $DEV_NODES{"ida/c".$k."d$i"} = {minor => 72 + $k, major => 16 * $i};
	}
	for(my $j = 1; $j <= 15; $j++)
	{
	    for(my $k = 0; $k <= 7; $k++)
	    {
		$DEV_NODES{"ida/c".$k."d".$i."p$j"} = {minor => 72 + $k, major => 16 * $i + $j};
	    }
	}
    }

    # i2o/hda - i2o/hdp
    for(my $i = 0; $i <= 15; $i++)
    {
	$DEV_NODES{"i2o/hd$ALPH[$i]"} = {minor => 80, major => 16 * $i};
	for(my $j = 1; $j <= 15; $j++)
	{
	    $DEV_NODES{"i2o/hd$ALPH[$i]$j"} = {minor => 80, major => 16 * $i + $j};
	}
    }

    # i2o/hdq - i2o/hdz
    for(my $i = 16; $i <= 25; $i++)
    {
	$DEV_NODES{"i2o/hd$ALPH[$i]"} = {minor => 81, major => 16 * ($i - 16)};
	for(my $j = 1; $j <= 15; $j++)
	{
	    $DEV_NODES{"i2o/hd$ALPH[$i]$j"} = {minor => 81, major => 16 * ($i - 16) + $j};
	}
    }

    # i2o/hdaa - i2o/hdaf
    for(my $i = 0; $i <= 5; $i++)
    {
	$DEV_NODES{"i2o/hda$ALPH[$i]"} = {minor => 81, major => 240-5*16 + 16 * $i};
	for(my $j = 1; $j <= 15; $j++)
	{
	    $DEV_NODES{"i2o/hda$ALPH[$i]$j"} = {minor => 81, major => 240-5*16 + 16 * $i + $j};
	}
    }

    # i2o/hdag - i2o/hdav
    for(my $i = 6; $i <= 21; $i++)
    {
	$DEV_NODES{"i2o/hda$ALPH[$i]"} = {minor => 82, major => 16 * ($i - 6)};
	for(my $j = 1; $j <= 15; $j++)
	{
	    $DEV_NODES{"i2o/hda$ALPH[$i]$j"} = {minor => 82, major => 16 * ($i - 6) + $j};
	}
    }

    # i2o/hdaw - i2o/hdaz
    for(my $i = 21; $i <= 25; $i++)
    {
	$DEV_NODES{"i2o/hda$ALPH[$i]"} = {minor => 83, major => 16 * ($i - 21)};
	for(my $j = 1; $j <= 15; $j++)
	{
	    $DEV_NODES{"i2o/hda$ALPH[$i]$j"} = {minor => 83, major => 16 * ($i - 21) + $j};
	}
    }

    # i2o/hdba - i2o/hdbl
    for(my $i = 0; $i <= 11; $i++)
    {
	$DEV_NODES{"i2o/hdb$ALPH[$i]"} = {minor => 83, major => 240-11*16 + 16 * $i};
	for(my $j = 1; $j <= 15; $j++)
	{
	    $DEV_NODES{"i2o/hdb$ALPH[$i]$j"} = {minor => 83, major => 240-11*16 + 16 * $i + $j};
	}
    }

    # i2o/hdbm - i2o/hdbz
    for(my $i = 12; $i <= 25; $i++)
    {
	$DEV_NODES{"i2o/hdb$ALPH[$i]"} = {minor => 84, major => 16 * ($i - 12)};
	for(my $j = 1; $j <= 15; $j++)
	{
	    $DEV_NODES{"i2o/hdb$ALPH[$i]$j"} = {minor => 84, major => 16 * ($i - 12) + $j};
	}
    }

    # i2o/hdca - i2o/hdcc
    for(my $i = 0; $i <= 2; $i++)
    {
	$DEV_NODES{"i2o/hdc$ALPH[$i]"} = {minor => 84, major => 240-2*16 + 16 * $i};
	for(my $j = 1; $j <= 15; $j++)
	{
	    $DEV_NODES{"i2o/hdc$ALPH[$i]$j"} = {minor => 84, major => 240-2*16 + 16 * $i + $j};
	}
    }

    # i2o/hdcc - i2o/hdcr
    for(my $i = 2; $i <= 17; $i++)
    {
	$DEV_NODES{"i2o/hdc$ALPH[$i]"} = {minor => 85, major => 16 * ($i - 2)};
	for(my $j = 1; $j <= 15; $j++)
	{
	    $DEV_NODES{"i2o/hdc$ALPH[$i]$j"} = {minor => 85, major => 16 * ($i - 2) + $j};
	}
    }

    # i2o/hdcs - i2o/hdcz
    for(my $i = 18; $i <= 25; $i++)
    {
	$DEV_NODES{"i2o/hdc$ALPH[$i]"} = {minor => 86, major => 16 * ($i - 18)};
	for(my $j = 1; $j <= 15; $j++)
	{
	    $DEV_NODES{"i2o/hdc$ALPH[$i]$j"} = {minor => 86, major => 16 * ($i - 18) + $j};
	}
    }

    # i2o/hdda - i2o/hddh
    for(my $i = 0; $i <= 7; $i++)
    {
	$DEV_NODES{"i2o/hdd$ALPH[$i]"} = {minor => 86, major => 240-7*16 + 16 * $i};
	for(my $j = 1; $j <= 15; $j++)
	{
	    $DEV_NODES{"i2o/hdd$ALPH[$i]$j"} = {minor => 86, major => 240-7*16 + 16 * $i + $j};
	}
    }

    # 87 / i2o/hddi - i2o/hddx
    # 88 / hdm - hdn
    # 89 / hdo - hdp
    # 90 / hdq - hdr
    # 91 / hds - hdt
    # 94 / dasda - dasdb

    # amiraid/ar* and partitions amiraod/ar*p*
    for(my $i = 0; $i <= 15; $i++)
    {
	$DEV_NODES{"amiraid/ar$i"} = {minor => 101, major => 16 * $i};
	for(my $j = 1; $j <= 15; $j++)
	{
	    $DEV_NODES{"amiraid/ar".$i."p$j"} = {minor => 101, major => 16 * $i + $j};
	}
    }

    # cciss/c*d* and partitions cciss/c*d*p*
    for(my $i = 0; $i <= 15; $i++)
    {
	for(my $k = 0; $k <= 7; $k++)
	{
	    $DEV_NODES{"cciss/c".$k."d$i"} = {minor => 104 + $k, major => 16 * $i};
	}
	for(my $j = 1; $j <= 15; $j++)
	{
	    for(my $k = 0; $k <= 7; $k++)
	    {
		$DEV_NODES{"cciss/c".$k."d".$i."p$j"} = {minor => 104 + $k, major => 16 * $i + $j};
	    }
	}
    }

    # 112 / iseries/vda - iseries/vdaf
    for(my $i = 0; $i <= 25; $i++)
    {
	$DEV_NODES{"iseries/vd$ALPH[$i]"} = {minor => 112, major => 8 * $i};
	for(my $j = 1; $j <= 7; $j++)
	{
	    $DEV_NODES{"iseries/vd$ALPH[$i]$j"} = {minor => 112, major => 8 * $i + $j};
	}
    }

    # 112 / iseries/vdaa - iseries/vdaf
    for(my $i = 0; $i <= 5; $i++)
    {
	$DEV_NODES{"iseries/vda$ALPH[$i]"} = {minor => 112, major => 208 + 8 * $i};
	for(my $j = 1; $j <= 7; $j++)
	{
	    $DEV_NODES{"iseries/vda$ALPH[$i]$j"} = {minor => 112, major => 208 + 8 * $i + $j};
	}
    }

    # 114 / ataraid/d* -- partitions ataraid/d*p*
    for(my $i = 0; $i <= 15; $i++)
    {
	$DEV_NODES{"ataraid/d$i"} = {minor => 114, major => 16 * $i};
	for(my $j = 0; $j <= 15; $j++)
	{
	    $DEV_NODES{"ataraid/d".$i."p$j"} = {minor => 114, major => 16 * $i + $j};
	}
    }

    # 133 / sdha - sdhp
    # 134 / sdhq - sdif
    # 135 / sdig - sdiv

    # 160, faire
}



__END__

=== Todos ===

  - Re-discover devices after applying the 512-byte dd file.
    Correct disk geometry if visibly wrong.
  - Correct any boot.ini ?
  - tar+gz => grub doesn't work.
  - Add support for ReiserFS.
  - Its_HDA_Stupid, don't ask if two drives the source included.
  - Bug with ntfsresize after Auto_Extend...=Y and extended.
  - Re-discover before ntfs maximize. Don't if 0 bytes long.
  - After "one last sync just in case", output a fdisk -l for
    debug purpose.
  - Sometimes, /dev/sda is a one-part device and must be mounted
    (usb devices). Maybe its mounting should be tested.

=== Hypothetical todos ===

  - Would be nice if a boot sector could be written... instead of
    dd-ing 20 first sectors of /dev/(h|s)da*. And FSs formated.
    But in that case, possible RAIDs should be rebuilt ?...
    Seems somewhat complicated.

  - Recreate_All_Parts should recreate labels, some day.

  - Zsplit can write to a ftp-mounted share: -c ... > /mnt/smbfs/aaa.
    Slow but working. Maybe an option. Yet, most people should prefer
    partimaging to a local device or LAN share, then upload to a FTP.
    Maybe it would be useful for support teams, though.

  - Yes piping partimage to  mkisofs and cdrecord/growisofs was what I
    thought                           of.                          The
    http://www.faqs.org/docs/Linux-HOWTO/CD-Writing-HOWTO.html    said
    that  such direct  piping  would be  unreliable  on machines  with
    CPU<400Mhz   and    with   disk   intensive    processes   running
    simultaneously. Well most machines have CPUs > 400Mhz and the only
    disk process running  is partimage. Yes it is true  I also have an
    older PC with CPU just about 233 Mhz. But that PC also came with a
    HDD of just 1,6 Gbyte and without CD burner.

    Having partimage to  store backup data temporarily locally  (like on a
    RAM  disk or  USB flash  disk or  a small  local partition  - whatever
    device available), burning these onto  multisession CD or DVD when the
    storage capacity  of the  temporary local device  is reached  and then
    continuing with partimage repeating  these steps until the full system
    backup is completed is also not bad idea.

    I think  almost anybody can burn  locally saved files on  to CD/DVD as
    long  as these  files are  smaller then  the storage  capacity  of the
    CD/DVD.  In   this  case  the   necessity  for  a  huge   empty  local
    partition/device remains. (petknize)

  - Proposed on a forum: to add this to the APPEND line:
      locale=fr_FR bootkbd=fr console-setup/layoutcode=fr \
      console-setup/variantcode=nodeadkeys
    Maybe some day, it will be worth adding these local keyboards, finally :p

  - Luke Yelavich (Ubuntu)
    As you have probably gathered, dmraid requires kernel modules to be fully
    functional. At minimum, device-mapper modules from the kernel are needed,
    as well as the dm_mirror and dm_stripe modules to access RAID0 and RAID1
    arrays. To get full functionality however, you also need dm-raid45, which
    is not yet in the kernel. You can get the source for dm-raid45 from
    http://people.redhat.com/heinzm/sw/dm/dm-raid45/.
    Hope this helps.
    Luke

  - dmraid -ay
    => We get:
       crw------- 1 root root  10, 62 Oct 28 12:48 control
       brw------- 1 root root 253,  0 Oct 28 12:48 isw_bidfbdeiab_TRACE
       brw------- 1 root root 253,  1 Oct 28 12:48 isw_bidfbdeiab_TRACE1
       brw------- 1 root root 253,  2 Oct 28 12:48 isw_bidfbdeiab_TRACE2
       brw------- 1 root root 253,  3 Oct 28 12:48 isw_bidfbdeiab_TRACE3

       Disk /dev/mapper/isw_bidfbdeiab_TRACE: 80.0 GB, 80023126016 bytes
       255 heads, 63 sectors/track, 9728 cylinders
       Units = cylinders of 16065 * 512 = 8225280 bytes

                                  Device Boot Start    End    Blocks Id  System
       /dev/mapper/isw_bidfbdeiab_TRACE1   *      1   4853  38981691  7  HPFS/NTFS
       /dev/mapper/isw_bidfbdeiab_TRACE2       4854   8933  32772600  c  W95 FAT32 (LBA)
       /dev/mapper/isw_bidfbdeiab_TRACE3       8934   9728   6385837+ c  W95 FAT32 (LBA)

       Disk /dev/sda: 80.0 GB, 80026361856 bytes
       255 heads, 63 sectors/track, 9729 cylinders
       Units = cylinders of 16065 * 512 = 8225280 bytes

          Device Boot      Start         End      Blocks   Id  System
       /dev/sda1   *           1        4853    38981691    7  HPFS/NTFS
       /dev/sda2            4854        8933    32772600    c  W95 FAT32 (LBA)
       /dev/sda3            8934        9728     6385837+   c  W95 FAT32 (LBA)


=== Remember... ===

* Creating LVM:

fdisk ... 8e
pvcreate /dev/hda2
vgcreate VolGroup00 /dev/hda2
vgscan
vgchange -ay
lvcreate -L1500 -nVol01 VolGroup00 /dev/hda2

dd if=/dev/hda2 of=... bs=512 count=20   => OK

* Dell utilities... fdisk output:

Disk /dev/sda: 120GB, 1200341234123776 bytes
255 heads, 63 sectors/track, 14593 cylinders
Units cylinders of 16065 * 512 = 8225280 bytes

Device    Boot Start End    Blocks    Id System
/dev/sda1      1     12     96358+    de Dell Utility
/dev/sda2 *    13    14592  117113850 7  HPFS/NTFS

* ntfsresize error

Checking Filesystem Consistency...
[Runs to 100%]
Accounting Clusters
Space in use: 5595Mb (7.0%)
Collecting resizing contraints...
Needed relocations 48395 (199Mb)
Schedule chkdsk for NTFS consistency check at Windows boot time...
Resetting $LogFile...
Relocating needed data
[Fails at 52.28%]
ERROR: Extended record needed (1392 > 1024), not yet supported! Please try to free less space. 

* NFS

mount -t nfs -o nolock SERVER:/home/y/share/ping /mnt/smbfs
The nolock option is not really to be used... yet reported to help, maybe.

* S-Tar

Enhanced tar archive containing ACLs (but not for NTFS):
star -c -acl artype=xustar mydir > a.tar


# Didn't work...
#
sub Write_NTFS_BS
{
    my($Dev) = shift || '';
    my($pD) = shift || '';

    $Dev =~s/^\/dev\///;

    return unless($Dev && $pD);

    LOG("WNTFS_BS> Making [/dev/$Dev] boot the NTFS way\n");

    my $D;
    foreach(@{ $pD })
    {
	if($_->{Dev} eq $Dev)
	{
	    $D = $_;
	}
    }
    unless(defined($D))
    {
	LOG("WNTFS_BS> No info about [/dev/$Dev]. Return.\n");
	return;
    }

    # The now-usual number of heads problem...
    # We've got the right number, not the one from the BIOS as read
    # by 2.6s kernel, but the one found in the first p.table, and
    # almost certainly credible for having worked.
    #
    LOG("WNTFS_BS> * Let's set the supposedly right number of heads\n");
    LOG("WNTFS_BS>   Should probably be [".$D->{Heads}."]\n");

    # Known to work... though deprecated
    #
    my $cmd = "echo bios_head:".$D->{Heads}." >/proc/ide/$Dev/settings";
    Exec_Log($cmd, 0, "WNTFS_BS>   ");
    Synchronize_Device($Dev);

    # Doesn't seem to work fine
    #
    $cmd = "cd /tmp; testdisk /log /cmd /dev/$Dev"
	." partition_i386,geometry,H,".$D->{Heads};
    LOG("WNTFS_BS>   Cmd: [$cmd]\n");
    system($cmd);
    if(-e "/tmp/testdisk.log")
    {
	LOG("WNTFS_BS>   Testdisk log:\n");
	open(L, "/tmp/testdisk.log");
	while(<L>)
	{
	    s/\s*$//;
	    LOG("WNTFS_BS>     $_\n");
	}
	close(L);
	unlink("/tmp/testdisk.log");
    }

    # Rewrite the MBR
    #
    LOG("WNTFS_BS> * New MBR\n");
    $cmd = "cd /tmp; echo -e \"y\\ny\"|testdisk /log /cmd /dev/"
	."$Dev partition_i386,mbr_code";
    LOG("WNTFS_BS>   Cmd: [$cmd]\n");
    system($cmd);
    if(-e "/tmp/testdisk.log")
    {
	LOG("WNTFS_BS>   Testdisk log:\n");
	open(L, "/tmp/testdisk.log");
	while(<L>)
	{
	    s/\s*$//;
	    LOG("WNTFS_BS>     $_\n");
	}
	close(L);
	unlink("/tmp/testdisk.log");
    }

    # Rewrite the boot sector itself
    #
    LOG("WNTFS_BS> * New bootsector\n");
    $cmd = "cd /tmp; testdisk /log /cmd /dev/$Dev"
	." partition_i386,advanced,boot,rebuildbs";
    LOG("WNTFS_BS>   Cmd: [$cmd]\n");
    system($cmd);
    if(-e "/tmp/testdisk.log")
    {
	LOG("WNTFS_BS>   Testdisk log:\n");
	open(L, "/tmp/testdisk.log");
	while(<L>)
	{
	    s/\s*$//;
	    LOG("WNTFS_BS>     $_\n");
	}
	close(L);
	unlink("/tmp/testdisk.log");
    }

    # And mirror the MFT
    #
    LOG("WNTFS_BS> * Mirroring \$MFT again\n");
    $cmd = "cd /tmp; echo y|testdisk /log /cmd /dev/$Dev"
	." partition_i386,advanced,boot,repairmft";
    LOG("WNTFS_BS>   Cmd: [$cmd]\n");
    system($cmd);
    if(-e "/tmp/testdisk.log")
    {
	LOG("WNTFS_BS>   Testdisk log:\n");
	open(L, "/tmp/testdisk.log");
	while(<L>)
	{
	    s/\s*$//;
	    LOG("WNTFS_BS>     $_\n");
	}
	close(L);
	unlink("/tmp/testdisk.log");
    }

    # Sync just in case
    #
    LOG("WNTFS_BS> * Syncing the p.table just in case\n");
    Synchronize_Device($Dev);

    # Clean up the mess
    #
    LOG("WNTFS_BS> * Must fix each NTFS partition contained in the device\n");
    for(my $i = 0; $i <= $#{ $D->{Parts} }; $i++)
    {
	if(Is_NTFS($D->{Types}->[$i]) && $D->{Parts}->[$i] ne 'null')
	{
	    my $cmd = "ntfsfix /dev/".$D->{Parts}->[$i];
	    Exec_Log($cmd, 0, "WNTFS_BS>   ");
	}
    }

    LOG("WNTFS_BS> Should be OK\n");
    return();
}



# Writes a NTFS boot sector. Well, tries to.
#
# Syntax: Write_NTFS_BS("/dev/hda", \@Dev_Rich);
#
sub STILLBAD_______Write_NTFS_BS
{
    my($Dev) = shift || '';
    my($pD) = shift || '';

    $Dev =~s/^\/dev\///;

    return unless($Dev && $pD);

    my $MNT = "/mnt/WNTFSBS_$$";
    if(-e $MNT)
    {
	system("rm -fr $MNT");
    }
    mkdir($MNT, 0755);

    LOG("WNTFSBS> Called on device [$Dev]\n");


    # Keep hardware info
    #
    LOG("WNTFSBS> * Find out hardware info of dev [$Dev]\n");

    my $Sectors_Track = 0;
    my $Heads = 0;
    foreach my $D (@{ $pD })
    {
	if($D->{Dev} eq $Dev)
	{
	    $Sectors_Track = $D->{Sectors_Track};
	    $Heads = $D->{Heads};
	}
    }

    LOG("WNTFSBS>   Sectors/Track: [$Sectors_Track]\n");
    LOG("WNTFSBS>   Nb of heads: [$Heads]\n");


    # We need a place to store a 40-MB file so to format it FAT32,
    # for the sole purpose to get the number of sectors per fat.
    #
    LOG("WNTFSBS>   * Finding out the number of sectors per fat\n");

    my($Writable_Part) = "";

  loop:
    foreach my $D (@{ $pD })
    {
	for(my $i = 0; $i <= $#{ $D->{Parts} }; $i++)
	{
	    if($D->{Parts}->[$i] eq 'null')
	    {
		next;
	    }

	    LOG("WNTFSBS>     Device [".$D->{Dev}."], Part [$i]\n");
	    my $Dev_Size = $D->{Heads} * $D->{Sectors_Track} *
		$D->{Cylinders} * 512;

	    LOG("WNTFSBS>       Size of the device: [$Dev_Size]\n");

	    my $Used_Space = Used_Space($D->{Parts}->[$i]);
	    LOG("WNTFSBS>       Space used: [$Used_Space]\n");

	    my $Avail_Space = $Dev_Size - $Used_Space;
	    LOG("WNTFSBS>       Available space: [$Avail_Space]\n");

	    if($Avail_Space > 40000000 && Is_Mountable($D->{Parts}->[$i]))
	    {
		$Writable_Part = "/dev/".$D->{Parts}->[$i];
		last loop;
	    }
	}
    }

    if($Writable_Part)
    {
	Mount($Writable_Part, $MNT, "RW");
    }

    Exec_Log("dd if=/dev/zero of=$MNT/fakefat$$ bs=40000 count=1000",
	     0, "WNTFSBS>       ");
    Exec_Log("mkfs.vfat -F 32 $MNT/fakefat$$", 0, "WNTFSBS>       ");
    Exec_Log("cd /tmp; dd if=$MNT/fakefat$$ of=linux.fat.bs count=16 bs=512",
	     0, "WNTFSBS>       ");

    my(@Nb_Sect_per_FAT) = ();
    {
	my $byteCount = 0;
	open(LNX, "< /tmp/linux.fat.bs_");
	binmode(LNX);
	while(read(LNX, $b, 1))
	{
	    if($byteCount >= 36 && $byteCount <= 39)
	    {
		push(@Nb_Sect_per_FAT, $b);
	    }
	    ++ $byteCount;
	}
	close(LNX);
    }

    if(-e "/tmp/linux.fat.bs")
    {
	unlink("/tmp/linux.fat.bs");
    }
    if(-e "$MNT/fakefat$$")
    {
	unlink("$MNT/fakefat$$");
    }

    LOG("WNTFSBS>       Found:\n");
    foreach(@Nb_Sect_per_FAT)
    {
	LOG("WNTFSBS>         [$_]\n");
    }

    Umount($MNT);


    # Finding out the partition that is supposed to be booted
    #
    LOG("WNTFSBS> * Finding out which part will have to boot\n");

    my $To_Boot = '';
    my $Size_To_Boot = 0;

  loop:
    foreach my $D (@{ $pD })
    {
	for(my $i = 0; $i <= $#{ $D->{Parts} }; $i++)
	{
	    if($D->{Parts}->[$i] eq 'null')
	    {
		next;
	    }
	    if($D->{Boot_Flags}->[$i])
	    {
		$To_Boot = "/dev/".$D->{Parts}->[$i];
		$Size_To_Boot = $D->{Blocks}->[$i] / 512 * 1024;
		last loop;
	    }
	}
    }

    LOG("WNTFSBS>   Will have to make [$To_Boot] boot\n");
    LOG("WNTFSBS>   Its size is [$Size_To_Boot]\n");


    # First the MBR
    #
    LOG("WNTFSBS> * Creating the MBR\n");
    Exec_Log("dd if=$PING_DIR/MBR.NTFS of=/dev/$Dev", 0, "WNTFSBS>   ");


    # Now we should have all needed info and be able to write the BS
    #
    LOG("WNTFSBS> * Creating the bootsector\n");

    my $byteCount = 0;

    open(IN, "< $PING_DIR/BS.NTFS");
    binmode(IN);
    open(OUT, "> /tmp/out.bs");

    binmode(OUT);
    while(read(IN, $b, 1))
    {
	# 0Dh => 08 instead of 10 ??
	#
	#if($byteCount == 13)
	#{
	#    print(OUT substr(pack("i", 8), 0, 1));
	#}

	# 18h => nb of sectors by track
	#
	if($byteCount == 24)
	{
	    my $tmp = sprintf("%x", $Sectors_Track);
	    while(length($tmp) < 4)
	    {
		$tmp = "0$tmp";
	    }
	    LOG("WNTFSBS>   Nb_Sect: will write [$tmp]\n");
	    print(OUT substr(pack("i", sprintf("%d", hex(substr($tmp, 2, 2)))), 0, 1));
	}
	elsif($byteCount == 25)
	{
	    my $tmp = sprintf("%x", $Sectors_Track);
	    while(length($tmp) < 4)
	    {
		$tmp = "0$tmp";
	    }
	    print(OUT substr(pack("i", sprintf("%d", hex(substr($tmp, 0, 2)))), 0, 1));
	}

	# 1Ah => nb of heads
	#
	elsif($byteCount == 26)
	{
	    my $tmp = sprintf("%x", $Heads);
	    while(length($tmp) < 4)
	    {
		$tmp = "0$tmp";
	    }
	    LOG("WNTFSBS>   Nb_Heads: will write [$tmp]\n");
	    print(OUT substr(pack("i", sprintf("%d", hex(substr($tmp, 2, 2)))), 0, 1));
	}
	elsif($byteCount == 27)
	{
	    my $tmp = sprintf("%x", $Heads);
	    while(length($tmp) < 4)
	    {
		$tmp = "0$tmp";
	    }
	    print(OUT substr(pack("i", sprintf("%d", hex(substr($tmp, 0, 2)))), 0, 1));
	}

	# 20h => Nb of blocks of boot sector's partition (hda1)
	#
	elsif($byteCount == 32)
	{
	    my $tmp = sprintf("%x", $Size_To_Boot);
	    while(length($tmp) < 8)
	    {
		$tmp = "0$tmp";
	    }
	    LOG("WNTFSBS>   Size_To_Boot: will write [$tmp]\n");
	    print(OUT substr(pack("i", sprintf("%d", hex(substr($tmp, 6, 2)))), 0, 1));
	}
	elsif($byteCount == 33)
	{
	    my $tmp = sprintf("%x", $Size_To_Boot);
	    while(length($tmp) < 8)
	    {
		$tmp = "0$tmp";
	    }
	    print(OUT substr(pack("i", sprintf("%d", hex(substr($tmp, 4, 2)))), 0, 1));
	}
	elsif($byteCount == 34)
	{
	    my $tmp = sprintf("%x", $Size_To_Boot);
	    while(length($tmp) < 8)
	    {
		$tmp = "0$tmp";
	    }
	    print(OUT substr(pack("i", sprintf("%d", hex(substr($tmp, 2, 2)))), 0, 1));
	}
	elsif($byteCount == 35)
	{
	    my $tmp = sprintf("%x", $Size_To_Boot);
	    while(length($tmp) < 8)
	    {
		$tmp = "0$tmp";
	    }
	    print(OUT substr(pack("i", sprintf("%d", hex(substr($tmp, 0, 2)))), 0, 1));
	}
    
	# 26h => Nb of sectors per FAT
	#
	elsif($byteCount >= 36 && $byteCount <= 39)
	{
	    LOG("WNTFSBS>     Nb_Sect_per_FAT[".($byteCount - 36)."]\n");
	    print(OUT $Nb_Sect_per_FAT[$byteCount - 36]);
	}

	else
	{
	    print(OUT $b);
	}

	$byteCount++;
    }

    close(IN);
    close(OUT);

    LOG("WNTFSBS>   Wrote [$byteCount] bytes.\n");

#    my @cmd = (
#	       "dd if=/tmp/out.bs of=/tmp/new count=90 bs=1",
#	       "cat $PING_DIR/bsnt.1 >>/tmp/new",
#	       "dd if=/tmp/out.bs of=/tmp/aaa count=488 skip=512 bs=1",
#	       "cat /tmp/aaa >>/tmp/new",
#	       "cat $PING_DIR/bsnt.2 >>/tmp/new",
#	       "dd if=/tmp/out.bs of=/tmp/aaa count=5138 skip=1006 bs=1",
#	       "cat /tmp/aaa >>/tmp/new",
#	       "cat $PING_DIR/bsnt.3 >>/tmp/new",
#	       "dd if=/tmp/out.bs of=/tmp/aaa count=1536 skip=6656 bs=1",
#	       "cat /tmp/aaa >>/tmp/new",
#	       "rm -f /tmp/aaa",
#	       "cat /tmp/new >/tmp/out.bs",
#	       "rm -f /tmp/new"
#	       );
#
#    foreach(@cmd)
#    {
#	Exec_Log($_, 0, "WNTFSBS>     ");
#    }


    # Let's write this thing and pray
    #
    LOG("WNTFSBS> * Writing the BS (pray)\n");

#    Exec_Log("cd /tmp; dd if=out.bs of=$To_Boot count=16 bs=512", 0, "WNTFSBS>   ");
    Exec_Log("dd if=/tmp/out.bs of=$To_Boot count=3442 bs=1", 0, "WNTFSBS>   ");


    # Synchronize the device
    #
    Synchronize_Device($Dev);


    # Clean up; we're done
    #
    system("rm -fr $MNT");
}
