#!/usr/bin/perl -w

#    BootRoot 0.4 by freesource 4.14.2000 Copyright (C) 2000 
#    Jonathan Rosenbaum - mttrader@access.mountain.net
#    http://the.netpedia.net/bootroot.html

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

#    This program is distributed in the hope that it will be useful,
#    but WITHOUT ANY WARRANTY; without even the implied warranty of
#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#    GNU General Public License for more details.

#    You should have received a copy of the GNU General Public License
#    along with this program; if not, write to the Free Software
#    Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.


#  CHANGES 
#
#           0.4 - 4.14.2000
#                  * copy over bzip2 only if specified in configuration
#                  * check and adjust the size of the initrd image, thanks 
#                    to Magnus Holmberg for reporting the bug when stuff 
#                    didn't fit the old default size for his setup, also 
#                    will make a leaner boot if the reverse is true.
#                  * add a new question by Magnus to the FAQ
#
#           0.3 - 3.17.2000
#                  * added more error checking                   
#
#           0.2 -  3.16.2000
#                  * beta .. works nicely
#                  * automatic y when mke2fs on loop device
#                  * extra cleanup for aborted attempt
#                  * removed init from boot    
#                  * size check - will abort if cp or mkdir fail and
#                    output to find out how much space is left.
#                  * added normalboot to lilo for booting from normal disk -
#                    requires root=device otherwise will default to /dev/hda1
#                  * added a message with lilo

#           0.1 - 3.12.2000
#                  * initial alpha release, will implement size test
#                    in next version.
#            

#  What are the REQUIREMENTS?
#
#  Check to make sure you have thes things, in these directories or the
#  program won't work correctly:
#  /bin/{ash,gzip,mount,umount} 
#  /sbin/init  (you better have this! .. only used for a test.)
#  /usr/bin/bzip2  (optional)

#  What does this program do?  
#
#  BootRoot creates a boot disk with lilo, a kernel and an initrd image.
#  The initrd script mounts another root disk with a compressed (gzip or 
#  bzip2) filesystem.
#
#  The root filesystem isn't made by this program.  This program is 
#  patterned after mkrboot, but unlike mkrboot it creates an unique bootdisk
#  and a separate root disk.

#  What's the advantage of using this program?
#
#  You can use a bzip2 compressed filesystem, this program is
#  easy to use, and it provides a framework showing a simple initrd method 
#  which you can freely modify.  Run a search for HEREDOC.  I wrote this 
#  program as a solution to help oster at EE (www.experts-exchange.com) 
#  create separate boot and root floppies for an emergency system for his 
#  customers.
#
#  If you make a cool change to this program, or if this program helps you 
#  I'd love to know, that's better than receiving pizza :)

#  How can I test BootRoot? 
#
#  Get SETUP.GZ as the filesystem from looplinux at 
#  http://www.tux.org/pub/people/kent-robotti/index.htm.
#  This filesystem works with 2.2 kernels.
#    
#  [Ctrl] ([Tab] to see available images) 
#  boot: bootdisk single [Enter]
#  ( now filesystem is single user mode)
#  exit [Enter]
#  (now you are in multi user mode)
#
#  Better yet, do [Ctrl]
#  boot: bootdisk 2 [Enter]
#
#  This works nicely with a compressed root filesystems made with yard
#  without "single" .. but looplinux comes with mc (mcedit)

#  Why doesn't looplinux work as "bootdisk 1?"
#
#  There is a difference between "1" and "single."  Looplinux was written
#  in a way that runlevel 1 doesn't work properly in relation to BootRoot
#  unless single is used.  And you thought they were the same thing. 
#  BootRoot proves otherwise.

#  What sort of configuration can I do?
#
#  Edit the variable $compress to either gzip (default) or bzip2. 

#  How do I use the program?
#
#  program_name lilo linux-kernel compressed-filesystem
#
#  "lilo" is the only method supported at the present.
#
#  For instance .. "linux-kernel" could be:  /boot/vmlinuz-2.2.14  
#                  "compressed-filesystem":  /home/createit/my_creation.gz
#  (if found in same directory when running the program)
#                  "linux-kernel could be":  vmlinuz-2.2.14
#                  "compressed-filesystem":  my_creation.gz
#        
#  "device" could be /dev/fd0 (default) or /dev/fd1 .. etc.
#  "size" is usually 1440 (default)

# Edit to "gzip" or "bzip2"
$compress = "gzip";

#######################################################################
# Don't edit from here, but you can if you want to change the here docs
# and/or the contents of initrd (in which case you need to make sure the
# right libraries are copied over to initrd).

# I need to remember to edit this
$version = "v0.4";
$date = "4.14.2000";

$device = "/dev/fd0";
$size = 1440;
$initrd =  "initrd_image";
$pwd = `pwd`; chomp $pwd;
use File::Basename;
$compress eq "gzip" ? ($compress = "gzip") : ($compress = "bzip2");

