#! /usr/bin/env perl #----------------------------------------------------------------------------- # mindi - mini-Linux distro based on the user's filesystem & distribution # # Mindi can create a multi-floppy boot/root kit. The first floppy is the boot # disk: it contains a kernel, a ramdisk etc. The second disk is data disk #1; # the third disk is data disk #2; and so it goes. # # See http://www.microwerks.net/~hugo/ for details. #----------------------------------------------------------------------------- use integer; use strict; use warnings; # Here are all the subroutines in Mindi! use subs qw[ mkdir_p twirling_baton loggit logit tar_up_files untar_files fatal_error find_homes print_progress find_boot_b_file find_isolinux_path determine_if_called_by_mondo copy_stuff_into_dir copy_files_into_dir get_size_of_stuff find_exec check_system usage add_to_cfg make_mondo_config get_ptype_of make_mountlist divide_bigdir_into_minidirs make_data_disks zip_minidirs_into_tarballs write_one_liner create_data_disk_images_from_tarballs create_tiny_filesystem make_zfile get_kmods get_kmod_paths make_ramdisk replace_line edit_ramdisk prepare_boot_disks make_bootable_cd copy_images_to_floppies make_one_floppy find_kernel find_specific_kmod_in_path make_modules_failsafe ]; # use diagnostics; package Mindi; use Cwd qw< getcwd chdir realpath >; use POSIX qw< SEEK_SET uname >; use File::Basename qw< basename dirname >; use File::Copy qw< copy move >; use File::Find qw< find finddepth >; use Fcntl qw< O_RDONLY O_WRONLY O_CREAT O_TRUNC >; use Getopt::Long qw< :config bundling >; use Data::Dumper qw< Dumper >; use Mindi::DiskSize qw< get_size_of_disk get_partition_type >; use IO::Handle; # 1000s of lines just for autoflush :-/ our $Version = "0.99"; our (@AfterInitialPhase, @PostBoot); ### Mindi configuration # You can override any of these settings by placing them in /etc/mindi.conf. # Or edit them here. # NOTE: these next two should be mini-shell scripts, not Perl: one thing per item @AfterInitialPhase = ("echo 'Loading data disks...'"); @PostBoot = ("echo 'Mindi-Linux has finished booting.'"); # end NOTE # Make a 1722K floppy? our $Make1722kFloppy = 1; # Change to 1 to get a stack backtrace to the screen as well as the # logfile on a fatal error. It's always logged to the logfile. our $TraceOnFatal = 0; # Paths to mount, umount, isolinux.bin # All will be located if blank -- only set these if they can't be found otherwise. our $MountCmd = ""; our $UnmountCmd = ""; our $IsolinuxPath = ""; # How much "breathing room" on the ramdisk our $ExtraSpace = 16384; # Whether to create EXTREMELY large logfile output our $LogVerbose = 0; # Maximum size of stuff that can go on a 1440k floppy our $MaxDiskSize = 1325; # Temp files are placed in /mindi/ our $TempDir = `mktemp -d /tmp/mindi.XXXXXXXXXX || echo /tmp`; chomp $TempDir; # Where to put our PID our $PIDfile = "/var/run/mindi.pid"; # Prompt to write images to floppies? our $Floppies = 0; # Ask before making CD? our $PromptCD = 0; # Path to mindi logfile our $Logfile = "/var/log/mindi.log"; # Where to put the output by default our $ImagesDir = "/root/images/mindi"; # Size of bigfile slices our $ChopSize = 240; # Size at which a file is considered "big" (see above) our $ChopAt = 2 * $ChopSize; # Where's your kernel? (Mindi will try to find it if blank) our $Kernel = ""; # Use my kernel or yours? (0=yours, 1=mine) our $KernelSucks = 0; # Use LILO if set, SYSLINUX if not. our $UseLILO = 0; # Extra boot options ('append = foo=bar,baz,0x123') our $ExtraBootOptions = "acpi=off apm=off devfs=nomount exec-shield=0"; # Message before boot: prompt my $apostrophe = "'"; # avoid confusing Perl (really, my editor) our $BootMediaMessage = <. To restore some/all files interactively, type 'interactive' . To compare the archives with your filesystem, type 'compare' . To boot to a command-line prompt (expert mode), type 'expert' . You may add one or more of the following parameters as well:- donteject - mondorestore will not eject the CD; this is useful if, for instance, your PC${apostrophe}s case has a concealed CD-ROM drive noresize - your mountlist will not be adjusted to use your unallocated hard disk space textonly - do not call any Newt library routines; this is unattractive but valuable if you find your Newt library has bugs in it e.g. Type 'nuke donteject textonly' if you have an unstable Newt library and a PC whose CD-ROM drive tray would be damaged if it unexpectedly ejected. END_OF_MESSAGE # Floppy modules our @FloppyMods = qw(floppy ide-floppy); # SCSI modules our @SCSIMods = ('53c7,8xx', qw(scsimod scsimon scsi_mon scsi-mon 3w-xxxx 3c59x gdth a100u2w advansys aha152x aha1542 aha1740 aic7xxx aic7xxx_mod aic79xx aic79xx_mod BusLogic cciss dtc eata_dma eata eata_pio fdomain ide-scsi ieee1394 imm initio ips iscsi mptscsih mptbase raw1394 scsi-cd scsi scsi_mod sd sd_mod seagate sr st osst sym53c8xx ht ftape wd7000 fasttrak)); # IDE modules our @IDEMods = qw(ide ide-generic ide-mod ide-disk ide-cd ide-cs paride edd ata_piix libata ); # CD-ROM modules our @CDMods = (@FloppyMods, @IDEMods, qw(af_packet cdrom isocd isofs inflate_fs nls_iso8859-1 nls_cp437 sg sr_mod zlib_inflate usb-storage usb-ohci usb-uhci usbcore hid)); # Net Modules our @NetMods = (qw(sunrpc nfs nfsacl lockd loop e100 bcm5700 e1000 eepro100 tg3)); # Extra modules our @ExtraMods = (@CDMods, qw(vfat fat loop linear raid0 raid1 raid5 lvm-mod dm-mod jfs xfs xfs_support pagebuf reiserfs ext2 ext3 minix nfs nfsd lockd sunrpc dm ufs)); ### End configuration. if (-f "/usr/local/etc/mindi.conf") { require "/usr/local/etc/mindi.conf"; } if (-f "/etc/mindi.conf") { require "/etc/mindi.conf"; } if (-f "/root/.mindi.conf") { require "/root/.mindi.conf"; } # All these variables are placeholders for --custom options. # Do not edit. our $MondoTemp = ""; our $TapeDevice = ""; our $TapeSize = 0; our $FilesInFilelist = 0; our $UsingLZO = 0; our $CDrecovery = 0; our @ImageDevs = (); our $LastFilelistNum = 0; our $EstNoofSlices = 0; our @ExcludeDevs = (); our $UsingCompression = 1; our $NonBootable = 0; our $DifferentialBkup = 0; # End --custom options. # Globals - do not edit our $called_by_mondo = -1; our @Mountpoints = (); our %Homes = ( mindi => "", mondo => "" ); our $BiggieNum = 0; our $IAmExiting = 0; our $ToolSize = 0; our $KernelVersion = ""; our $FailsafeKernelVersion = ""; our $RamdiskSize = 24000; # Why 24000? I don't know... our $ModuleSuffix = "o"; our $LVMVersion = 0; our $LVMVersionString = ""; # End globals. # Master definition of all symlinks in rootfs our %RootfsSymlinks = ( 'bin/[' => 'busybox', 'bin/ash' => 'busybox', 'bin/cat' => 'busybox', 'bin/chgrp' => 'busybox', 'bin/chmod' => 'busybox', 'bin/chown' => 'busybox', 'bin/chroot' => 'busybox', 'bin/cp' => 'busybox', 'bin/date' => 'busybox', 'bin/dd' => 'busybox', 'bin/df' => 'busybox', 'bin/dmesg' => 'busybox', 'bin/echo' => 'busybox', 'bin/false' => 'busybox', 'bin/fdflush' => 'busybox', 'bin/getopt' => 'busybox', 'bin/grep' => 'busybox', 'bin/gunzip' => 'busybox', 'bin/gzip' => 'busybox', 'bin/halt' => 'busybox', 'bin/hostname' => 'busybox', 'bin/kill' => 'busybox', 'bin/ln' => 'busybox', 'bin/loadkmap' => 'busybox', 'bin/ls' => 'busybox', 'bin/mkdir' => 'busybox', 'bin/mknod' => 'busybox', 'bin/mktemp' => 'busybox', 'bin/mv' => 'busybox', 'bin/more' => 'busybox', 'bin/mount' => 'busybox', 'bin/pidof' => 'busybox', 'bin/ping' => 'busybox', 'bin/ps' => 'busybox', 'bin/pwd' => 'busybox', 'bin/rm' => 'busybox', 'bin/rmdir' => 'busybox', 'bin/sed' => 'busybox', 'bin/sh' => 'busybox', 'bin/sh.busybox' => 'busybox', 'bin/sleep' => 'busybox', 'bin/stty' => 'busybox', 'bin/sync' => 'busybox', 'bin/sync.busybox' => 'busybox', 'bin/tar' => 'busybox', 'bin/touch' => 'busybox', 'bin/true' => 'busybox', 'bin/umount' => 'busybox', 'bin/uname' => 'busybox', 'bin/update' => 'busybox', 'bin/usleep' => 'busybox', 'bin/vi' => 'busybox', 'bin/zcat' => 'busybox', 'etc/init' => '../sbin/init', 'etc/mtab' => '../proc/mounts', 'etc/profile' => 'bashrc', 'etc/shrc' => 'bashrc', 'lib/libcom_err.so.2' => 'libcom_err.so.2.0', 'lib/libtermcap.so.2' => 'libtermcap.so.2.0.8', 'sbin/halt' => '../bin/busybox', 'sbin/hostname' => '../bin/busybox', 'sbin/ifconfig' => '../bin/busybox', 'sbin/insmod' => '../bin/busybox', 'sbin/klogd' => '../bin/busybox', 'sbin/loadkmap' => '../bin/busybox', 'sbin/losetup' => '../bin/busybox', 'sbin/lsmod' => '../bin/busybox', 'sbin/makedevs' => '../bin/busybox', 'sbin/mkswap' => '../bin/busybox', 'sbin/modprobe' => '../bin/busybox', 'sbin/poweroff' => '../bin/busybox', 'sbin/reboot' => '../bin/busybox', 'sbin/rmmod' => '../bin/busybox', 'sbin/route' => '../bin/busybox', 'sbin/swapoff' => '../bin/busybox', 'sbin/swapon' => '../bin/busybox', 'sbin/syslogd' => '../bin/busybox', 'sbin/update' => '../bin/busybox', 'tmp/mondo-restore.log' => '../var/log/mondo-archive.log', 'usr/bin/[' => '../../bin/busybox', 'usr/bin/basename' => '../../bin/busybox', 'usr/bin/chvt' => '../../bin/busybox', 'usr/bin/clear' => '../../bin/busybox', 'usr/bin/cut' => '../../bin/busybox', 'usr/bin/deallocvt' => '../../bin/busybox', 'usr/bin/dirname' => '../../bin/busybox', 'usr/bin/du' => '../../bin/busybox', 'usr/bin/env' => '../../bin/busybox', 'usr/bin/expr' => '../../bin/busybox', 'usr/bin/find' => '../../bin/busybox', 'usr/bin/free' => '../../bin/busybox', 'usr/bin/head' => '../../bin/busybox', 'usr/bin/hostid' => '../../bin/busybox', 'usr/bin/id' => '../../bin/busybox', 'usr/bin/killall' => '../../bin/busybox', 'usr/bin/length' => '../../bin/busybox', 'usr/bin/logger' => '../../bin/busybox', 'usr/bin/logname' => '../../bin/busybox', 'usr/bin/md5sum' => '../../bin/busybox', 'usr/bin/mkfifo' => '../../bin/busybox', 'usr/bin/nslookup' => '../../bin/busybox', 'usr/bin/printf' => '../../bin/busybox', 'usr/bin/reset' => '../../bin/busybox', 'usr/bin/sort' => '../../bin/busybox', 'usr/bin/tail' => '../../bin/busybox', 'usr/bin/tee' => '../../bin/busybox', 'usr/bin/telnet' => '../../bin/busybox', 'usr/bin/test' => '../../bin/busybox', 'usr/bin/tftp' => '../../bin/busybox', 'usr/bin/time' => '../../bin/busybox', 'usr/bin/tr' => '../../bin/busybox', 'usr/bin/traceroute' => '../../bin/busybox', 'usr/bin/tty' => '../../bin/busybox', 'usr/bin/uniq' => '../../bin/busybox', 'usr/bin/uptime' => '../../bin/busybox', 'usr/bin/wc' => '../../bin/busybox', 'usr/bin/which' => '../../bin/busybox', 'usr/bin/whoami' => '../../bin/busybox', 'usr/bin/xargs' => '../../bin/busybox', 'usr/bin/yes' => '../../bin/busybox', 'usr/i386-linux-uclibc/bin/i386-uclibc-addr2line' => '/usr/bin/addr2line', 'usr/i386-linux-uclibc/bin/i386-uclibc-ar' => '/usr/bin/ar', 'usr/i386-linux-uclibc/bin/i386-uclibc-as' => '/usr/bin/as', 'usr/i386-linux-uclibc/bin/i386-uclibc-cpp' => '/usr/bin/cpp', 'usr/i386-linux-uclibc/bin/i386-uclibc-nm' => '/usr/bin/nm', 'usr/i386-linux-uclibc/bin/i386-uclibc-objcopy' => '/usr/bin/objcopy', 'usr/i386-linux-uclibc/bin/i386-uclibc-objdump' => '/usr/bin/objdump', 'usr/i386-linux-uclibc/bin/i386-uclibc-ranlib' => '/usr/bin/ranlib', 'usr/i386-linux-uclibc/bin/i386-uclibc-size' => '/usr/bin/size', 'usr/i386-linux-uclibc/bin/i386-uclibc-strings' => '/usr/bin/strings', 'usr/i386-linux-uclibc/bin/i386-uclibc-strip' => '/usr/bin/strip', 'usr/i386-linux-uclibc/lib/ld-uClibc.so.0' => 'ld-uClibc-0.9.19.so', 'usr/i386-linux-uclibc/lib/libc.so' => 'libuClibc-0.9.19.so', 'usr/i386-linux-uclibc/lib/libc.so.0' => 'libuClibc-0.9.19.so', 'usr/i386-linux-uclibc/lib/libcrypt.so' => 'libcrypt-0.9.19.so', 'usr/i386-linux-uclibc/lib/libcrypt.so.0' => 'libcrypt-0.9.19.so', 'usr/i386-linux-uclibc/lib/libdl.so' => 'libdl-0.9.19.so', 'usr/i386-linux-uclibc/lib/libdl.so.0' => 'libdl-0.9.19.so', 'usr/i386-linux-uclibc/lib/libm.so' => 'libm-0.9.19.so', 'usr/i386-linux-uclibc/lib/libm.so.0' => 'libm-0.9.19.so', 'usr/i386-linux-uclibc/lib/libnsl.so' => 'libnsl-0.9.19.so', 'usr/i386-linux-uclibc/lib/libnsl.so.0' => 'libnsl-0.9.19.so', 'usr/i386-linux-uclibc/lib/libpthread.so' => 'libpthread-0.9.19.so', 'usr/i386-linux-uclibc/lib/libpthread.so.0' => 'libpthread-0.9.19.so', 'usr/i386-linux-uclibc/lib/libresolv.so' => 'libresolv-0.9.19.so', 'usr/i386-linux-uclibc/lib/libresolv.so.0' => 'libresolv-0.9.19.so', 'usr/i386-linux-uclibc/lib/libutil.so' => 'libutil-0.9.19.so', 'usr/i386-linux-uclibc/lib/libutil.so.0' => 'libutil-0.9.19.so', 'usr/i386-linux-uclibc/usr/bin/addr2line' => '/usr/bin/addr2line', 'usr/i386-linux-uclibc/usr/bin/ar' => '/usr/bin/ar', 'usr/i386-linux-uclibc/usr/bin/as' => '/usr/bin/as', 'usr/i386-linux-uclibc/usr/bin/c++' => '/usr/i386-linux-uclibc/bin/i386-uclibc-gcc', 'usr/i386-linux-uclibc/usr/bin/cc' => '/usr/i386-linux-uclibc/bin/i386-uclibc-gcc', 'usr/i386-linux-uclibc/usr/bin/cpp' => '/usr/bin/cpp', 'usr/i386-linux-uclibc/usr/bin/g++' => '/usr/i386-linux-uclibc/bin/i386-uclibc-gcc', 'usr/i386-linux-uclibc/usr/bin/gcc' => '/usr/i386-linux-uclibc/bin/i386-uclibc-gcc', 'usr/i386-linux-uclibc/usr/bin/ld' => '/usr/i386-linux-uclibc/bin/i386-uclibc-ld', 'usr/i386-linux-uclibc/usr/bin/ldd' => '/usr/i386-linux-uclibc/bin/i386-uclibc-ldd', 'usr/i386-linux-uclibc/usr/bin/nm' => '/usr/bin/nm', 'usr/i386-linux-uclibc/usr/bin/objcopy' => '/usr/bin/objcopy', 'usr/i386-linux-uclibc/usr/bin/objdump' => '/usr/bin/objdump', 'usr/i386-linux-uclibc/usr/bin/ranlib' => '/usr/bin/ranlib', 'usr/i386-linux-uclibc/usr/bin/size' => '/usr/bin/size', 'usr/i386-linux-uclibc/usr/bin/strings' => '/usr/bin/strings', 'usr/i386-linux-uclibc/usr/bin/strip' => '/usr/bin/strip', 'usr/lib/libncurses.so.4' => '/usr/lib/libncurses.so.5', 'usr/sbin/chroot' => '../../bin/busybox', 'usr/sbin/dutmp' => '../../bin/busybox', 'usr/sbin/fbset' => '../../bin/busybox', ); # Fix up $ENV{'PATH'} $ENV{'PATH'} .= ":/sbin:/usr/sbin:/usr/local/sbin"; # Make sure we're running on Linux. if ( $^O !~ /linux/i ) { die "Only runnable under Linux, not your $^O system.\n"; } ############################################################ # mkdir_p(@) - make some directories + all their parents # # @_ - list of directories # ############################################################ sub mkdir_p { my @args = @_; # change pass-by-ref to pass-by-val my @dirs; foreach my $dir (@args) { while ($dir ne "." and $dir ne "/") { unshift @dirs, $dir; $dir = dirname $dir; } } foreach my $dir2make (@dirs) { mkdir $dir2make; } } ############################################################ # twirling_baton($) - twirl the baton! \ | / - \ | / - ... # # $_[0] - number of times to twirl # ############################################################ sub twirling_baton { my $ntwirls = shift || fatal_error ("Too few arguments to twirling_baton"); my $modulo = $ntwirls % 4; $modulo == 0 ? '|' : $modulo == 1 ? '/' : $modulo == 2 ? '-' : $modulo == 3 ? '\\' : '|'; } ############################################################ # loggit(@) - log message to logfile # # @_ = the message # ############################################################ sub loggit { unless (defined open LOG, ">> $Mindi::Logfile") { unless ($IAmExiting) { fatal_error ("Could not open $Mindi::Logfile: $!"); } } print LOG @_, "\n"; close LOG; } ############################################################ # logit(@) - log message to logfile + stderr # # @_ - the message # ############################################################ sub logit { loggit @_; print STDERR @_, "\n"; } ############################################################ # tar_up_files($@) - Make tarball out of files # # $_[0] = the tarball # # rest of @_ = the files to tar up # # RET = true for success # ############################################################ sub tar_up_files { my $tarball = shift || fatal_error ("Too few arguments to tar_up_files"); my $compression_flag = ""; my $pid; my @tarfiles; foreach my $tarfile (@_) { push @tarfiles, $tarfile if -e $tarfile; } for ($tarball) { /.*\.t(ar\.)?gz/ and do { $compression_flag = " | gzip -9 > "; last; }; /.*\.t(ar\.)?bz2?/ and do { $compression_flag = " | bzip2 -9 >"; last; }; /.*\.t(ar\.l)?zop?/ and do { $compression_flag = " | lzop >"; last; }; /.*\.ta(r\.)?[zZ]/ and do { $compression_flag = " | compress >"; last; }; /.*\.tar/ and do { $compression_flag = " > "; last; }; fatal_error ("Don't recognize $tarball as a valid tarball"); } loggit "tar -cf- @tarfiles $compression_flag $tarball 2>> $Logfile"; not system "tar -cf- @tarfiles $compression_flag $tarball 2>> $Logfile"; } ############################################################ # untar_files($;$$) - Make files out of tarball # # $_[0] - tarball # # [OPTIONAL] $_[1] - directory to untar in # # [OPTIONAL] $_[2] - progress msg to enable them # ############################################################ sub untar_files { my $tarball = shift || fatal_error ("Too few arguments to untar_files"); my $directory = shift || getcwd; my $print_progress = shift || ""; my $print_progress_get; # = shift || not called_by_mondo(); my $decompression_flag = ""; my $ret = 1; my $olddir = getcwd; my ($progress, $noof_lines); if (scalar @_) { $print_progress_get = shift; } else { $print_progress_get = not called_by_mondo(); } mkdir_p $directory; for ($tarball) { /.*\.t(ar\.)?gz/ and do { $decompression_flag = "z"; next; }; /.*\.t(ar\.)?bz2?/ and do { $decompression_flag = "j"; next; }; /.*\.ta(r\.)?[zZ]/ and do { $decompression_flag = "Z"; next; }; /.*\.tar/ and do { next; }; fatal_error ("Don't recognize $tarball as a valid tarball"); } chdir $directory; if ($print_progress) { if ($print_progress_get) { print "\r$print_progress", "[", ' ' x 30, "] |" unless called_by_mondo(); open TAR_T, "/usr/bin/env tar t${decompression_flag}f $tarball |" or fatal_error ("Can't open pipe from tar: $!"); while () { print_progress (++$noof_lines, -1, $print_progress); } close TAR_T or fatal_error ("Error closing tar pipe: $!"); if (called_by_mondo()) { $print_progress =~ s/:\t*$/.../; print "$print_progress\n"; } else { print "\r$print_progress", "[", ' ' x 30, "] 0", '% |'; } open TAR_X, "/usr/bin/env tar xv${decompression_flag}f $tarball |" or fatal_error ("Can't open pipe from tar: $!"); while () { print_progress (++$progress, $noof_lines, $print_progress); } close TAR_X or fatal_error ("Error closing tar pipe: $!"); } else { if (called_by_mondo()) { $print_progress =~ s/:\t+/.../; print "$print_progress\n"; } else { print "\r$print_progress", "[", ' ' x 30, "] |"; } open TAR, "/usr/bin/env tar xv${decompression_flag}f $tarball |" or fatal_error ("Can't open pipe from tar: $!"); while () { print_progress (++$noof_lines, -1, $print_progress); } close TAR or fatal_error ("Error closing tar pipe: $!"); print "\r$print_progress", "[", '=' x 29, ">] Done.\n" unless called_by_mondo(); } } else { $ret = not system 'tar', "x${decompression_flag}f", $tarball; } chdir $olddir; $ret; } ############################################################ # fatal_error(@) - Abort with a fatal error # # @_ - the error msg # # ~RET - does not return # ############################################################ sub fatal_error { my $mess = join '', @_; my $stackmess = ""; my $stacklevel = 0; my $mountpoint; my ($pkg, $file, $line, $subr, $hasargs, $eval, $require); my (@files, @a); $IAmExiting = 1; logit "\nMindi version ${Mindi::Version}:"; logit "---FATAL ERROR--- $mess"; if ($TraceOnFatal) { logit "** Stack backtrace follows. **"; } else { loggit "** Stack backtrace follows. **"; } # Now for the magical code! (modified from Carp/Heavy.pm) # When we're in `package DB' caller() returns more info while (do {{ package DB; @a = caller $stacklevel }}) { # retrieve return values from caller() ($pkg, $file, $line, $subr, $hasargs, $eval, $require) = @a; if (defined $eval) { # it's an eval '...' or a require if ($require) { # --> it's a require '...' $subr = "require $eval"; } elsif ($eval eq "0") { # --> probably an anonymous sub (or a signal handler) $subr = "[SIGNAL]"; } else { # --> it's an eval '...' $subr = "eval '$eval'"; } } elsif ($subr eq "(eval)") { # it's an eval { ... } $subr = "eval { ... }"; } # otherwise, it's a real subroutine, so we don't have to do anything name-wise # if we were called from a signal handler, everything from the handler up is an # "anonymous" subroutine ... but not us! if ($stacklevel == 0) { $subr = "fatal_error"; } # set arguments if ($hasargs) { @a = @DB::args; # get args from the call if ($subr eq "[SIGNAL]") { if ($#a == 1 and $a[0] =~ /[A-Z]{3,5}/) { $subr = "[handler for SIG$a[0]]"; } else { $subr = "[unknown]"; goto ARG; } } else { ARG: foreach (@a) { $_ = "undef", next ARG if not defined $_; if (ref $_) { $_ .= ''; } s/'/\\'/g; # is it a number or string? if (not /^-?[\d.]+$/) { $_ = "'$_'"; # it's a string } # print extended-ASCII as '\nnn' s/([\200-\377])/sprintf '\%o', ord $1/eg; # print control-chars as ^x s/([\0-\37\177])/sprintf '^%c', ord($1) ^ 64/eg; } $subr .= " (@{[ join ', ' => @a ]})"; } } else { $subr .= " ()"; } if ($TraceOnFatal) { logit "#$stacklevel in $subr called at $file line $line"; } else { loggit "#$stacklevel in $subr called at $file line $line"; } ++$stacklevel; } # End stack magic. if ($TraceOnFatal) { logit "** End stack backtrace. **"; } else { loggit "** End stack backtrace. **"; } chdir "/"; foreach $mountpoint (@Mountpoints) { loggit "-> Unmounting $mountpoint."; system "$UnmountCmd $mountpoint" and logit "-> ** Could not unmount $mountpoint!"; } system "rm -rf $TempDir/minidir $TempDir/bigdir $TempDir/tardir"; system "rm -rf /tmp/mindi.err.*"; mkdir_p "/tmp/mindi.err.$$"; chdir "/tmp/mindi.err.$$" or logit "Mindi is exiting -- can't cd to errors dir ($!)", exit 2; $TempDir .= "/mindi/$$" if $TempDir !~ /$$/; for my $file ("/etc/fstab", $Mindi::Logfile, "/var/log/mondo-archive.log") { system 'cp', '-R', "$file", '.' if -e $file; } tar_up_files ("/tmp/mindi.err.$$.tar.gz", "fstab", basename ($Mindi::Logfile, ""), "mondo-archive.log"); if ($TempDir =~ /$$/) { system "rm -rf $TempDir" and warn "Could not remove $TempDir!\n"; } else { logit "TempDir is $TempDir. This does not contain my PID, so I shan't remove it."; } print "Please e-mail a copy of /tmp/mindi.err.$$.tar.gz to the mailing list.\n", "See http://www.mondorescue.org for more information.\n", "We CANNOT HELP unless you enclose that file!\n"; unlink $PIDfile unless $mess =~ /instance/; logit "Mindi is exiting.", exit 1; } ############################################################ # determine_if_called_by_mondo() - Mondo called me? # ############################################################ sub determine_if_called_by_mondo { my $pts = 0; my $fh; if (-e "/var/run/monitas-mondo.pid") { ++$pts }; open $fh, "ps ax |" or fatal_error "Could not open output of ps ax: $!"; while (<$fh>) { chomp; if (/mondoarchive/) { ++$pts; last; } } $called_by_mondo = $pts; } ############################################################ # called_by_mondo() - return $called_by_mondo (must be set)# # RET - true for yes, false for no # ############################################################ sub called_by_mondo() { $called_by_mondo; } ############################################################ # find_homes() - find Mindi and Mondo's homedirs # ############################################################ sub find_homes { my $dir; foreach $dir ("/usr/share", "/usr/local", "/usr/local/share", "/usr", "/opt/share") { if (-d "$dir/mindi") { $Homes{'mindi'} = "$dir/mindi"; } if (-d "$dir/mondo") { $Homes{'mondo'} = "$dir/mondo"; } } if (not $Homes{'mindi'}) { fatal_error "Can't find Mindi's home directory!"; } if (not $Homes{'mondo'}) { fatal_error "Can't find Mondo's home directory!" if called_by_mondo; logit "Warning: Could not find Mondo's home directory."; logit "Some features may not work without Mondo installed."; } 1; } $Mindi::lastpct = 0; ############################################################ # print_progress($$@) - print a progress message # # $_[0] - progress so far ... # # $_[1] - ... out of how much to do # # rest of @_ - progress message: inc \t # ############################################################ sub print_progress { my $progress = shift || fatal_error "Too few arguments to print_progress (want >2, got 0)"; my $noof_lines = shift || fatal_error "Too few arguments to print_progress (want >2, got 1)"; my $msg = join "", @_; my ($percent, $nstars, $ndots); our $lastpct; if ($noof_lines != -1) { $percent = int ($progress * 100 / $noof_lines); if (not called_by_mondo) { my ($nstars, $ndots); $nstars = int ($percent * 3/10); $ndots = 30 - $nstars; $ndots-- if $nstars == 0; if ($percent == 100) { print "\r$msg", "[", '=' x 29, '>', "] Done. \n"; } else { print "\r$msg", "[", '=' x ($nstars - 1), '>', ' ' x $ndots, "] $percent", '%', " @{[ twirling_baton $progress ]}"; } } else { if (not $percent % 25) { # it's a multiple of 25 if ($percent != $lastpct) { if ($percent == 100) { print "--> Done.\n"; } else { print "--> $percent", '%', " done...\n"; } $lastpct = $percent; } } } } elsif (not called_by_mondo) { my $pos = 0; my $iter = 0; my $safter = 0; $pos = $progress; $iter = int ($pos / 24) + 1; if (($iter % 2) == 0) { $pos = 24 - ($pos % 24) while $pos > 24; } else { $pos = $pos % 24 while $pos > 24; } $safter = 25 - $pos; print "\r$msg", "[", ' ' x $pos, '<===>', ' ' x $safter, "] @{[ twirling_baton $progress ]}"; } } ############################################################ # generate_depends_list() - make giant dependency list # ############################################################ sub generate_depends_list() { my $progress = 0; my $noof_lines = 0; my $mr_found = 0; my $modutils_yet = 0; my @tmpdeps = (); my @deps = (); if (called_by_mondo) { print "Analyzing dependency requirements...\n"; } else { print "Analyzing dependency requirements:\t[", ' ' x 30, "] 0", '% |'; } loggit "--> Analyzing dependency requirements..."; open DEPLIST, "/etc/mindi/deplist.txt" or open DEPLIST, "$Homes{'mindi'}/deplist.txt" or fatal_error "Could not open $Homes{'mindi'}/deplist.txt: $!"; open TMPLIST, "> $TempDir/deplist.txt" or fatal_error "Could not open > $TempDir/deplist.txt: $!"; $progress = $noof_lines = 0; ++$noof_lines while ; seek DEPLIST, 0, SEEK_SET; while () { my (@line, $file, $dir, $lib, $entity); next if /^\#/; s/\s+\#.*//; chomp; @line = split; $mr_found = 1 if /mondo.?restore/; if (!$modutils_yet and $KernelVersion =~ /2.6/) { @line = ('insmod', 'rmmod', 'modprobe', 'depmod', 'lsmod', 'kallsyms', @line); $modutils_yet = 1; } foreach $file (@line) { if (/\.${ModuleSuffix}$/) { if ($KernelSucks) { if (-e "$TempDir/lib/modules/$FailsafeKernelVersion/$file") { print TMPLIST "$TempDir/lib/modules/$FailsafeKernelVersion/$file\n"; loggit "GDL: Adding kernel object $file to deplist" if $LogVerbose; } else { loggit "GDL: Ignoring kernel object $file -- does not exist"; } } else { if (-e "/lib/modules/".`uname -r | tr -d '\n'`."/$file") { print TMPLIST "/lib/modules/".`uname -r | tr -d '\n'`."/$file\n"; loggit "GDL: Adding kernel object $file to deplist" if $LogVerbose; } else { loggit "GDL: Ignoring kernel object $file -- does not exist" } } } else { my($found) = 0; if ($file =~ /^(([lp]v)|vg)/) { # it's an LVM file if (-x "/sbin/lvm-$LVMVersionString/$file") { $found = 1; print TMPLIST "/sbin/lvm-$LVMVersionString/$file\n"; } elsif (-x "/lib/lvm-$LVMVersionString/$file") { $found = 1; print TMPLIST "/lib/lvm-$LVMVersionString/$file\n"; } if ($found and $LogVerbose) { loggit "GDL: Found LVM-Tool $file in version-specific location"; } } if (!$found) { DIR: foreach my $thedir ('/etc', '/lib', '/bin', '/sbin', '/usr', '/usr/bin', '/usr/sbin', '/usr/lib', '/usr/local/bin', '/usr/local/sbin', '/usr/local/lib', '/usr/local', '/usr/X11R6', '/usr/X11R6/bin', '/usr/X11R6/lib', '/opt/bin', '/opt/sbin', '/opt/lib', '/opt/mondo/bin', '/opt/mondo/sbin', '/opt/mondo/lib') { my $dir = $thedir; if ($dir =~ /lib$/ and $file =~ /^lib/) { foreach $lib (glob "$dir/$file*so") { while (-l $lib) { print TMPLIST "$lib\n"; $lib = $dir . '/' . readlink "$lib"; } print TMPLIST "$lib\n"; loggit "GDL: Adding library $lib to deplist (similar to $file)" if $LogVerbose; } } elsif (-e "$dir/$file") { while (-l "$dir/$file") { print TMPLIST "$dir/$file\n"; $file = readlink "$dir/$file"; $dir = "" if $file =~ m[^/]; } $entity = "$dir/$file"; print TMPLIST "$entity\n"; loggit "GDL: Adding $entity to deplist" if $LogVerbose; last DIR; } } }} } } continue { print_progress ++$progress, $noof_lines, "Analyzing dependency requirements:\t"; } if (not $mr_found) { foreach my $dir ("/usr/bin", "/usr/sbin", "/usr/local/bin", "/usr/local/sbin") { print TMPLIST "$dir/mondorestore", $mr_found = 1, last if -e "$dir/mondorestore"; print TMPLIST "$dir/mondo-restore", $mr_found = 1, last if -e "$dir/mondo-restore"; } if ((not $mr_found) and called_by_mondo) { fatal_error "I tried hard but I couldn't find mondorestore!"; } } loggit "--> Analyzing dependency requirements...done."; close DEPLIST; close TMPLIST; $progress = $noof_lines = 0; if (called_by_mondo) { print "Making complete dependency list...\n"; } else { print "Making complete dependency list:\t[", ' ' x 30, "] 0", '% |'; } loggit "--> Making complete dependency list..."; open TMPLIST, "< $TempDir/deplist.txt" or fatal_error "Can't open $TempDir/deplist.txt for reading: $!"; ++$noof_lines while ; seek TMPLIST, 0, 0; while () { chomp; next if /^\s*$/; push @tmpdeps, $_; loggit "GDL: Adding $_ to " if $LogVerbose; open LDD, "/usr/bin/ldd $_ 2>&1 |"; while () { chomp; next unless /^\t.*=>.*\(.*\)/; # get rid of errors s/^\t.*=>\s*//; s/\s*\(.*\)\s*//; while (-l $_) { push @tmpdeps, $_; my($dir, $link); $dir = dirname $_; $link = readlink $_; $_ = $dir . '/' . $link; } push @tmpdeps, $_; loggit "--> Adding dependency $_ to " if $LogVerbose; } close LDD; } continue { print_progress ++$progress, $noof_lines, "Making complete dependency list\t\t"; } @tmpdeps = sort @tmpdeps; @deps = (); for (my $i = 1; $i < scalar @tmpdeps; ++$i) { if ($tmpdeps[$i] ne $tmpdeps[$i-1]) { push @deps, $tmpdeps[$i-1]; } } push @deps, $tmpdeps[$#tmpdeps]; close TMPLIST; open DEPLIST, "> $TempDir/deplist.txt" or fatal_error "Can't open $TempDir/deplist.txt: $!"; print DEPLIST join ("\n", @deps), "\n"; close DEPLIST; loggit "--> Making complete dependency list...done."; } my($CSID_number_dots) = 0; ############################################################ # copy_stuff_into_dir() - copy file/directory into dir # # $_[0] - the file/directory # # $_[1] - the directory to copy to # ############################################################ sub copy_stuff_into_dir { my $thing = shift || fatal_error "Too few arguments to copy_stuff_into_dir() - want 2 or 3, got 0"; my $dir = shift || fatal_error "Too few arguments to copy_stuff_into_dir() - want 2 or 3, got 1"; my $newname = shift || $thing; my ($file, $dirname, $block, $mode); my $sliceno = 0; (undef, undef, $mode) = lstat $thing; if (-d _) { loggit "--> CSID: $thing is a directory; recursing..." if $LogVerbose; if ($thing eq ".") { ++$CSID_number_dots; if ($CSID_number_dots > 5) { fatal_error "CSID: Deadlock avoided"; $CSID_number_dots = 0; return; } } else { $CSID_number_dots = 0; } opendir my $dirhandle, $thing or fatal_error "Can't opendir $thing: $!"; while (defined ($file = readdir $dirhandle)) { next if $file =~ /^\./; copy_stuff_into_dir ($file, $dir); } closedir $dirhandle; } elsif (-e _) { my($stripped) = 0; if ($thing =~ /o\.gz$/) { if ($KernelSucks) { ($newname = $thing) =~ s/$TempDir//; } system "gunzip", $thing; $thing =~ s/\.gz//; $newname =~ s/\.gz//; (undef, undef, $mode) = lstat $thing; # it's a new thing, with a new size } elsif (!-l _ and -f _ and -x _ and $thing !~ m[^/lib/ld.+so]) { if (system("file '$thing' | grep -q not.stripped") == 0) { copy $thing, "$TempDir/stripped.file"; if (system("strip --strip-unneeded $TempDir/stripped.file") == 0) { $stripped = 1; loggit "CSID: Stripped -$thing-"; lstat "$TempDir/stripped.file"; } else { unlink "$TempDir/stripped.file"; } } } $dirname = dirname $newname; mkdir_p "$dir/$dirname"; if (!-l _ and (-s _) > ($ChopAt * 1024)) { loggit "--> CSID: Copying $thing into $dir as a biggiefile"; open SLICENAME, "> $dir/slice-$BiggieNum.name" or fatal_error "Can't open $dir/slice-$BiggieNum.name: $!"; print SLICENAME $newname, "\n"; close SLICENAME; open SLICESIZE, "> $dir/slice-$BiggieNum.size" or fatal_error "Can't open $dir/slice-$BiggieNum.name: $!"; print SLICESIZE int ((-s _) / 1024), "\n"; close SLICESIZE; sysopen BIGGIE, $stripped ? "$TempDir/stripped.file" : $thing, O_RDONLY or fatal_error "Can't sysopen $thing O_RDONLY: $!"; while (sysread BIGGIE, $block, $ChopSize * 1024) { sysopen SLICE, "$dir/slice-$BiggieNum.@{[ sprintf '%03d', $sliceno ]}", O_WRONLY | O_TRUNC | O_CREAT, 0644 or fatal_error "Can't sysopen $dir/slice-$BiggieNum.@{[ sprintf '%03d', $sliceno ]} O_WRONLY: $!"; syswrite SLICE, $block or fatal_error "Couldn't write block to SLICE: $!"; close SLICE; loggit " --> Slice $sliceno written successfully."; ++$sliceno; } close BIGGIE; ++$BiggieNum; loggit " --> Copied successfully, $sliceno slices."; $sliceno = 0; } else { if (-f _) { copy $stripped ? "$TempDir/stripped.file" : $thing, "$dir/$newname" or fatal_error "Can't copy $thing to $dir/$newname: $!"; } else { system "cp", "-a", $thing, "$dir/$newname"; } chmod $mode & 07777, "$dir/$newname"; } unlink "$TempDir/stripped.file" if $stripped; } else { loggit "$thing in deplist does not exist"; } } ############################################################ # copy_files_into_dir() - chop up and copy files into dir # # $_[0] - the filelist of files # # $_[1] - the directory # ############################################################ sub copy_files_into_dir { my $filelist = shift || fatal_error "Too few arguments to copy_files_into_dir() - want 2, got 0"; my $dir = shift || fatal_error "Too few arguments to copy_files_into_dir() - want 2, got 1"; my $file; my ($progress, $noof_lines) = (0, 0); open FILELIST, $filelist or fatal_error "Cannot open $filelist: $!"; ++$noof_lines while ; seek FILELIST, 0, 0; if (called_by_mondo) { print "Assembling dependency files...\n"; } else { print "Assembling dependency files:\t\t[", ' ' x 30, "] 0", '% |'; } while () { chomp; next if /^$/; next if /^#/; loggit "CFID: Copying $_ into $dir" if $LogVerbose; copy_stuff_into_dir $_, $dir; print_progress ++$progress, $noof_lines, "Assembling dependency files:\t\t"; } close FILELIST or warn "Can't close FILELIST: $!"; print_progress $noof_lines, $noof_lines, "Assembling dependency files:\t\t"; } our $count_of_things = 0; ############################################################ # get_size_of_stuff(\%@) - get size of some files/dirs # # $_[0] - the hash to put sz into # # $_[1] - the stuff for sizing # ############################################################ sub get_size_of_stuff { my $sizes = shift || fatal_error "Too few arguments to get_size_of_stuff() - want 2+, got 0"; my @stuff = @_; my $sizeref; ref $sizes or fatal_error "You didn't give me a reference!"; $sizeref = ref $sizes; $sizeref =~ /hash/i or fatal_error "You gave me a @{[ ref $sizes ]} reference, not a HASH reference."; foreach my $thing (@stuff) { lstat $thing; if (-f _) { my $thingsize = ((-s $thing) / 1024) + 1; system "cat $thing | gzip > $thing.gz"; my $thingcsize = ((-s "$thing.gz") / 1024) + 1; $sizes->{$thing} = { NORMAL => $thingsize, COMPRESSED => $thingcsize }; unlink "$thing.gz"; loggit "GSOS: $thing is ${thingsize}K uncompressed, ${thingcsize}K compressed" if $LogVerbose; ++$count_of_things; print_progress $count_of_things / 2 + 1, -1, "Dividing data into several groups:\t"; } elsif (-d _) { my @files; $sizes->{$thing}{COMPRESSED} = 0; $sizes->{$thing}{UNCOMPRESSED} = 0; loggit "GSOS: $thing is a directory; recursing..." if $LogVerbose; opendir THINGDIR, $thing or fatal_error "Can't opendir $thing: $!"; @files = readdir THINGDIR; closedir THINGDIR; for (@files) { next if /^\.+$/; get_size_of_stuff ($sizes, "$thing/$_"); } } else { # it's a socket, FIFO, symlink, or special file - 0 size loggit "GSOS: $thing is not a normal file; setting size to 0"; $sizes->{$thing}{COMPRESSED} = 0; $sizes->{$thing}{UNCOMPRESSED} = 0; } } } ############################################################ # divide_bigdir_into_minidirs() - splits one big directory # # into a few smaller ones # # $_[0] - the dir to split # # $_[1] - the size of each # # minidir # # $_[2] - the minidir root # ############################################################ sub divide_bigdir_into_minidirs { my $dir = shift || fatal_error "Too few arguments to divide_bigdir_into_minidirs() - want 3, got 0"; my $sz = shift || fatal_error "Too few arguments to divide_bigdir_into_minidirs() - want 3, got 1"; my $minidir_root = shift || fatal_error "Too few arguments to divide_bigdir_into_minidirs() - want 3, got 2"; $sz =~ /-?[\d.]+/ or fatal_error "Size ($sz) should be a number!"; my %filesizes; my (@filesorter, @current_size, @fileparts, @tmpdirparts); my ($progress, $total); if (called_by_mondo) { print "Dividing data into several groups...\n"; } else { print "Dividing data into several groups:\t"; } get_size_of_stuff \%filesizes, $dir; # If we put the big files on the floppies first, then the small ones, we get very even sorting. # Example: ($MaxDiskSize = 1325) # Disk 1 should be about 1324K # Disk 2 should be about 1324K # Disk 3 should be about 1324K # Disk 4 should be about 1324K # Disk 5 should be about 1324K # Disk 6 should be about 1324K # Disk 7 should be about 865K # # A reverse sort (put small files on first) gives: # Disk 1 should be about 1306K # Disk 2 should be about 1300K # Disk 3 should be about 1267K # Disk 4 should be about 1312K # Disk 5 should be about 1268K # Disk 6 should be about 1301K # Disk 7 should be about 1055K # # The first choice is obviously preferable. @filesorter = sort {$filesizes{$Mindi::b}{COMPRESSED} <=> $filesizes{$Mindi::a}{COMPRESSED}} keys %filesizes; $total = scalar @filesorter; foreach my $file (@filesorter) { my $newname; my $mode; my $disk_to_file_under = 1; for (; $disk_to_file_under < scalar @current_size; ++$disk_to_file_under) { if (($current_size[$disk_to_file_under] + $filesizes{$file}{COMPRESSED}) <= $MaxDiskSize) { last; } } # If we found a suitable disk, then $disk_to_file_under is set to it. Easy. # If we *didn't* find a suitable disk, then $disk_to_file_under is set to 1 over the # last array subscript. The code below will "initialize" that disk, and the files will # get copied there. @fileparts = split "/", $file; @tmpdirparts = split "/", $TempDir; splice @fileparts, 0, scalar @tmpdirparts + 1; # splice out 1 levels under the temp dir (tempdir/bigdir) $newname = join "/", @fileparts; (undef, undef, $mode) = lstat $file; $mode &= 07777; mkdir_p "$minidir_root/$disk_to_file_under"; $current_size[$disk_to_file_under] += 0; # create entry if it doesn't exist mkdir_p "$minidir_root/$disk_to_file_under/@{[ dirname $newname ]}"; if (-f $file && !-l $file) { copy $file, "$minidir_root/$disk_to_file_under/$newname" or fatal_error "Could not copy $file to $minidir_root/$disk_to_file_under/$newname: $!"; chmod $mode, "$minidir_root/$disk_to_file_under/$newname" or fatal_error "Could not chmod $mode $minidir_root/$disk_to_file_under/$newname: $!"; loggit "DISK $disk_to_file_under: Filed $file ($filesizes{$file}{COMPRESSED}K) under $newname using regular copy()" if $LogVerbose; } elsif (-d $file) { mkdir "$minidir_root/$disk_to_file_under/$newname"; loggit "DISK $disk_to_file_under: Filed $file under $newname using mkdir()" if $LogVerbose; } else { # weird file type; let the system cp deal with it system 'cp', '-R', "$file", "$minidir_root/$disk_to_file_under/$newname"; loggit "DISK $disk_to_file_under: Filed $file under $newname using system cp" if $LogVerbose; } $current_size[$disk_to_file_under] += $filesizes{$file}{COMPRESSED}; print_progress ++$progress, $total, "Dividing data into several groups:\t"; } for (my $diskno = 1; $diskno < scalar @current_size; ++$diskno) { loggit "Disk $diskno should be about $current_size[$diskno]K"; } my $cnt; opendir MINIDIR, $minidir_root or fatal_error "Can't opendir $minidir_root: $!"; ++$cnt while readdir MINIDIR; closedir MINIDIR; $cnt -= 2; # account for `.' and `..' loggit "$cnt disks were created."; return $cnt; } ############################################################ # make_data_disks() - make those data disks! require GenDL # ############################################################ sub make_data_disks { my ($bigdir, $tardir, $minidir_root, @mpts) = ("$TempDir/bigdir", "$TempDir/tardir", "$TempDir/minidir", ""); my $noof_disks; mkdir_p "$bigdir/usr/bin", $minidir_root, "$bigdir/tmp"; open NEEDLIST, ">> $TempDir/deplist.txt" or fatal_error "Can't append to $TempDir/deplist.txt: $!"; if ($KernelSucks) { my (@kmod_paths, @failsafe_paths, $kver); (undef, undef, $kver) = uname(); chdir $TempDir; @kmod_paths = get_kmod_paths(); find sub { -f $File::Find::name or return; my($basename) = basename $File::Find::name, qr{\.${ModuleSuffix}$}; push @kmod_paths, $File::Find::name if grep { m[$basename] } @ExtraMods; # add in extra modules too }, "/lib/modules/$kver"; @failsafe_paths = make_paths_failsafe (@kmod_paths); foreach my $module (@failsafe_paths) { copy_stuff_into_dir $module, $bigdir; # code in copy_stuff_into_dir will # automatically remove the $TempDir part # for the destination file } } else { foreach my $kmod_path (get_kmod_paths()) { loggit "MDD: Adding kmod $kmod_path to NEEDLIST"; print NEEDLIST "$kmod_path\n"; } } close NEEDLIST; loggit "--> Assembling dependency files..."; copy_files_into_dir "$TempDir/deplist.txt", $bigdir; loggit "--> Assembling dependency files...done."; if (called_by_mondo) { make_mondo_config ("$TempDir/mondo-restore.cfg"); copy "$TempDir/mondo-restore.cfg", "$bigdir/tmp/mondo-restore.cfg"; } # copy io.sys and msdos.sys, if they're there open MOUNT, "$MountCmd |" or fatal_error "Can't open pipe from $MountCmd: $! [path=$ENV{'PATH'}]"; while () { (undef, undef, my $mpt) = split; push @mpts, $mpt; } close MOUNT; foreach my $mountpoint (@mpts) { foreach my $msfile ("io.sys", "msdos.sys") { if (-e "$mountpoint/$msfile") { copy ("$mountpoint/$msfile", "$bigdir/$msfile") or logit "*** Can't copy $mountpoint/$msfile to $bigdir/$msfile: $!"; } } } if (called_by_mondo) { # MBR too open BLD, "$MondoTemp/BOOTLOADER.DEVICE" and do { my $dev = ; chomp $dev; loggit "Backing up ${dev}'s MBR"; system "dd if=$dev of=$bigdir/BOOTLOADER.MBR bs=512 count=1 >>$Logfile 2>>$Logfile" and logit "*** Can't dd MBR of $dev to $bigdir/BOOTLOADER.MBR: exit $?"; loggit "Creating /dev/boot_device -> $dev"; mkdir_p "$bigdir/dev"; system 'cp', '-pR', "$dev", "$bigdir/dev/boot_device" and logit "Warning - Can't copy $dev to $bigdir/dev/boot_device"; close BLD; system "cp $MondoTemp/BOOTLOADER.* $bigdir/ >>$Logfile 2>&1"; }; # NFS stuff if (-e "$MondoTemp/start-nfs") { loggit "NFS backup detected."; print "Copying NFS settings: "; copy "$MondoTemp/start-nfs", "$bigdir/sbin/start-nfs" or fatal_error "Can't copy start-nfs: $!"; chmod 0755, "$bigdir/sbin/start-nfs" or logit "Warning: Could not chmod +x start-nfs: $!"; print "start-nfs "; for my $parm ('SERVER-MOUNT', 'SERVER-PATH', 'DEV', 'CLIENT-IPADDR', 'SERVER-IPADDR') { copy "$MondoTemp/NFS-$parm", "$bigdir/tmp/NFS-$parm" or fatal_error "Can't copy NFS-$parm: $!"; print "NFS-$parm "; } print "done.\n"; } } # embleer - phased out #copy ("$Homes{'mindi'}/embleer.B.bz2", "$bigdir/embleer.B.bz2"); #copy ("$Homes{'mindi'}/embleer.C.bz2", "$bigdir/embleer.C.bz2"); #loggit "MDD: copied embleer"; # aux-tools if (-d "$Homes{'mindi'}/aux-tools") { system "cp -R $Homes{'mindi'}/aux-tools/* $bigdir/" and logit "*** Error copying aux-tools: exit $?"; } loggit "MDD: copied aux-tools OK"; if ($Homes{'mondo'} and -d "$Homes{'mondo'}/restore-scripts") { if (system "cp -R $Homes{'mondo'}/restore-scripts/* $bigdir/") { logit "*** Error copying restore-scripts"; fatal_error "These are needed for Mondo" if called_by_mondo; } else { loggit "MDD: copied restore-scripts OK"; } } # post-nuke if (called_by_mondo and -e "$MondoTemp/post-nuke.tgz") { untar_files "$MondoTemp/post-nuke.tgz", "$bigdir/" or fatal_error "Error extracting post-nuke tarball"; loggit "MDD: copied post-nuke tarball OK"; } else { loggit "MDD: no post-nuke tarball to copy"; } $noof_disks = divide_bigdir_into_minidirs $bigdir, $MaxDiskSize, $minidir_root; make_mountlist ("$TempDir/mountlist.txt"); mkdir_p "$minidir_root/1/tmp", "$minidir_root/1/proc"; system "touch $minidir_root/$noof_disks/LAST-DISK"; copy "$TempDir/mountlist.txt", "$bigdir/tmp/mountlist.txt"; copy "$TempDir/mountlist.txt", "$minidir_root/1/tmp/mountlist.txt"; if (-d "/proc/lvm" or -d "/dev/mapper") { open ANALYZE_MY_LVM, "$Homes{'mindi'}/analyze-my-lvm |" or fatal_error "Can't open pipe from analyze-my-lvm: $!"; open I_WANT_MY_LVM, ">$TempDir/i-want-my-lvm" or fatal_error "Can't write to $TempDir/i-want-my-lvm: $!"; while () { print I_WANT_MY_LVM $_; } copy "$TempDir/i-want-my-lvm", "$bigdir/tmp/i-want-my-lvm"; copy "$TempDir/i-want-my-lvm", "$minidir_root/1/tmp/i-want-my-lvm"; } system 'touch "$minidir_root/$noof_disks/LAST-DISK"'; zip_minidirs_into_tarballs ($minidir_root, $tardir, $noof_disks); # rejig_symlinks ($minidir_root, $noof_disks); create_data_disk_images_from_tarballs ($noof_disks, $tardir); return $noof_disks; } # ############################################################ # # move_symlink_sensibly($$$$) - move symlink to right disk # # # $_[0] - the file # # # $_[1] - the minidir_root # # # $_[2] - the disk it's on # # # $_[3] - the number of disks total # # ############################################################ # sub move_symlink_sensibly { # my $symlink = shift || fatal_error "Too few arguments to move_symlink_sensibly - want 4, got 0"; # my $minidir_root = shift || fatal_error "Too few arguments to move_symlink_sensibly - want 4, got 1"; # my $cur_disk = shift || fatal_error "Too few arguments to move_symlink_sensibly - want 4, got 2"; # my $noof_disks = shift || fatal_error "Too few arguments to move_symlink_sensibly - want 4, got 3"; # my $tmp_disk; # for ($tmp_disk = 1; $tmp_disk <= $noof_disks; ++$tmp_disk) { # loggit "Warning! move_symlink_sensibly() was called, but I don't know what to do!"; # # XXX What is this function supposed to do? # } # } # ############################################################ # # rejig_symlinks($$) - fix symlinks on minidirs # # # $_[0] - minidir root # # # $_[1] - number of disks # # ############################################################ # sub rejig_symlinks { # my $minidir_root = shift || fatal_error "Too few arguments to rejig_symlinks - want 2, got 0"; # my $noof_disks = shift || fatal_error "Too few arguments to rejig_symlinks - want 2, got 1"; # my $cur_disk; # for ($cur_disk = 1; $cur_disk <= $noof_disks; ++$cur_disk) { # chdir "$minidir_root/$cur_disk" # or fatal_error "Can't chdir to $minidir_root/$cur_disk: $!"; # find (sub { move_symlink_sensibly $_, $minidir_root, $cur_disk, $noof_disks if -l }, '.'); # } # } ############################################################ # find_exec() - can I find some program? # # $_[0] - the program # # RET - true (found) or false (not) # ############################################################ sub find_exec { my @path = split /:/, $ENV{'PATH'}; my $prog = shift || fatal_error "Too few arguments to find_exec()"; foreach my $pathitem (@path) { return "$pathitem/$prog" if -e "$pathitem/$prog"; } return; } ############################################################ # check_system() - some basic sanity checks # ############################################################ sub check_system { if (determine_if_called_by_mondo) { find_exec ("afio") or fatal_error "Mondo needs afio. Mondo called me. I shall abort."; } find_exec "strings" or fatal_error "Please install binutils - you have no strings."; find_exec "mke2fs" or fatal_error "Where's mke2fs?"; find_exec "mkdosfs" or find_exec "mkfs.vfat" or fatal_error "Where's mkfs.vfat? [Please install dosfstools.]"; find_exec "syslinux" or do { logit "WARNING - you have no syslinux; boot disk will use LILO"; $UseLILO = 1; }; -e "/etc/modules.conf" or fatal_error "What happened to /etc/modules.conf?"; find_isolinux_path(); $MountCmd = find_exec "mount"; $UnmountCmd = find_exec "umount"; fatal_error "Can't find mount" if not defined $MountCmd; fatal_error "Can't find umount" if not defined $UnmountCmd; loggit "System passed Mindi's sanity check."; } ############################################################ # usage() - print a usage message # ############################################################ sub usage { my $fh; if (-t STDOUT) { open $fh, "| @{[ $ENV{'PAGER'} || 'less' ]}"; } else { $fh = *STDOUT{IO}; } print $fh < '' For mindi --custom, all arguments with a ? at the end should be 'yes' or 'no'. Default is 'no' if there is a minus sign at the end of the help for it, 'yes' if there is a plus sign. Lists are indicated with a plus sign at the end of the help for that item and should be separated by '|', ' ', or ','. Any argument can be omitted by making it empty (''). -c [--prompt-cd] : prompt if CD image wanted, otherwise just make it -C [--max-csize] SIZ : max size of stuff to put on floppy (Kb) [1200] -f [--make-floppies] : ask if floppies wanted as well as CD, otherwise skip -F [--max-fsize] SIZ : maximum size of file before it is chopped [$ChopAt] -h [--help] [-?] : this screen -i [--image-dir] DIR : directory to put generated images in [$ImagesDir] -l [--log[file]] LOG : logfile [$Logfile] -L [--use-lilo] : use LILO (not syslinux) for boot disk. -k [--kernel] KERNEL : path to your kernel [will try to find] -m [--mondo] : assume called by mondo -M [--nomondo] : assume not called by mondo -o [--option] K=V : Set a mindi.conf option (K is the option name, V is the value) -p [--pid-file] FILE : file to register PID in -P [--force-pid] : kill other Mindi processes instead of aborting -t [--temp-dir] DIR : temporary directory to use [$TempDir] -v [--log-verbose] : log a LOT of information to the logfile (as much as 33K!) -x [--extra-space] X : amount of extra space on the ramdisk [$ExtraSpace] -X [--chop-size] SIZ : size of file slices [$ChopSize] -2 [--only-2880] : Only make 2880k floppies, no 1722k ones EOU close $fh if -t STDOUT; exit 1; } ############################################################ # add_to_cfg($*) - add file to Mondo configuration file # # $_[0] - config var to add # # $_[1] - reference to cfg filehandle # ############################################################ sub add_to_cfg { my $var = shift || fatal_error "Too few arguments to add_to_cfg - want 2, got 0"; my $fh = shift || fatal_error "Too few arguments to add_to_cfg - want 2, got 1"; my $file = "$MondoTemp/@{[ uc $var ]}"; my $res; if ((ref $fh) !~ /IO/) { fatal_error "You gave me a @{[ ref $fh ]} reference, but I want an IO::Handle reference.\nTry `*CFG{IO}'."; } return unless -e $file; open VAR, $file or fatal_error "$file exists, but I couldn't open it: $!"; $res = ; chomp $res; print $fh "$var $res\n"; close VAR; } ############################################################ # make_mondo_config($) - make Mondo configuration file # # $_[0] - place to put it # ############################################################ sub make_mondo_config { open CFG, "> $_[0]" or fatal_error "MMC: can't open $_[0]: $!"; print CFG "media-size $TapeSize\n" if $TapeSize; print CFG "media-dev $TapeDevice\n" if $TapeDevice; print CFG "files-in-filelist $FilesInFilelist\n" if $FilesInFilelist; print CFG "last-filelist-number $LastFilelistNum\n" if $LastFilelistNum; if ($UsingLZO) { print CFG "use-lzo yes\n"; } else { print CFG "use-lzo no\n"; } if ($UsingCompression) { print CFG "use-comp yes\n"; } else { print CFG "use-comp no\n"; } print CFG "datestamp @{[ time ]}\n"; print CFG "total-slices $EstNoofSlices\n" if $EstNoofSlices; add_to_cfg "nfs-client-ipaddr", *CFG{IO}; add_to_cfg "nfs-server-mount", *CFG{IO}; add_to_cfg "nfs-server-path", *CFG{IO}; add_to_cfg "nfs-dev", *CFG{IO}; add_to_cfg "nfs-server-ipaddr", *CFG{IO}; add_to_cfg "iso-dev", *CFG{IO}; add_to_cfg "isodir", *CFG{IO}; add_to_cfg "bootloader.device", *CFG{IO}; add_to_cfg "bootloader.name", *CFG{IO}; add_to_cfg "keymap-lives-here", *CFG{IO}; add_to_cfg "tapedev-has-data-disks", *CFG{IO}; add_to_cfg "backup-media-type", *CFG{IO}; add_to_cfg "differential", *CFG{IO}; close CFG; } ############################################################ # get_ptype_of($) - get partition type of a partition # # Returns - the two-letter hexcode # ############################################################ sub get_ptype_of { my $device = shift; my $ptno = $device; my $disk = $device; $ptno =~ s/.*(\d+)$/$1/; $disk =~ s/\d+$//; sprintf "%02x", get_partition_type($disk, $ptno); } ############################################################ # make_mountlist($) - create mountlist of partitions # # $_[0] - the file to put it in # ############################################################ sub make_mountlist { my (@partitions, @raid_partitions); my @ignorepatterns = ("/dev/a?f(d|loppy/)[0-9]+([.uhH][0-9]+)?", # floppy drive (including IDE Zip) "/dev/([as]?cd(rom)?(s/cdrom)?.*|sr)[0-9]*", # CD-ROMs "/dev/(sa|ht|st|ast)[0-9]+", # tape drives (why could they show up? beyond me) "(([0-9]{1,3}\.){3}[0-9]{1,3}|[a-z.-]+):/.*", # NFS mounts (both DNS and IP) ); my (%curline, @curline); my @fstab_lines; my @unmount_these; my $mountlist = shift || fatal_error "Too few args to make_mountlist() - want 1, got 0"; if (-d "/proc/lvm" or -d "/dev/mapper") { open LVM_ANALYZER, "$Homes{'mindi'}/analyze-my-lvm |" or fatal_error "Can't open from analyze-my-lvm: $!"; while () { chomp; if (/>>>/) { s/^>+\s*//; @partitions = (@partitions, split); } } close LVM_ANALYZER; } open FSTAB, "/etc/fstab" or fatal_error "Can't open /etc/fstab: $!"; while () { my $comment = '#'; next if /^$comment/; next if /^$/; next if /(floppy|fd)|cd(rom)?|dvd|zip|media|^\/proc|^\/dev\s+|^\/dev\/pts|devpts/; next if m[^/sys\s+] && $KernelVersion =~ /2.6/; chomp; push @fstab_lines, $_; @curline = split; next if $curline[3] =~ /noauto/ and $curline[1] !~ /boot/; if ($curline[0] =~ /dev/) { push @partitions, $curline[0]; } elsif ($curline[0] =~ m|LABEL=([a-zA-Z0-9./-]+)|) { my $label = $1; my $found = 0; my $needs_umount = 0; unless (grep { m[$label] } `mount -l`) { system "$MountCmd $curline[1]" and fatal_error "LABEL=$label is not mounted and I can't mount it."; $needs_umount = 1; } open MOUNT_L, "$MountCmd -l |" or fatal_error "Can't open pipe from mount to read labels: $!"; while () { chomp; if (/\[$label\]/) { if (/^(.*) on .* type .*/) { $found = 1; push @partitions, $1; push(@unmount_these, $1), $needs_umount = 0 if $needs_umount; } } } close MOUNT_L; if (not $found) { fatal_error "Couldn't resolve LABEL=$label; are you *sure* that device is mounted?"; } } } close FSTAB; if (-e "/etc/raidtab") { if (open RAIDTAB, "/etc/raidtab") { while () { my @raidtabline; next unless /^\s*device/; chomp; s/^\s+//; @raidtabline = split; if (scalar @raidtabline >= 2) { push @partitions, $raidtabline[1]; push @raid_partitions, $raidtabline[1]; } } close RAIDTAB; } else { logit "*** Can't open /etc/raidtab [$!] -- not sure about your RAID configuration"; } } foreach my $imagedev (@ImageDevs) { if (grep { m[$imagedev] } `mount`) { fatal_error "Cannot imagedev $imagedev when it's mounted! Please unmount it and try again."; } push @partitions, $imagedev unless grep { m[$imagedev] } @partitions; } open MOUNTLIST, "> $mountlist" or fatal_error "Can't open $mountlist for writing: $!"; logit "\nYour mountlist will look like this:-"; # | # 80th column here v format STDOUT_TOP = @<<<<<<<<<<<<<<<<<< @<<<<<<<<<<<<<<<< @<<<<<<<< @>>>>>>>>>>>> @>>>>>>>>>>>>>>>> "PARTITION", "MOUNTPOINT", "FSTYPE", "SIZE (KB)", "[LABEL]" ------------------- ----------------- --------- ------------- ----------------- . format STDOUT = @<<<<<<<<<<<<<<<<<< @<<<<<<<<<<<<<<<< @<<<<<<<< @>>>>>>>>>>>> @<<<<<<<<<<<<<<<< $curline{'partition'}, $curline{'mountpoint'}, $curline{'fstype'}, $curline{'size'}, $curline{'label'} . PARTITION: foreach (@partitions) { foreach my $ipat (@ignorepatterns) { next PARTITION if /$ipat/; } next PARTITION if grep { m[$_] } @ExcludeDevs; my $partition = $_; my $search_with = $_; my $neededmount = 0; open MOUNT_L, "$MountCmd -l |" or fatal_error "Can't open pipe from mount to read labels: $!"; while () { chomp; if (/$partition/) { if (/\[(.*)\]/) { $search_with .= "|LABEL=$1"; # regex: alternatives (partition|LABEL=label) } } } close MOUNT_L; $_ = $partition; my($alternative) = ""; if ($LVMVersion == 2 and -d "/dev/mapper") { ($alternative = $partition) =~ s[/mapper/vg([0-9]*)-][/vg$1/]g; $alternative =~ s/\-\-/-/g; if ($alternative ne $partition) { $search_with .= "|$alternative"; } } $curline{'found'} = 0; # LVM is completely braindead about its return code... if (grep { /Logical volume/ } `lvdisplay $_ 2>/dev/null`) { $curline{'size'} = "lvm"; } elsif ($LVMVersion == 2 and -d "/dev/mapper" and grep { /.../ } `lvm lvdisplay $_ 2>/dev/null`) { $curline{'size'} = "lvm"; } else { $curline{'size'} = get_size_of_disk $_; } foreach my $fstab_line (@fstab_lines) { if ($fstab_line =~ /^($search_with)\s/) { $fstab_line =~ s/\s+/ /; @curline = split ' ', $fstab_line; $curline{'found'} = 1; $curline{'partition'} = $_; $curline{'mountpoint'} = $curline[1]; $curline{'fstype'} = $curline[2]; if ($search_with =~ /LABEL/) { $search_with =~ s/.*LABEL=//; $curline{'label'} = $search_with; } last; } } if (not $curline{'found'}) { if (grep { m[$_] } @raid_partitions) { $curline{'found'} = 1; $curline{'partition'} = $_; $curline{'mountpoint'} = "raid"; $curline{'fstype'} = "raid"; } } if (not $curline{'found'}) { if (grep { /Physical volume/ } `pvdisplay $_ 2>/dev/null` or grep { /.../ } `lvm pvdisplay $_ 2>/dev/null`) { $curline{'found'} = 1; $curline{'fstype'} = "lvm"; $curline{'mountpoint'} = "lvm"; $curline{'partition'} = $_; } } if (grep { m[$_] } @ImageDevs) { $curline{'found'} = 1; $curline{'fstype'} = get_ptype_of $_; $curline{'partition'} = $_; $curline{'mountpoint'} = "image"; $curline{'size'}++; } fatal_error "Can't find $_!" unless $curline{'found'}; fatal_error "Can't find mountpoint of $curline{'partition'}" unless $curline{'mountpoint'}; fatal_error "Can't find fstype of $curline{'partition'}" unless $curline{'fstype'}; fatal_error "Can't find size of $curline{'partition'}" unless $curline{'size'}; $curline{'fstype'} = "swap" if $curline{'mountpoint'} eq "swap"; $curline{'mountpoint'} = "swap" if $curline{'fstype'} eq "swap"; $curline{'label'} = "" unless defined $curline{'label'}; if ($alternative ne "") { $curline{'partition'} = $alternative; } print MOUNTLIST "$curline{'partition'} $curline{'mountpoint'} $curline{'fstype'} $curline{'size'} $curline{'label'}\n"; loggit "$curline{'partition'} $curline{'mountpoint'} $curline{'fstype'} $curline{'size'} $curline{'label'}"; # We've already written to the logfile + mountlist: let's shorten the stuff a little. $curline{'label'} = "[no label]" unless $curline{'label'}; $curline{'partition'} =~ s/.{4,}(.{16})/...$1/; $curline{'mountpoint'} =~ s/.{4,}(.{14})/...$1/; $curline{'label'} =~ s/.{4,}(.{14})/...$1/; write; $curline{'label'} = ""; } for (@unmount_these) { loggit "$_ was mounted temporarily. Unmounting... "; system "$UnmountCmd $_" and logit "Warning: FAILED to unmount $_ again"; } close MOUNTLIST; } ############################################################ # write_one_liner($$) - write one line file # # $_[0] - the file # # $_[1] - the line # ############################################################ sub write_one_liner { my $file = shift || fatal_error "Too few arguments to write_one_liner - want 2, got 0"; open WRITER, "> $file" or fatal_error "Can't open $file for writing: $!"; print WRITER @_, "\n"; close WRITER; } ############################################################ # zip_minidirs_into_tarballs($$$) - self-explanatory # # $_[0] - the minidir root # # $_[1] - the tardir # # $_[2] - the number of disks # ############################################################ sub zip_minidirs_into_tarballs { my $minidir_root = shift || fatal_error "Too few arguments to zip_minidirs_into_tarballs() - want 3, got 0"; my $tardir = shift || fatal_error "Too few arguments to zip_minidirs_into_tarballs() - want 3, got 1"; my $noof_disks = shift || fatal_error "Too few arguments to zip_minidirs_into_tarballs() - want 3, got 2"; my $compressed_tool_size; my @disk_sizes; mkdir_p $tardir; mkdir_p "$minidir_root/all/tmp"; print "\nTarring and zipping the groups...\n" if called_by_mondo; print "\nTarring and zipping the groups:\t\t[>", ' ' x 29, "] 0% |" unless called_by_mondo; for (my $cur_disk = 1; $cur_disk <= $noof_disks; ++$cur_disk) { chdir "$minidir_root/$cur_disk" or fatal_error "Can't chdir to $minidir_root/$cur_disk: $!"; tar_up_files ("$tardir/$cur_disk.tar.gz", ".", "Tarring and zipping the groups:\t\t") # , 2 * $noof_disks, ($cur_disk - 1) * (100 / (2 * $noof_disks))) or fatal_error "Can't tar/gzip disk $cur_disk!"; system "cp -pRf * $minidir_root/all/" and fatal_error "cp -pRf @{[ <*> ]} $minidir_root/all/ failed: exit $?"; copy "$tardir/$cur_disk.tar.gz", "$ImagesDir/$cur_disk.tar.gz" or fatal_error "Can't copy $tardir/$cur_disk.tar.gz to $ImagesDir/: $!"; if ($ImagesDir ne "/root/images/mindi") { copy "$tardir/$cur_disk.tar.gz", "/root/images/mindi/$cur_disk.tar.gz"; } $disk_sizes[$cur_disk] = `du -sk $tardir/$cur_disk.tar.gz | cut -f1 | tr -d '\n'`; print_progress $cur_disk, $noof_disks + 1, "Tarring and zipping the groups:\t\t"; } for (my $cur_disk = 1; $cur_disk <= $noof_disks; ++$cur_disk) { loggit "Disk $cur_disk is $disk_sizes[$cur_disk]K"; } chdir "$minidir_root/all" or fatal_error "Can't chdir to $minidir_root/all: $!"; $ToolSize = `du -sk . | cut -f1 | tr -d '\n'`; if (called_by_mondo) { copy "$MondoTemp/filelist.full", "tmp/filelist.full" or fatal_error "Could not copyin filelist.full: $!"; system "gzip tmp/filelist.full" or fatal_error "Could not compress filelist.full: $!"; copy "$MondoTemp/biggielist.txt", "tmp/biggielist.txt" or fatal_error "Could not copyin biggielist.txt: $!"; write_one_liner "$minidir_root/all/tmp/FILES-IN-FILELIST", $FilesInFilelist; write_one_liner "$minidir_root/all/tmp/LAST-FILELIST-NUMBER", $LastFilelistNum; } tar_up_files "$tardir/all.tar.gz", "-b 4096", glob "*" or fatal_error "Could not tar up all.tar.gz: exit $?"; copy "$tardir/all.tar.gz", "$ImagesDir/all.tar.gz" or fatal_error "Could not copy all.tar.gz to imagedir: $!"; if ($ImagesDir ne "/root/images/mindi") { copy "$tardir/all.tar.gz", "/root/images/mindi/all.tar.gz"; } print_progress $noof_disks + 1, $noof_disks + 1, "Tarring and zipping the groups:\t\t"; $compressed_tool_size = `du -sk $tardir/all.tar.gz | cut -f1 | tr -d '\n'`; fatal_error "You have too many tools in your shed!" if $compressed_tool_size >= 30*1024; } ############################################################ # make_zfile() - make a file full of zeroes # # $_[0] - the size in kilobytes # # $_[1] - the file # ############################################################ sub make_zfile { my $size = shift || fatal_error "Too few arguments to make_zfile() - want 2, got 0"; my $file = shift || fatal_error "Too few arguments to make_zfile() - want 2, got 1"; system "dd if=/dev/zero of=$file bs=1k count=$size >/dev/null 2>/dev/null"; } ############################################################ # create_tiny_filesystem() - like it says # # $_[0] - device to create on # ############################################################ sub create_tiny_filesystem { my $device = shift || fatal_error "Too few arguments to create_tiny_filesystem() - want 1, got 0"; my $inodes = shift || 200; my $inodes_str = ""; $inodes_str = "-N $inodes" unless $inodes == -1; system "mke2fs -m 0 $inodes_str -F $device >>$Logfile 2>>$Logfile"; } ############################################################ # create_data_disk_images_from_tarballs() - like it says # # $_[0] - number of disks # # $_[1] - tarball directory # ############################################################ sub create_data_disk_images_from_tarballs { my $noof_disks = shift || fatal_error "Too few arguments to create_data_disk_images_from_tarballs() - want 2, got 0"; my $tardir = shift || fatal_error "Too few arguments to create_data_disk_images_from_tarballs() - want 2, got 1"; my $cur_disk; if (called_by_mondo) { print "Creating data disks...\n"; } else { print "Creating data disks:\t\t\t[>", ' ' x 29, "] 0% |"; } for ($cur_disk = 1; $cur_disk <= $noof_disks; ++$cur_disk) { my ($mountpoint, $file); make_zfile 1440, "$ImagesDir/mindi-data-$cur_disk.img"; $file = "$ImagesDir/mindi-data-$cur_disk.img"; $mountpoint = "$TempDir/mountpoint.$$"; mkdir_p $mountpoint; create_tiny_filesystem $file, 12; # 12 inodes system "$MountCmd -t ext2 -o loop $file $mountpoint" and fatal_error "Can't loopmount $file at $mountpoint: exit $?"; push @Mountpoints, $mountpoint; # save in case of crash copy "$tardir/$cur_disk.tar.gz", "$mountpoint/$cur_disk.tar.gz" or fatal_error "Can't copy $tardir/$cur_disk.tar.gz to $mountpoint/$cur_disk.tar.gz: $!"; system "$UnmountCmd $mountpoint" and logit "\nWarning - unable to umount $mountpoint (exit $?)"; pop @Mountpoints; if ($ImagesDir ne "/root/images/mindi") { copy $file, "/root/images/mindi/"; } print_progress $cur_disk, $noof_disks, "Creating data disks:\t\t\t"; } } ############################################################ # replace_line($$@) - replace a line in a file # # $_[0] - the file to look in # # $_[1] - the text in the line to repl # # $_[2] - the stuff to replace it with # ############################################################ sub replace_line { my $file = shift || fatal_error "Too few arguments to replace_line() - want 3+, got 0"; my $search = shift || fatal_error "Too few arguments to replace_line() - want 3+, got 1"; my @replace = @_; move $file, "$file.bak" or fatal_error "Can't move $file to $file.bak: $!"; open OLD, "$file.bak" or fatal_error "Can't open $file.bak for reading: $!"; open NEW, "> $file" or fatal_error "Can't open $file for writing: $!"; while () { if (/$search/) { foreach my $line (@replace) { print NEW $line, "\n"; } } else { print NEW $_; } } my $mode = 0; (undef, undef, $mode) = stat "$file.bak"; if ($mode != 0) { chmod $mode & 07777, $file; } close OLD; close NEW; } ############################################################ # make_module_loader($) - make script to load klds # # $_[0] - the script # ############################################################ sub make_module_loader { my $script = shift || fatal_error "Too few arguments to make_module_loader() - want 1, got 0"; my $kver; open SCRIPT, "> $script" or fatal_error "Can't open $script for writing: $!"; print SCRIPT "#!/bin/sh\n# module loading script\n# generated by mindi $Version\n\n"; print SCRIPT "echo -n 'Loading your modules...'\n\n"; if ($KernelSucks) { chdir $TempDir; $kver = $FailsafeKernelVersion; } else { chdir "/"; (undef, undef, $kver) = uname(); } print SCRIPT "for outerloop in 1 2 3; do true;\n"; for my $module (@ExtraMods, get_kmods()) { my ($params, @modpaths); $params = ""; open MODULES_CONF, "/etc/modules.conf" or fatal_error "Can't open /etc/modules.conf: $!"; while () { chomp; next unless /\s*options\s+$module\s+(.*)/; $params = $1; last; } close MODULES_CONF; @modpaths = find_specific_kmod_in_path ("lib/modules/$kver", $module); foreach my $submod (@modpaths) { $submod =~ s/\.gz$//; print SCRIPT "\t# Load $submod.$ModuleSuffix\n"; print SCRIPT "\tif test -f $submod; then\n"; print SCRIPT "\t\tif insmod $submod $params >/dev/null 2>&1; then\n"; print SCRIPT "\t\t\techo -n .\n\t\tfi\n\tfi\n\n"; } } print SCRIPT "done\n"; print SCRIPT "\necho 'Done.'\n"; close SCRIPT; chmod 0755, $script; } ############################################################ # make_ramdisk($$$) - self-explanatory # # $_[0] - the directory to make from # # $_[1] - the filename of the ramdisk # # $_[2] - the size of the ramdisk # ############################################################ sub make_ramdisk { my $dirname = shift || fatal_error "Too few arguments to make_ramdisk() - want 3, got 0"; my $ramdisk = shift || fatal_error "Too few arguments to make_ramdisk() - want 3, got 1"; my $ramsize = shift || fatal_error "Too few arguments to make_ramdisk() - want 3, got 2"; my $tempram = "$TempDir/temp.rd"; my $mountpt = "$TempDir/mountpoint.$$"; my $kver; my @groovy_mods = @CDMods; $RamdiskSize = $ramsize; print "Creating ramdisk..."; print "\n" if called_by_mondo; make_zfile $ramsize, $tempram; print "..." unless called_by_mondo; create_tiny_filesystem $tempram, 65536; print "..." unless called_by_mondo; mkdir_p $mountpt; system "$MountCmd -t ext2 -o loop $tempram $mountpt" and fatal_error "Can't mount $tempram on $mountpt (exit $?)"; push @Mountpoints, $mountpt; print "..." unless called_by_mondo; system "cp -R $dirname/* $mountpt/"; finddepth sub { system "rm -rf $_" if /CVS$/; }, $mountpt; # system 'find', $mountpt, '-depth', '-name', 'CVS', '-exec', 'rm', '-rf', '{}', ';'; mkdir_p "$mountpt/dev"; untar_files "$dirname/dev/dev-entries.tgz", "$mountpt/dev"; replace_line "$mountpt/sbin/init", "#WHOLIVESINAPINEAPPLEUNDERTHESEA#", @AfterInitialPhase; replace_line "$mountpt/sbin/init", "#ABSORBENTANDYELLOWANDPOROUSISHE#", @PostBoot; print "..." unless called_by_mondo; make_module_loader "$mountpt/sbin/insert-all-my-modules"; print "..." unless called_by_mondo; mkdir_p "$mountpt/tmp"; if (called_by_mondo) { make_mondo_config "$mountpt/tmp/mondo-restore.cfg"; copy "$mountpt/tmp/mondo-restore.cfg", "$MondoTemp/mondo-restore.cfg"; copy "$TempDir/mountlist.txt", "$mountpt/tmp/mountlist.txt" or fatal_error "Can't copy mountlist to ramdisk: $!"; write_one_liner "$mountpt/tmp/TAPEDEV-LIVES-HERE", $TapeDevice if $TapeDevice; write_one_liner "$mountpt/tmp/FILES-IN-FILELIST", $FilesInFilelist; write_one_liner "$mountpt/tmp/LAST-FILELIST-NUMBER", $LastFilelistNum; write_one_liner "$mountpt/tmp/USING-LZO", "Pras 4 Pres 2004" if $UsingLZO; write_one_liner "$mountpt/tmp/USING-COMP", "Compression, yep" if $UsingCompression; } write_one_liner "$mountpt/tmp/2880.siz", "2880"; open LSMOD, "lsmod |" or fatal_error "Can't open pipe from lsmod: $!"; while () { my @line; chomp; next if /Module/; # skip header line @line = split; push @groovy_mods, $line[0] if grep { /$line[0]/ } @SCSIMods; } close LSMOD; if ($KernelSucks) { $kver = $FailsafeKernelVersion; chdir $TempDir; } else { (undef, undef, $kver) = uname(); chdir "/"; } # If NFS/PXE add related modules and start-nfs if (-e "$MondoTemp/start-nfs") { @groovy_mods = (@groovy_mods,@NetMods); copy "$mountpt/sbin/start-nfs", "$MondoTemp/start-nfs"; # Here we need the net busybox move "$mountp/busybox.net", "$mountp/busybox"; } else { unlink "$mountp/busybox.net"; foreach my $module (@groovy_mods) { my @fullmods = find_specific_kmod_in_path ("lib/modules/$kver", $module); foreach my $submod (@fullmods) { copy $submod, "$mountpt/$module.$ModuleSuffix"; if ($submod =~ /\.gz/) { move "$mountpt/$module.$ModuleSuffix", "$mountpt/$module.$ModuleSuffix.gz"; system "gunzip $mountpt/$module.$ModuleSuffix.gz"; } } } print "..." unless called_by_mondo; if ($LogVerbose) { loggit "Creating symlinks... Here is the table:"; loggit Dumper \%RootfsSymlinks; } chdir $mountpt; for my $link (keys %RootfsSymlinks) { unless (-e $link or -l $link) { mkdir_p dirname $link; loggit "Symlink $link => $RootfsSymlinks{$link}" if $LogVerbose; # weird syntax here: symlink $target, $link ==> symlink $link to $target symlink $RootfsSymlinks{$link}, $link or do { loggit `ls -lR`; fatal_error "Can't symlink $link to $RootfsSymlinks{$link}: $!"; } } } chdir "/"; system "$UnmountCmd $mountpt" and fatal_error "Can't umount $mountpt (exit $?)"; pop @Mountpoints; system "gzip -9 -c $tempram > $ramdisk"; print " done.\n" unless called_by_mondo; } ############################################################ # edit_ramdisk(\&$$;$) - do something to the ramdisk # # $_[0] - the thing to do (sub ref) # # $_[1] - the input ramdisk # # $_[2] - the output ramdisk # # [OPTIONAL] $_[3] - the progress message # ############################################################ sub edit_ramdisk { my $sub = shift || fatal_error "Too few arguments to edit_ramdisk - want 3-4, got 0"; my $iram = shift || fatal_error "Too few arguments to edit_ramdisk - want 3-4, got 1"; my $oram = shift || fatal_error "Too few arguments to edit_ramdisk - want 3-4, got 2"; my $message = shift; my $tempram = "$TempDir/editram.rd"; my $mountpoint = "$TempDir/editram.$$"; my $reftype = ref $sub; fatal_error "You didn't give me a ref for arg 0 (you passed $sub; I want a CODE ref)" unless defined $reftype; fatal_error "You gave me a $reftype ref, but I want a CODE ref" unless $reftype =~ /CODE/i; if (defined $message) { print $message; print "\n" if called_by_mondo; } system "gzip -dc $iram > $tempram" and fatal_error "Can't unzip $iram to $tempram: exit $?"; mkdir_p $mountpoint; system "$MountCmd -t ext2 -o loop $tempram $mountpoint" and fatal_error "Can't mount $tempram on $mountpoint: exit $?"; push @Mountpoints, $mountpoint; chdir $mountpoint; $sub->($mountpoint); chdir "/"; system "$UnmountCmd $mountpoint" and logit "Warning! Can't umount $tempram (mounted on $mountpoint): exit $?"; pop @Mountpoints; system "dd if=$tempram 2>>$Logfile | gzip -9 >$oram" and fatal_error "Can't compress ramdisk: exit $?"; unlink $tempram; if (defined $message) { if (called_by_mondo) { print "Done.\n"; } else { print " done.\n"; } } } ############################################################ # prepare_boot_disks($) - make the boot disk(s) # # $_[0] - ramdisk size # # $_[1] - disk size (1722 or 2880) # ############################################################ sub prepare_boot_disks { my $ramsize = shift || fatal_error "Too few arguments to prepare_boot_disks() - want 2, got 0"; my $disksize = shift || fatal_error "Too few arguments to prepare_boot_disks() - want 2, got 1"; my ($disk, $mountpoint, $distro); fatal_error "$ramsize is not an integer, so it can't be a ramsize" if $ramsize !~ /^[-+]?[0-9]+$/; if ($disksize == 2880) { make_ramdisk "$Homes{'mindi'}/rootfs", "$TempDir/mindi-2880.rdz", $ramsize; } elsif ($disksize == 1722) { edit_ramdisk sub { my $kver; our $ramdir = $_[0]; (undef, undef, $kver) = uname(); chdir $_[0]; unlink "sbin/devfsd"; unlink "tmp/2880.siz"; write_one_liner "tmp/1722.siz", "1722"; foreach my $module (@CDMods) { unlink "$module.$ModuleSuffix"; } if ($KernelSucks) { chdir "$TempDir"; $kver = $FailsafeKernelVersion } else { chdir "/"; } foreach my $module (@FloppyMods) { find sub { return unless /$module/; copy $_, $ramdir."/$module.$ModuleSuffix"; if (/\.gz/) { move "$ramdir/$module.$ModuleSuffix", "$ramdir/$module.$ModuleSuffix.gz"; system "gunzip $ramdir/$module.$ModuleSuffix.gz"; } }, "lib/modules/$kver"; } }, "$TempDir/mindi-2880.rdz", "$TempDir/mindi-1722.rdz", "Modifying the ramdisk for a 1722k floppy..."; } else { fatal_error "Bad size $disksize - want 1722 or 2880"; } print "Creating ${disksize}k boot disk..."; print "\n" if called_by_mondo; $disk = "$ImagesDir/mindi-boot.$disksize.img"; $mountpoint = "$TempDir/mountpoint.$$"; if ($UseLILO) { make_zfile $disksize, $disk; create_tiny_filesystem $disk, 26; # 26 inodes } elsif ($disksize == 2880) { make_zfile $disksize, $disk; system "mkfs.vfat $disk >>$Logfile 2>>$Logfile"; system "syslinux $disk" and fatal_error "Can't syslinux $disk: exit $?"; } else { system "gunzip -dc $Homes{'mindi'}/sys-disk.raw.gz > $disk"; } mkdir_p $mountpoint; my($fstype) = $UseLILO ? "ext2" : "msdos"; system "$MountCmd -t $fstype -o loop $disk $mountpoint" and fatal_error "Can't mount $disk on $mountpoint: exit $?"; push @Mountpoints, $mountpoint; mkdir "$mountpoint/etc"; untar_files "$Homes{'mindi'}/dev.tgz", "$mountpoint/" or fatal_error "Can't untar $Homes{'mindi'}/dev.tgz" if $UseLILO; if ($UseLILO) { if (not system "losetup /dev/loop0 >/dev/null 2>/dev/null") { if (system "losetup /dev/loop0 -d") { fatal_error "/dev/loop0 is being used, and Mindi couldn't free it. Please unmount\n/dev/loop0, deconfigure it (losetup /dev/loop0 -d), and re-run Mindi. If all else fails, reboot."; } } my ($sectors, $cylinders); if ($disksize == 2880) { $sectors = 36; $cylinders = 80; } else { $sectors = 21; $cylinders = 82; } open LILOCONF, "> $mountpoint/etc/lilo.conf" or fatal_error "Can't write to $mountpoint/etc/lilo.conf: $!"; print LILOCONF < $mountpoint/syslinux.cfg" or fatal_error "Can't open > $mountpoint/syslinux.cfg: $!"; while () { chomp; s/24000/$ramsize/; if (/append/) { $_ .= " $ExtraBootOptions"; } if (-e "$MondoTemp/start-nfs") { s/interactive/iso/; } print SYSLINUX_WRITE "$_\n"; } close SYSLINUX_READ; close SYSLINUX_WRITE; unlink "$mountpoint/syslinux.ofg"; } copy "$TempDir/mindi-${disksize}.rdz", "$ImagesDir/"; copy "$TempDir/mindi-${disksize}.rdz", "$mountpoint/initrd.img" or do { if (($disksize == 2880) && called_by_mondo) { fatal_error "Can't copy $TempDir/mindi-${disksize}.rdz to ./mindi.rdz: $!\nPlease unload some of your modules and try again."; } logit "WARNING - Can't copy $TempDir/mindi-${disksize}.rdz to ./mindi.rdz: $!\nPlease unload some of your modules and try again.\n(This error is non-fatal, BTW)."; return; # only if !fatal_error }; $distro = ""; open ISSUE_NET, "/etc/issue.net" and do { while () { chomp; if (/linux/i) { $distro = $_; } } close ISSUE_NET; }; open MSG_TXT, "$Homes{'mindi'}/msg-txt" or fatal_error "Can't open $Homes{'mindi'}/msg-txt: $!"; open MSG_OUT, "> $mountpoint/message.txt" or fatal_error "Can't write to $mountpoint/message.txt: $!"; while () { my $kver; my $date = `date`; if ($KernelSucks) { $kver = $FailsafeKernelVersion; } else { $kver = `uname -r`; chomp $kver; } chomp $date; s/ZZZZZ/$Version/; s/YYYYY/Mondo Rescue/; s/XXXXX/a cousin of/; s/DDDDD/$distro/; s/KKKKK/Kernel $kver/; s/TTTTT/$date/; print MSG_OUT $_; } close MSG_TXT; if (called_by_mondo) { if ($CDrecovery) { print MSG_OUT <. CAUTION: THIS WILL ERASE YOUR WHOLE DISK!!! EOF } elsif (-e "$MondoTemp/start-nfs") { print MSG_OUT "Press to continue.\n"; } else { print MSG_OUT $BootMediaMessage; } } else { print MSG_OUT "FYI, this is _not_ a Mondo Rescue CD.\n"; } print MSG_OUT "\n\n\n"; close MSG_OUT; my($need_rootdisk); loggit "PBD: \$mountpoint is $mountpoint"; copy $Kernel, "$mountpoint/vmlinuz" or do { if (($disksize == 2880) && called_by_mondo) { fatal_error "Sorry, your kernel is too big.\nEither recompile it to reduce its size, or use Mindi's failsafe kernel.\nTo use Mindi's failsafe kernel, run Mondo (or Mindi if you're running it standalone) with the -k FAILSAFE switch."; } # if we're here, we're not 2880k w/Mondo if ($disksize == 2880) { logit "WARNING - kernel is too big for ${disksize}K floppy"; logit "This error is non-fatal if you are using a Mindi CD (with isolinux)"; system "$UnmountCmd $mountpoint"; pop @Mountpoints; unlink $disk; return; } # 1722k, didn't fit - make root disk logit "Kernel is too big for one boot+root floppy"; logit "Therefore, a separate root floppy (1.44MB) will be created."; unlink "$mountpoint/initrd.img" or fatal_error "Tried to remove ramdisk to copy kernel but the ramdisk vanished!"; copy $Kernel, "$mountpoint/vmlinuz" or do { print "Kernel doesn't even fit when it's alone! Too big. Goodbye."; system "$UnmountCmd $mountpoint"; pop @Mountpoints; unlink $disk; return; }; system "rdev -R $Kernel 0" and fatal_error "Couldn't set kernel to mount rootfs in r/w mode!"; system "rdev -r $Kernel 49152" and fatal_error "Couldn't set kernel to boot from root floppy!"; $need_rootdisk = 1; }; if ($UseLILO) { system "lilo -r $mountpoint >>$Logfile 2>>$Logfile" and fatal_error "Error running LILO."; } chdir "/"; system "$UnmountCmd $mountpoint" and fatal_error "Can't umount $disk from $mountpoint: exit $?"; pop @Mountpoints; if ($ImagesDir ne "/root/images/mindi") { copy $disk, "/root/images/mindi/"; } if (called_by_mondo) { print "Done.\n"; } else { print " done.\n"; } if ($need_rootdisk) { print "Making root disk... "; copy "$TempDir/mindi-1722.rdz", "$ImagesDir/mindi-root.1440.img"; system "dd if=/dev/zero bs=1 count=" . 1024 - ((-s "$ImagesDir/mindi-root.1440.img") % 1024) . " >> $ImagesDir/mindi-root.1440.img"; system "dd if=/dev/zero bs=1k count=" . 1440 - ((-s "$ImagesDir/mindi-root.1440.img") / 1024) . " >> $ImagesDir/mindi-root.1440.img"; if ((-s "$ImagesDir/mindi-root.1440.img") != (1440 * 1024)) { fatal_error "Root disk is not exactly 1.44 MB!"; } if ($ImagesDir ne "/root/images/mindi") { copy "$ImagesDir/mindi-root.1440.img", "/root/images/mindi/"; } print " done.\n"; } } ############################################################ # make_floppy($$$) - copy an image to a real FD disk # # $_[0] - the image # # $_[1] - the floppy drive # # $_[2] - the description # ############################################################ sub make_floppy { my $image = shift || fatal_error "Too few arguments to make_floppy: want 3, got 0"; my $fd = shift || fatal_error "Too few arguments to make_floppy: want 3, got 1"; my $desc = shift || fatal_error "Too few arguments to make_floppy: want 3, got 2"; my $bufsize = shift || (-s $image) / 40; my $buf; my $read = 0; my $imgsize = -s $image; my ($infile, $outfloppy); print "Please insert a blank or expendable floppy for $desc.\n"; print "Press ENTER when ready, or type QUIT to abort: "; my $answer = ; return if $answer =~ /q/i; print "Formatting $desc.\n"; system "fdformat -y $fd" and fatal_error "Could not format $fd - please check above messages."; open $infile, "< $image" or fatal_error "Can't open $image for reading: $!"; open $outfloppy, "> $fd" or logit "Skipping $desc: Can't open $fd for writing: $!", return; autoflush $outfloppy 1; print "Writing $desc...\n" if called_by_mondo; while (sysread $infile, $buf, $bufsize) { syswrite $outfloppy, $buf; $read += $bufsize; $read = $imgsize if $read > $imgsize; print_progress $read, $imgsize, "Writing $desc:\t\t\t\t" unless called_by_mondo; } close $infile; close $outfloppy; } ############################################################ # copy_images_to_floppies($) - self-explanatory # # $_[0] - number of disks # ############################################################ sub copy_images_to_floppies { my $noof_disks = shift || fatal_error "Too few arguments to copy_images_to_floppies() - want 1, got 0"; my $cur_disk; print "WARNING: THIS WILL ERASE YOUR FLOPPIES!\n"; make_floppy "$ImagesDir/mindi-boot.1722.img", "/dev/fd0u1722", "boot floppy"; make_floppy "$ImagesDir/mindi-root.1440.img", "/dev/fd0", "root floppy" if -e "$ImagesDir/mindi-root.1440.img"; for ($cur_disk = 1; $cur_disk <= $noof_disks; ++$cur_disk) { make_floppy "$ImagesDir/mindi-data-$cur_disk.img", "/dev/fd0", "data disk #$cur_disk"; } } ############################################################ # make_bootable_cd() - make a bootable ISO image # ############################################################ sub make_bootable_cd { my $stage = "$TempDir/iso"; logit "Creating bootable CD..."; mkdir_p "$stage/images", "$stage/archives", "$stage/isolinux"; for my $imagefile (<$ImagesDir/*.img>, <$ImagesDir/*.gz>) { copy $imagefile, "$stage/images/" or fatal_error "Can't copy $imagefile to $stage/images/: $!"; } copy "$Homes{'mondo'}/autorun", "$stage/"; chmod 0755, "$stage/autorun"; my $distro = ""; open MSG_TXT, "$Homes{'mindi'}/msg-txt" or fatal_error "Can't open $Homes{'mindi'}/msg-txt: $!"; open MSG_OUT, "> $stage/isolinux/message.txt" or fatal_error "Can't write to $stage/isolinux/message.txt: $!"; while () { my $kver; my $date = `date`; if ($KernelSucks) { $kver = $FailsafeKernelVersion; } else { $kver = `uname -r`; chomp $kver; } chomp $date; s/ZZZZZ/$Version/; s/YYYYY/Mondo Rescue/; s/XXXXX/a cousin of/; s/DDDDD/$distro/; s/KKKKK/Kernel $kver/; s/TTTTT/$date/; print MSG_OUT $_; } close MSG_TXT; if (called_by_mondo) { if ($CDrecovery) { print MSG_OUT <. CAUTION: THIS WILL ERASE YOUR WHOLE DISK!!! EOF } elsif (-e "$MondoTemp/start-nfs") { print MSG_OUT "Press to continue.\n"; } else { print MSG_OUT $BootMediaMessage; } } else { print MSG_OUT "FYI, this is _not_ a Mondo Rescue CD.\n"; } print MSG_OUT "\n\n\n"; close MSG_OUT; if ($CDrecovery) { copy "$Homes{'mindi'}/isolinux-H.cfg", "$stage/isolinux/isolinux.cfg.old"; } else { copy "$Homes{'mindi'}/isolinux.cfg", "$stage/isolinux/isolinux.cfg.old"; } open SYSLINUX_READ, "< $stage/isolinux/isolinux.cfg.old" or fatal_error "Can't open < $stage/isolinux/isolinux.cfg.old: $!"; open SYSLINUX_WRITE, "> $stage/isolinux/isolinux.cfg" or fatal_error "Can't open > $stage/isolinux/isolinux.cfg: $!"; while () { chomp; s/24000/$RamdiskSize/; if (/append/) { $_ .= " $ExtraBootOptions"; } if (-e "$MondoTemp/start-nfs") { s/interactive/iso/; } print SYSLINUX_WRITE "$_\n"; } close SYSLINUX_READ; close SYSLINUX_WRITE; copy $Kernel, "$stage/isolinux/vmlinuz" or fatal_error "Can't copy kernel to bootable ISO: $!"; copy "$TempDir/mindi-2880.rdz", "$stage/isolinux/initrd.img" or fatal_error "Can't copy ramdisk to bootable ISO: $!"; copy $IsolinuxPath, "$stage/isolinux/isolinux.bin" or fatal_error "Can't copy isolinux to bootable ISO: $!"; copy "$stage/isolinux/initrd.img", "$ImagesDir/../" if called_by_mondo; copy "$stage/isolinux/isolinux.bin", "$ImagesDir/../" if called_by_mondo; copy "$stage/isolinux/isolinux.cfg", "$ImagesDir/../" if called_by_mondo; copy "$stage/isolinux/message.txt", "$ImagesDir/../" if called_by_mondo; copy "$stage/isolinux/vmlinuz", "$ImagesDir/../" if called_by_mondo; chdir $stage; system "mkisofs -U -J -r -o $ImagesDir/mindi.iso -b isolinux/isolinux.bin -c isolinux/boot.cat -no-emul-boot -boot-load-size 4 -boot-info-table . >/dev/null 2>$TempDir/mkisofs.out"; if ($?) { logit "------------------- mkisofs's errors -----------------------"; open ERRS, "$TempDir/mkisofs.out" or fatal_error "Can't open mkisofs errors file."; while () { chomp; logit $_; } close ERRS; logit "------------------------------------------------------------"; logit "Failed to create ISO image."; } else { logit "Created bootable ISO image at $ImagesDir/mindi.iso."; if ($ImagesDir ne "/root/images/mindi") { copy "$ImagesDir/mindi.iso", "/root/images/mindi/mindi.iso"; logit "... copied to /root/images/mindi/mindi.iso."; } } unlink "$TempDir/mkisofs.out"; } ############################################################ # find_kernel() - try and find the user's kernel # # returns - kernel found, or if none # ############################################################ sub find_kernel { my (@kernels, @duff_kernels); my ($kver, $kdate); (undef, undef, $kver, $kdate) = uname(); KERNEL: foreach my $kernel (glob ("/boot/vmlinuz*"), glob ("/boot/*zImage*"), glob ("/boot/*kernel*"), glob ("/vmlinuz*"), glob ("/*zImage*"), glob ("/*kernel*")) { next KERNEL unless defined $kernel; next KERNEL unless -e $kernel; # skip nonexistent kernels next KERNEL if -l $kernel; # skip symlinks push (@duff_kernels, $kernel), next KERNEL if $kernel =~ /shipped/; # use SuSE's extra kernel as a last resort next KERNEL if system "strings $kernel | grep -qF '$kver'"; # skip kernels ! this version unless (system "strings $kernel | grep -qF '$kdate'") { # we found it! foreach my $existing_kernel (@kernels) { next KERNEL if $existing_kernel eq $kernel; # but it's the same one as before :( } push @kernels, $kernel; } else { # Debian/Gentoo builddate problems, anyone? foreach my $existing_kernel (@duff_kernels) { next KERNEL if $existing_kernel eq $kernel; # already in there } push @duff_kernels, $kernel; } } @kernels = sort @kernels; @duff_kernels = sort @duff_kernels; if (scalar (@kernels) == 0) { logit "Sorry, couldn't find your kernel."; logit "Are there any duff kernels?"; if (scalar @duff_kernels) { logit "Lucky you -- found a duff kernel."; @kernels = @duff_kernels; } else { logit "Nope, no duff kernels either."; return; } } if (scalar (@kernels) == 1) { loggit "Found one kernel -- $kernels[0] (v$kver)"; return $kernels[0]; } else { logit "I have found more than one suitable kernel: @kernels"; foreach my $kernel (@kernels) { if ($kernel =~ /$kver/) { logit "I'm picking $kernel."; return $kernel; } } logit "Still don't know which one to pick... I'll return the first one."; return $kernels[0]; } } ############################################################ # find_isolinux_path -- self-explanatory # # returns -- nothing; sets -- $IsolinuxPath # ############################################################ sub find_isolinux_path { if (-e "/usr/lib/isolinux.bin") { $IsolinuxPath = "/usr/lib/isolinux.bin" } elsif (-e "/usr/lib/syslinux/isolinux.bin") { $IsolinuxPath = "/usr/lib/syslinux/isolinux.bin" } elsif (-e "/usr/share/syslinux/isolinux.bin") { $IsolinuxPath = "/usr/share/syslinux/isolinux.bin" } elsif (-e "/usr/share/lib/syslinux/isolinux.bin") { $IsolinuxPath = "/usr/share/lib/syslinux/isolinux.bin" } else { open LOCATE, "locate isolinux.bin |" or fatal_error "Can't find isolinux, and locate isn't helping..."; while () { chomp; if (m[/.*/isolinux\.bin$ ]x) { # use /x 'cause the $] at the end would be $PERL_VERSION $IsolinuxPath = $_; # otherwise... [I think :-] } } unless ($IsolinuxPath) { fatal_error "Can't find isolinux. Please make sure you have the right syslinux RPM installed.\nUse the one at mondorescue.org if you're unsure -- that one contains isolinux."; } } } ############################################################ # get_kmods() - return a list of all loaded kernel modules # ############################################################ sub get_kmods { my @kmods; open LSMOD, "lsmod |" or fatal_error "Can't open pipe from lsmod: $!"; while () { my @line; next if /Module/; # skip header line chomp; @line = split; push @kmods, $line[0]; } close LSMOD; return @kmods; } ############################################################ # get_kmod_paths() - return paths to all loaded kmods # ############################################################ sub get_kmod_paths { my @kmod_paths; my $kver; foreach my $module (@ExtraMods, get_kmods()) { (undef, undef, $kver) = uname(); find sub { push @kmod_paths, $File::Find::name if -f && /$module\.$ModuleSuffix/; }, "/lib/modules/$kver"; } find sub { push @kmod_paths, $File::Find::name }, glob "/lib/modules/$kver/modules.*"; return @kmod_paths; } ############################################################ # make_paths_failsafe() - transform /lib/modules/* into # # the failsafe kernel's modules # # @_ - paths to transform # # returns - transformed paths # ############################################################ sub make_paths_failsafe { my @failsafe_mods; my $kver; (undef, undef, $kver) = uname(); chdir $TempDir; foreach my $module (@_) { foreach my $newmod (find_specific_kmod_in_path ("lib/modules/$FailsafeKernelVersion", basename ($module, "NoExtension"))) { push @failsafe_mods, $newmod; } } return @failsafe_mods; } ############################################################ # find_specific_kmod_in_path($$) # # $_[0] - the path # # $_[1] - the module name # ############################################################ sub find_specific_kmod_in_path { my @matches; my $path = shift || fatal_error "Too few arguments to find_specific_kmod_in_path() - want 2, got 0"; my $module = shift || fatal_error "Too few arguments to find_specific_kmod_in_path() - want 2, got 1"; find sub { /$module/ && push @matches, $File::Find::name; }, $path; return @matches; } ############################################################ #-------------------------- main --------------------------# ############################################################ $| = 1; my $want_help = 0; my ($tmp_imgdevs, $tmp_exdevs); my $boolval; my $custom = 0; my $force_pid = 0; if ($< && $>) { die "You are not root. Please su.\n"; } &check_system; &find_homes; (undef, undef, $KernelVersion) = uname(); loggit "Running kernel version = $KernelVersion"; if ($KernelVersion =~ /2.6/) { logit "Warning: 2.6 kernel detected. Support is untested. Continue at your own risk."; $ModuleSuffix = "ko"; foreach my $symlink (keys %RootfsSymlinks) { if ($symlink =~ m[/sbin/(modprobe|insmod|rmmod|lsmod|depmod|kallsyms)]) { loggit "Removing $symlink symlink: we will use the non-Busybox version"; delete $RootfsSymlinks{$symlink}; } } } if (!system "which lvmdiskscan") { open LVMVER, "lvmdiskscan --version |" and do { while () { chomp; if (/LVM version:/) { s/.*LVM version:\s*//; $LVMVersionString = $_; s/([0-9]).*/$1/; $LVMVersion = $_; } } close LVMVER; } } GetOptions ("help|h|?" => \$want_help, "extra-space|x=i" => \$ExtraSpace, "max-csize|C=i" => \$MaxDiskSize, "temp-dir|t=s" => \$TempDir, "make-floppies!" => \$Floppies, "f" => \$Floppies, "prompt-cd!" => \$PromptCD, "c" => \$PromptCD, "kernel|k=s" => \$Kernel, "logfile|log|l=s" => \$Logfile, "lilo|use-lilo|L" => \$UseLILO, "image-dir|i=s" => \$ImagesDir, "mondo|m" => sub { $called_by_mondo = 10 }, # 10 is the sentinel value that shows "set on cmd line" "nomondo|M" => sub { $called_by_mondo = 0 }, "option|o=s" => sub { $_ = $_[1]; fatal_error "Bad -o option (no = sign): $_" unless /=/; my @opts = split /,/; eval "\$$_" for @opts; }, "eval=s" => sub { eval $_[1] }, "print|I=s" => sub { # -I for (I)nspect $_ = $_[1]; my @opts = split /,/; eval "print '$_ = ', \$$_, \"\\n\"" for @opts; }, "do-nothing|n" => sub { exit 0 }, "makemountlist=s" => sub { make_mountlist $_[1]; exit 0 }, "max-fsize|F=i" => \$ChopAt, "chop-size|X=i" => \$ChopSize, "pid-file|p=s" => \$PIDfile, "force-pid|P" => \$force_pid, "log-verbose|v" => \$LogVerbose, "version|V" => sub { print "mindi.pl v$Version\n"; exit 0 }, "findkernel|K" => sub { my @kernels = find_kernel; if (scalar @kernels) { print "@kernels\n"; exit 0 } else { exit 1 } }, "test-error" => sub { fatal_error "--$_[0] given" }, "custom" => sub { $custom = 1; die "!FINISH" }, "only-2880|2|8" => sub { $Make1722kFloppy = 0 }) or usage(); usage() if $want_help; unlink $Logfile; if ($custom) { unshift @ARGV, "--custom"; loggit "CUSTOM: ", join (" ", @ARGV); if ((scalar @ARGV) < 15) { usage(); } $boolval = sub { # $_[0] - the word, $_[1] - default value return 0 unless defined $_[0]; return 1 if ($_[0] =~ /y/i); return 1 if ($_[0] =~ /nonbootable/i); # a special case of the next one return $_[1] if ($_[0] =~ /null/i); # no value, so default return 0 if ($_[0] =~ /n/i); return $_[1]; }; $TempDir = $ARGV[1]; $MondoTemp = $ARGV[1]; $ImagesDir = $ARGV[2]; $Kernel = $ARGV[3]; $TapeDevice = $ARGV[4]; $TapeSize = $ARGV[5]; $FilesInFilelist = $ARGV[6]; $UsingLZO = $boolval->($ARGV[7], 0); $CDrecovery = $boolval->($ARGV[8], 0); $tmp_imgdevs = $ARGV[9]; $LastFilelistNum = $ARGV[11]; $EstNoofSlices = $ARGV[12]; $tmp_exdevs = $ARGV[13]; $UsingCompression = $boolval->($ARGV[14], 1); $NonBootable = $boolval->($ARGV[16], 0); $DifferentialBkup = $ARGV[17]; $tmp_imgdevs =~ s/\(null\)//; $tmp_exdevs =~ s/\(null\)//; $tmp_imgdevs =~ y/|,/ /; $tmp_exdevs =~ y/|,/ /; @ImageDevs = split ' ', $tmp_imgdevs; @ExcludeDevs = split ' ', $tmp_exdevs; } our $OldTemp = $TempDir; unlink (<$ImagesDir/*gz>, <$ImagesDir/*img>); loggit "Mindi v$Version"; loggit "-----------------------"; loggit `uname -a`; loggit "-----------------------"; logit "Mindi-Linux mini-distro generator v$Version by Hugo Rabson"; logit "Port to Perl done by Joshua Oreman"; logit "Mindi uses Busybox, which is available from www.busybox.net"; logit '-' x (($ENV{'COLUMNS'} || $ENV{'COLS'} || 80) - 1); system "rm -rf $TempDir/mindi"; $TempDir .= "/mindi/$$"; mkdir_p $TempDir, $ImagesDir; if (-e $PIDfile) { if ($force_pid) { logit "--> Killing Mindi PID @{[ `cat $PIDfile | tr -d '\n'` ]}"; kill 15, `cat $PIDfile | tr -d '\n'`; unlink $PIDfile; } else { logit "Another instance of Mindi, pid @{[ `cat $PIDfile | tr -d '\n'` ]}, is already running."; logit "If you want to kill this process, use the -P option, or remove /var/run/mindi.pid.\n"; exit 1; } } open (PID, "> $PIDfile") or die "Can't write to $PIDfile: $!\n"; print PID $$, "\n"; close PID; if ($NonBootable) { logit "Just creating mondo-restore.cfg and a small all.tar.gz for Mondo. Nothing else."; make_mondo_config "$MondoTemp/mondo-restore.cfg"; mkdir_p "$MondoTemp/small-all/tmp"; chdir "$MondoTemp/small-all"; for my $file ("mondo-restore.cfg", "filelist.full", "biggielist.txt") { copy "$MondoTemp/$file", "./tmp/$file" or fatal_error "Can't copy $file to the small all.tar.gz: $!"; } tar_up_files ("$MondoTemp/all.tar.gz", "tmp/"); logit "Done copying all.tar.gz - exiting."; unlink $PIDfile; exit 0; } $SIG{'HUP'} = sub { fatal_error "Hangup" }; $SIG{'INT'} = sub { fatal_error "Interrupt" }; $SIG{'TERM'} = sub { fatal_error "Terminated" }; if ((not $KernelSucks) && ($Kernel !~ /failsafe/i) && ($Kernel !~ /mindi/) && ($Kernel !~ /sucks/i)) { if ($Kernel) { if (not -e $Kernel) { logit "Specified kernel does not exist. I will use your running kernel."; $Kernel = ""; } else { if (open KERNSTRINGS, "strings '$Kernel' |") { while () { if (/^[2-9].[0-9].[0-9]/) { my(@line) = split; $KernelVersion = $line[0]; } } unless ($KernelVersion) { logit "Specified kernel contains no version information. I will use your running kernel."; $Kernel = ""; } close KERNSTRINGS; } else { logit "Specified kernel could not be opened. I will use your running kernel."; $Kernel = ""; } } } unless ($Kernel) { my @kernels = find_kernel; if (scalar (@kernels) == 0) { logit "Could not find your kernel. Using mine."; $Kernel = "$Homes{'mindi'}/vmlinuz"; $KernelSucks = 1; } else { $Kernel = $kernels[0]; $KernelSucks = 0; } } } else { logit "Using my kernel 'cause you told me to."; $Kernel = "$Homes{'mindi'}/vmlinuz"; $KernelSucks = 1; } if ($KernelSucks) { unless (-f $Kernel) { fatal_error "Can't find the failsafe kernel. Get the mindi-kernel package from\nhttp://www.mondorescue.org and install it -- you need it."; } untar_files "$Homes{'mindi'}/lib.tar.bz2", "$TempDir", "Extracting kernel modules:\t\t", 0 or fatal_error "Can't unzip lib.tar.bz2 -- do you need to install mindi-kernel?"; open STRINGS, "strings $Kernel |" or fatal_error "Can't open pipe from strings: $!"; while () { chomp; if (/^2\.4/) { ($FailsafeKernelVersion) = split; } } close STRINGS; } if ($called_by_mondo == -1) { &determine_if_called_by_mondo; } if ($custom == 1 and $called_by_mondo == 0) { logit "WARNING! You are not Mondo, but you called me with --custom arguments."; logit "This is untested behavior."; print "Continue anyway (y/n)? [n] "; my $answer = ; chomp $answer; if ($answer !~ /y/i) { fatal_error "Aborting at user's request."; } logit "--> OK, continuing."; } elsif ($custom == 0 and $called_by_mondo) { logit "I did not get a --custom argument, but I was seemingly called_by_mondo."; if ($called_by_mondo == 10) { logit "Since I was requested to pretend I was called by Mondo on the command line,"; logit "I shall continue this way."; } else { logit "Since this was not due to a command line option, I will assume that you are"; logit "running me at the same time as another Mondo process is running: the two are"; logit "not related. Therefore, I shall unset called_by_mondo."; $called_by_mondo = 0; } } &generate_depends_list; my $noof_disks = &make_data_disks; &prepare_boot_disks (($ToolSize + $ExtraSpace) - (($ToolSize + $ExtraSpace) % 4096), 2880); &prepare_boot_disks (($ToolSize + $ExtraSpace) - (($ToolSize + $ExtraSpace) % 4096), 1722) if $Make1722kFloppy; logit "\nYour disks may be found in $ImagesDir:-"; system "ls -C $ImagesDir | tee -a $Logfile"; print "\n"; if (called_by_mondo && $TapeDevice) { copy "$ImagesDir/all.tar.gz $MondoTemp/all.tar.gz" or fatal_error "Can't copy $ImagesDir/all.tar.gz to $MondoTemp/all.tar.gz: $!"; } if ($Floppies && not called_by_mondo) { my $answer; print "Copy the images to floppies (y/n)? [n] "; $answer = ; chomp $answer; ©_images_to_floppies ($noof_disks) if $answer =~ /y/i; } my $mkcd = 1; if ($PromptCD and not called_by_mondo) { my $ans; print "Make a bootable ISO image (y/n)? [y] "; $ans = ; chomp $ans; if ($ans =~ /n/i) { $mkcd = 0; } else { $mkcd = 1; } } &make_bootable_cd if $mkcd; sleep 1; print "\nFinished.\n"; sleep 1; print "\n"; print "One 1722K boot disk, " if -e "$ImagesDir/mindi-boot.1722.img"; print "one 1440K root disk, " if -e "$ImagesDir/mindi-root.1440.img"; print "one 2880K boot disk, " if -e "$ImagesDir/mindi-boot.2880.img"; print "\n"; print "and $noof_disks data disk images were created.\n"; print "\n"; chdir "/"; foreach my $mountpoint (@Mountpoints) { logit "Unmounting $mountpoint."; system "$UnmountCmd $mountpoint" and logit "WARNING: Could not unmount $mountpoint, please unmount it yourself"; } if ($TempDir !~ /$$/i) { logit "BAD TEMP DIR: $TempDir!"; logit "If I had removed this tempdir, you would not be happy right now!"; logit "Exiting."; unlink $PIDfile; exit 1; } system "rm", '-rf', $TempDir and warn "WARNING: could not remove the temp directory\n"; if ($OldTemp =~ m[/tmp/mindi.[A-Za-z0-9]{10}.*]) { system "rm", '-rf', $OldTemp; } unlink $PIDfile; exit 0; __END__