From 2b0cf7577bf41292c82ee09e48bc353d49613f17 Mon Sep 17 00:00:00 2001 From: Olivier Blin Date: Tue, 18 Mar 2008 13:54:07 +0000 Subject: branch 2008.0 --- trunk/.perl_checker | 25 + trunk/ChangeLog | 437 +++++++++ trunk/Makefile | 18 + trunk/NEWS | 291 ++++++ trunk/draklive | 1736 ++++++++++++++++++++++++++++++++++++ trunk/draklive-copy-wizard.desktop | 7 + trunk/kmod/readlog.c | 230 +++++ 7 files changed, 2744 insertions(+) create mode 100644 trunk/.perl_checker create mode 100644 trunk/ChangeLog create mode 100644 trunk/Makefile create mode 100644 trunk/NEWS create mode 100755 trunk/draklive create mode 100644 trunk/draklive-copy-wizard.desktop create mode 100644 trunk/kmod/readlog.c diff --git a/trunk/.perl_checker b/trunk/.perl_checker new file mode 100644 index 0000000..c7068c0 --- /dev/null +++ b/trunk/.perl_checker @@ -0,0 +1,25 @@ +constant +AutoLoader +Carp::Heavy +Compress::Zlib +Cwd +Date::Manip +Digest::MD5 +Encode +encoding +File::Find +File::FnMatch +File::GlobMapper +File::Path +File::Temp +Gtk2::Gdk::Keysyms +IO::Compress::Gzip +IO::Uncompress::Gunzip +IO::Handle +Pod::Usage +Scalar::Util +String::ShellQuote +Text::ParseWords +Term::ReadKey +urpm::media +urpm::parallel diff --git a/trunk/ChangeLog b/trunk/ChangeLog new file mode 100644 index 0000000..450d616 --- /dev/null +++ b/trunk/ChangeLog @@ -0,0 +1,437 @@ +2007-09-29 15:57 Olivier Blin + + * draklive: do not run service_harddrake stop anymore, it does not + exist in 2008.0 + +2007-09-25 15:20 Olivier Blin + + * draklive: fix typo + +2007-09-25 14:05 Olivier Blin + + * draklive: add newlines in die messages, not to trigger the + infamous drakbug + +2007-09-25 13:55 Olivier Blin + + * draklive: print usage if no option is specified + +2007-09-25 10:31 Olivier Blin + + * draklive: fix division by zero /o\ + +2007-09-19 14:53 Olivier Blin + + * draklive: copy additional modules after plain modules, so that + they can overwrite them + +2007-09-19 14:53 Olivier Blin + + * draklive: fix additional modules copy + +2007-09-03 08:44 Olivier Blin + + * draklive: please perl_checker + +2007-09-03 08:42 Olivier Blin + + * draklive: don't check symlinks when looking for kernels (fix + matching vmlinuz-tmb-desktop586) + +2007-09-03 08:41 Olivier Blin + + * draklive: simplify + +2007-08-28 08:52 Olivier Blin + + * draklive: allow to copy per-fs files in mkinitrd + +2007-08-28 08:50 Olivier Blin + + * draklive: move mount actions in a storage_fs hash + +2007-08-28 08:49 Olivier Blin + + * .perl_checker: supplement perl_checker blacklist + +2007-08-28 08:41 Olivier Blin + + * draklive: run mkdevices to create nodes for existing devices + +2007-08-28 08:40 Olivier Blin + + * draklive: fix typo + +2007-08-28 08:39 Olivier Blin + + * draklive: move modules mount point in new root + +2007-08-28 08:39 Olivier Blin + + * draklive: cosmetics + +2007-08-28 08:38 Olivier Blin + + * draklive: remove more dirs + +2007-08-28 08:35 Olivier Blin + + * draklive: add a new is_loopback variable to better list selected + loopbacks + +2007-08-28 08:33 Olivier Blin + + * draklive: create less loop devices by default + +2007-08-28 08:23 Olivier Blin + + * draklive: allow to specify ro unionfs branches + +2007-08-28 08:19 Olivier Blin + + * draklive: make bootloader timeout configurable + +2007-08-28 08:16 Olivier Blin + + * draklive: fix unionfs branches list by removing trailing colon + (for unionfs2) + +2007-08-28 08:15 Olivier Blin + + * draklive: make boot and loopbacks read-only + +2007-08-28 08:15 Olivier Blin + + * draklive: mark hidden files hidden only + +2007-07-16 12:56 Olivier Blin + + * draklive: fix vfat check... + +2007-06-12 09:47 Olivier Blin + + * draklive: oops, I really meant vfat when stripping labels for + nash + +2007-06-11 15:26 Olivier Blin + + * draklive: add a get_media_source_for_nash functions that formats + ext2 labels accordingly + +2007-06-07 16:33 Olivier Blin + + * draklive: do not select modules directory together with loopbacks + +2007-06-07 14:43 Olivier Blin + + * draklive: hidden_files is a better name + +2007-06-07 14:42 Olivier Blin + + * draklive: hide more autorun files + +2007-06-07 09:47 Olivier Blin + + * draklive: remove multi squash union, better replaced with modules + +2007-06-07 09:47 Olivier Blin + + * draklive: use modules in volatile_squash_union mounts + +2007-06-07 09:41 Olivier Blin + + * draklive: fix extra files selection in cdrom master + +2007-06-05 18:57 Olivier Blin + + * draklive: perl_checker fixes + +2007-06-05 18:57 Olivier Blin + + * draklive: only run mattrib for vfat + +2007-06-05 18:55 Olivier Blin + + * draklive: also hide autorun.inf (yes, hardcode it) + +2007-06-05 18:54 Olivier Blin + + * draklive: hide all files begining with a dot on USB media + +2007-06-05 11:54 Olivier Blin + + * draklive: do not copy extra media files in boot images + +2007-06-05 07:17 Olivier Blin + + * draklive: add a glob wrapper that dotglob behavior and expand + only if needed + +2007-06-05 01:12 Olivier Blin + + * draklive: protect rm_rf from die + +2007-06-04 15:48 Olivier Blin + + * draklive: run mattrib with MTOOLS_SKIP_CHECK + +2007-06-04 15:48 Olivier Blin + + * draklive: create mtools_run_ + +2007-06-04 14:49 Olivier Blin + + * draklive: use glob_ to match dot files (since File::Glob does not + provide a dotglob option) + +2007-05-31 15:14 Olivier Blin + + * draklive: revert to initrd + pivot_root (or else unionfs panicks + in unionfs_file_revalidate when using unionctl in new root) + +2007-05-31 13:39 Olivier Blin + + * draklive: modify custom nfs syntax + +2007-05-31 13:38 Olivier Blin + + * draklive: oops, add back mountpoint + +2007-05-31 13:36 Olivier Blin + + * draklive: use modules source for squash_union + +2007-05-31 13:35 Olivier Blin + + * draklive: add modules source type + +2007-05-31 13:19 Olivier Blin + + * draklive: allow to get loopbacks list from a file + +2007-05-31 13:16 Olivier Blin + + * draklive: allow to delay mount of specific loopbacks + +2007-05-31 12:29 Olivier Blin + + * draklive: try to get interactive debug shell + +2007-05-31 12:08 Olivier Blin + + * draklive: allow to specify module options in + $live->{system}{module_options} + +2007-05-31 12:06 Olivier Blin + + * draklive: allow to use real glob patterns in + live->{system}{remove_files} + +2007-05-31 12:05 Olivier Blin + + * draklive: fix directory creation when dest is a dir in + copy_files_to + +2007-05-31 12:04 Olivier Blin + + * draklive: clean build files before creating new ones + +2007-05-29 16:39 Olivier Blin + + * draklive: pre-create devfsd compatibility loop devices (since + busybox is still built with devfsd support) + +2007-05-29 16:37 Olivier Blin + + * draklive: pre-create more loop devices + +2007-05-26 17:39 Olivier Blin + + * draklive: modify loopback modules syntax to be compatible with + file list expansion + +2007-05-26 17:38 Olivier Blin + + * draklive: copy resolv.conf in chroot for urpmi to work (and clean + it at end of post-install) + +2007-05-25 16:32 Olivier Blin + + * draklive: fix excluded loopback path + +2007-05-21 13:14 Olivier Blin + + * draklive: pass opts when formatting + +2007-05-21 13:13 Olivier Blin + + * draklive: fix typo... + +2007-05-21 13:12 Olivier Blin + + * draklive: make optional device usage more consistent + +2007-05-21 13:05 Olivier Blin + + * draklive: remove extra media support + +2007-05-21 13:02 Olivier Blin + + * draklive: fix some typos + +2007-05-21 13:02 Olivier Blin + + * draklive: remove optional media support + +2007-05-17 22:07 Olivier Blin + + * draklive: move new root mount point in /sysroot, so that live dir + can be properly removed from initramfs + +2007-05-16 22:07 Olivier Blin + + * draklive: remove more initrd relics + +2007-05-16 22:06 Olivier Blin + + * draklive: use busybox's mkdir since nash's "mkdir -p" does not + have the standard behavior + +2007-05-16 21:02 Olivier Blin + + * draklive: fix silly typo + +2007-05-16 20:48 Olivier Blin + + * draklive: allow to include loopback modules in master images by + defining modules sets in live->{packs} and selecting them from + live->{settings}{pack} + +2007-05-16 20:42 Olivier Blin + + * draklive: define /modules in prefix hash + +2007-05-16 20:40 Olivier Blin + + * draklive: make directory path prefixed by / (to make subpath + usage easier) + +2007-05-16 20:38 Olivier Blin + + * draklive: rename directory "location" field as "path" + +2007-05-16 20:31 Olivier Blin + + * draklive: allow to create loopback modules from + live->{loopbacks}{modules} (to be used for separate i18n + loopbacks) + +2007-05-16 20:25 Olivier Blin + + * draklive: archive excluded files list as loopback type defined by + live->{loopbacks}{exclude}{type}} instead of tarball + +2007-05-16 20:23 Olivier Blin + + * draklive: use hardlink_filtered to build excluded files tarball + +2007-05-16 20:18 Olivier Blin + + * draklive: add hardlink_filtered function to hardlink recursively + file list to a directory (using rsync --files-from --link-dest) + +2007-05-16 20:14 Olivier Blin + + * draklive: allow to use glob when expand file list + +2007-05-16 20:14 Olivier Blin + + * draklive: protect deref + +2007-05-16 20:13 Olivier Blin + + * draklive: rename live->{loopbacks}{exclude_files} as + live->{loopbacks}{exclude}{files} + +2007-05-16 20:12 Olivier Blin + + * draklive: transform list_excluded_files as more generic + expand_file_list + +2007-05-16 20:10 Olivier Blin + + * draklive: pass excluded files list in loopback directory hash to + allow refined exclusion + +2007-05-16 20:03 Olivier Blin + + * draklive: really set bootlogo variable (and simplify) + +2007-05-16 19:56 Olivier Blin + + * draklive: add extension for some loopback types + +2007-05-16 19:42 Olivier Blin + + * draklive: add list_selected_loopbacks() function + +2007-05-16 19:35 Olivier Blin + + * draklive: copy only selected loopbacks in master image + +2007-05-16 19:29 Olivier Blin + + * draklive: allow to build squashfs with prefix different from + chroot + +2007-05-16 19:26 Olivier Blin + + * draklive: differentiate squashfs source and prefix to be able to + build from sources != '/' + +2007-05-16 19:23 Olivier Blin + + * draklive: create loopbacks directory in indivual build functions + and not on top directory (they may have to create subdirs anyway) + +2007-05-16 19:20 Olivier Blin + + * draklive: really use $dir variable and not indefinite $_ + +2007-05-16 19:17 Olivier Blin + + * draklive: rename directory "source" field as "location" + +2007-05-16 19:01 Olivier Blin + + * draklive: use initramfs instead of initrd + +2007-05-16 18:57 Olivier Blin + + * draklive: rename create_initrd_linuxrc as create_initrd_scriptlet + +2007-05-02 11:56 Olivier Blin + + * draklive: pre-create tarballs directory + +2007-05-02 09:48 Olivier Blin + + * draklive: perl_checker fix + +2007-05-02 09:47 Olivier Blin + + * draklive: include media files on CD as well + +2007-05-02 09:46 Olivier Blin + + * draklive: move media files copy in post-install step + +2007-05-02 09:45 Olivier Blin + + * draklive: move umask restoration at end of post-install + +2007-05-02 09:23 Olivier Blin + + * ., .perl_checker, Makefile, NEWS, draklive, + draklive-copy-wizard.desktop: reimport draklive in SVN + diff --git a/trunk/Makefile b/trunk/Makefile new file mode 100644 index 0000000..f28b567 --- /dev/null +++ b/trunk/Makefile @@ -0,0 +1,18 @@ +NAME = draklive +VERSION = 0.6 + +check: + perl -cw $(NAME) + +clean: + find -name '*~' -exec rm {} \; + +.PHONY: ChangeLog log changelog + +log: ChangeLog + +changelog: ChangeLog + +ChangeLog: + svn2cl --accum --authors ../../soft/common/username.xml + rm -f *.bak diff --git a/trunk/NEWS b/trunk/NEWS new file mode 100644 index 0000000..ed5bb2c --- /dev/null +++ b/trunk/NEWS @@ -0,0 +1,291 @@ +0.6: + +* config +- use config and additional files from live->{settings}{config_root}, + defaulting to current directory, or to /etc/draklive if no config file + is present in current directory +- allow to configure files root directory with --config-root +- use /var/lib/draklive/build as default build dir and + /var/lib/draklive/chroot as default chroot dir + +* bootloader +- improve gfxboot support by copying files from both Mandriva and + system-chosen theme +- install gfxboot files in /isolinux on CD image +- gfxboot support for grub +- drop unused F1 binding when gfxboot is used +- use livecd mode in gfxboot (thanks Pixel \o/) +- copy gfxmenu instead of moving it (#31879) +- enable gfxboot only if live->{system}{gfxboot} is set +- remove extra media support +- remove optional media support +- allow to specify bootloader timeout in + live->{media}{bootloader_timeout} + +* initrd +- rename debug command line option as initrd_debug not to conflict + with kernel option +- handle module names with underscores, using new module dependencies + functions from list_modules (such as modname2path) +- add and use probe-modules helper from stage1 to load modules +- drop useless ide-generic hack, probe-modules will load it as + fallback only +- drop usb-storage hack, done by probe-modules +- do not explicitely load storage module in linuxrc + (done by probe-modules helper) +- add modules.dep, modules.description, modules.alias, ldetect-lst + tables, pci.ids and fallback-modules.alias for probe-modules +- add .ko.gz symlinks to .ko files (used by probe-modules, gzopen + transparently opens uncompressed files) +- load dependencies of extra modules only +- install modules in /modules instead of /lib in initrd +- add partition "loop" type to be able to mount partitions in initrd +- create empty fstab and mtab for fsck programs not to complain +- run fsck when mounting ext2 partition +- move mount actions in a storage_fs hash +- be less verbose when compressing modules +- be less verbose when printing skipped modules +- allow to exclude modules from initrd in + live->{system}{exclude_modules} +- use busybox's mkdir since nash's "mkdir -p" does not have the + standard behavior +- pre-create devfsd compatibility loop devices (since busybox is still + built with devfsd support) +- pre-create more loop devices +- allow to specify module options in live->{system}{module_options} +- allow to delay mount of specific loopbacks +- do not add disk/raw category for cdrom media +- allow to specify ro unionfs branches +- fix unionfs branches list by removing trailing colon (for unionfs2) +- don't check symlinks when looking for kernels + (fix matching vmlinuz-tmb-desktop586) +- run mkdevices to create nodes for existing devices +- move modules mount point in new root +- do not mess / tree if no live modules are used +- allow to copy per-fs files in initrd +- copy additional modules after plain modules, so that they can overwrite them +- do not uppercase label anymore for vfat partitions, nash with vol_id + seems to handle it fine + +* chroot +- be less verbose when running patch dry runs +- don't hardcode list of files to be removed in chroot, they must be + listed in live->{system}{remove_files} now (dbus/modprobe/udev files + for example) +- allow to use real glob patterns in live->{system}{remove_files} +- copy resolv.conf in chroot for urpmi to work (and clean it at end of + post-install) +- do not run service_harddrake stop anymore, it does not exist in 2008.0 +- run postInstall after copying packages + +* loopbacks +- allow to create loopback modules from live->{loopbacks}{modules} + (to be used for separate i18n loopbacks) +- allow to include loopback modules in master images by defining + modules sets in live->{packs} and selecting them from live->{settings}{pack} +- write a loopbacks/excluded.tar.bz2 tarball containing files that + have been excluded from other loopbacks +- write excluded.tar.bz2 in /tarballs, so that it does not get + included in final ISO image +- remove multi squash union, better replaced with modules +- add "modules" source type, and use it for squash_union and + volatile_squash_union mounts (which are now custom mounts) +- allow to exclude files in loopbacks with live->{system}{exclude_files} +- pass -no-progress option to mksquashfs, draklive handles its own + progress bar +- fix division by zero in progress code /o\ + +* master +- drop some genisoimage verbosity +- do not allow to create USB master, it is not correctly implemented yet +- name images after live name instead of live.iso/live.img +- compute md5sum and sha1sum for ISO images + (in addition to the mkcd checksum) +- do not copy extra media files in boot images +- fix extra files selection in cdrom master +- compute media dirs on the fly instead of hardcoding at start + (to support CD-Rom boot images with non-hidden media directories) + +* record +- make device option used for boot-only as well +- do not set label if boot-only +- use bootloader setting from optional media if specified +- umount usb device if loopback copy fails +- unlink list file if loopbacks copy fails as well +- mark as read-only boot and loopbacks on vfat USB media +- mark as hidden files beginning with a dot, autorun files, boot and + loopbacks read-only on vfat USB media +- only run mattrib for vfat +- run mattrib with MTOOLS_SKIP_CHECK (like mlabel) + +* misc +- print usage if no option is specified +- log files copy +- add newlines in die messages, not to trigger the infamous drakbug +- make warn messages less verbose +- use live->{settings}{name/product/media/desktop/region} in + build/chroot names +- write a packages.lst file containing the installed packages list +- add a glob wrapper with dotglob behavior, and expand only if needed +- custom NFS is now in custom{fs} instead of custom{mounts} +- reorder init so that arch is set before calling get_builddir and + get_system_root (#35105) +- chomp arch returned by rpm to fix directory names when arch is not + in settings.cfg (#35105) + + +0.5: + +* chroot +- use glob() instead of glob_() to support again non-wildcarded + filenames in live->{system}{files} and live->{media}{files} + +* loopbacks +- use noappend option in mksquashfs to overwrite previous loopbacks + + +0.4: + +* config +- make loopback fs configurable +- allow to define fallbacks directories + (will be mounted only if the mountpoint is not already mounted) +- XiB are now used instead of XB when specified fs size is expanded +- allow to specify min_size in custom squash_union helper +- introduce "plain" mount type (instead of empty mount type) +- split build/media prefixes +- allow to specify additional mount options for storage media + (such as umask=...) +- add live-{system}{hide_media_dirs} and live->{media}{files} + (see details in "master" section) +- allow to use glob patterns in live->{system}{files} and + live->{media}{files} + +* bootloader +- use grub-install and grub files from chroot, and make it use custom + boot directory + +* chroot +- create draklive-resize configuration file and do not build loopback + fs if min_size is specified + +* loopbacks +- use tmpfs as fallback if the ext2 loopback can not be mounted +- repair loopbacks non-interactively + +* master +- set MTOOLS_SKIP_CHECK to 1 when running mlabel (some USB pens are + misdetected by mtools) +- allow to hide media dirs by adding '.' in prefixes when + live-{system}{hide_media_dirs} is set +- use mattrib to hide media dirs from windows +- install grub on both master device and partition (fix boot on buggy + Intel BIOS) +- allow to copy files directly to the final image/media using + live->{media}{files} (implemented for USB only) + + +0.3: + +* interface +- make --boot-only copy only boot files (instead of using boot media) +- add --boot-image option to create a boot image for the selected + method +- make string command line options mandatory +- add --dump-config option +- allow to load settings from command line using --define options +- drop --region option (use --define region=) + +* config +- make the system.loop size configurable +- handle GigaBytes for loopback sizes +- rename live->{post} as live->{system}{initrd_post} +- export region as DRAKLIVE_REGION for auto_install +- allow to provide stage2 updates with live->{system}{stage2_updates} +- allow to make the initrd reread a device partition table using + live->{media}{rereadpt} + (useful if USB partition changes after some PIN input) +- allow to customize bootloader title in live->{media}{title} +- default to config/live.cfg config path +- allow to load live->{settings} from a file and default to + config/settings.cfg +- use region/repository/root/workdir variables from live->{settings} +- rename "workdir" setting as "builddir" +- rename "root" setting as "chroot" +- automatically add arch to specified repository + +* chroot installation +- remove /etc/dbus-1/machine-id +- really don't apply patches if they look already applied +- workaround buggy installation of directories that are not owned by + any packages (using 022 umask) +- do not try to install packages if the additional media is only aimed + at adding extra sources (warly) +- remove urpmi media before starting additional media installation +- install additional media just before they are required +- get drakx-in-chroot from the repository +- run install/rpm commands for targetted architecture + +* bootloader +- unlink grub device map so that grub rechecks the map +- die when grub or rsync fail +- don't install bootloader on non-block device +- partial gfxboot support (#26430) +- build bootloader files for boot methods listed in + media->{extra_boot} +- display help file if not bootlogo is available +- add missing newlines in syslinux/grub configuration files + +* loopbacks +- don't use sort file if it doesn't exist +- adapt mksquashfs output parsing to latest release +- die if mksquashfs fails +- don't pass float to mkfs.vfat +- run fsck on rw loopback files before mounting them +- don't reserv blocks on ext2/3 filesystems, we don't create root fs +- compute an approximative size for USB master images + +* master/record +- don't use a timeout for rsync copy (should fix USB mastering) +- pass -F option to mke2fs only for non-block devices +- use genisoimage +- die if genisoimage can not be run +- use wodim instead of cdrecord + +* copy wizard +- don't create a master for onthefly USB recording +- don't show Previous button in copy wizard (#25868) +- allow to select a master image in the copy wizard +- update progress during live USB recording +- add an end step +- warn if an error occurs during live copy +- use 788 as default vga mode + +* misc: +- don't add splash/vga settings on cmdline if no vga mode is defined +- umount any nfs volume in /mnt (warly) +- use the 'never' timeout value instead of hardcoded values +- automatically select storage type if only one is available +- define directory mounts in their mount order, and reverse the order + when mounting unionfs +- guess arch using rpm _target_cpu macro +- add arch in chroot and builddir paths +- include desktop in builddir/chroot name if specified + + +0.2: + +- remove modprobe.preload.d files +- remove /etc/udev/rules.d/61-*_config.rules files +- use patch batch mode (-t) not to apply already applied patches and die if a patch can't be applied +- run shell in initrd when the "debug" option is on cmdline +- allow to add additionnal boot entries +- umount filesystem which could stay mounted in the chroot before cleaning (warly) +- default splash image is now splash.xpm.gz for grub +- rename draklive.desktop as draklive-copy-wizard.desktop + + +0.1: + +- use system's mount to mount NFS loopbacks +- sort squashfs loopback if a config/distrib.sort file is present diff --git a/trunk/draklive b/trunk/draklive new file mode 100755 index 0000000..3be76a3 --- /dev/null +++ b/trunk/draklive @@ -0,0 +1,1736 @@ +#!/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 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; + +my $dir_distrib_sqfs = { + mountpoint => '/distrib', + type => 'squashfs', + path => '/distrib.sqfs', + # 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 => '/live-modules', +}; + +# 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, + ], + }, + }, +); + +# 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.loop' + }, + { + mountpoint => '/system', + type => 'tmpfs', + fallback => 1, + }, + ], + }; + }, + }, +); + +my %storage = ( + cdrom => { + modules => 'bus/usb disk/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_usb_master, + format => \&format_usb_device, + record => \&record_usb_master, + }, +); + +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'), + ); + '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) = @_; + if_(get_media_setting($live->{media}, 'fs') eq 'vfat', 'nls_cp437'), #- default FAT codepage + 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 = ( + squashfs => { + read_only => 1, + is_loopback => 1, + modules => [ qw(loop squashfs) ], + extension => '.sqfs', + build => sub { + my ($live, $dir) = @_; + my $dest = get_builddir($live) . $live->{prefix}{build}{loopbacks} . $dir->{path}; + mkdir_p(dirname($dest)); + my $root = $dir->{root} || get_system_root($live); + 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', + '-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}", + "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/:\$// > $dir->{list}'"; + }, + }, + loopfs => { + is_loopback => 1, + modules => [], + extension => '.loop', + build => sub { + my ($live, $dir) = @_; + my $dest = get_builddir($live) . $live->{prefix}{build}{loopbacks} . $dir->{path}; + mkdir_p(dirname($dest)); + device_allocate_fs($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}", + qq(sh -c "$fsck -a || $fsck -y"), + "mount -t $dir->{fs} $dir->{loop} $live->{prefix}{live}{mnt}$dir->{mountpoint}", + ); + }, + }, + plain => {}, + 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'), + "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 none $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 $_->{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 directory_usage { first(split /\s/, `du -sb $_[0]`) } + +#- expand only if the pattern contains '*' +#- and matches dot characters (like shell dotglob) +sub glob__ { + my ($pattern) = @_; + $pattern =~ /\*/ ? glob_($pattern) : $pattern; +} + +sub run_ { + my $options = ref $_[0] eq 'HASH' ? shift @_ : {}; + my @cmd = @_; + $options->{timeout} ||= 'never'; + my $setarch = delete $options->{setarch}; + unshift @cmd, 'setarch', $setarch if $setarch; + print STDERR "running " . (exists $options->{root} && "(in chroot) ") . join(' ', @cmd) . "\n"; + run_program::raw($options, @cmd); +} + +sub run_foreach { + my ($foreach, @command) = @_; + print STDERR "running " . join(' ', @command) . "\n"; + open(my $OUTPUT, '-|', @command); + local $_; #- avoid outside $_ to be overwritten + $foreach->() while <$OUTPUT>; + close $OUTPUT; +} + +sub mtools_run_ { + local $ENV{MTOOLS_SKIP_CHECK} = 1; + &run_; +} + +sub get_live_name { + my ($live) = @_; + join('-', grep { $_ } @{$live->{settings}}{qw(name product desktop region media arch)}); +} + +sub get_builddir { + my ($live) = @_; + $live->{settings}{builddir} . '/' . get_live_name($live); +} + +sub get_system_root { + my ($live) = @_; + $live->{settings}{chroot} . '/' . get_live_name($live); +} + +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 find_kernel { + my ($live) = @_; + my $kernel = $live->{system}{kernel}; + unless ($kernel) { + my $vmlinuz = readlink(get_system_root($live) . '/boot/vmlinuz'); + $vmlinuz ||= find { -e $_ && ! -l $_ } glob_(get_system_root($live) . '/boot/vmlinuz-*'); + ($kernel) = $vmlinuz =~ /\bvmlinuz-(.*)$/ or die "no kernel can be found\n"; + } + $kernel; +} + +sub create_initrd { + my ($live) = @_; + foreach ($live->{media}) { + create_initrd_for_media($live, $_); + } + cp_f(get_builddir($live) . $live->{prefix}{build}{boot} . get_initrd_path($live->{media}), + $live->{copy_initrd}) if $live->{copy_initrd}; +} +sub inst_initrd_bin { + my ($root, $initrd_tree, $bin) = @_; + cp_f($root . $bin, $initrd_tree . '/bin/'); + foreach my $file (`chroot $root ldd $bin | awk '/\\// {if(\$2 == "=>") {print \$3} else {print \$1}}'`) { + chomp $file; + 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 create_initrd_for_media { + my ($live, $media) = @_; + + my $initrd_tree = get_builddir($live) . $live->{prefix}{build}{initrd} . '/' . $media->{storage}; + rm_rf($initrd_tree) if -e $initrd_tree; + + mkdir_p($initrd_tree . $_) foreach + qw(/bin /dev /modules /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(get_system_root($live), $initrd_tree, '/sbin/nash'); + inst_initrd_bin(get_system_root($live), $initrd_tree, '/usr/lib/drakx-installer-binaries/probe-modules'); + inst_initrd_bin(get_system_root($live), $initrd_tree, '/sbin/blockdev') + if get_media_setting($media, 'rereadpt'); + inst_initrd_bin(get_system_root($live), $initrd_tree, '/usr/bin/strace') + if $live->{debug}; + + #- 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(get_system_root($live), $initrd_tree, $busybox); + my $busybox_rooted = get_system_root($live) . $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(get_system_root($live), $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; + require devices; + devices::make($initrd_tree . "/dev/$_") foreach + if_($rrpt_dev, $rrpt_dev), + qw(console initrd null ram systty), + (map { "tty$_" } 0..5), + (map { "loop$_" } 0 .. $loop_nb); + #- 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 = find_kernel($live); + print "using kernel $kernel\n"; + my $kernel_root = "/lib/modules/" . $kernel; + list_modules::load_dependencies(get_system_root($live) . $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 @missing = sort(difference2($extra_missing, \@additional_modules)); + @missing and die "missing mandatory modules:\n" . join("\n", @missing, ''); + + my @additional_modules = map { m!([^/]+)\.ko! } @{$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 @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); + my $f = list_modules::modname2filename($m); + run_program::run('gzip', '>', $initrd_tree . "/modules/$f.ko", '-dc', get_system_root($live) . $full); + #- gzopen transparently opens uncompressed files + symlink($f . ".ko", $initrd_tree . "/modules/$f.ko.gz"); + } + cp_f((map { $live->{settings}{config_root} . '/' . $_ } @{$live->{system}{additional_modules}}), $initrd_tree . "/modules/"); + + cp_f(get_system_root($live) . $kernel_root . "/" . $_, $initrd_tree . "/modules/") + foreach qw(modules.alias modules.dep modules.description); + substInFile { s![^ ]*/!!g; s!\.ko!!g; s!\.gz!!g } $initrd_tree . "/modules/modules.dep"; + + mkdir_p($initrd_tree . "/lib/module-init-tools"); + cp_f(get_system_root($live) . "/lib/module-init-tools/ldetect-lst-modules.alias", $initrd_tree . "/lib/module-init-tools"); + mkdir_p($initrd_tree . "/usr/share/ldetect-lst"); + cp_f(get_system_root($live) . "/usr/share/pci.ids", $initrd_tree . "/usr/share"); + cp_f(get_system_root($live) . "/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(get_builddir($live) . $live->{prefix}{build}{initrd} . '/' . $media->{storage} . '/linuxrc', 0755, + join("\n", + "#!/bin/nash", + #- required for labels and ps + "mount -t proc none /proc", + #- required for cdrom labels + "mount -t sysfs none /sys", + (map { join(" ", "insmod", "/modules/$_.ko", 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", + $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) : ()), + "echo 0x0100 > /proc/sys/kernel/real-root-dev", + "umount /sys", + "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 mkdir -p /live/$i; mount -n --move $i /live/$i; rmdir $i; done; rmdir /initrd/live'), + if_($live->{system}{initrd_post}, deref_array($live->{system}{initrd_post})), + "")); +} + +sub compress_initrd_tree { + my ($live, $media) = @_; + + my $initrd_tree = get_builddir($live) . $live->{prefix}{build}{initrd} . '/' . $media->{storage}; + my $size = run_program::get_stdout("du -ks $initrd_tree | awk '{print \$1}'") + 250; + my $inodes = run_program::get_stdout("find $initrd_tree | wc -l") + 1250; + $size = int($size + $inodes / 10) + 1; #- 10 inodes needs 1K + my $initrd = get_builddir($live) . $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=$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"); + run_('umount', $live->{mnt}); + 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 = get_builddir($live) . $live->{prefix}{build}{boot} . get_initrd_path($media); + my $tmp_initrd = '/tmp/initrd.gz'; + cp_f($initrd, get_system_root($live) . $tmp_initrd); + { + local $::prefix = get_system_root($live); + bootloader::add_boot_splash($tmp_initrd, $live->{system}{vga_mode}); + } + cp_f(get_system_root($live) . $tmp_initrd, $initrd); + unlink(get_system_root($live) . $tmp_initrd); + } +} + +sub get_default_append { + my ($live) = @_; + join(' ', + 'fastboot', #- needed to avoid fsck + if_($live->{system}{vga_mode}, + 'splash=silent', + 'vga=' . $live->{system}{vga_mode}), + ); +} + +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); + #- 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 get_builddir($live) . $live->{prefix}{build}{boot} . '/syslinux/bootlogo'; + my $timeout = get_bootloader_timeout($live) * 10; + join("\n", + "default $live->{media}{title}", + "prompt 1", + "timeout $timeout", + $has_bootlogo ? "gfxboot $bootlogo" : "display $help", + (map { + my ($name, $cmdline) = @$_; + $name =~ s/\s/_/g; + "label " . ($name || $live->{media}{title}), + " kernel $kernel", + " append initrd=$initrd $append $cmdline"; + } group_by2('' => '', @{$live->{system}{boot_entries}})), + "", + ); +} + +sub build_grub_cfg { + my ($live, $media, $device) = @_; + #- FIXME: use the bootloader module from drakx + my ($part_nb) = $device =~ /(\d+)$/; + my $grub_part = "(hd0" . (defined $part_nb ? "," . ($part_nb-1) : "") . ")"; + my $boot = get_media_prefix($live, 'boot'); #- FIXME handle boot media + join("\n", + "timeout " . get_bootloader_timeout($live), + if_($live->{system}{gfxboot}, "gfxmenu $grub_part" . $boot . "/gfxmenu"), + "default 0", + (map { + my ($name, $cmdline) = @$_; + "title " . $live->{media}{title} . if_($name, " ($name)"), + "kernel $grub_part" . $boot . "/vmlinuz " . get_default_append($live) . if_($cmdline, " $cmdline"), + "initrd " . $boot . get_initrd_path($media); + } group_by2('' => '', @{$live->{system}{boot_entries}})), + "", + ); +} + +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 = get_builddir($live) . $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(':', uniq((ref $live->{regions} ? @{$live->{regions}{$live->{settings}{region}}} : ()), @{$live->{system}{langs_always}})); + $ENV{DRAKLIVE_REGION} = $live->{settings}{region}; + run_({ setarch => $live->{settings}{arch} }, + 'perl', $drakx_in_chroot, + $repository, + get_system_root($live), + 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(get_system_root($live) . '/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_perm) = @$_; + $dest = $root . '/' . $dest; + mkdir_p($dest =~ m|/$| ? $dest : dirname($dest)); + my @sources = glob__($live->{settings}{config_root} . '/' . $source); + print STDERR "copying @sources to $dest\n"; + cp_f(@sources, $dest); + 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", get_system_root($live) . "/etc"); + + #- remove previous draklive leftovers if needed + run_({ root => get_system_root($live) }, 'urpmi.removemedia', '-a'); + + foreach (@{$live->{system}{additional_media}}) { + run_({ root => get_system_root($live) }, 'urpmi.addmedia', if_($_->{distrib}, '--distrib'), $_->{name}, $_->{path}) + or die "unable to add media from $_->{path}\n"; + @{$_->{packages}} or next; + run_({ root => get_system_root($live), 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(get_system_root($live) . $rpm_tmp_dir); + cp_f((map { $live->{settings}{config_root} . '/' . $_ } @{$live->{system}{rpms}}), get_system_root($live) . $rpm_tmp_dir); + run_({ root => get_system_root($live), 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(get_system_root($live) . $rpm_tmp_dir); + } + + #- remove urpmi media added by drakx-in-chroot and additional media, they're unusable + run_({ root => get_system_root($live) }, 'urpmi.removemedia', '-a'); + + my $erase = join(' ', @{$live->{system}{erase_rpms}}); + run_({ root => get_system_root($live), setarch => $live->{settings}{arch} }, + 'sh', '-c', "rpm -qa $erase | xargs rpm -e ") if $erase; + + run_({ root => get_system_root($live) }, '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 => get_system_root($live) }, + 'perl', '-MStorable', '-e', qq(Storable::store({ UNKNOWN => {} }, '/etc/sysconfig/harddrake2/previous_hw'))); + + #- remove some build-machine specific configuration + substInFile { undef $_ if /^[^#]/ } get_system_root($live) . $_ + 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(get_system_root($live) . $_) } @{$live->{system}{remove_files} || []}; + + output_with_perm(get_system_root($live) . '/etc/fstab', 0644, "none / unionfs rw 0 0\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/ } get_system_root($live) . '/etc/sysconfig/init'; + + configure_draklive_resize($live); + + if ($live->{system}{preselect_kdm_user}) { + #- preselect specified user in kdm + my $kdm_cfg = get_system_root($live) . '/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', get_system_root($live), '-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}, get_system_root($live)); + + eval { rm_rf(get_builddir($live) . $live->{prefix}{build}{files}) }; + mkdir_p(get_builddir($live) . $live->{prefix}{build}{files}); + if ($live->{media}{files}) { + copy_files_to($live, $live->{media}{files}, get_builddir($live) . $live->{prefix}{build}{files}); + } + remove_files_from($live->{media}{remove_files}, get_builddir($live) . $live->{prefix}{build}{files}); + + run_({ setarch => $live->{settings}{arch} }, + "chroot", get_system_root($live), "bash", "-c", $live->{system}{postInstall}) if $live->{system}{postInstall}; + + run_("chroot " . get_system_root($live) . " rpm -qa | sort > " . get_builddir($live) . "/packages.lst"); + + umask $previous_umask; +} + +sub umount_external_filesystem { + my ($live) = @_; + my $mnt = get_system_root($live) . "/mnt/"; + eval { fs::mount::umount("$mnt/$_") } foreach all($mnt); +} + +sub expand_file_list { + my ($live, @files) = @_; + map { + $_->{path} ? + $_->{path} : + chomp_(cat_(glob(($_->{rooted} && get_system_root($live)) . $_->{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} && get_system_root($live)) . $_->{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 = get_system_root($live) . "/tmp/draklive/loop/$module->{name}"; + eval { rm_rf($copy_tree) }; + hardlink_filtered(get_system_root($live), $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 = get_system_root($live) . "/tmp/draklive/excluded/all"; + eval { rm_rf($excluded_tree) }; + hardlink_filtered(get_system_root($live), $excluded_tree, \@excluded_files); + + foreach my $module (list_loopback_modules($live)) { + my $copy_tree = get_system_root($live) . "/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} : () } @{$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=' . substr($label, 0, 11) : + get_media_setting($media, 'source'); +} + +sub prepare_bootloader { + my ($live) = @_; + create_initrd($live); + cp_f(get_system_root($live) . '/boot/vmlinuz-' . find_kernel($live), get_builddir($live) . $live->{prefix}{build}{boot} . '/vmlinuz'); + require bootsplash; + my $theme = do { + local $::prefix = get_system_root($live); + bootsplash::themes_read_sysconfig('800x600'); + }; + my $syslinux_dir = get_builddir($live) . $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_(get_system_root($live) . "/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_(get_system_root($live) . "/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"); + + run_({ root => get_system_root($live) }, '/usr/sbin/grub-gfxmenu', '--update-gfxmenu'); + cp_f(get_system_root($live) . '/boot/gfxmenu', get_builddir($live) . $live->{prefix}{build}{boot}); + + output(get_builddir($live) . $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 $media ($live->{media}) { + foreach my $boot ('', @{$media->{extra_boot}}) { + my $opts = { boot => $boot }; + output(get_builddir($live) . $live->{prefix}{build}{boot} . get_syslinux_path($media, $opts), + build_syslinux_cfg($live, $media, $opts)); + } + } +} + +sub device_allocate_fs { + 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') { + 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) = @_; + get_builddir($live) . $live->{prefix}{build}{images} . '/' . get_live_name($live) . ($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)); + } + run_('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=' . get_builddir($live) . $live->{prefix}{build}{boot} . '/syslinux', + 'isolinux/isolinux.bin=/usr/lib/syslinux/isolinux.bin', + 'isolinux/isolinux.cfg=' . get_builddir($live) . $live->{prefix}{build}{boot} . get_syslinux_path($live->{media}, $opts), + get_media_prefix($live, 'boot', $opts->{boot}) . '=' . get_builddir($live) . $live->{prefix}{build}{boot}, + if_(!$opts->{boot_only}, + (map { + get_media_prefix($live, 'loopbacks', $opts->{boot}) . $_ . + '=' . + get_builddir($live) . $live->{prefix}{build}{loopbacks} . $_; + } list_selected_loopbacks($live)), + if_($live->{media}{files}, + map { + $_ . '=' . get_builddir($live) . $live->{prefix}{build}{files} . '/' . $_; + } all(get_builddir($live) . $live->{prefix}{build}{files}) + ), + ), + ) or die "unable to run genisoimage\n"; + if ($dest) { + my $dir = dirname($dest); + my $filename = basename($dest); + run_('mkcd', '--addmd5', $dest); + run_({ chdir => $dir }, 'md5sum', '>', $dest . '.md5', $filename); + run_({ chdir => $dir }, 'sha1sum', '>', $dest . '.sha1', $filename); + } +} + +sub get_usb_master_path { + my ($live) = @_; + get_builddir($live) . $live->{prefix}{build}{images} . '/' . get_live_name($live) . '.img'; +} + +sub create_usb_master { + my ($live, $opts) = @_; + my $dest = get_usb_master_path($live); + mkdir_p(dirname($dest)); + #- dumb guess, a bit too large, and valid for FAT only + my $size = directory_usage(get_builddir($live) . $live->{prefix}{build}{loopbacks}) + + directory_usage(get_builddir($live) . $live->{prefix}{build}{boot}); + device_allocate_fs($dest, $size); + device_mkfs($dest, get_media_setting($live->{media}, 'fs')) + or die "unable to format $dest\n"; + local $opts->{device} = $dest; + record_usb_master($live, $opts); +} + +#- $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 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"; + my $bootloader = get_boot_setting($media, 'bootloader', $opts); + member($bootloader, 'grub', 'syslinux') or die "no bootloader defined in media configuration\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(get_builddir($live) . $live->{prefix}{build}{boot}, $live->{mnt} . $media_boot); + + my $grub_device_map = $live->{mnt} . $media_boot . "/grub/device.map"; + if ($bootloader eq 'syslinux') { + cp_f(get_builddir($live) . $_, $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(get_builddir($live) . $live->{prefix}{build}{boot} . '/gfxmenu', $live->{mnt} . $media_boot); + output_p($live->{mnt} . $media_boot . '/grub/menu.lst', build_grub_cfg($live, $media, $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 $local_grub_install = get_builddir($live) . $live->{prefix}{build}{scripts} . '/grub-install'; + mkdir_p(dirname($local_grub_install)); + cp_f(get_system_root($live) . '/sbin/grub-install', $local_grub_install); + my $root = get_system_root($live); + 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; + + my $master_device = $device; + $master_device =~ s/(\d+)$//; + foreach ($master_device, $device) { + run_($local_grub_install, '--root-directory=' . $live->{mnt}, '--no-floppy', $_) + or die "unable to run grub on $device\n"; + } + unlink $grub_device_map; + } + } else { + warn "not running $bootloader on non block device $device\n"; + } + + do { + my $loopbacks_source = get_builddir($live) . $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_(get_builddir($live) . $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') { + mtools_run_('mattrib', '+h', '-i', $device, '::' . $_) foreach @hidden_files; + mtools_run_('mattrib', '+r', '+s', '-/', '-i', $device, '::' . $_) + foreach $media_boot, $media_loopbacks; + } +} + +#- $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 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', + images => '/images', + 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"; + + mkdir_p(get_builddir($live)); + mkdir_p(get_system_root($live)); + $live->{mnt} ||= get_builddir($live) . "/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 $_ } get_builddir($live), get_system_root($live); +} + +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 => 'format', do => \&format_device }, + { name => 'record', do => \&record_master }, + { name => 'record-onthefly', do => \&record_onthefly }, +); +my @all = qw(install boot loop master); + +require_root_capability(); + +my (%live, %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{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{all_regions} = 1 }, + "config-root=s" => \$config_root, + "config=s" => \$config_path, + "settings=s" => \$settings_path, + "define=s" => \%{$live{settings}}, +) or Pod::Usage::pod2usage(); + +require standalone; +if ($live{copy_wizard}) { + copy_wizard(\%live); +} else { + every { !$_->{to_run} } @actions and Pod::Usage::pod2usage(); + read_config(\%live, $config_root, $config_path, $settings_path); + check_config(\%live); + complete_config(\%live); + foreach my $region ($live{all_regions} ? sort(keys %{$live{regions}}) : $live{settings}{region}) { + $region and print qq(=== proceeding with region "$region"\n); + $live{settings}{region} = $region; + foreach (grep { $_->{to_run} } @actions) { + print qq(* entering step "$_->{name}"\n); + $_->{do}->(\%live, \%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 + + --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 diff --git a/trunk/draklive-copy-wizard.desktop b/trunk/draklive-copy-wizard.desktop new file mode 100644 index 0000000..2c73b47 --- /dev/null +++ b/trunk/draklive-copy-wizard.desktop @@ -0,0 +1,7 @@ +[Desktop Entry] +Encoding=UTF-8 +Exec=/usr/sbin/draklive --copy-wizard +Icon=MandrivaOne-copy-icon +Terminal=false +Type=Application +Name=Live copy diff --git a/trunk/kmod/readlog.c b/trunk/kmod/readlog.c new file mode 100644 index 0000000..8a1c5ad --- /dev/null +++ b/trunk/kmod/readlog.c @@ -0,0 +1,230 @@ +#include +#include + +#include +#include +#include +#include +#include +#include + +#include + +#include +#include +#include + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Olivier Blin "); + +struct opened_file { + struct list_head s_list; + char *name; +}; + +unsigned long **sys_call_table; +static struct dentry *dbg_dir; +static struct dentry *dbg_file; +struct opened_file bp; +DECLARE_MUTEX(files_mutex); + +ssize_t (*read)(int f, const void *buf, size_t n); +long (*open)(const char __user *filename, int flags, int mode); + +static void *profile_seq_start(struct seq_file *file, loff_t *pos) +{ + struct list_head *p; + loff_t l = *pos; + down(&files_mutex); + list_for_each(p, &bp.s_list) + if (!l--) + return list_entry(p, struct opened_file, s_list); + return NULL; +} + +static void *profile_seq_next(struct seq_file *file, void *v, loff_t *pos) +{ + struct list_head *p = ((struct opened_file *)v)->s_list.next; + (*pos)++; + return p == &bp.s_list ? NULL : list_entry(p, struct opened_file, s_list); +} + +static void profile_seq_stop(struct seq_file *file, void *v) +{ + up(&files_mutex); +} + +static int profile_seq_show(struct seq_file *file, void *v) +{ + seq_printf(file, ((struct opened_file *)v)->name); + seq_putc(file, '\n'); + return 0; +} + +static struct seq_operations profile_seq_ops = { + .start = profile_seq_start, + .next = profile_seq_next, + .stop = profile_seq_stop, + .show = profile_seq_show, +}; + +static int profile_open(struct inode *inode, struct file *file) +{ + return seq_open(file, &profile_seq_ops); +} + +static struct file_operations profile_fops = { + .owner = THIS_MODULE, + .open = profile_open, + .read = seq_read, + .llseek = seq_lseek, + .release = seq_release +}; + +/* Borrowed from + * http://downloads.securityfocus.com/downloads/scprint.tar.gz + * http://www.gnome.org/~lcolitti/gnome-startup/linux-iolog/readlog.c + * http://kerneltrap.org/node/5793 + */ +unsigned long **find_sys_call_table(void) { + unsigned long *p; + + for (p = (unsigned long *)((init_mm.end_code + 4) & 0xfffffffc); + p < (unsigned long *)init_mm.end_data; p++) { + if (p[__NR_close] == (unsigned long) sys_close){ + return (unsigned long **) p; + } + } + + return NULL; +} + +void addfilename(const char *filename) { + struct opened_file *f; + + f = kmalloc(sizeof(struct opened_file), GFP_KERNEL); + if (f == NULL) + return; + + f->name = kmalloc(strlen(filename) + 1, GFP_KERNEL); + if (f->name == NULL) + return; + strcpy(f->name, filename); + + INIT_LIST_HEAD(&f->s_list); + + printk(KERN_INFO "locking while adding: %s\n", filename); + + down(&files_mutex); + list_add_tail(&f->s_list, &bp.s_list); + up(&files_mutex); + + printk(KERN_INFO "unlocking after adding: %s\n", filename); +} + +long loggingopen(const char __user *filename, int flags, int mode) { + struct file *f = NULL; + long fd; + + fd = open(filename, flags, mode); + + printk(KERN_INFO "opening fd %ld for %s\n", fd, filename); + + if(fd > 0 && current) { + f = current->files->fd_array[fd]; + if(f && f->f_dentry && f->f_vfsmnt) { + char tmpname[PATH_MAX]; + char *name = d_path(f->f_dentry, f->f_vfsmnt, tmpname, sizeof(tmpname) - 1); + if (name) + addfilename(name); + } + } + + return fd; +} + + +ssize_t loggingread(int fd, const void *buf, size_t n) { + struct file *f = NULL; + char *name = NULL; + loff_t offset = 0; + struct timeval tv; + char tmpname[256]; + + do_gettimeofday(&tv); + + if(current) + f = current->files->fd_array[fd]; + + printk(KERN_DEBUG "READ: f: %p\n", f); + + + if(f) + offset = f->f_pos; + + if (f) + printk(KERN_DEBUG "READ: d_entry: %p, vfsmnt: %p\n", f->f_dentry, f->f_vfsmnt); + + if(f && f->f_dentry && f->f_vfsmnt) { + printk(KERN_DEBUG "READ: d_path\n"); + name = d_path(f->f_dentry, f->f_vfsmnt, tmpname, sizeof(tmpname) - 1); + if (name) + addfilename(name); + } + + printk(KERN_DEBUG "READ: %lu.%lu (%s/%d) %Lu %s\n", + tv.tv_sec, tv.tv_usec, + current ? current->comm : "?", + current ? current->pid : 0, + offset, + name ? name : "" + ); + + return read(fd, buf, n); +} + +static int __init readlog_init(void) { + sys_call_table = find_sys_call_table(); + /* compare with grep sys_call_table /boot/System.map */ + printk(KERN_INFO "Found sys_call_table at %p\n", sys_call_table); + + open = (void *) (sys_call_table[__NR_open]); + sys_call_table[__NR_open] = (void *) loggingopen; + + read = (void *) (sys_call_table[__NR_read]); + /* sys_call_table[__NR_read] = (void *) loggingread; */ + + dbg_dir = debugfs_create_dir("dmc", NULL); + if (IS_ERR(dbg_dir)) { + printk(KERN_NOTICE ": debugfs is not available\n"); + return -ENODEV; + } + if (dbg_dir == NULL) { + printk(KERN_NOTICE ": unable to create usbmon directory\n"); + return -ENODEV; + } + + dbg_file = debugfs_create_file("bp", 0600, dbg_dir, NULL, &profile_fops); + if (dbg_file == NULL) { + debugfs_remove(dbg_dir); + return -ENODEV; + } + + INIT_LIST_HEAD(&bp.s_list); + + return 0; +} + +static void __exit readlog_exit(void) { + debugfs_remove(dbg_file); + debugfs_remove(dbg_dir); + + sys_call_table[__NR_open] = (void *) open; + sys_call_table[__NR_read] = (void *) read; + + printk(KERN_INFO "sys_call_table restored\n"); + +} + +module_init(readlog_init); +module_exit(readlog_exit); -- cgit v1.2.1