if ($#ARGV == -1) { 
    print "boot_root - Make a separate boot and root disk\n";
    print "-----------------------------------------------\n";
    print "boot_root <method> [ <linux-kernel > [ <root-image> [ <device> [ <size> ]]]]\n";
    print "\nMethods available:\n\nlilo    -> Generates a separate boot and root disk for lilo\n\n";
    exit;
}
if ($ARGV[0] ne "lilo") {
    die "Please supply a method\n";
}
$method = $ARGV[0];
if (defined $ARGV[1] && -e $ARGV[1] && !-d $ARGV[1]) {
    $kernel = $ARGV[1];
}
else {
    die "boot_root: ERROR: Kernel not found\n";
}
if (defined $ARGV[2] && -e $ARGV[1] && !-d $ARGV[1] ) {
    $root_image = $ARGV[2];
}
else {
    die "boot_root: ERROR: Rootimage not found\n";
}
$device = $ARGV[3] if defined $ARGV[3];
$size = $ARGV[4] if defined $ARGV[4]; 

# lilo method
if ($method eq "lilo") {


  # Do a little cleanup just in case
  system "rm /tmp/initrd_image.gz 2> /dev/null; rmdir /tmp/initrd_mnt 2> /dev/null"; 

    initrd();
    mtab();


  print "Making ext2 filesystem\n";
  system "mke2fs -m0 -i8192 $device $size";
  die "boot_root: ERROR: You need to insert a disk\n" if $? != 0;
  print "Mounting the device\n";
  errm(system "mount -t ext2  $device /mnt");

  # Time to do a little calculations 
  $device_size = (split(/\s+/,`df /mnt`))[8]; 
  $boot_size = (stat($kernel))[12]/2 + (stat("/tmp/$initrd"))[12]/2; 
  $root_image_size = (stat($root_image))[12]/2;
  $enough_boot = $device_size - $boot_size;
  $enough_root = $device_size - $root_image_size;
  $remain_boot = $device_size - $boot_size;
  $remain_root = $device_size - $root_image_size;

  # A little output
  $enough_boot  =~ /^-+\d+$/ ? 
  die "boot_root: ERROR: Not enough room: boot stuff = $boot_size k, device = $device_size\n" :
  print "boot_root: Looks good so far:  boot stuff = $boot_size k, device = $device_size k, remaining = $remain_boot k\n";

   # Better do this first
  print "Copy over initrd ramdisk\n";
  system "cp /tmp/$initrd /mnt/$initrd";
  die "boot_root: ERROR: Could not copy over initrd\n" if $? != 0;
 
  print "Copying over kernel\n";
  system "rm -rf /mnt/lost+found; cp $kernel /mnt/kernel";
  die "boot_root: ERROR: Could not copy over the kernel\n" if $? != 0;
  print "Making stuff for lilo\n";
  err(system "mkdir /mnt/{boot,dev}; cp -a /dev/{null,fd?,hda1} /mnt/dev");
  print "Copy over important lilo stuff\n";
  err(system "cp /boot/boot.b /mnt/boot");

# HEREDOC
$brlilo = << "LILOCONF";
boot = $device
message = message
delay = 50
vga = normal
install = /boot/boot.b
map = /boot/map
backup = /dev/null
compact

# bootdisk
image = kernel 
append = "load_ramdisk = 1 debug"
initrd = $initrd
root = $device
label = bootdisk
read-write

# normalboot
image = kernel 
root = /dev/hda1
label = normalboot
read-only
LILOCONF
    open(LC, ">/mnt/brlilo.conf") or die "Couldn't write /mnt/brlilo.conf\n";
    print LC $brlilo; close(LC);


# HEREDOC
$message = << "MESSAGE";

BootRoot $version written by Jonathan Rosenbaum $date GPL 
mailto:mttrader\@access.mountain.net

Press [Ctrl] to see the lilo prompt.

Press [Tab] to see a list of boot options.

bootdisk   = This will boot a compressed root filesystem
             on another floppy.
normalboot = This will boot up a specified filesystem. 
             default: /dev/hda1  a = 1st drive 
                                 1 = 1st partition
             Use root=/dev/(h or s)dXX
                                 h = IDE Drive
                                 s = SCSI Drive

Trouble:  Do not forget boot: option single
Fix a filesystem:  e2fsck /dev/(h or s)dXX  
Bad superblock:    e2fsck -b 8192 /dev/(h or s)dXX

MESSAGE
    open(M, ">/mnt/message") or die "Couldn't write /mnt/message\n";
    print M $message; close(M);

  # Got to umount,mount, and umount again to make sure everything is
  # copied over before doing lilo
  errum(system "umount /mnt");
  print "Umount device\n";
  print "Remount device\n";
  errm(system "mount -t ext2 $device /mnt");  
  print "Configuring lilo\n";
  chdir("/mnt") or die "boot_root: ERROR: Could not change directories\n";
  system "lilo -v -C brlilo.conf -r /mnt"; 
  die "boot_root: ERROR: lilo failed\n" if $? != 0; # code 0 regardless
  chdir($pwd) or die "boot_root: ERROR: Could not change directories\n";
  print "Umounting /mnt\n";
  # y I know
  $um = system "umount /mnt";

  print "Doing a little cleanup\n";
  system "rm /tmp/$initrd; rmdir /tmp/initrd_mnt"; 

  # This could be put on the top, but all that needs to be done now is
  # to mke2fs & cp over /compressed_filesystem
  $enough_root  =~ /^-+\d+$/ ? 
  die "boot_root: ERROR: Not enough room: root stuff = $root_image_size k, device = $device_size\n" :
  print "boot_root: Looks good:  boot stuff = $boot_size k, device = $device_size k, remaining = $remain_root k\n";


# Here's where we copy over that compressed filesystem
# We could separate $device = boot,root allowing two 
# different devices to be used.
if ($um == 0) {
    mtab(); 
    print "Making ext2 filesystem\n";
    system "mke2fs -m0 -i8192 $device $size";
    die "boot_root: ERROR: You need to insert a disk\n" if $? != 0;
    errm(system "mount -t ext2 /dev/fd0 /mnt");
    print "Copy over the compressed filesystem\n"; 
    system "rmdir /mnt/lost+found";
    $broot_image = basename($root_image);
    system "cp $root_image /mnt/$broot_image"; 
    die "boot_root: ERROR: Could not copy over the root filesystem\n" if $? != 0; 
    errum(system "umount /mnt"); 
    print "Root disk did not properly umount\n" if $? != 0;   
    print "Finished!\n";
}
else {
    die "boot_root: ERROR: Boot disk was never umounted\n";
}  # copy over the compressed

  
} # lilo method


