#!/usr/bin/perl -w
#
# LiveCD iso build script
#
# Copyright (C) 2002-2004, Jaco Greeff <jaco@puxedo.org>
# Copyright (C) 2003, Buchan Milne <bgmilne@obsidian.co.za>
# Copyright (C) 2004, Tom Kelly  <tom_kelly33@yahoo.com>
#
#    This program is free software; you can redistribute it and/or modify
#    it under the terms of the GNU General Public License as published by
#    the Free Software Foundation; either version 2 of the License, or
#    (at your option) any later version.
#
#    This program is distributed in the hope that it will be useful,
#    but WITHOUT ANY WARRANTY; without even the implied warranty of
#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#    GNU General Public License for more details.
#
#    You should have received a copy of the GNU General Public License
#    along with this program; if not, write to the Free Software
#    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
#
# Adapted from the MiniCD (http://www.linuxminicd.org) and mkinitrd build scripts
#
# $Id: mklivecd.in,v 1.150 2006/04/01 23:01:22 ikerekes Exp $
#

use strict;
use lib qw(/usr/lib/libDrakX);

### dependancies
use Getopt::Long;
use MDK::Common;
use run_program;
use Storable qw(store);
use threads::shared;

### useful functions
sub get_exec { my $r = qx($_[0]); chomp($r); $r; };

### global "constants"
my $PROG_VERSION   = '0.6.0-20060503';
my $PROG_NAME      = 'mklivecd';
my $ESC            = "\x1B[";
my $URL            = "http://livecd.berlios.de/";
my $COPYRIGHT      = "Copyright (C) 2002-2004, Jaco Greeff <jaco\@puxedo.org>";

### global variables
my $starttime      = time;
my $workdir        = undef;
my $kernel26       = undef;
my $dietarch       = undef;
my $depmod         = "/sbin/depmod";
my $modfile        = "/etc/modules.conf";
my $modext         = ".o";
my $modules        = undef;
my $modules_opt    = undef;

my $modules_26     = 'fs/nls/nls_iso8859-1 fs/nls/nls_iso8859-2 fs/nls/nls_cp437 fs/jbd/jbd fs/ext3/ext3 fs/reiserfs/reiserfs fs/fat/fat fs/msdos/msdos fs/vfat/vfat fs/ntfs/ntfs fs/nls/nls_utf8 fs/isofs/isofs drivers/ide/ide-cd drivers/block/loop  lib/zlib_inflate/zlib_inflate drivers/scsi/aha1542 drivers/scsi/ahci drivers/scsi/aic7xxx/aic7xxx drivers/scsi/BusLogic drivers/scsi/dmx3191d drivers/scsi/dtc drivers/scsi/eata drivers/scsi/fdomain drivers/scsi/gdth drivers/scsi/g_NCR5380 drivers/scsi/in2000 drivers/scsi/NCR53c406a drivers/scsi/pas16 drivers/scsi/psi240i drivers/scsi/qlogicfas drivers/scsi/qlogicfc drivers/scsi/sata_nv drivers/scsi/sata_promise drivers/scsi/sata_qstor drivers/scsi/sata_sil drivers/scsi/sata_sis drivers/scsi/sata_svw drivers/scsi/sata_sx4 drivers/scsi/sata_uli drivers/scsi/sata_via drivers/scsi/sata_vsc drivers/scsi/sym53c416 drivers/scsi/t128 drivers/scsi/tmscsim drivers/scsi/u14-34f drivers/scsi/wd7000 drivers/block/sx8 drivers/message/fusion/mptscsih drivers/scsi/3w-xxxx drivers/scsi/scsi_mod drivers/scsi/sr_mod drivers/scsi/libata drivers/scsi/ata_piix drivers/scsi/sg drivers/scsi/sd_mod drivers/scsi/scsi_transport_spi drivers/message/fusion/mptbase drivers/usb/core/usbcore drivers/usb/host/uhci-hcd drivers/usb/host/ohci-hcd drivers/usb/host/ehci-hcd drivers/usb/storage/usb-storage';
my $modules_opt_26 = 'lib/zlib_inflate/zlib_inflate drivers/scsi/3w-xxxx drivers/usb/host/uhci-hcd drivers/usb/host/ohci-hcd drivers/usb/host/ehci-hcd drivers/usb/storage/usb-storage';
my $nodirs         = '^/[.].* ^/dev$ ^/initrd$ ^/lost+found$ ^/mnt$ ^/proc$ ^/tmp$ ^/var/tmp$';
my $nofiles        = '^/[.].* ^/fastboot$ /core[.][0-9][0-9]*$ .*[.]rpmnew$ .*[.]rpmsave$ /[.]bash_history$ /[.]fonts[.]cache-[0-9]$ /[.]xauth.* /[.]xsession-errors$ ^/var/run/ ^/var/lock/subsys/';
my $loopmod        = undef;
my $loopcmp        = undef;
my $blocksize      = 32;
my $finaliso       = "livecd.iso";
my $md5sum	   = undef;
my $mkisofs_opts   = "";	# Fix for concat of undef in iso looptype

