#!/usr/bin/perl # draklive $Id: draklive 150793 2007-04-05 12:08:47Z blino $ # Copyright (C) 2005 Mandriva # Olivier Blin # # 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, 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. use lib qw(/usr/lib/libDrakX); use strict; use MDK::Common; use common; use list_modules; use fs; use modules; use detect_devices; use run_program; use POSIX qw(strftime); use Cwd 'getcwd'; use Getopt::Long; use Pod::Usage; use File::Temp; use IPC::Open3; use IO::Select; use IO::Handle; use MDV::Draklive::Utils; use MDV::Draklive::Live; my $dir_distrib_sqfs = { mountpoint => '/distrib', type => 'squashfs', path => '/distrib', # perl -MMDK::Common -e 'print map_index { (32767 - $::i) . " $_" } grep { !m,^/(?:dev|proc|sys|live/distrib), } uniq(<>)' < bootlog.list > config/distrib.sort sort => "config/distrib.sort", build_from => '/', }; my $dir_memory = { mountpoint => '/memory', type => 'tmpfs', }; my $dir_modules = { mountpoint => '/modules', type => 'modules', path => '/modules', list => 'modules.lst', }; # this is not unused (it can be used from config file): my %predefined = ( mounts => { simple_union => { root => '/union', overlay => 'unionfs', dirs => [ { mountpoint => '/media', type => 'plain', }, $dir_memory, ], }, squash_rw_union => { root => '/union', overlay => 'unionfs', dirs => [ $dir_distrib_sqfs, { mountpoint => '/media/system', type => 'plain', }, ], }, }, ); # this is not unused (it can be used from config file): my %custom = ( fs => { nfs => sub { my ($module, $client, $path) = @_; ( fs => 'nfs', modules => 'nfs', extra_modules => [ $module ], pre => "ifconfig eth0 $client up", source => $path, ); }, }, mounts => { volatile_squash_union => sub { my ($o_modules) = @_; { root => '/union', overlay => 'unionfs', dirs => [ $dir_distrib_sqfs, if_($o_modules, $dir_modules), $dir_memory, ], }; }, squash_union => sub { my ($default_size, $o_min_size, $o_modules) = @_; { root => '/union', overlay => 'unionfs', dirs => [ $dir_distrib_sqfs, if_($o_modules, $dir_modules), { mountpoint => '/system', type => 'loopfs', pre_allocate => $default_size, if_(defined $o_min_size, min_size => $o_min_size), fs => 'ext2', path => '/system' }, { mountpoint => '/system', type => 'tmpfs', fallback => 1, }, ], }; }, }, ); my %storage = ( cdrom => { modules => 'bus/firewire|usb disk/firewire|hardware_raid|ide|sata|scsi|usb', media_modules => 'disk/cdrom', fs => 'iso9660', can_hide => 0, source => 'LABEL=MDVCDROOT', read_only => 1, detect => \&detect_devices::burners, create => \&create_cdrom_master, format => \&format_cdrom_device, record_needs_master => 1, record => \&record_cdrom_master, }, usb => { modules => 'bus/usb disk/usb', media_modules => 'disk/raw', fs => 'vfat', can_hide => 1, bootloader => 'grub', source => 'LABEL=MDVUSBROOT', detect => sub { grep { detect_devices::isKeyUsb($_) } detect_devices::get() }, create => \&create_disk_master, format => \&format_usb_device, record => \&record_usb_master, image => \&create_disk_image, replicator => \&create_usb_replicator, }, harddisk => { fs => 'ext3', create => \&create_disk_master, record => \&record_harddisk_master, image => \&create_disk_image, }, ); my %storage_fs; %storage_fs = ( generic => { mount => sub { my ($live, $media) = @_; my @mount_options = ( if_(get_media_setting($media, 'read_only'), "ro"), grep { $_ } get_media_setting($media, 'mount_options'), ); 'nash-mount' . if_(@mount_options, " -o " . join(",", @mount_options)) . " -t " . get_media_setting($media, 'fs') . " " . get_media_source_for_nash($media) . " $live->{prefix}{live}{mnt}$live->{prefix}{media}{mnt}"; }, }, nfs => { files => [ '/sbin/ifconfig', '/bin/mount' ], #- needed to mount NFS (with nolock) mount => sub { my ($live, $media) = @_; '/bin/mount -n -o ro,nolock -t nfs ' . get_media_source_for_nash($media) . " $live->{prefix}{live}{mnt}$live->{prefix}{media}{mnt}"; } }, ext2 => { files => [ '/sbin/fsck.ext2' ], mount => sub { my ($live, $media) = @_; my $fsck = "/bin/fsck.ext2"; my $source = get_media_setting($media, 'source'); qq(sh -c '$fsck -a $source || $fsck -y $source'), $storage_fs{generic}{mount}->($live, $media); }, }, vfat => { #files => [ '/sbin/dosfsck' ], mount => sub { my ($live, $media) = @_; #$storage_fs{generic}{mount}->($live, $media), #qq(awk '\$2 == "$live->{prefix}{live}{mnt}$live->{prefix}{media}{mnt}" { system("umount " \$1 "; mount; echo dosfsck -a " \$1 "; dosfsck -a " \$1) }' /proc/mounts), $storage_fs{generic}{mount}->($live, $media); }, }, ); sub nls_modules { my ($live) = @_; my $fs = get_media_setting($live->{media}, 'fs'); if_($fs eq 'vfat', 'nls_cp437'), #- default FAT codepage if_($fs !~ /^ext/, map { "nls_$_" } (map { "iso8859_$_" } 1..7, 9, 13..15), 'utf8'); } sub progress_start { my ($total, $time, $o_exp_divide) = @_; { total => $total, current => 0, start_time => $time, exp_divide => $o_exp_divide, maxl => length($total) - $o_exp_divide, }; } sub progress_show { my ($progress, $time) = @_; my $elapsed_time = $time - $progress->{start_time}; my $eta = $progress->{current} ? int($elapsed_time*$progress->{total}/$progress->{current}) : -1; printf("\r%3d%% (%$progress->{maxl}s/%-$progress->{maxl}s), %8s/%8s (ETA)", int(100*$progress->{current}/$progress->{total}), (map { substr($_, 0, length($_)-$progress->{exp_divide}) } $progress->{current}, $progress->{total}), (map { POSIX::strftime("%H:%M:%S", gmtime($_)) } $elapsed_time, $eta)); } sub progress_end() { print "\n" } my $loop_number = 0; my %loop; %loop = ( squashfs => { read_only => 1, is_loopback => 1, modules => [ qw(loop sqlzma squashfs_lzma) ], extension => '-lzma.sqfs', build => sub { my ($live, $dir) = @_; my $dest = $live->get_builddir . $live->{prefix}{build}{loopbacks} . $dir->{path} . $loop{squashfs}{extension}; mkdir_p(dirname($dest)); my $root = $dir->{root} || $live->get_system_root; my $src = $root . $dir->{build_from}; my $total = directory_usage($src); print "have to process " . int($total/1000000) . " MB\n"; my $progress = progress_start($total, time(), 6); my $exclude_file = tmpnam(); output_p($exclude_file, map { $root . "$_\n" } grep { -e $root . $_ } @{$dir->{exclude} || []}); my $sort = $live->{settings}{config_root} . '/' . $dir->{sort}; run_foreach(sub { if (/^mksquashfs: file .*, uncompressed size (\d+) bytes\s*(?:DUPLICATE|LINK)?$/) { $progress->{current} += $1; progress_show($progress, time()); } }, 'mksquashfs', $src, $dest, '-noappend', '-no-progress', '-info', '-lzma', #'-processors', 1, '-ef', $exclude_file, if_(-f $sort, '-sort', $sort), ) or die "unable to run mksquashfs\n"; progress_end(); unlink $exclude_file; }, mount => sub { my ($live, $dir) = @_; $dir->{loop} = "/dev/loop" . $loop_number++; my $media_loopbacks = get_media_prefix($live, 'loopbacks'); ( "/bin/losetup $dir->{loop} $live->{prefix}{live}{mnt}$live->{prefix}{media}{mnt}${media_loopbacks}$dir->{path}$loop{squashfs}{extension}", "nash-mount -o ro -t squashfs $dir->{loop} $live->{prefix}{live}{mnt}$dir->{mountpoint}", ); }, }, modules => { read_only => 1, delay_mount => 1, mount => sub { my ($live, $dir) = @_; my $media_loopbacks = get_media_prefix($live, 'loopbacks'); "sh -c 'modules=; for m in $live->{prefix}{live}{mnt}$live->{prefix}{media}{mnt}${media_loopbacks}$dir->{path}/*; do n=\$(basename \$m); n=\${n%.sqfs}; d=$live->{prefix}{live}{mnt}$dir->{mountpoint}/\$n; mkdir -p \$d; mount -n -o loop,ro -t squashfs \$m \$d && modules=\$modules\$d=ro:; done; echo \$modules | sed -e s/:\$// > $live->{prefix}{live}{mnt}/$dir->{list}'"; }, }, loopfs => { is_loopback => 1, modules => [], extension => '.loop', build => sub { my ($live, $dir) = @_; my $dest = $live->get_builddir . $live->{prefix}{build}{loopbacks} . $dir->{path} . $loop{loopfs}{extension}; mkdir_p(dirname($dest)); device_allocate_file($dest, $dir->{pre_allocate}); device_mkfs($dest, $dir->{fs}) if !defined $dir->{min_size}; }, mount => sub { my ($live, $dir) = @_; $dir->{loop} = "/dev/loop" . $loop_number++; my $fsck = "chroot {prefix}{live}{mnt}$dir_distrib_sqfs->{mountpoint} /sbin/fsck $dir->{loop}"; my $media_loopbacks = get_media_prefix($live, 'loopbacks'); ( "losetup $dir->{loop} $live->{prefix}{live}{mnt}$live->{prefix}{media}{mnt}${media_loopbacks}$dir->{path}$loop{loopfs}{extension}", qq(sh -c "$fsck -a || $fsck -y"), "nash-mount -t $dir->{fs} $dir->{loop} $live->{prefix}{live}{mnt}$dir->{mountpoint}", ); }, }, plain => { mount => sub { my ($live, $dir) = @_; qq(sh -c "mkdir -p $live->{prefix}{live}{mnt}$dir->{mountpoint}"); }, }, partition => { files => [ '/sbin/fsck', '/sbin/blkid' ], mount => sub { my ($live, $dir) = @_; my $fsck = "/bin/fsck"; ( qq(sh -c 'dev=`blkid -l -t $dir->{path} -o device`; [ -z "\$dev" ] || $fsck -a \$dev || $fsck -y \$dev'), "nash-mount -t $dir->{fs} $dir->{path} $live->{prefix}{live}{mnt}$dir->{mountpoint}", ); }, }, tmpfs => { mount => sub { my ($live, $dir) = @_; my $mnt = $live->{prefix}{live}{mnt} . $dir->{mountpoint}; my $cmd = "mount -t tmpfs $mnt $mnt"; $dir->{fallback} ? qq(sh -c 'if ! grep -q " $mnt " /proc/mounts; then $cmd; fi') : $cmd; }, }, ); my %overlay = ( unionfs => { modules => [ qw(unionfs) ], mount => sub { my ($live) = @_; #- build dirs list: "dir1=ro:dir2:ro:dir3=rw" my $dirs = join(':', map { $_->{list} ? "\$(cat $live->{prefix}{live}{mnt}/$_->{list})" : "$live->{prefix}{live}{mnt}$_->{mountpoint}=" . (!$loop{$_->{type}}{read_only} && !$_->{read_only} ? 'rw' : 'ro'); } reverse grep { !$_->{fallback} } @{$live->{mount}{dirs} || []}); "sh -c 'mount -o dirs=$dirs -t unionfs unionfs $live->{prefix}{live}{mnt}$live->{mount}{root}'"; }, }, ); sub get_initrd_path { my ($media) = @_; '/' . $media->{storage} . '/initrd.gz'; } sub get_syslinux_path { my ($media, $opts) = @_; '/' . $media->{storage} . '/syslinux' . ($opts->{boot} && '-boot-' . $opts->{boot}) . '.cfg'; } sub need_media_specific_boot { my ($live) = @_; to_bool(list_selected_loopbacks($live)); } sub create_media_initrd { my ($live) = @_; create_initrd_for_media($live, $live->{media}); cp_f($live->get_builddir . $live->{prefix}{build}{boot} . get_initrd_path($live->{media}), $live->{copy_initrd}) if $live->{copy_initrd}; } sub inst_initrd_file { my ($root, $initrd_tree, $file) = @_; if ($file =~ m|/s?bin/|) { inst_initrd_bin($root, $initrd_tree, $file); } else { mkdir_p($initrd_tree . dirname($file)); inst_initrd_dso_deps($root, $initrd_tree, $file) if $file =~ m|/lib[^/]*/|; cp_f($root . $file, $initrd_tree . $file) or die "unable to copy $file from system chroot\n"; } } sub inst_initrd_dso_deps { my ($root, $initrd_tree, $dso) = @_; foreach my $file (`chroot $root ldd $dso | awk '/\\// {if(\$2 == "=>") {print \$3} else {print \$1}}'`) { chomp $file; $file =~ s!^(/lib[^/]*)/(?:i686|tls)!$1!; my ($lib_prefix, $filename) = $file =~ m|(/lib[^/]*).*/([^/]+)$| or next; my $dest = $initrd_tree . $lib_prefix . '/' . $filename; mkdir_p($initrd_tree . $lib_prefix); -f $dest || cp_f($root . $file, $dest) or die "unable to copy $filename from system chroot\n"; } } sub inst_initrd_bin { my ($root, $initrd_tree, $bin) = @_; cp_f($root . $bin, $initrd_tree . '/bin/'); inst_initrd_dso_deps($root, $initrd_tree, $bin); } sub create_initrd_for_media { my ($live, $media) = @_; my $lib_prefix = find { glob($live->get_system_root . $_ . '/libc.so.*') } qw(/lib64 /lib); $lib_prefix or die 'unable to find system libraries in /lib or /lib64'; my $initrd_tree = $live->get_builddir . $live->{prefix}{build}{initrd} . '/' . $media->{storage}; rm_rf($initrd_tree) if -e $initrd_tree; mkdir_p($initrd_tree . $_) foreach qw(/bin /dev /proc /sys /tmp), map { $live->{prefix}{live}{mnt} . $_ } $live->{prefix}{media}{mnt}, $live->{mount}{root}, map { $_->{mountpoint} } @{$live->{mount}{dirs} || []}; #- use nash with label support inst_initrd_bin($live->get_system_root, $initrd_tree, '/sbin/nash'); inst_initrd_bin($live->get_system_root, $initrd_tree, '/usr' . $lib_prefix . '/drakx-installer-binaries/probe-modules'); inst_initrd_bin($live->get_system_root, $initrd_tree, '/sbin/blockdev') if get_media_setting($media, 'rereadpt'); inst_initrd_bin($live->get_system_root, $initrd_tree, '/usr/bin/strace') if $live->{debug}; foreach (chomp_(run_program::rooted_get_stdout($live->get_system_root, "/usr/sbin/splashy_find_files"))) { inst_initrd_file($live->get_system_root, $initrd_tree, $_); } #- busybox is required to: #- detect usb-storage process (we need sh/while/ps/grep) #- mount loopbacks read-only with losetup (useful over NFS) my $busybox = '/usr/bin/busybox'; inst_initrd_bin($live->get_system_root, $initrd_tree, $busybox); my $busybox_rooted = $live->get_system_root . $busybox; my @l = map { /functions:/ .. /^$/ ? do { s/\s//g; split /,/ } : () } `$busybox_rooted`; shift @l; symlink('busybox', $initrd_tree . "/bin/$_") foreach @l; my $fs = get_media_setting($media, 'fs'); my @loop_types = uniq(map { $_->{type} } @{$live->{mount}{dirs}}); inst_initrd_bin($live->get_system_root, $initrd_tree, $_) foreach @{$storage_fs{$fs} && $storage_fs{$fs}{files} || []}, (map { @{$loop{$_} && $loop{$_}{files} || []} } @loop_types); output_p($initrd_tree . '/etc/fstab', ''); output_p($initrd_tree . '/etc/mtab', ''); my $loop_nb = 254; my $rrpt_dev = get_media_setting($media, 'rereadpt'); require devices; devices::make($initrd_tree . "/dev/$_") foreach if_($rrpt_dev, $rrpt_dev), qw(console initrd null ram systty), (map { "tty$_" } 0..8), (map { "loop$_" } 0 .. $loop_nb); syscall_('mknod', $initrd_tree . "/dev/fb0", c::S_IFCHR(), makedev(29, 0)) or die "mknod failed (dev $_): $!"; #- pre-create devfsd compatibility loop devices (since busybox is still built with devfsd support) mkdir_p($initrd_tree . "/dev/loop"); cp_af($initrd_tree . "/dev/loop$_", $initrd_tree . "/dev/loop/$_") foreach 0 .. $loop_nb; my $kernel = $live->find_kernel; print "using kernel $kernel\n"; my $kernel_root = "/lib/modules/" . $kernel; list_modules::load_dependencies($live->get_system_root . $kernel_root . "/modules.dep"); my ($storage_modules, $skipped) = partition { list_modules::modname2filename($_) } uniq(map { modules::cond_mapping_24_26($_) } category2modules(get_media_setting($media, 'modules'))); my ($extra_modules, $extra_missing) = partition { list_modules::modname2filename($_) } category2modules(get_media_setting($media, 'media_modules')), nls_modules($live), get_media_fs_module($media), @{get_media_setting($media, 'extra_modules') || []}, (map { @{$loop{$_}{modules} || []} } uniq(map { $_->{type} } @{$live->{mount}{dirs} || []})), ($live->{mount}{overlay} ? @{$overlay{$live->{mount}{overlay}}{modules} || []} : ()); my @additional_modules = map { if_(m!([^/]+)\.ko(?:\.gz)?!, list_modules::filename2modname($1)) } @{$live->{system}{additional_modules}}; @$extra_modules = difference2($extra_modules, \@additional_modules); if (@{$live->{system}{exclude_modules} || []}) { print STDERR "excluding modules: " . join(' ', @{$live->{system}{exclude_modules}}) . "\n"; @$_ = difference2($_, $live->{system}{exclude_modules}) foreach $storage_modules, $extra_modules; } my @missing = sort(difference2($extra_missing, \@additional_modules)); @missing and die "missing mandatory modules:\n" . join("\n", @missing, ''); mkdir_p($initrd_tree . $kernel_root . "/kernel"); my @modules = (@$storage_modules, @$extra_modules); my @modules_closure = uniq(map { list_modules::dependencies_closure($_) } @modules); foreach my $m (@modules_closure) { my $full = list_modules::modname2path($m); mkdir_p(dirname($initrd_tree . $full)); cp_f($live->get_system_root . $full, $initrd_tree . $full); } foreach my $f (@{$live->{system}{additional_modules}}) { my $destdir = $initrd_tree . $kernel_root . "/kernel"; if ($f =~ /.gz$/) { cp_f($live->{settings}{config_root} . '/' . $f, $destdir); } else { my $m = basename($f); run_program::run('gzip', '>', "$destdir/$m.gz", '-c', $live->{settings}{config_root} . '/' . $f); } } run_('depmod', '-b', $initrd_tree, $kernel); mkdir_p($initrd_tree . "/etc/blkid"); #- for nash and showlabels cache mkdir_p($initrd_tree . "/lib/module-init-tools"); cp_f($live->get_system_root . "/lib/module-init-tools/ldetect-lst-modules.alias", $initrd_tree . "/lib/module-init-tools"); mkdir_p($initrd_tree . "/usr/share/ldetect-lst"); cp_f($live->get_system_root . "/usr/share/pci.ids", $initrd_tree . "/usr/share"); cp_f($live->get_system_root . "/usr/share/ldetect-lst/" . $_, $initrd_tree . "/usr/share/ldetect-lst") foreach qw(fallback-modules.alias pcitable.gz usbtable.gz); @$skipped and print STDERR "skipped modules: " . join(' ', sort(@$skipped)) . "\n"; my @extra_modules_closure = map { list_modules::modname2filename($_) } uniq(map { list_modules::dependencies_closure($_) } @$extra_modules); create_initrd_scriptlet($live, $media, @extra_modules_closure, @additional_modules); compress_initrd_tree($live, $media); add_splash($live, $media); } sub create_initrd_scriptlet { my ($live, $media, @modules) = @_; my $target = $live->{prefix}{live}{mnt} . ($live->{mount}{root} || $live->{prefix}{media}{mnt}); my $pre = get_media_setting($media, 'pre'); my $fs = get_media_setting($media, 'fs'); my $rrpt_dev = get_media_setting($media, 'rereadpt'); my $debug_shell = "sh -c 'if grep -q initrd_debug /proc/cmdline; then exec sh /dev/console 2>/dev/console; fi'"; my ($mount_first, $mount_last) = partition { !$loop{$_->{type}}{delay_mount} } grep { exists $loop{$_->{type}}{mount} } @{$live->{mount}{dirs} || []}; output_with_perm($live->get_builddir . $live->{prefix}{build}{initrd} . '/' . $media->{storage} . '/linuxrc', 0755, join("\n", "#!/bin/nash", #- required for labels and ps "nash-mount -t proc /proc /proc", #- required for cdrom labels "nash-mount -t sysfs /sys /sys", "splashy_chvt 8", "splashy boot", (map { join(" ", "probe-modules", list_modules::filename2modname($_), grep { $_ } $live->{system}{module_options}{$_}) } @modules), "probe-modules --$media->{storage}", if_($rrpt_dev, "echo *** Waiting for new partitions on device ${rrpt_dev} ***", "sh -c 'while ! ls /sys/block/${rrpt_dev}/${rrpt_dev}* >/dev/null 2>&1; do sleep 3; blockdev --rereadpt /dev/${rrpt_dev} >/dev/null 2>&1; done'"), $debug_shell, if_($pre, deref_array($pre)), "mkdevices /dev", "showlabels --removable", $storage_fs{exists $storage_fs{$fs}{mount} ? $fs : 'generic'}{mount}->($live, $media), (map { $loop{$_->{type}}{mount}->($live, $_) } @$mount_first, @$mount_last), ($live->{mount}{overlay} ? $overlay{$live->{mount}{overlay}}{mount}->($live) : ()), if_($live->{system}{initrd_pre_pivot}, deref_array($live->{system}{initrd_pre_pivot})), qq(splashy_update "chroot $target"), "echo 0x0100 > /proc/sys/kernel/real-root-dev", "umount /sys", "sh -c 'umount /proc/bus/usb 2>/dev/null'", "umount /proc", "pivot_root $target $target/initrd", q(sh -c 'rmdir /initrd/live/union'), q(sh -c 'if [ -d /initrd/live/modules ]; then cd /initrd/live/modules; for i in `ls -1`; do mkdir -p /live/modules/$i; mount -n --move $i /live/modules/$i; rmdir $i; done; rmdir /initrd/live/modules; fi'), q(sh -c 'cd /initrd/live; for i in `ls -1`; do [ -d $i ] || continue; mkdir -p /live/$i; mount -n --move $i /live/$i; rmdir $i; done'), q(sh -c 'mv /initrd/live/* /live/'), q(rmdir /initrd/live), if_($live->{system}{initrd_post}, deref_array($live->{system}{initrd_post})), "")); } sub compress_initrd_tree { my ($live, $media) = @_; my $initrd_tree = $live->get_builddir . $live->{prefix}{build}{initrd} . '/' . $media->{storage}; my $size = chomp_(run_program::get_stdout("du -ks $initrd_tree | awk '{print \$1}'")); my $inodes = chomp_(run_program::get_stdout("find $initrd_tree | wc -l")) + 100; my $initrd_size = $size + 350 + int($inodes / 10); #- 10 inodes needs 1K $initrd_size += 600; # splashy my $initrd = $live->get_builddir . $live->{prefix}{build}{boot} . get_initrd_path($media); $initrd =~ s/.gz$//; mkdir_p(dirname($initrd)); run_('dd', 'if=/dev/zero', "of=$initrd", 'bs=1k', "count=$initrd_size"); run_('mke2fs', '-q', '-m', 0, '-F', '-N', $inodes, '-s', 1, $initrd); mkdir_p($live->{mnt}); run_('mount', '-o', 'loop', '-t', 'ext2', $initrd, $live->{mnt}); cp_af(glob("$initrd_tree/*"), $live->{mnt}); rm_rf($live->{mnt} . "/lost+found"); my $left = chomp_(run_program::get_stdout("df -Pk $live->{mnt} | tail -n 1 | awk '{ print \$4 }'")); run_('umount', $live->{mnt}); $left < 200 and die "not enough room to create initrd (only ${left}K left)\n"; run_('gzip', '-f', '-9', $initrd); } sub add_splash { my ($live, $media) = @_; if ($live->{system}{vga_mode} && $live->{system}{splash} ne 'no') { require bootloader; my $initrd = $live->get_builddir . $live->{prefix}{build}{boot} . get_initrd_path($media); my $tmp_initrd = '/tmp/initrd.gz'; cp_f($initrd, $live->get_system_root . $tmp_initrd); { local $::prefix = $live->get_system_root; bootloader::add_boot_splash($tmp_initrd, $live->{system}{vga_mode}); } cp_f($live->get_system_root . $tmp_initrd, $initrd); unlink($live->get_system_root . $tmp_initrd); } } sub create_classical_initrd { my ($live) = @_; my $kernel = $live->find_kernel; #- FIXME: use bootloader-config and allow it not to require a bootloader? print "using kernel $kernel\n"; my $initrd_long = '/boot/initrd-'. $kernel . '.img'; my $initrd_short = '/boot/initrd.img'; my $vmlinuz_long = '/boot/vmlinuz-' . $kernel; my $vmlinuz_short = '/boot/vmlinuz'; my $root = $live->get_system_root; run_({ root => $root }, 'mkinitrd', '-v', '-f', $initrd_long, $kernel); symlinkf(basename($initrd_long), $root . $initrd_short); symlinkf(basename($vmlinuz_long), $root . $vmlinuz_short); if ($live->{system}{vga_mode} && $live->{system}{splash} ne 'no') { require bootloader; local $::prefix = $live->get_system_root; bootloader::add_boot_splash($initrd_long, $live->{system}{vga_mode}); } } sub get_default_append { my ($live, $opts) = @_; join(' ', if_(!need_media_specific_boot($live), 'root=' . get_media_source_for_nash($live->{media})), if_($live->{system}{vga_mode}, 'splash=silent', 'vga=' . $live->{system}{vga_mode}), ($opts->{append} ? $opts->{append} : $live->{system}{append} ? $live->{system}{append} : ()), ); } sub get_bootloader_timeout { my ($live) = @_; defined $live->{media}{bootloader_timeout} ? $live->{media}{bootloader_timeout} : 4; } my @syslinux_boot_files = qw(/vmlinuz /syslinux/bootlogo /help.msg); sub build_syslinux_cfg { my ($live, $media, $opts) = @_; my $append = get_default_append($live, $opts); #- syslinux wants files at root (used with vfat fs) my $to_root = get_boot_setting($media, 'fs', $opts) eq 'vfat'; my $boot = get_media_prefix($live, 'boot', $opts->{boot}); my ($initrd, $kernel, $bootlogo, $help) = map { $to_root ? basename($_) : $_ } map { $boot . $_ } get_initrd_path($media), @syslinux_boot_files; my $has_bootlogo = $live->{system}{gfxboot} && -e $live->get_builddir . $live->{prefix}{build}{boot} . '/syslinux/bootlogo'; my $timeout = get_bootloader_timeout($live) * 10; my $title = $media->{title} || $live->{media}{title}; join("\n", "default $title", "prompt 1", "timeout $timeout", $has_bootlogo ? "gfxboot $bootlogo" : "display $help", (map { my ($name, $cmdline) = @$_; $name =~ s/\s/_/g; "label " . ($name || $title), " kernel $kernel", " append initrd=$initrd $append $cmdline"; } group_by2('' => '', @{$live->{system}{boot_entries}})), "", ); } sub build_grub_cfg { my ($live, $media, $opts, $device) = @_; #- FIXME? first partition is hardcoded for loopback (master images) my ($part_nb) = $device =~ m!/dev/loop! ? 1 : $device =~ /(\d+)$/; my $initrd = get_initrd_path($media); build_grub_cfg_raw($live, $media, $initrd, $opts, $part_nb); } sub build_grub_cfg_raw { my ($live, $media, $initrd, $opts, $part_nb) = @_; #- FIXME: use the bootloader module from drakx my $grub_part = "(hd0" . (defined $part_nb ? "," . ($part_nb-1) : "") . ")"; my $boot = get_media_prefix($live, 'boot'); #- FIXME handle boot media my $title = $media->{title} || $live->{media}{title}; join("\n", "timeout " . get_bootloader_timeout($live), if_($live->{system}{gfxboot}, "gfxmenu $grub_part" . $boot . "/gfxmenu"), "default 0", (map { my ($name, $cmdline) = @$_; "title " . $title . if_($name, " ($name)"), "kernel $grub_part" . $boot . "/vmlinuz " . get_default_append($live, $opts) . if_($cmdline, " $cmdline"), "initrd " . $boot . $initrd; } group_by2('' => '', @{$live->{system}{boot_entries}})), "", ); } sub get_langs { my ($live) = @_; uniq( (ref $live->{regions} ? @{$live->{regions}{$live->{settings}{region}}} : ()), @{$live->{system}{langs_always}} ); } sub install_system { my ($live) = @_; my $repository = $live->{settings}{repository} . '/' . $live->{settings}{arch}; my $drakx_in_chroot = $repository . '/misc/drakx-in-chroot'; my $remote_repository = $repository =~ m!^(ftp|http)://! && $1; if ($remote_repository) { my $local_drakx_in_chroot = $live->get_builddir . $live->{prefix}{build}{scripts} . '/drakx-in-chroot'; mkdir_p(dirname($local_drakx_in_chroot)); run_('curl', '--silent', '-o', $local_drakx_in_chroot, $drakx_in_chroot) or die "unable to get drakx-in-chroot from remote repository\n"; $drakx_in_chroot = $local_drakx_in_chroot; } local %ENV = (%ENV, %{$live->{system}{install_env}}); $ENV{DRAKLIVE_LANGS} = join(':', get_langs($live)); $ENV{DRAKLIVE_REGION} = $live->{settings}{region}; run_({ setarch => $live->{settings}{arch} }, 'perl', $drakx_in_chroot, $repository, $live->get_system_root, if_($live->{system}{auto_install}, '--auto_install', $live->{settings}{config_root} . '/' . $live->{system}{auto_install}), if_($live->{system}{patch_install}, '--defcfg', $live->{settings}{config_root} . '/' . $live->{system}{patch_install}), if_($live->{system}{rpmsrate}, '--rpmsrate', $live->{settings}{config_root} . '/' . $live->{system}{rpmsrate}), ($live->{system}{stage2_updates} ? (map { ('--stage2-update', $live->{settings}{config_root} . '/' . $_->[0], $_->[1]) } @{$live->{system}{stage2_updates}}) : ()), ) or die "unable to install system chroot\n"; post_install_system($live); } sub configure_draklive_resize { my ($live) = @_; my $resizable_loopback = find { $_->{min_size} } @{$live->{mount}{dirs} || []}; if ($resizable_loopback) { my $media_loopbacks = get_media_prefix($live, 'loopbacks'); output($live->get_system_root . '/etc/sysconfig/draklive-resize', <{prefix}{live}{mnt}$live->{prefix}{media}{mnt}${media_loopbacks}$resizable_loopback->{path} TYPE=$resizable_loopback->{fs} MIN_SIZE=$resizable_loopback->{min_size} MOUNT=$live->{prefix}{live}{mnt}$resizable_loopback->{mountpoint}_resized OLD_MOUNT=$live->{prefix}{live}{mnt}$resizable_loopback->{mountpoint} UNION=/ EOF } } sub copy_files_to { my ($live, $files, $root) = @_; foreach (@$files) { my ($source, $dest, $o_opts) = @$_; $dest = $root . '/' . $dest; mkdir_p($dest =~ m|/$| ? $dest : dirname($dest)); my @sources = MDV::Draklive::Utils::glob__($live->{settings}{config_root} . '/' . $source); print STDERR "copying @sources to $dest\n"; cp_f(@sources, $dest); my $o_perm = $o_opts && $o_opts->{mode}; chmod $o_perm, $dest if $o_perm; } } sub join_lists { my ($separator, $head, @lists) = @_; @{$head || []}, map { $separator, @$_ } @lists; } sub remove_files_from { my ($files, $root) = @_; run_('find', $root, '(', join_lists('-o', map { [ '-name', $_ ] } @$files), ')', '-exec', 'rm', '-r', '{}', ';') if $files && @$files; } sub post_install_system { my ($live) = @_; my $previous_umask = umask; #- workaround buggy installation of directories that are not owned by any packages umask 022; #- copy resolv.conf for name resolution to work when adding media cp_f("/etc/resolv.conf", $live->get_system_root . "/etc"); #- remove previous draklive leftovers if needed run_({ root => $live->get_system_root }, 'urpmi.removemedia', '-a'); foreach (@{$live->{system}{additional_media}}) { run_({ root => $live->get_system_root }, 'urpmi.addmedia', if_($_->{distrib}, '--distrib'), $_->{name}, $_->{path}) or die "unable to add media from $_->{path}\n"; @{$_->{packages}} or next; run_({ root => $live->get_system_root, setarch => $live->{settings}{arch} }, 'urpmi', '--auto', '--no-verify-rpm', if_(!$_->{distrib}, '--searchmedia', $_->{name}), @{$_->{packages}}) or die "unable to install packages from $_->{path}\n"; } #- additional rpms may have dependencies in additional media if (@{$live->{system}{rpms}}) { my $rpm_tmp_dir = '/tmp/draklive_rpms'; mkdir_p($live->get_system_root . $rpm_tmp_dir); cp_f((map { $live->{settings}{config_root} . '/' . $_ } @{$live->{system}{rpms}}), $live->get_system_root . $rpm_tmp_dir); run_({ root => $live->get_system_root, setarch => $live->{settings}{arch} }, 'urpmi', '--auto', '--no-verify-rpm', map { $rpm_tmp_dir . '/' . basename($_) } @{$live->{system}{rpms}}) or die "unable to install additional system rpms\n"; rm_rf($live->get_system_root . $rpm_tmp_dir); } #- remove urpmi media added by drakx-in-chroot and additional media, they're unusable run_({ root => $live->get_system_root }, 'urpmi.removemedia', '-a'); my $erase = join(' ', @{$live->{system}{erase_rpms}}); run_({ root => $live->get_system_root, setarch => $live->{settings}{arch} }, 'sh', '-c', "rpm -qa $erase | xargs rpm -e ") if $erase; run_({ root => $live->get_system_root }, 'chkconfig', '--del', $_) foreach @{$live->{system}{disable_services}}; #- make sure harddrake is run: #- if previous HW config file is empty, we assumes DrakX has just completed the installation #- (do it in chroot, or else Storable from the build box may write an incompatible config file) run_({ root => $live->get_system_root }, 'perl', '-MStorable', '-e', qq(Storable::store({ UNKNOWN => {} }, '/etc/sysconfig/harddrake2/previous_hw'))); #- remove some build-machine specific configuration substInFile { undef $_ if /^[^#]/ } $live->get_system_root . $_ foreach qw(/etc/fstab /etc/mtab /etc/modprobe.conf /etc/modprobe.preload /etc/iftab /etc/shorewall/interfaces /etc/mdadm.conf /etc/resolv.conf); unlink($_) foreach map { glob($live->get_system_root . $_) } @{$live->{system}{remove_files} || []}; if ($live->{system}{modules_conf}) { local $::prefix = $live->get_system_root; local *modules::write_preload_conf = sub {}; #- FIXME, make this an option my $modules_conf = modules::any_conf->vnew; put_in_hash($modules_conf, $live->{system}{modules_conf}); $modules_conf->write; } output_with_perm($live->get_system_root . '/etc/fstab', 0644, $live->{mount}{overlay} ? "none / $live->{mount}{overlay} rw 0 0\n" : get_media_setting($live->{media}, 'source') . " / " . get_media_setting($live->{media}, 'fs') . " defaults 1 1\n" ); #- interactive mode can lead to race in initscripts #- (don't use addVarsInSh from MDK::Common, it breaks shell escapes) substInFile { s/^PROMPT=.*/PROMPT=no/ } $live->get_system_root . '/etc/sysconfig/init'; configure_draklive_resize($live); if ($live->{system}{preselect_kdm_user}) { #- preselect specified user in kdm my $kdm_cfg = $live->get_system_root . '/etc/kde/kdm/kdmrc'; update_gnomekderc($kdm_cfg, 'X-:0-Greeter' => (PreselectUser => 'Default', DefaultUser => $live->{system}{preselect_kdm_user})) if -f $kdm_cfg; } #- apply patches and install files after the configuration is cleaned #- to allow special configuration files (especially modprobe.preload) foreach (@{$live->{system}{patches}}) { my $patch = $live->{settings}{config_root} . '/' . $_; my @args = ('-p0', '-d', $live->get_system_root, '-i', $patch); run_program::run('patch', '>', '/dev/null', '--dry-run', '-f', '-R', @args) || run_('patch', @args) or die "unable to apply patch " . $patch . "\n"; } copy_files_to($live, $live->{system}{files}, $live->get_system_root); my @no_install_files = map { $_->[1] } grep { $_->[2] && $_->[2]{no_install} } @{$live->{system}{files}}; output_p($live->get_system_root . '/etc/draklive-install.d/remove.d/draklive', map { "$_\n" } @no_install_files); eval { rm_rf($live->get_builddir . $live->{prefix}{build}{files}) }; mkdir_p($live->get_builddir . $live->{prefix}{build}{files}); if ($live->{media}{files}) { copy_files_to($live, $live->{media}{files}, $live->get_builddir . $live->{prefix}{build}{files}); } remove_files_from($live->{media}{remove_files}, $live->get_builddir . $live->{prefix}{build}{files}); run_({ setarch => $live->{settings}{arch} }, "chroot", $live->get_system_root, "bash", "-c", $live->{system}{postInstall}) if $live->{system}{postInstall}; write_dist_lists($live); umask $previous_umask; } sub write_dist_lists { my ($live) = @_; my $lst = $live->get_builddir . $live->{prefix}{build}{dist} . '/' . $live->get_name . '.lst'; mkdir_p(dirname($lst)); run_("chroot " . $live->get_system_root . " rpm -qa | sort > " . $lst); my $full = $live->get_builddir . $live->{prefix}{build}{dist} . '/' . $live->get_name . '.lst.full'; mkdir_p(dirname($full)); run_("chroot " . $live->get_system_root . qq( sh -c "rpm -qa --qf '%{size} \t%{name}\n' | sort -n" > ) . $full); my $leaves = $live->get_builddir . $live->{prefix}{build}{dist} . '/' . $live->get_name . '.lst.leaves'; mkdir_p(dirname($leaves)); run_("chroot " . $live->get_system_root . qq( sh -c "urpmi_rpm-find-leaves | xargs rpm -q --qf '%{size} \t%{name}\n' | sort -n" > ) . $leaves); require lang; my @live_langs = get_langs($live); my @langs = grep { member(lang::locale_to_main_locale($_), @live_langs) } lang::list_langs(); my $langs_file = $live->get_builddir . $live->{prefix}{build}{dist} . '/' . $live->get_name . '.langs'; output_p($langs_file, map { lang::l2name($_) . " (" . $_ . ")\n" } sort(@langs)); } sub umount_external_filesystem { my ($live) = @_; my $mnt = $live->get_system_root . "/mnt/"; eval { fs::mount::umount("$mnt/$_") } foreach all($mnt); } sub expand_file_list { my ($live, @files) = @_; map { $_->{path} ? $_->{path} : chomp_(cat_(glob(($_->{rooted} && $live->get_system_root) . $_->{source}))); } @files; } #- hardlink recursively file list to a directory sub hardlink_filtered { my ($src, $dest, $files) = @_; mkdir_p($dest); my $pwd = $ENV{PWD}; chdir($src); my $list_file = tmpnam(); output_p($list_file, map { "$_\n" } grep { -e $src . $_ } @$files); #- cpio -pldm won't copy recursively, use rsync -r instead system('rsync', '-ar', '--files-from=' . $list_file, '--link-dest=' . $src, $src, $dest); unlink $list_file; chdir($pwd); } sub list_loopback_modules { my ($live) = @_; map { my $l = $_; map { my $list = $_; my $name = basename($list); $name =~ s/\.[^.]+$//; { type => $l->{type}, name => $name, files => [ expand_file_list($live, { source => $list }) ] }; } glob(($_->{rooted} && $live->get_system_root) . $_->{source}); } @{$live->{loopbacks}{modules}}; } sub create_loopback_files { my ($live) = @_; # umount filesystem in the live before creating the loopback umount_external_filesystem($live); my @excluded_files = expand_file_list($live, @{$live->{loopbacks}{exclude}{files} || []}); my @modules_files = expand_file_list($live, @{$live->{loopbacks}{modules} || []}); foreach (grep { exists $loop{$_->{type}}{build} } @{$live->{mount}{dirs} || []}) { local $_->{exclude} = [ @excluded_files, @modules_files ]; $loop{$_->{type}}{build}->($live, $_); } foreach my $module (list_loopback_modules($live)) { my $copy_tree = $live->get_system_root . "/tmp/draklive/loop/$module->{name}"; eval { rm_rf($copy_tree) }; hardlink_filtered($live->get_system_root, $copy_tree, $module->{files}); my $loop = $loop{$module->{type}}; $loop->{build}->($live, { path => "$live->{prefix}{build}{modules}/$module->{name}$loop->{extension}", root => $copy_tree, exclude => \@excluded_files }); eval { rm_rf($copy_tree) }; } if (@excluded_files) { my $excluded_tree = $live->get_system_root . "/tmp/draklive/excluded/all"; eval { rm_rf($excluded_tree) }; hardlink_filtered($live->get_system_root, $excluded_tree, \@excluded_files); foreach my $module (list_loopback_modules($live)) { my $copy_tree = $live->get_system_root . "/tmp/draklive/excluded/$module->{name}"; eval { rm_rf($copy_tree) }; hardlink_filtered($excluded_tree, $copy_tree, $module->{files}); my $loop = $loop{$module->{type}}; $loop->{build}->($live, { path => "$live->{prefix}{build}{modules}/excluded-$module->{name}$loop->{extension}", root => $copy_tree }); eval { rm_rf($copy_tree) }; } my $loop = $loop{$live->{loopbacks}{exclude}{type}}; $loop->{build}->($live, { path => "/excluded$loop->{extension}", root => $excluded_tree, exclude => \@modules_files }); eval { rm_rf($excluded_tree) }; } } sub list_selected_loopbacks { my ($live) = @_; (map { $loop{$_->{type}}{is_loopback} && $_->{path} ? $_->{path} . $loop{$_->{type}}{extension} : () } @{$live->{mount}{dirs} || []}), (map { $live->{prefix}{build}{modules} . '/' . $_ } $live->{settings}{pack} ? @{$live->{packs}{$live->{settings}{pack}} || []} : ()); } #- mainly for storage-specific subroutines sub get_storage_setting { my ($media, $setting) = @_; $storage{$media->{storage}}{$setting}; } #- for actions that support an optional boot storage type sub get_boot_setting { my ($media, $setting, $opts) = @_; $opts->{boot} ? $storage{$opts->{boot}}{$setting} : get_media_setting($media, $setting); } #- for user-customisable media setting, that can override storage setting sub get_media_setting { my ($media, $setting) = @_; $media->{$setting} || get_storage_setting($media, $setting); } sub get_media_fs_module { my ($media) = @_; my $fs = get_media_setting($media, 'fs'); $fs eq 'iso9660' ? 'isofs' : $fs eq 'ext2' ? @{[]} : $fs; } sub get_media_label { my ($media) = @_; first(get_media_setting($media, 'source') =~ /^LABEL=(.*)$/); } sub get_media_device { my ($live, $opts) = @_; return $opts->{device} if $opts->{device}; my $label = get_media_label($live->{media}) or return get_media_setting($live->{media}, 'source'); my $device = chomp_(`readlink -f /dev/disk/by-label/$label`) or die "unable to find device for /dev/disk/by-label/$label\n"; $device; } sub get_media_prefix { my ($live, $setting, $o_boot) = @_; my $hidden = $live->{system}{hide_media_dirs} && get_boot_setting($live->{media}, 'can_hide', { boot => $o_boot }); $live->{prefix}{media}{$hidden ? "hidden_$setting" : $setting}; } sub get_media_source_for_nash { my ($media) = @_; my $label = get_media_label($media); #- strip vfat labels to 11 chars and upper-case it $label && get_media_setting($media, 'fs') eq 'vfat' ? 'LABEL=' . uc(substr($label, 0, 11)) : get_media_setting($media, 'source'); } sub prepare_bootloader { my ($live) = @_; create_initrd($live); create_bootloader($live); } sub create_initrd { my ($live) = @_; if (need_media_specific_boot($live)) { create_media_initrd($live); } else { create_classical_initrd($live); } } sub create_bootloader { my ($live) = @_; if (need_media_specific_boot($live)) { create_media_bootloader($live); } else { create_classical_bootloader($live); } run_({ root => $live->get_system_root }, '/usr/sbin/grub-gfxmenu', '--update-gfxmenu'); my $boot_dir = $live->get_builddir . $live->{prefix}{build}{boot}; mkdir_p($boot_dir); cp_f($live->get_system_root . '/boot/gfxmenu', $boot_dir); } sub create_media_bootloader { my ($live) = @_; cp_f($live->get_system_root . '/boot/vmlinuz-' . $live->find_kernel, $live->get_builddir . $live->{prefix}{build}{boot} . '/vmlinuz'); require bootsplash; my $theme = do { local $::prefix = $live->get_system_root; bootsplash::themes_read_sysconfig('800x600'); }; my $syslinux_dir = $live->get_builddir . $live->{prefix}{build}{boot} . '/syslinux'; mkdir_p($syslinux_dir); my $default_gfxboot_theme = "Mandriva"; print "copying $default_gfxboot_theme gfxboot theme\n"; cp_f(glob_($live->get_system_root . "/usr/share/gfxboot/themes/$default_gfxboot_theme/install/*"), $syslinux_dir); if ($theme->{name} ne $default_gfxboot_theme) { print "copying $theme->{name} gfxboot theme\n"; cp_f(glob_($live->get_system_root . "/usr/share/gfxboot/themes/$theme->{name}/*"), $syslinux_dir); } my $bootlogo = $syslinux_dir . '/bootlogo'; warn "unable to find gfxboot splash ($bootlogo)\n" if ! -f $bootlogo; output_p($syslinux_dir . '/gfxboot.cfg', "livecd=1\n"); output($live->get_builddir . $live->{prefix}{build}{boot} . '/help.msg', pack("C*", 0x0E, 0x80, 0x03, 0x00, 0xC) . qq( Welcome to Mandriva live! The command line can be used to specify kernel options. $live->{media}{title} )); foreach my $boot ('', @{$live->{media}{extra_boot}}) { my $opts = { boot => $boot }; output($live->get_builddir . $live->{prefix}{build}{boot} . get_syslinux_path($live->{media}, $opts), build_syslinux_cfg($live, $live->{media}, $opts)); } } sub create_classical_bootloader { my ($live) = @_; my $part_nb = 1; #- FIXME: get from partition layout config output_p($live->get_system_root . '/boot/grub/menu.lst', build_grub_cfg_raw($live, $live->{media}, "/initrd.img", {}, $part_nb)); } sub device_allocate_file { my ($device, $size) = @_; run_('dd', "of=$device", 'count=0', 'bs=1', "seek=" . removeXiBSuffix($size)); } #- format $device as type $type sub device_mkfs { my ($device, $type) = @_; if ($type eq 'vfat') { run_('mkfs.vfat', $device); } elsif (member($type, 'ext2', 'ext3')) { run_("mkfs.$type", "-m", 0, if_(!-b $device, '-F'), $device); } else { die "unable to mkfs for unsupported media type $type\n"; } } sub set_device_label { my ($device, $type, $label) = @_; if ($type eq 'vfat') { MDV::Draklive::Utils::mtools_run_('mlabel', '-i', $device, '::' . $label); } elsif (member($type, 'ext2', 'ext3')) { run_('e2label', $device, $label); } else { die "unable to set label for unsupported media type $type\n"; } } sub get_cdrom_master_path { my ($live, $opts) = @_; $live->get_builddir . $live->{prefix}{build}{dist} . '/' . $live->get_name . ($opts->{boot} && "-boot-$opts->{boot}") . '.iso'; } sub create_cdrom_master { my ($live, $opts) = @_; my $label = get_media_label($live->{media}) or die "the source device must be described by a label\n"; my $dest; unless ($opts->{onthefly}) { $dest = get_cdrom_master_path($live, $opts); mkdir_p(dirname($dest)); } my $progress = progress_start(100, time()); my $in_progress; autoflush STDOUT 1; run_foreach(sub { if (/^\s*([0-9.]+)%\s*done,/) { $progress->{current} = int($1); progress_show($progress, time()); $in_progress = 1; } else { print "\n" if $in_progress; print $_; $in_progress = 0; } }, 'genisoimage', '-pad', '-l', '-R', '-J', '-V', $label, #'-A', $application, '-p', $preparer, '-P', $publisher, '-b', 'isolinux/isolinux.bin', '-c', 'isolinux/boot.cat', '-hide-rr-moved', '-no-emul-boot', '-boot-load-size', 4, '-boot-info-table', '-graft-points', if_($dest, '-o', $dest), 'isolinux=' . $live->get_builddir . $live->{prefix}{build}{boot} . '/syslinux', 'isolinux/isolinux.bin=/usr/lib/syslinux/isolinux.bin', 'isolinux/isolinux.cfg=' . $live->get_builddir . $live->{prefix}{build}{boot} . get_syslinux_path($live->{media}, $opts), get_media_prefix($live, 'boot', $opts->{boot}) . '=' . $live->get_builddir . $live->{prefix}{build}{boot}, if_(!$opts->{boot_only}, (map { get_media_prefix($live, 'loopbacks', $opts->{boot}) . $_ . '=' . $live->get_builddir . $live->{prefix}{build}{loopbacks} . $_; } list_selected_loopbacks($live)), if_($live->{media}{files}, map { $_ . '=' . $live->get_builddir . $live->{prefix}{build}{files} . '/' . $_; } all($live->get_builddir . $live->{prefix}{build}{files}) ), ), ) or die "unable to run genisoimage\n"; autoflush STDOUT 0; progress_end(); if ($dest) { my $dir = dirname($dest); my $filename = basename($dest); run_('mkcd', '>', '/dev/null', '2>', '/dev/null', '--addmd5', $dest); run_({ chdir => $dir }, 'md5sum', '>', $dest . '.md5', $filename); run_({ chdir => $dir }, 'sha1sum', '>', $dest . '.sha1', $filename); } } sub get_disk_master_path { my ($live) = @_; $live->get_builddir . $live->{prefix}{build}{dist} . '/' . $live->get_name . '.img'; } sub allocate_master { my ($dest, $size, $fs) = @_; mkdir_p(dirname($dest)); my $heads = 16; my $sectors_per_track = 63; my $sector_size = 512; my $track_size = $sector_size * $sectors_per_track; my $cylinder_size = $track_size * $heads; my $disk_size = $size + $track_size; my $cylinders = int($disk_size / $cylinder_size); device_allocate_file($dest, $disk_size); #- FIXME: use parted? require fs::type; my $pt_type = sprintf('%x', fs::type::fs_type2subpart($fs)->{pt_type}); open(my $fdisk, "| fdisk -C $cylinders -S $sectors_per_track -H $heads $dest"); print $fdisk <{mount}{dirs} || []} ? (directory_usage($live->get_builddir . $live->{prefix}{build}{loopbacks}) + directory_usage($live->get_builddir . $live->{prefix}{build}{boot})) : directory_usage($live->get_system_root); my $loop = allocate_master($dest, 500000000 + $part_size, get_media_setting($live->{media}, 'fs')); local $opts->{device} = $loop; local $opts->{disk_device} = $dest; record_master($live, $opts); run_('losetup', '-d', $loop); } #- $opts: #- media: alternate media #- onthefly : if true, the create function must output to stdout sub create_master { my ($live, $opts) = @_; if (my $create = get_boot_setting($live->{media}, 'create', $opts)) { $create->($live, $opts); } else { warn "not implemented yet\n"; } } sub maybe_umount_device { my ($device) = @_; run_('umount', $device) if cat_('/proc/mounts') =~ m!^$device\s+!m; } sub format_cdrom_device { my ($live, $opts) = @_; run_('wodim', '-v', 'dev=' . get_media_device($live, $opts), "blank=fast"); } sub format_usb_device { my ($live, $opts) = @_; my $device = get_media_device($live, $opts); maybe_umount_device($device); device_mkfs($device, get_media_setting($live->{media}, 'fs')) or die "unable to format device $device\n"; } #- $opts: #- media: alternate media sub format_device { my ($live, $opts) = @_; get_media_device($live, $opts) or die "no device defined in media configuration\n"; if (my $format = get_boot_setting($live->{media}, 'format', $opts)) { $format->($live, $opts); } else { warn "not implemented yet\n"; } } sub record_cdrom_master { my ($live, $opts) = @_; my $device = get_media_device($live, $opts) or die "no device defined in media configuration\n"; my $src = $opts->{onthefly} ? '-' : get_cdrom_master_path($live, $opts); run_('wodim', '-v', 'dev=' . $device, $src); } sub install_grub_to_image { my ($live, $img) = @_; cp_af(glob_($live->get_system_root . "/lib/grub/i386-mandriva/*"), $live->{mnt} . "/boot/grub"); open(my $grub, "| /sbin/grub --batch --no-floppy"); # using disk loopback fails, have to use image path print $grub <{boot}); my $device = get_media_device($live, $opts); my $bootloader = get_boot_setting($media, 'bootloader', $opts); member($bootloader, 'grub', 'syslinux') or die "no bootloader defined in media configuration\n"; my $grub_device_map = $live->{mnt} . $media_boot . "/grub/device.map"; if ($bootloader eq 'syslinux') { cp_f($live->get_builddir . $_, $live->{mnt}) foreach map { $live->{prefix}{boot} . $_; } get_syslinux_path($media, $opts), get_initrd_path($media), @syslinux_boot_files; } elsif ($bootloader eq 'grub') { #- FIXME: add get_grub_path (when building boot configuration files) # and get_bootloader_path (when copying) mkdir_p($live->{mnt} . $media_boot . '/grub'); cp_f($live->get_builddir . $live->{prefix}{build}{boot} . '/gfxmenu', $live->{mnt} . $media_boot); output_p($live->{mnt} . $media_boot . '/grub/menu.lst', build_grub_cfg($live, $media, $opts, $device)); unlink $grub_device_map; } if (-b $device) { if ($bootloader eq 'syslinux') { #- use syslinux -s, "safe, slow and stupid" version of SYSLINUX, unless specified otherwise run_('syslinux', if_(!$media->{fast_syslinux}, '-s'), $device) or die "unable to run syslinux on $device\n"; } elsif ($bootloader eq 'grub') { my $master_device = $opts->{disk_device}; if (!$master_device) { $master_device = $device; $master_device =~ s/(\d+)$//; } if ($master_device =~ m!/dev/!) { my $local_grub_install = $live->get_builddir . $live->{prefix}{build}{scripts} . '/grub-install'; mkdir_p(dirname($local_grub_install)); cp_f($live->get_system_root . '/sbin/grub-install', $local_grub_install); my $root = $live->get_system_root; substInFile { s!^\s*exec_prefix=.*!exec_prefix=$root!; s!^\s*grub_prefix=/boot/!grub_prefix=${media_boot}/!; s!^\s*bootdir=(.*)/boot$!bootdir=$1${media_boot}!; } $local_grub_install; foreach ($master_device, $device) { run_($local_grub_install, '--root-directory=' . $live->{mnt}, '--no-floppy', $_) or die "unable to run grub on $device\n"; } } else { install_grub_to_image($live, $master_device); } unlink $grub_device_map; } } else { warn "not running $bootloader on non block device $device\n"; } } sub record_usb_master { my ($live, $opts) = @_; my $media = $live->{media}; my $media_boot = get_media_prefix($live, 'boot', $opts->{boot}); my $media_loopbacks = get_media_prefix($live, 'loopbacks', $opts->{boot}); if (my $label = !$opts->{boot_only} && $opts->{device} && get_media_label($media)) { set_device_label($opts->{device}, get_media_setting($media, 'fs'), $label); } my $device = get_media_device($live, $opts) or die "unable to find recording device (missing label? try with --device )\n"; mkdir_p($live->{mnt}); run_('mount', if_(-f $device, '-o', 'loop'), $device, $live->{mnt}) or die "unable to mount $device\n"; rm_rf($live->{mnt} . $media_boot) if -e $live->{mnt} . $media_boot; cp_af($live->get_builddir . $live->{prefix}{build}{boot}, $live->{mnt} . $media_boot); install_usb_bootloader($live, $media, $opts); do { my $loopbacks_source = $live->get_builddir . $live->{prefix}{build}{loopbacks} . '/'; my $total = directory_usage($loopbacks_source); my $all_files = 0; my $current_file = 0; my $list_file = tmpnam(); output_p($list_file, map { ".$_\n" } list_selected_loopbacks($live)); local $/ = "\r"; my $r = run_foreach( $live->{update_progress} ? sub { if (/^\s*(\d+)\s+\d+%\s+/) { $current_file = $1; $live->{update_progress}->(undef, $all_files + $current_file, $total); } if (/(?:^|\n)\S+/) { $all_files += $current_file; $current_file = 0; } } : sub {}, 'rsync', '-vdP', '--inplace', '--files-from=' . $list_file, $loopbacks_source, $live->{mnt} . $media_loopbacks, ); unlink $list_file; if (!$r) { run_('umount', $live->{mnt}); maybe_umount_device($device); die "unable to copy loopback files\n"; } cp_af(glob_($live->get_builddir . $live->{prefix}{build}{files} . '/*'), $live->{mnt}); } unless $opts->{boot_only}; my @hidden_files = map { basename($_) } glob_($live->{mnt} . "/.*"), glob_($live->{mnt} . "/autorun.*"); run_('umount', $live->{mnt}); maybe_umount_device($device); if (get_media_setting($media, 'fs') eq 'vfat') { MDV::Draklive::Utils::mtools_run_('mattrib', '+h', '-i', $device, '::' . $_) foreach @hidden_files; MDV::Draklive::Utils::mtools_run_('mattrib', '+r', '+s', '-/', '-i', $device, '::' . $_) foreach $media_boot, $media_loopbacks; } } sub record_harddisk_master { my ($live, $opts) = @_; my $media = $live->{media}; my $media_boot = get_media_prefix($live, 'boot', $opts->{boot}); my $media_loopbacks = get_media_prefix($live, 'loopbacks', $opts->{boot}); if (my $label = !$opts->{boot_only} && $opts->{device} && get_media_label($media)) { set_device_label($opts->{device}, get_media_setting($media, 'fs'), $label); } my $device = get_media_device($live, $opts) or die "unable to find recording device (missing label? try with --device )\n"; mkdir_p($live->{mnt}); run_('mount', if_(-f $device, '-o', 'loop'), $device, $live->{mnt}) or die "unable to mount $device\n"; do { my $source = $live->get_system_root; my $total = directory_usage($source); my $all_files = 0; my $current_file = 0; local $/ = "\r"; my $r = run_foreach( $live->{update_progress} ? sub { if (/^\s*(\d+)\s+\d+%\s+/) { $current_file = $1; $live->{update_progress}->(undef, $all_files + $current_file, $total); } if (/(?:^|\n)\S+/) { $all_files += $current_file; $current_file = 0; } } : sub {}, 'rsync', '-avP', $source . '/', $live->{mnt}, ); if (!$r) { run_('umount', $live->{mnt}); maybe_umount_device($device); die "unable to copy system files\n"; } } unless $opts->{boot_only}; install_grub_to_image($live, $opts->{disk_device}); run_('umount', $live->{mnt}); maybe_umount_device($device); } #- $opts: #- onthefly : if true, the record function must read from stdin sub record_master { my ($live, $opts) = @_; if (my $record = get_boot_setting($live->{media}, 'record', $opts)) { $record->($live, $opts); } else { warn "not implemented yet\n"; } } sub pipe_subs { my ($writer, $reader) = @_; my ($r, $w) = POSIX::pipe; if (my $pid = fork()) { POSIX::close($w) or die "couldn't close: $!\n"; my $stdin = POSIX::dup(0) or die "couldn't dup: $!\n"; POSIX::dup2($r, 0) or die "couldn't dup2: $!\n"; POSIX::close($r); $reader->(); POSIX::close(0) or warn "writer exited $?\n"; POSIX::dup2($stdin, 0) or die "couldn't dup2: $!\n"; waitpid($pid, 0); } else { POSIX::close($r) or die "couldn't close: $!\n"; #- don't screw up reader POSIX::dup2(POSIX::open('/dev/null', &POSIX::O_WRONLY), 2) or die "couldn't dup2: $!\n"; POSIX::dup2($w, 1) or die "couldn't dup2: $!\n"; POSIX::close($w); $| = 1; #- autoflush write exit !$writer->(); } } sub record_onthefly { my ($live, $opts) = @_; my $record = get_storage_setting($live->{media}, 'record'); unless ($record) { warn "not implemented yet\n"; return; } if (my $create = get_storage_setting($live->{media}, 'record_needs_master') && get_storage_setting($live->{media}, 'create')) { local $opts->{onthefly} = 1; #- pipe creation step to recording step pipe_subs(sub { $create->($live, $opts) }, sub { $record->($live, $opts) }); } else { #- no creation step, record directly $record->($live, $opts); } } sub need_compressed_image { my ($live) = @_; list_selected_loopbacks($live); } sub get_disk_image_path { my ($live) = @_; get_disk_master_path($live) . if_(need_compressed_image($live), '.bz2'); } sub create_disk_image { my ($live) = @_; if (!need_compressed_image($live)) { warn "no loopback: skipping image creation"; return; } my $master = get_disk_master_path($live); my $dest = get_disk_image_path($live); mkdir_p(dirname($dest)); run_('bzip2', '>', $dest, '-f', '-k', '-c', $master); } sub create_image { my ($live) = @_; if (my $create = get_media_setting($live->{media}, 'image')) { $create->($live); } else { warn "not implemented yet\n"; } } sub get_disk_replicator_path { my ($live) = @_; $live->get_builddir . $live->{prefix}{build}{dist} . '/' . $live->get_name . '.rest.img'; } sub get_disk_replicator_files { my ($live) = @_; my $media_boot = get_media_prefix($live, 'boot'); my $initrd = $media_boot . get_initrd_path($live->{replicator}{media}); ( '/usr/lib/drakx-installer-images/isolinux/alt0/vmlinuz' => $media_boot . '/vmlinuz', '/usr/lib/drakx-installer-images/isolinux/alt0/all.rdz' => $initrd, '/usr/lib/drakx-installer-rescue/rescue.sqfs' => '/install/stage2/', get_disk_image_path($live) => '/images/', ); } sub create_usb_replicator { my ($live) = @_; my $dest = get_disk_replicator_path($live); my %files = get_disk_replicator_files($live); my $size = fold_left { $::a + $::b } map { directory_usage($_) } keys(%files); my $loop = allocate_master($dest, 5000000 + $size, 'vfat'); my $opts = { device => $loop, disk_device => $dest, append => $live->{replicator}{append} }; record_usb_replicator($live, $opts); run_('losetup', '-d', $loop); } sub record_usb_replicator { my ($live, $opts) = @_; my $media = $live->{replicator}{media}; my $media_boot = get_media_prefix($live, 'boot'); my $device = get_media_device($live, $opts) or die "unable to find recording device (missing label? try with --device )\n"; mkdir_p($live->{mnt}); run_('mount', if_(-f $device, '-o', 'loop'), $device, $live->{mnt}) or die "unable to mount $device\n"; rm_rf($live->{mnt} . $media_boot) if -e $live->{mnt} . $media_boot; install_usb_bootloader($live, $media, $opts); my $failed; foreach (group_by2(get_disk_replicator_files($live))) { my ($src, $dest) = @$_; $dest = $live->{mnt} . $dest; mkdir_p($dest =~ m!/$! ? $dest : dirname($dest)); if (!run_('rsync', '-vdP', '--inplace', $src, $dest)) { $failed = 1; last; } } if ($failed) { run_('umount', $live->{mnt}); maybe_umount_device($device); die "unable to copy replicator files\n"; } output_p($live->{mnt} . "/images/list", "EN,English," . basename(get_disk_image_path($live)) . ",on\n"); run_('umount', $live->{mnt}); maybe_umount_device($device); } sub create_replicator { my ($live) = @_; if (my $create = get_media_setting($live->{replicator}{media}, 'replicator')) { $create->($live); } else { warn "not implemented yet\n"; } } sub copy_wizard { my ($live) = @_; complete_config($live); $live->{system}{vga_mode} = 788 if !defined $live->{system}{vga_mode}; my $live_media = $live->{prefix}{live}{mnt} . $live->{prefix}{media}{mnt}; require interactive; require wizards; my $in = 'interactive'->vnew('su'); my (@available_storage, @available_devices); my ($storage, $device, $format); my %source_types = ( live => N("Use current live system"), file => N("Select a file"), ); my ($source_type, $source_path); my $media_boot = get_media_prefix($live, 'boot'); my $media_loopbacks = get_media_prefix($live, 'loopbacks'); my $source_is_mounted = sub { -d ($live->{settings}{builddir} . $media_boot) }; my $umount_source = sub { if ($source_type ne 'live' && $source_is_mounted->()) { run_('umount', $live->{settings}{builddir}); rmdir($live->{settings}{builddir}); } }; my $w; $w = wizards->new({ name => N("Live system copy wizard"), pages => { welcome => { name => N("Welcome to the live system copy wizard"), no_back => 1, next => 'source', }, source => { name => N("Which live system do you want to copy?"), data => [ if_(-d ($live_media . $media_loopbacks), { type => 'list', val => \$source_type, list => sort(keys(%source_types)), format => sub { $source_types{$_[0]} } }), { type => 'file', val => \$source_path, disabled => sub { $source_type eq 'live' } } ], pre => $umount_source, complete => sub { if ($source_type eq 'live') { $live->{settings}{builddir} = $live_media; } else { $live->{settings}{builddir} = File::Temp::tempdir(); if (!run_('mount', '-o', 'loop', $source_path, $live->{settings}{builddir})) { $in->ask_warn(N("Error"), N("Unable to use selected file")); return 1; } } 0; }, post => sub { my $boot = $live->{settings}{builddir} . $media_boot; @available_storage = sort(grep { -d "$boot/$_" && exists $storage{$_}{detect} } all($boot)); if (@available_storage == 1) { $storage = $available_storage[0]; return 'device'; } return 'storage'; } }, storage => { name => N("Please select the medium type"), data => [ { type => 'list', allow_empty_list => 1, val => \$storage, list => \@available_storage } ], next => 'device', }, device => { name => N("Please select the device that will contain the new live system"), pre => sub { my %devices = map { $_->{device} => $_ } $storage{$storage}{detect}->(); $_->{formatted_name} = $_->{usb_description} || $_->{info} || $_->{device} foreach values %devices; @available_devices = (); require fs::proc_partitions; foreach (fs::proc_partitions::read([ values %devices ])) { if ($_->{rootDevice} && exists $devices{$_->{rootDevice}}) { my $description = $devices{$_->{rootDevice}}{usb_description} || $devices{$_->{rootDevice}}{info}; $_->{formatted_name} = $description ? "$description ($_->{device})" : $_->{device}; push @available_devices, $_; } } delete $devices{$_->{rootDevice}} foreach @available_devices; unshift @available_devices, map { $devices{$_} } sort keys %devices; undef $device; }, data => [ { type => 'list', allow_empty_list => 1, val => \$device, , list => \@available_devices, format => sub { $_[0]{formatted_name} } }, { text => N("Format selected device"), val => \$format, type => 'bool' } ], complete => sub { return 0 if defined $device; $in->ask_warn(N("Error"), N("You must select a device!")); 1; }, post => sub { (my $_wait, $live->{update_progress}) = $in->wait_message_with_progress_bar; do { local $::isInstall = 1; # quick hack to embed the wait message $live->{update_progress}->(N("Copying in progress")); }; eval { my $opts = { media => { storage => $storage, device => '/dev/' . $device->{device} } }; format_device($live, $opts) if $format; record_onthefly($live, $opts); }; delete $live->{update_progress}; if (my $error = $@) { $in->ask_warn(N("Error"), $error); $w->{pages}{device}{end} = 1; } return "end"; }, }, end => { name => N("Congratulations") . "\n\n" . N("Your live system is now copied."), no_back => 1, end => 1, }, } }); $w->process($in); $umount_source->(); $in->exit; } sub read_config { my ($live, $config_root, $config_path, $settings_path) = @_; if ($config_path && -e getcwd() . '/' . $config_path) { $config_root = getcwd(); } print "using $config_root as directory root\n"; add2hash($live->{settings} ||= {}, { getVarsFromSh($config_root . '/' . $settings_path) }) if $settings_path; if ($config_path) { #- don't use do(), since it can't see lexicals in the enclosing scope my $cfg = eval(cat_($config_root . '/' . $config_path)) or die "unable to load $config_path: $@\n"; put_in_hash($live, $cfg); print "loaded $config_path as config file\n"; } $live->{settings}{config_root} = $config_root; } sub check_config { my ($live) = @_; unless (keys(%$live)) { warn "no live definition\n"; Pod::Usage::pod2usage(); } #- check for minimum requirements ref $live->{media} && $live->{media}{storage} or die "no media storage definition\n"; ref $live->{system} or die "no system definition\n"; } sub complete_config { my ($live) = @_; my $default_prefix = { build => { boot => '/boot', files => '/files', dist => '/dist', initrd => '/initrd', loopbacks => '/loopbacks', modules => '/modules', scripts => '/scripts', }, media => { boot => '/boot', hidden_boot => '/.boot', loopbacks => '/loopbacks', hidden_loopbacks => '/.loopbacks', mnt => '/media', }, live => { mnt => '/live', }, }; #- set unsupplied config dirs add2hash($live->{prefix}{$_} ||= {}, $default_prefix->{$_}) foreach keys %$default_prefix; $live->{settings}{builddir} ||= '/var/lib/draklive/build'; $live->{settings}{chroot} ||= '/var/lib/draklive/chroot'; $live->{settings}{arch} ||= chomp_(`rpm --eval '%{_target_cpu}'`); $live->{media}{title} ||= "live"; bless $live->{media}, 'MDV::Draklive::Media'; mkdir_p($live->get_builddir); mkdir_p($live->get_system_root); $live->{mnt} ||= $live->get_builddir . "/mnt"; } sub dump_config { my ($live) = @_; use Data::Dumper; print Data::Dumper->Dump([ $live ], [ "live" ]); } sub clean { my ($live) = @_; # umount filesystem in the live before cleaning umount_external_filesystem($live); rm_rf($_) foreach grep { -e $_ } $live->get_builddir, $live->get_system_root; } my @actions = ( { name => 'dump-config', do => \&dump_config }, { name => 'clean', do => \&clean }, { name => 'install', do => \&install_system }, { name => 'post-install', do => \&post_install_system }, { name => 'initrd', do => \&create_initrd }, { name => 'boot', do => \&prepare_bootloader }, { name => 'loop', do => \&create_loopback_files }, { name => 'master', do => \&create_master }, { name => 'image', do => \&create_image }, { name => 'replicator', do => \&create_replicator }, { name => 'format', do => \&format_device }, { name => 'record', do => \&record_master }, { name => 'record-onthefly', do => \&record_onthefly }, ); my @all = qw(install boot loop master image replicator); die "you must be root to run this program\n" if $>; my $live_object = 'MDV::Draklive::Live'->new; my %opts; my $config_root = '/etc/draklive'; my $config_path = 'config/live.cfg'; my $settings_path = 'config/settings.cfg'; GetOptions( "help" => sub { Pod::Usage::pod2usage('-verbose' => 1) }, "copy-wizard" => \$live_object->{copy_wizard}, "boot-only" => \$opts{boot_only}, "boot-image=s" => sub { $opts{boot} = $_[1]; $opts{boot_only} = 1 }, "all" => sub { $_->{to_run} = 1 foreach grep { member($_->{name}, @all) } @actions }, (map { $_->{name} => \$_->{to_run} } @actions), "device=s" => sub { $opts{device} = $_[1] }, "all-regions" => sub { $live_object->{all_regions} = 1 }, "config-root=s" => \$config_root, "config=s" => \$config_path, "settings=s" => \$settings_path, "define=s" => \%{$live_object->{settings}}, ) or Pod::Usage::pod2usage(); require standalone; if ($live_object->{copy_wizard}) { copy_wizard($live_object); } else { every { !$_->{to_run} } @actions and Pod::Usage::pod2usage(); read_config($live_object, $config_root, $config_path, $settings_path); check_config($live_object); complete_config($live_object); foreach my $region ($live_object->{all_regions} ? sort(keys %{$live_object->{regions}}) : $live_object->{settings}{region}) { $region and print qq(=== proceeding with region "$region"\n); $live_object->{settings}{region} = $region; foreach (grep { $_->{to_run} } @actions) { print qq(* entering step "$_->{name}"\n); $_->{do}->($live_object, \%opts); print qq(* step "$_->{name}" done\n); } } } __END__ =head1 NAME draklive - A live distribution mastering tool =head1 SYNOPSIS draklive [options] Options: --help long help message --install install selected distribution in chroot --boot prepare initrd and bootloader files --loop build compressed loopback files --master build master image --image build compressed master image --replicator build replicator image (to dump master on systems) --all run all steps, from installation to mastering --clean clean installation chroot and work directory --device use this device for live recording (not needed if the device already has the required label) --format format selected device --record record live on selected media --record-onthefly record live by creating master from loopback files on the fly --initrd build initrd only --post-install run post install only (rpms and patches installation) --config-root root directory containing config files and additionnal files defaults to current directory if it contains a configuration file else, "/etc/draklive" is used --config use this configuration file as live description defaults to "config/live.cfg" --settings use this file as live settings (key=value format) defaults to "config/settings.cfg" --define key=value set setting "key" to "value" takes precedence over values from a settings file --all-regions proceed with all configured regions --copy-wizard run the copy wizard --boot-only copy only boot files (affects master/record steps) --boot-image create a boot image for the selected method (affects master/record steps, implies --boot-only) Examples: draklive --clean draklive --all draklive --record --device /dev/sdb1 draklive --config config/live.cfg --install =head1 OPTIONS =over 8 =item B<--config> Makes draklive use the next argument as a configuration file. This file should contain an hash describing the live distribution, meaning the system (chroot and boot), media (usb, cdrom, nfs), and mount type (simple R/W union, union with squash files). Here's a configuration sample: { settings { repository => '/mnt/ken/2006.0', root => '/chroot/live-move', }, system => { kernel => '2.6.12-12mdk-i586-up-1GB', auto_install => 'config/auto_inst.cfg.pl', patch_install => 'config/patch-2006-live.pl', rpmsrate => 'config/rpmsrate', rpms => [ 'rpms/unionfs-kernel-2.6.12-12mdk-i586-up-1GB-1.1.1.1.20051124.1mdk-1mdk.i586.rpm' ], patches => [ 'patches/halt.loopfs.patch', ], vga_mode => 788, }, media => { storage => 'cdrom', }, mount => $predefined{mounts}{squash_union} }; =item B<--settings> Makes draklive load the next argument as a file in key=value format into the $live->{settings} hash ($live being the global live configuration hash). Built-in keys: arch: build architecture builddir: directory hosting build files (initrd, loopbacks, images) chroot: directory hosting chrooted installations region: use the matching set of langs from the regions configuration hash repository: path to the Mandriva distribution repository (ftp/http/local) Example keys: desktop media product =back =head1 DESCRIPTION B builds a live distribution according to a configuration file, creates a master image, and optionally installs it on a device. See L =head1 AUTHOR Olivier Blin =cut