# Some functions

sub errmk {
  die "boot_root: ERROR: Could not make important directories\n" if $? != 0; 
}

sub errcp {
  die "boot_root: ERROR: Could not copy over important stuff\n" if $? != 0; 
}

sub errum {
  die "boot_root: ERROR: Could not umount the device\n" if $? != 0; 
}

sub errm {
 die "boot_root: ERROR: Could not mount device\n" if $? != 0;
}

sub err { 
 die "boot_root: ERROR: Not enough space after all\n" if ($? > 0); 
}

sub mtab {

# /proc/mount could be used, but maybe there is no /proc

# \n from initrd()
print "\nPlease insert a floppy and then press [Enter]: ";
<STDIN>;
# Check to see if $device is mounted
open (MTAB, "/etc/mtab") or die "no mtab!\n";
while (<MTAB>) {
    if (m,$device,) {
	print "DANGER!\n";
	print "This next step will create a new filesystem on the floppy removing all data\n";
	print "Please umount the device first, and put in a new floppy.\n";
	exit;
    }
}
close(MTAB);
} # end sub mtab


sub initrd_size {

  ($linuxrc_size) = @_;
  print "Checking size needed for initrd\n";

  # the size of the loop device should be at least 1.63% larger than what
  # it will contain (i.e. 8192 inode), but to keep on the safe size it will
  #  be 2.00% larger.
  # 9 dirs  = 1024 each (increase if modified)
  # {ash,gzip,mount,umount} (required executables)
  # bzip2 if $compress eq bzip2 (optional)
  # 1 for ld.so.cache

  # change dir size if needed
  $dir_size = 9 + 1;
  $initrd_size = $dir_size + $linuxrc_size;

  # add other executables here
  @initrd_stuff = qw(ash gzip mount umount);
  foreach (@initrd_stuff) {
     $initrd_size =  $initrd_size + ((stat("/bin/$_"))[12]/2);
  }

  if ($compress eq "bzip2" && -e "/usr/bin/$compress") {
     print "hi\n";
     $initrd_size = $initrd_size + ((stat("/usr/bin/$compress"))[12]/2); 
  }

  # lib sizes
  open(L,"ldd /sbin/init|") or die "Oops, no init could be found :)\n"; # safe to use ldd
   while (<L>) { 
      $lib = (split(/=>/,$_))[0]; 
      $lib =~ s/\s+//;  
      $lib = basename($lib); 
      $lib =~ s/\s+$//; 
      open (SL,"ls -l /lib/$lib|") or die "humm: $!\n"; 
        while (<SL>) { 
          # symbolic link
          if (-l "/lib/$lib") {
            $what = (split(/\s+/,$_))[10]; 
            $initrd_size = $initrd_size + 1;
            $initrd_size = $initrd_size + ((stat("/lib/$what"))[12]/2);
          } 
          # no symbolic link
          else { 
           $initrd_size = $initrd_size + ((stat("/lib/$lib"))[12]/2);
          } 
        }
   }

  $initrd_size = $initrd_size + ($initrd_size * 0.02);
  # For perfection 1 (rounded up) is o.k., but for safety 10 would be 
  # better  
  $initrd_size = sprintf("%.f",$initrd_size) + 10;
  return $initrd_size;

} # end sub initrd_size