### command-line options
my $o_verbose;
my $o_workdir;
my $o_root         = "/";
my $o_tmp          = "/tmp";
my $o_looptype     = "sqfs";
my $o_keyboard     = 'us';
my $o_resolution   = '1024x768';
my $o_vgamode      = '791';
my $o_splash       = "silent";
my $o_kernel       = get_exec("uname -r");
my $o_timeout      = 150;
my %opts           = ( # these are all the options with defaults
	'root'       => \$o_root,
	'tmp'        => \$o_tmp,
	'looptype'   => \$o_looptype,
	'keyboard'   => \$o_keyboard,
	'resolution' => \$o_resolution,
	'splash'     => \$o_splash,
	'kernel'     => \$o_kernel,
	'timeout'    => \$o_timeout
);


### startup banner
sub print_banner {
	print STDERR "$PROG_NAME, version $PROG_VERSION, $URL
$COPYRIGHT

This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.
";
}


### usage
sub print_usage {
	print STDERR "Usage:
   $0 [options] <livecd-image>

General Options:
   --help                          Display this message
   --version                       Display version information
   --verbose                       Be verbose in output
   --noclean                       Don't remove temporary files on exit. #'
   --workdir			   Specify a working directory which will not
   				   be cleaned.
   --debug                         Display some extra debugging information 
                                   while building the CD. (Usefull for bug 
                                   reports to the developers.)

Image generation:
   --root <rootdir>                Root directory of the live filesystem to use
                                   as the for the image of the LiveCD. 
                                   (default: $o_root)
   --tmp <tmpdir>                  Name of the directory to be used for 
                                   temporary file storage. 
                                   (default: $o_tmp)
   --img <image>                   Name of the saved compressed image. When an 
                                   image by this name is found, it will not be 
                                   re-created or overwritten, rather the 
                                   existing image will be re-used, i.e. the 
                                   compressed image is not re-built.
   --nofile <ex1>[,][...]          Excludes files from the final image. (Also
                                   see the --nodir option for a full 
                                   description)
   --nodir <ex1>[,][...]           Excludes directories from the final image.
                                   Patterns passed to this option (as with the
                                   --nofile option) should be valid in a grep(1)
                                   search, e.g. --nodir=^/home/jaco,^/root/.mcop
                                   will exclude both the /home/jaco and 
                                   /root/.mcop directories from the final 
                                   LiveCD.
   --sort <file>                   Sort the files on the compressed iso image
                                   according to the mkisofs-style sort specifier
                                   as detailed in file.
   --kernel <kernel>               Kernel to use as default for the LiveCD
                                   image. (Output of uname -r)
                                   (default: $o_kernel)

Compression options:
   --blocksize <size>              Compression (if used) blocksize in K 
                                   (default: $blocksize)
   --looptype <clp|sqfs|iso|ziso>  Create a cloop, squashfs or iso (non-
                                   compressed) image or compressed iso image. 
                                   (default: $o_looptype)

Boot options:
   --bootopt <option>              Specify an additional boot option to pass to 
                                   the kernel command-line.
   --bootmsg <msg>                 Use 'msg' as the isolinux boot message.
   --bootkey <key=msg>             Display 'msg' on key 'key' from isolinux.
   --bootimg <img>                 Use 'img' (LSS format) as the isolinux.
                                   background display image.
   --mdkboot                       Use the Mandrake boot image extension to 
                                   display the boot message.
   --timeout <sec>                 Specify the default ISO Linux prompt timeout
                                   in seconds. 
                                   (default: $o_timeout)
   --noprompt                      Disable ISO Linux prompt (i.e. prompt 0).
   --keyboard <mapping>            Specify a different keyboard layout as 
                                   default for the LiveCD. 
                                   (default: $o_keyboard)
   --resolution <res>              Specify the resolution for the framebuffer 
                                   output device. (Either resolution or normal)
                                   (default: $o_resolution)
   --splash <silent|verbose|no>    Create the LiveCD with bootsplash support if
                                   available on the root filesystem. 
                                   (default: $o_splash)
   --fstab <options>               Override the default options for the fstab on
                                   the LiveCD. Options are one or more of 'auto'
                                   and 'rw', for example '--fstab=rw,auto' will
                                   automatically mount all detected partitions 
                                   rw.

ISO Image options:
   --isoextrafiles <path>          Add the files in 'path' to the root of the
                                   LiveCD ISO image.
   --application <id>              Use the specified iso application ID, as '-A'
                                   option to mkisofs.
   --volumeid <id>                 Use the specified iso volume ID, as a '-V'
                                   option to mkisofs.
   --preparer <prep>               Use the specified preparer ID, as a '-p'
                                   option to mkisofs.
   --publisher <pub>               Use the specified publisher ID, as a '-P'
                                   option to mkisofs.
   --md5sum			   Compute and implant the md5sum to verify media.

Behaviour:
   --usbhome                       Use USB memory stick devices as a persistent
                                   home when available/connected on bootup.

Examples:
    $0 --nodir ^/usr/src/RPM,^/root/tmp livecd.iso
    $0 --blocksize 224 --splash=silent livecd.iso

";
	exit(1);
}


### print our version (already contained in banner)
sub print_version {
	exit(1);
}


### format an elapsed time
sub fmt_elapsed {
	my ($t) = @_;
	my $h = int($t/3600);
	my $m = int(($t - $h*3600)/60);
	my $s = int($t - $h*3600 - $m*60);
	sprintf("%02d:%02d:%02d", $h, $m, $s);
}


### format a number
sub fmt_number {
	my ($n) = @_;
	$n = $n/1000;
	my $t = int($n/1000);
	my $h = $n - $t*1000;
	my $r = sprintf("%0.3f", $h);
	$r =~ s/[.]/,/;
	if ($t > 0) {
		$r = "0$r" while (length($r) < 7);
		$r = "$t,$r";
	}
	$r;
}


### progress bar globals
my $PROGRESS_MAX     = 76;
my $progress_max     : shared = 0;
my $progress_inc     : shared = 0;
my $progress_curr    : shared = 0;
my $progress_start   : shared = 0;
my $progress_tot     : shared = 0;
my $progress_text    : shared = undef;
my $progress_ttext   : shared = undef;
my $progress_timer   : shared = -1; # 0 to active, -1 to deactivate
my $progress_last    : shared = 0;

### to be called before we use the progress bar
sub start_progress {
	my ($title, $max) = @_;
	my $i = 0;
	$| = 1;
	print $title.":\n";
	print "[";
	print " " while ($i++ < $PROGRESS_MAX);
	print "]";
	$progress_max = $max;
	$progress_inc = $progress_max/$PROGRESS_MAX;
	$progress_start = time;
	$progress_curr = 0;
	$progress_tot = 0;
	$progress_text = undef;
	$progress_ttext = undef;
	$progress_last = 0;
	print $ESC."1A".$ESC."52G";
	print "[  0.00% ".fmt_elapsed(0)."/".fmt_elapsed(0)."]";
	start_thread();
}


### while we are progressing
sub set_progress {
	my ($curr, $text, $thr) = @_;
	$progress_last = $curr;
	$progress_last = $progress_max if ($progress_last > $progress_max);
	my $num = int($progress_last/$progress_inc) - $progress_curr;
	if (defined($opts{verbose}) && defined($text)) {
		$text =~ s/\[//g;
		$text =~ s/\]//g;
		$text =~ s/\+//g;
		if (!defined($progress_text) || !($text =~ m/^$progress_text$/)) {
			if (defined($thr) || ($progress_timer == -1)) {
				$progress_text = $text;
				$progress_ttext = $text;
				print "\n";
				$progress_curr += $num;
				print $ESC."1G";
				chomp($text);
				$text =~ s#\r?\n# #gs; # remove newlines in text
				if (length($text) > 72) {
					print "  ".pack("A72", $text)." ...";
				}
				else {
					print "  ".pack("A76",$text);
				}
				print $ESC."1A";
			}
			else {
				$progress_ttext = $text;
			}
		}
	}
	elsif ($num) {
		if (defined($thr) || ($progress_timer == -1)) {
			print "\n";
			my $col = 2+$progress_curr;
			$progress_curr += $num;
			print $ESC.$col."G";
			print "=" while ($num--);
			print $ESC."1A";
		}
	}

	if (defined($thr) || ($progress_timer == -1)) {
		my $elapsed = time - $progress_start;
		my $remain = 0;
		if ($progress_curr < $PROGRESS_MAX) {
			$remain = $progress_curr ? ($elapsed/$progress_curr)*($PROGRESS_MAX - $progress_curr) : 0;
		}
		print $ESC."52G";
		print sprintf("[%6.2f%% ", $curr*100/$progress_max);
		print fmt_elapsed($elapsed)."/".fmt_elapsed($remain+$elapsed)."]";
	}
}


### to close off the progress bar
sub end_progress {
	end_thread();
	set_progress($progress_max, $progress_text);
	print "\n";
	my $i = -2;
	print " " while ($i++ < $PROGRESS_MAX);
	print $ESC."1G";
}


### this is our timer thingy
sub timer_progress {
	while (($progress_timer > 0) && ($progress_timer < 2)) {
		set_progress($progress_last, $progress_ttext, 1);
		sleep(1);
	}
	$progress_timer = 0;
}


### start the progress thread
sub start_thread {
	if ($progress_timer > -1) {
		$progress_timer = 1;
		threads->new(\&timer_progress);
	}
}


### end the progress thread
sub end_thread {
	if ($progress_timer > -1) {
		$progress_timer = 2;
		sleep(2) if ($progress_timer > 0);
	}
}