sub initrd {

$broot_image = basename($root_image);

# Here's where the initrd is put together using a loop device
# HEREDOC
$initrd_exec = << "INITRD";
#!/bin/ash

export PATH=/bin:/sbin:/usr/bin:

echo Preparing to setup ramdisk.

mount -o remount,rw / 2>/dev/null

echo Mounting proc...
mount -t proc none /proc

echo -n 'Please insert the root floppy, and press [Enter]: '
read ENTER

echo Mounting floppy drive readonly ...
mount -o ro -t ext2 /dev/fd0 /mnt

echo -n Copying new root to ramdisk .. please wait ...
$compress -cd /mnt/$broot_image > /dev/ram1
echo done.

echo -n Unmounting floppy ...
umount /mnt
echo done.

echo Changing to the new root.
echo 257 >/proc/sys/kernel/real-root-dev

echo -n Unmounting proc ...
umount /proc
echo done.

echo Continuing normal boot procedure from ramdisk.
INITRD

  open(LC, ">/tmp/linuxrc") or die "Couldn't write linuxrc to loop device\n";
  print LC $initrd_exec; close(LC); 
  $size_needed = initrd_size((stat("/tmp/linuxrc"))[12]/2);
  unlink("/tmp/linuxrc");

  print "Using loop device to make initrd\n";
  print "Make sure you have loop device capability in your running kernel\n";
  system "dd if=/dev/zero of=/tmp/$initrd bs=1024 count=$size_needed";
  # no need to enter y every time
  open(T,"|mke2fs -m0 -i8192 /tmp/$initrd") or die "Problem here: $!\n";  print T "y\n"; close(T);
  print "Mounting initrd in tmp\n";
  errmk(system "mkdir /tmp/initrd_mnt; mount -o loop -t ext2 /tmp/$initrd /tmp/initrd_mnt");
  print "Putting everything together\n";
  open(LC, ">/tmp/initrd_mnt/linuxrc") or die "Couldn't write linuxrc to loop device\n";
  print LC $initrd_exec; close(LC); 
  # I could test this but somebody's system may do permissions differently
  system "chmod 755 /tmp/initrd_mnt/linuxrc";
  system "rmdir /tmp/initrd_mnt/lost+found";

  print "... the dirs\n";
  errmk(system "mkdir /tmp/initrd_mnt/{bin,dev,etc,lib,mnt,proc,sbin,usr}; mkdir /tmp/initrd_mnt/usr/lib");
  errcp(system "cp -a /dev/{console,fd0,null,ram0,ram1,tty0} /tmp/initrd_mnt/dev");
  # future implementation
  #errcp(system "cp -a $device /tmp/initrd_mnt/dev");

  print ".. the bins\n";
  errcp(system "cp -a /bin/{ash,gzip,mount,umount} /tmp/initrd_mnt/bin");
  if ($compress eq "bzip2") {
    errcp(system "cp -a /usr/bin/$compress /tmp/initrd_mnt/bin") if -e "/usr/bin/$compress";
  }

  # Testing init is sufficient for grabbing the correct libraries for the
  # executables immediately above.  This could be modified to test a
  # list of executables.  
  print ".. the libs\n";
  open(L,"ldd /sbin/init|") or die "Oops, no init could be found :)\n"; # safe to use ldd
   while (<L>) { 
      $lib = (split(/=>/,$_))[0]; 
      $lib =~ s/\s+//;  
      $lib = basename($lib); 
      $lib =~ s/\s+$//; 
      open (SL,"ls -l /lib/$lib|") or die "humm: $!\n"; 
        while (<SL>) { 
          # symbolic link
          if (-l "/lib/$lib") {
            $what = (split(/\s+/,$_))[10]; 
            errcp(system "cp -a /lib/$lib /tmp/initrd_mnt/lib");
            errcp(system "cp -a /lib/$what /tmp/initrd_mnt/lib");
          } 
          # no symbolic link
          else { 
            errcp(system "cp -a /lib/$lib /tmp/initrd_mnt/lib");
          } 
        }
   }

  print "Determine run-time link bindings\n";
  # Has a return code of 0 regardless
  system "ldconfig -r /tmp/initrd_mnt";
  print "Umounting loop device, and compressing initrd";
  errum(system "umount /tmp/initrd_mnt; gzip -9 /tmp/$initrd");  
  $initrd = $initrd . ".gz";

}


