### parse the command-line options
sub parse_options {
	GetOptions(\%opts,
		'help',
		'version',
		'verbose+',
		'noclean',
		'workdir=s',
		'debug',
		'root=s',
		'tmp=s',
		'img=s',
		'nofile=s@',
		'nodir=s@',
		'sort=s',
		'kernel=s',
		'lowmem',
		'looptype=s',
		'blocksize=i',
		'bootopt=s@',
		'bootmsg=s',
		'bootkey=s%',
		'bootimg=s',
		'mdkboot',
		'timeout=i',
		'noprompt',
		'keyboard=s',
		'resolution=s',
		'splash=s',
		'fstab=s',
		'isoextrafiles=s',
		'application=s',
		'volumeid=s',
		'preparer=s',
		'publisher=s',
		'md5sum',
		'usbhome'
	);

	# help and stuff
	print_version if (defined($opts{version}));
	print_usage if (defined($opts{help}));

	# mandatory stuff
	$o_root =~ s|/$||;
	$o_root .= "/";
	die_("\nFATAL: Root directory (--root) '$o_root' does not exist\n") unless (-d $o_root);
	$o_tmp =~ s|/$||;
	$o_tmp .= "/";
	die_("\nFATAL: Temporary directory (--tmp) '$o_tmp' does not exist\n") unless (-d $o_tmp);
	die_("\nFATAL: Unknown loop type (--looptype) '$o_looptype'\n") unless ($o_looptype =~ /clp|bzlp|sqfs|iso|ziso/);

	# optional stuff
	die_("\nFATAL: Specified sort file (--sort) '".$opts{sort}."' does not exist\n") if (defined($opts{sort}) && !(-f $opts{sort}));
	#print STDERR "\nWARNING: Sort option ignored for 'sqfs' (squashfs) looptype" if (defined($opts{sort}) && ($o_looptype =~ /sqfs/));
	die_("\nFATAL: Kernel (--kernel) '".$o_kernel."' not installed on the root image. (Directory '".$o_root."/lib/modules/".$o_kernel."' does not exist.)\n") if (!(-d $o_root."/lib/modules/".$o_kernel));
	die_("\nFATAL: Extra ISO directory (--isoextrafiles) '".$opts{isoextrafiles}."' does not exist\n") if (defined($opts{isoextrafiles}) && !(-d $opts{isoextrafiles}));
	die_("\nFATAL: Unknown splash (--splash) option '$o_splash'\n") unless ($o_splash =~ /silent|verbose|no/);
	die_("\nFATAL: Work directory (--workdir) '".$opts{workdir}."' does not exist\n") if (defined($
opts{workdir}) && !(-d $opts{workdir}));

	# final iso name
	if (scalar(@ARGV) > 0) {
		die_("\nFATAL: Too many command-line arguments\n") if (scalar(@ARGV) > 1);
		$finaliso = $ARGV[0];
	}
	else {
		die_("\nFATAL: No final iso name specified\n");
	}

	# set some options
	my $rhr = "$o_root/etc/redhat-release";
	my $distro  = (-f $rhr) ? get_exec("gawk -F' ' '{ print \$1 \" \" \$2 }' $rhr") : "$PROG_NAME";
	my $version = (-f $rhr) ? get_exec("gawk -F' ' '{ print \$4 \" \" \$5 }' $rhr") : "$PROG_VERSION";
	my $date    = get_exec("date +\%Y\%m\%d");
	$opts{volumeid} = "$date" unless (defined($opts{volumeid}));
	$opts{application} = "$distro $version LiveCD" unless (defined($opts{application}));
	$opts{preparer} = "$PROG_NAME $PROG_VERSION" unless (defined($opts{preparer}));
	$opts{publisher} = "$URL" unless (defined($opts{publisher}));

	# create our working dir if none given
	if ($opts{workdir}) {
		$workdir = $opts{workdir};
		$opts{noclean} = 1;
	} else {
		$workdir = $o_tmp."mklivecd.$$";
	}
	mkdir_p($workdir);
	die_("\nFATAL: Unable to create working directory, '$workdir'\n") unless (-e $workdir);
	print STDERR "\nWARNING: The temporary directory '$workdir' will not be removed at exit, please do so manually" if (defined($opts{noclean}));

	# massage where we might have options csv
	@{$opts{nofile}} = split(/,/, join(',', @{$opts{nofile}})) if (defined($opts{nofile}));
	push @{$opts{nofile}}, split(/ /, $nofiles);
	@{$opts{nodir}} = split(/,/, join(',', @{$opts{nodir}})) if (defined($opts{nodir}));
	push @{$opts{nodir}}, split(/ /, $nodirs);

	# setup executables
	
	$loopcmp = "/usr/bin/mksquashfs";
	$loopmod = "fs/squashfs/squashfs";
	$blocksize = defined($opts{blocksize}) ? $opts{blocksize} : 32;

	# do other things before starting
	check_kernel();
	check_dietlibc();
	check_resolution();
	check_root();
	check_apps();

	# pretty up
	print "\n\n";
}


### check for the kernel version
sub check_kernel {
	# this should really be perl regex's

	$kernel26 = 1;
	$modfile = "/etc/modprobe.conf";
	$depmod = "/sbin/depmod-25";
	$modext = ".ko";
	$modules = $modules_26;
	$modules_opt = $modules_opt_26;
}


### checks if this is a MDK dietlibc architecture
sub check_dietlibc {
	my $arch = get_exec("uname -m");
	$dietarch = 1 if ($arch =~ m/i.86|x86_64/);
}


### checks the resolution given
sub check_resolution {
	if ($o_resolution =~ m/^normal/) {
		$o_vgamode = "normal";
		$o_splash = "no";
	}
	elsif ($o_resolution =~ m/^640x480/) {
		$o_vgamode = 785;
	}
	elsif ($o_resolution =~ m/^800x600/) {
		$o_vgamode = 788;
	}
	elsif ($o_resolution =~ m/^1024x768/) {
		$o_vgamode = 791;
	}
	elsif ($o_resolution =~ m/^1280x1024/) {
		$o_vgamode = 794;
	}
	elsif ($o_resolution =~ m/^1600x1200/) {
		$o_vgamode = 797;
	}
	else {
		die_("\nFATAL: Invalid resolution '$o_resolution' specified with '--resolution' option
       Valid resolutions are:
             normal 
            640x480
            800x600
           1024x768
          1280x1024
          1600x1200\n");
	}
}


### see if we are indeed root
sub check_root {
	die_("\nFATAL: You have to be root to execute this program\n") if ($> > 0);
}


### see if we are indeed root
sub check_apps {
	die_("\nFATAL: The '/usr/bin/mkisofs' program is not available, please install the correct package\n") unless (-f '/usr/bin/mkisofs');
	die("\nFATAL: The '$loopcmp' program is not available, please install the correct package\n") unless (!defined($loopcmp) || -f $loopcmp);
}


### execute a command
sub do_cmd {
	my ($cmd, $prog) = @_;
	set_progress($prog-1, $cmd) if (defined($prog));
	system($cmd) and die_("\nFATAL: Execution of '$cmd' failed\n");
	set_progress($prog, $cmd) if (defined($prog));
}


### copy a file
sub do_copy {
	my ($src, $mode, $dest, $prog) = @_;
	my $cmd = ($mode > 0) ? "install -m $mode $src $dest" : "cp -aL $src $dest";
	set_progress($prog-1, $cmd) if (defined($prog));
	system($cmd);
	set_progress($prog, $cmd) if (defined($prog));
}


### create an initrd image
sub create_initrd {
	# get our modules
	my @allmods = split(/ /, $modules);
	push @allmods, $loopmod if (defined($loopmod));
	my @allmods_opt = split(/ /, $modules_opt);

	# start progress bar
	my $pos = 0;
	start_progress("Creating initrd", 42+scalar(@allmods)); 

	# create directories
	my $dir = $workdir."/initrd.dir";
	#system("mkdir -p $dir/{bin,dev,etc/livecd/hwdetect,etc/rc.d/,lib/modules/$o_kernel/kernel,proc,ramfs,sbin,usr/{bin,sbin},cdrom,loopfs,ramfs}") and die_("\nFATAL: Unable to create initrd directory, '$dir'\n");
	mkdir_p("$dir/$_") foreach "lib/modules/$o_kernel/kernel", qw(bin dev etc/livecd/hwdetect etc/rc.d/ proc ramfs sbin usr/bin usr/sbin cdrom loopfs ramfs);
	set_progress(++$pos, "mkdir -p $dir/...");

	# find our insmod
	if (defined($dietarch)) {
		if (-e "$o_root/sbin/insmod-25")  {do_copy("$o_root/sbin/insmod-25",	755,	"$dir/sbin/insmod-25", ++$pos)}
		if (-e "$o_root/sbin/insmod-24")  {do_copy("$o_root/sbin/insmod-24",	755,	"$dir/sbin/insmod-24", ++$pos)}
		if (-e "$o_root/sbin/insmod.old") {do_copy("$o_root/sbin/insmod.old",	755,	"$dir/sbin/insmod.old", ++$pos)}
		if (defined($kernel26)) {
			do_copy("$dir/sbin/insmod-25",	755,	"$dir/sbin/insmod",	++$pos);
		} else {
			do_copy("$dir/sbin/insmod-24",	755,	"$dir/sbin/insmod",	++$pos);
		}
	} else {
		if (-e "$o_root/sbin/insmod.static") {	
			do_copy("$o_root/sbin/insmod.static",	755,	"$dir/sbin/insmod.static", ++$pos);
		}
	} 

	# copy files
	do_copy("/usr/bin/busybox",               755, "$dir/bin/busybox",             ++$pos);
	do_copy("/usr/share/mklivecd/linuxrc",    755, "$dir/linuxrc",                 ++$pos);
	do_copy("/usr/share/mklivecd/rc.sysinit", 755, "$dir/etc/rc.d/rc.sysinit",     ++$pos);
	do_copy("/usr/sbin/hwdetect",             755, "$dir/usr/sbin/hwdetect",       ++$pos);
	do_copy("/usr/share/mklivecd/halt.local", 755, "$dir/sbin/halt.local",         ++$pos);
	do_copy($o_root."etc/inittab",            644, "$dir/etc/inittab",             ++$pos);
	do_copy($o_root."sbin/init",              755, "$dir/sbin/init.dynamic",       ++$pos);
	do_copy($o_root."lib/libc.so.6",            0, "$dir/lib",                     ++$pos);
	do_copy($o_root."lib/ld-linux.so.2",        0, "$dir/lib",                     ++$pos);

	
	# Copy but hide the udev binaries
	do_cmd("mkdir $dir/sbin/tmp",			++$pos);
	do_copy($o_root."/sbin/udev*",	  755, "$dir/sbin/tmp",			++$pos);
	do_cmd("cp -a $o_root/etc/udev $dir/etc",	++$pos); 
	if (-e "$o_root/etc/dev.d") {
	    do_cmd("cp -a $o_root/etc/dev.d $dir/etc",	++$pos);
	}  
	do_cmd("cp -a $o_root/etc/hotplug $dir/etc",	++$pos);
	do_cmd("cp -a $o_root/etc/hotplug.d $dir/etc",	++$pos);
	do_cmd("cp -a $o_root/etc/sysconfig $dir/etc",	++$pos);

	do_cmd("mknod $dir/dev/initrd b 1 250", ++$pos);
	do_cmd("mknod $dir/dev/console c 5 1",  ++$pos);
	do_cmd("mknod $dir/dev/null c 1 3",     ++$pos);
	do_cmd("mknod $dir/dev/systty c 4 0",   ++$pos);
	do_cmd("mknod $dir/dev/ram b 1 1",      ++$pos);
	do_cmd("mknod $dir/dev/tty$_ c 4 $_",   ++$pos) foreach((1,2,3,4));
	do_cmd("mknod $dir/dev/lvm b 109 0",    ++$pos);

	
	# copy modules
	foreach my $mod (@allmods) {
		my $modpath = $o_root."lib/modules/$o_kernel/kernel/$mod$modext";
		#$mod = get_exec("basename $mod");
		$mod =~ s,.*/,, ; ## basename and perl (file::basename)
		my $tomod = "$dir/lib/modules/$o_kernel/kernel/$mod$modext";
		if (-f $modpath) {
			do_copy($modpath, 644, $tomod, ++$pos);
		}
		else {
			$modpath = $modpath.".gz";
			if (-f $modpath) {
				do_cmd("gzip -dc $modpath >$tomod", ++$pos);
			}
			else {
				my $modopt = undef;
				foreach my $opt (@allmods_opt) {
					$opt = "$opt$modext";
					$modopt = 1 if ($opt =~ m/$mod/);
				}
			#	die_("\nFATAL: unable to find kernel module '$mod'\n") unless (defined($modopt) || $mod=~'cdrom');
				set_progress(++$pos, $modpath);
			}
		}
	}

	# generate modules file
	do_cmd(":>$dir$modfile", ++$pos);
	my $cfg = defined($kernel26) ? "" : "--config $dir$modfile";
	do_cmd("(depmod -a --basedir $dir ".$cfg." $o_kernel || cp -f $o_root/lib/modules/$o_kernel/modules.dep $dir/lib/modules/$o_kernel) 2>/dev/null", ++$pos);

	# get sizes$o_kernel
	my $size = get_exec("du -ks $dir | awk '{print \$1}'");
	$size = $size + 250;
	set_progress(++$pos, "du -ks $dir");

	# number of inodes
	my $inodes = 1250;
	my $num = get_exec("find $dir | wc -l");
	$inodes = $inodes + $num;
	$size = int($size + ($inodes / 10)) + 1; # 10 inodes needs 1K
	set_progress(++$pos, "find $dir | wc -l");

	# do magic
	my $initrd = "$workdir/livecd/isolinux/initrd";
	my $mnt = "$workdir/initrd.mnt";
	do_cmd("mkdir -p $workdir/livecd/isolinux",                         ++$pos);
	do_cmd("dd if=/dev/zero of=$initrd bs=1k count=$size 2> /dev/null", ++$pos);
	do_cmd("mke2fs -q -m 0 -F -N $inodes -s 1 $initrd",                 ++$pos);
	do_cmd("mkdir -p $mnt ; mount -o loop -t ext2 $initrd $mnt",        ++$pos);
	do_cmd("rm -rf $mnt/lost+found",                                    ++$pos);
	do_cmd("(cd $dir ; tar cf - .) | (cd $mnt ; tar xf -)",             ++$pos);
	do_cmd("umount $mnt",                                               ++$pos);
	do_cmd("(cd $workdir/livecd/isolinux ; gzip -9 initrd)",            ++$pos);

	# make splash
	if ($o_splash !~ m/no/) {
	do_cmd("mv -f $initrd.gz $o_root/tmp ; \
		chroot $o_root /usr/share/bootsplash/scripts/make-boot-splash /tmp/initrd.gz $o_resolution; \
		mv -f $o_root/tmp/initrd.gz $workdir/livecd/isolinux", ++$pos);
	}
	# we are done
	end_progress();
}


### create the compressed image
sub create_compressed {
	if (defined($opts{img}) && (-f $opts{img})) {
		do_cmd("ln $opts{img} $workdir/livecd/livecd.$o_looptype");
	}
	else {
		my $pos = 0;
		start_progress("Setting filesystem parameters", 5);

		# handle excludes
		do_cmd(":>$workdir/excludes.list", ++$pos);
		if (defined($opts{nodir})) {
			my $ex = join("\n", @{$opts{nodir}});
			do_cmd("(find $o_root -type d 2>/dev/null | sed -e 's,^$o_root,/,g' | grep '$ex' | sed 's,^/,$o_root,' >>$workdir/excludes.list)", ++$pos);

		}
		if (defined($opts{nofile})) {
			my $ex = join("\n", @{$opts{nofile}});
			do_cmd("(find $o_root -type f 2>/dev/null | sed -e 's,^$o_root,/,g' | grep '$ex' | sed 's,^/,$o_root,' >>$workdir/excludes.list)", ++$pos);
		}

		# handle sort
		do_cmd(":>$workdir/sort.list", ++$pos);
		do_cmd("cat ".$opts{sort}." >>$workdir/sort.list", ++$pos) if (defined($opts{sort}));
		end_progress();

		$pos = 0;
		my $size = $blocksize*1024;
		if ($o_looptype =~ m/sqfs/) {
			my @files = qx(find $o_root -type f 2>/dev/null);
			my $total = scalar(@files);
			start_progress("Creating compressed image", $total+2);
			my $iso = "$loopcmp $o_root $workdir/livecd/livecd.$o_looptype -info -ef $workdir/excludes.list";
			$iso = "$iso -sort $workdir/sort.list" if (defined($opts{sort})); 
			$iso = "$iso 2>&1";
			print "DEBUG: $iso\n" if (defined($opts{debug}));
			open CMP, "$iso |" or die_("\nFATAL: Unable to execute '$iso'\n");
			my $line;
			my $linep = "";
			while ($line = <CMP>) {
				if ($line =~ m/mksquashfs: file/) {
					$linep = $line;
					$pos++;
				}
				set_progress($pos, $linep);
			}
			close CMP;
			do_cmd("chmod 644 $workdir/livecd/livecd.$o_looptype", $total+1);
			do_cmd("ln $workdir/livecd/livecd.$o_looptype $opts{img}", $total+2) if (defined($opts{img}));
			end_progress();
		}
		else {
			start_progress("Creating loop image", 10001);
			my $iso = "mkisofs $mkisofs_opts -R -exclude-list $workdir/excludes.list -hide-rr-moved -cache-inodes -no-bak -pad -v -v";
			$iso = "$iso -sort $workdir/sort.list" if (defined($opts{sort})); 
			
			
			$iso = "$iso -o $workdir/livecd/livecd.$o_looptype $o_root";
			
			$iso = "($iso) 2>&1";
			print "DEBUG: $iso\n" if (defined($opts{debug}));
			open CMP, "$iso |" or die_("\nFATAL: Unable to execute '$iso'\n");
			my $line;
			while ($line = <CMP>) {
				if ($line =~ m/done, estimate/) {
					$line =~ s/^ //g while ($line =~ m/^ /);
					my ($per, @rest) = split(/ /, $line);
					$per =~ s/%//;
					$pos = int($per*100);
				}
				set_progress($pos, $line);
			}
			close CMP;
			do_cmd("ln $workdir/livecd/livecd.$o_looptype $opts{img}", 10001) if (defined($opts{img}));
			end_progress();
		}
	}
}


### create the isolinux stuff
sub create_isolinux {
	my $pos = 0;
	start_progress("Creating isolinux boot", 9);

	# copy boot images
	my $bin = "/usr/lib/syslinux/isolinux.bin";
	$bin = "/usr/lib/syslinux/isolinux-graphic.bin" if (defined($opts{mdkboot}));
	die_("\nFATAL: '$bin' does not exist on your machine. You need to install the syslinux package.\n") unless (-f $bin);
	do_copy($bin, 644, "$workdir/livecd/isolinux/isolinux.bin", ++$pos);
	die_("\nFATAL: The kernel '".$o_root."boot/vmlinuz-$o_kernel' does not exist on your machine.\n") unless (-f $o_root."boot/vmlinuz-$o_kernel");
	do_copy($o_root."boot/vmlinuz-$o_kernel", 644, "$workdir/livecd/isolinux/vmlinuz", ++$pos);
	do_copy("/usr/bin/mediacheck", 755, "$workdir/livecd/isolinux/mediacheck", ++$pos);

	# copy messages
	do_copy($opts{bootmsg}, 644, "$workdir/livecd/isolinux/livecd.msg", ++$pos) if (defined($opts{bootmsg}));
	if (defined($opts{bootimg})) {
		do_cmd("echo -n '' >$workdir/livecd/isolinux/livecd.msg", ++$pos);
		do_cmd("echo -e '\\030livecd.lss' >>$workdir/livecd/isolinux/livecd.msg", ++$pos);
		do_copy($opts{bootimg}, 644, "$workdir/livecd/isolinux/livecd.lss", ++$pos);
	}

	# write config
	my $appopt;
	if ($kernel26 == 1) {
		$appopt = "initrd=initrd.gz root=/dev/rd/3 noapic nolapic psmouse.proto=imps acpi=ht nomce vga=$o_vgamode keyb=$o_keyboard";
	} else {
		$appopt = "initrd=initrd.gz root=/dev/rd/3 devfs=mount vga=$o_vgamode keyb=$o_keyboard";
	}	
	$appopt .= " splash=$o_splash" if ($o_splash !~ m/no/);
	$appopt = "$appopt fastboot=yes automatic=method:cdrom ramdisk_size=32000" if (!defined($kernel26));
	$appopt = "$appopt fstab=".$opts{fstab} if (defined($opts{fstab}));
	$appopt = "$appopt home=usb" if (defined($opts{usbhome}));
	$appopt = "$appopt $_" foreach (@{$opts{bootopt}});
	open CFG, '>', "$workdir/livecd/isolinux/isolinux.cfg";
	print CFG "default livecd\n";
	print CFG "prompt  ".(defined($opts{noprompt}) ? "0" : "1")."\n";
	print CFG "timeout $o_timeout\n";
	print CFG "display livecd.msg\n" if (-f "$workdir/livecd/isolinux/livecd.msg");
	foreach my $k (keys %{$opts{bootkey}}) {
		do_copy(${$opts{bootkey}}{$k}, 644, "$workdir/livecd/isolinux/livecd_$k.msg");
		print CFG "$k livecd_$k.msg\n";
	}
	print CFG "label livecd\n";
	print CFG "    kernel vmlinuz\n";
	print CFG "    append livecd=livecd $appopt\n";
	print CFG "label safeboot\n";
	print CFG "    kernel vmlinuz\n";
	print CFG "    append livecd=livecd initrd=initrd.gz root=/dev/rd/3 acpi=off vga=normal keyb=us noapic nolapic noscsi nopcmcia nomce unionfs=no\n";
	print CFG "label initrd\n";
	print CFG "    kernel vmlinuz\n";
	print CFG "    append livecd=initrd $appopt\n";
	print CFG "label memtest\n";
	print CFG "    kernel memtest\n";
	print CFG "label mediacheck\n";
	print CFG "    kernel vmlinuz\n";
	print CFG "    append livecd=livecd md5sum $appopt\n";
	close CFG;
	set_progress(++$pos, "$workdir/livecd/isolinux/isolinux.cfg");

	# create boot catalogue
	do_cmd("dd if=/dev/zero of=$workdir/livecd/isolinux/boot.cat bs=1k count=2 2> /dev/null", ++$pos);

	end_progress();
}


### create the final iso
sub create_finaliso {
	# create a sort-file
	my $pos = 0;
	start_progress("Creating final iso", 10001);
	open SORT, '>', "$workdir/sort_iso.list";
	print SORT "$workdir/livecd/isolinux/isolinux.bin 500\n";
	print SORT "$workdir/livecd/isolinux/isolinux.cfg 499\n";
	print SORT "$workdir/livecd/isolinux/vmlinuz 498\n";
	print SORT "$workdir/livecd/isolinux/initrd.gz 497\n";
	print SORT "$workdir/livecd/isolinux/* 450\n";
	print SORT "$workdir/livecd/livecd.$o_looptype 400\n";
	close SORT;
	set_progress(++$pos, "$workdir/sort_iso.list");

	# create actual iso
	$opts{isoextrafiles} = "" unless (defined($opts{isoextrafiles}));
	my $cmd="mkisofs -pad -l -R -J -v -v \\\
		-V '".$opts{volumeid}."' \\\
		-A '".$opts{application}."' \\\
		-p '".$opts{preparer}."' \\\
		-P '".$opts{publisher}."' \\\
		-b isolinux/isolinux.bin -c isolinux/boot.cat -hide-rr-moved -no-emul-boot -boot-load-size 4 -boot-info-table -sort $workdir/sort_iso.list -o $finaliso $workdir/livecd/ ".$opts{isoextrafiles}." 2>&1";
	open ISO, "$cmd |" or die_("\nFATAL: Unable to execute '$cmd'\n");
	my $line;
	while ($line = <ISO>) {
		if ($line =~ m/done, estimate/) {
			$line =~ s/^ //g while ($line =~ m/^ /);
			my ($per, @rest) = split(/ /, $line);
			$per =~ s/%//;
			$pos = int($per*100)+1;
		}
		set_progress($pos, $line);
	}
	close ISO;
	end_progress();
}


### create the embedded md5sum
sub create_md5 {
	my $isosize = get_exec("ls -al --block-size=1M $finaliso | awk '{print \$5 }'");
	start_progress("Embedding MD5 checksum", $isosize);
	open MD5, "implantisomd5 $finaliso |" or die_("\nFATAL: Unable to execute 'implantisomd5'\n");
	my $line;
	my $pos = 0;
	while ($line = <MD5>) {
		chomp($line);
		if ($line =~ /^Read/) {
			$pos = $line;
			$pos =~ s/Read//;
			$pos =~ s/MB//;
			$pos =~ s/\s// while ($pos =~ /\s/);
			#print "[pos=$pos, line=$line]\n";
		}
		set_progress($pos, $line);
	}
	close MD5;
	end_progress();
}


### clean everything
sub cleanup {
	if (defined($workdir)) {
		system("umount $workdir/initrd.mnt 2>/dev/null");
		system("rm -rf $workdir") unless (defined($opts{noclean}));
	}
}


### signals
sub die_ {
	#do_signal('DIE', join(' ', @_));
	die('DIE', join(' ', @_));
}
sub do_signal {
	my ($signal, $text) = @_;
	end_thread();
	my $to = $text ? $text : "\nFATAL: Interrupted.\n";
	chomp($to);
	print pack("A80", $to)."\n";
	cleanup();
	exit(1);
}


### main program entry point
MAIN: {
	$SIG{INT} = \&do_signal;
	$SIG{KILL} = \&do_signal;
	$SIG{PIPE} = \&do_signal;

	print_banner();
	parse_options();

	create_initrd();

#	{
#		local $::prefix = $o_root;
#		require keyboard;
#		keyboard::write(keyboard::lang2keyboard($o_keyboard));
#	}

	create_compressed();
	create_isolinux();
	create_finaliso();
	create_md5() if (defined($opts{md5sum}));;
	cleanup();

	my $finalsize = get_exec("ls -al $finaliso | awk '{print \$5 }'");
	print "\nCreated '$finaliso' (".fmt_number($finalsize)." bytes) in ".fmt_elapsed(time-$starttime)."\n\n";
}
