#!/bin/sh
# Copyright 2004  Slackware Linux, Inc., Concord, CA, USA
# Copyright 2004  Patrick J. Volkerding, Concord, CA, USA
# All rights reserved.
#
# Redistribution and use of this script, with or without modification, is
# permitted provided that the following conditions are met:
#
# 1. Redistributions of this script must retain the above copyright
#    notice, this list of conditions and the following disclaimer.
#
#  THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
#  WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
#  MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO
#  EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
#  SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
#  PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
#  OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
#  WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
#  OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
#  ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

MKINITRD_VERSION=1.0.1

print_usage() {
  cat << EOF
Usage: mkinitrd [OPTION]

mkinitrd creates an initial ramdisk used to load kernel modules that
are needed to mount the root filesystem, or other modules that might
be needed before the root filesystem is available.

  -c      Clear the existing initrd tree first
  -f      Filesystem to use for root partition (must be used with -r)
  --help  Display this message
  -k      Kernel version to use
  -m      A colon (:) delimited list of kernel modules to load.
          Additional options may be added to use when loading the
          kernel modules (but in this case the entire list must be
          wrapped with double quotes).
  -o      Output image (default /boot/initrd.gz)
  -r      Root partition device (must be used with -f)
  -s      Initrd source tree (default /boot/initrd-tree/)
  -V      Display version number

A simple example:  Build an initrd for a reiserfs root partition:

  mkinitrd -c -m reiserfs

Another example:  Build an initrd image using Linux 2.6.7 kernel
modules for a system with an ext3 root partition on /dev/hdb3:

  mkinitrd -c -k 2.6.7 -m jbd:ext3 -f ext3 -r /dev/hdb3

If run without options, mkinitrd will rebuild an initrd image using
the contents of the $SOURCE_TREE directory, or, if that directory
does not exist it will be created and populated, and then mkinitrd
will exit.

EOF
}

create_new_source_tree() {
  mkdir -p $SOURCE_TREE
  ( cd $SOURCE_TREE ; tar xzf /usr/share/mkinitrd/initrd-tree.tar.gz )
  # Make sure we have any block devices that might be needed:
  SLOPPY_DEV_LIST=$(cat /proc/partitions)
  for device in $SLOPPY_DEV_LIST ; do
    if [ ! -r $SOURCE_TREE/dev/$device -a -b /dev/$device ]; then
      cp -a /dev/$device $SOURCE_TREE/dev
    fi
  done
  # Make sure a kernel module directory exists:
  mkdir -p $SOURCE_TREE/lib/modules/${KERNEL_VERSION}
}

clear_source_tree() {
  if [ -d "$SOURCE_TREE" ]; then
    rm -rf $SOURCE_TREE
  fi
}

build_initrd_image() {
  # Calculate size needed for initrd:
  SIZE="$(du -s -k $SOURCE_TREE | tr "\t" " " | cut -f 1 -d ' ')"
  # Add a bit of padding:
  PAD=98
  SIZE="$(expr $SIZE + $PAD)"
  # Make an empty file of the proper size:
  INITRD=$(mktemp)
  dd if=/dev/zero of=$INITRD bs=1k count=$SIZE 2> /dev/null
  # Create a filesystem on it:
  mke2fs -F $INITRD 1> /dev/null 2> /dev/null
  # Fix broken ext2fs defaults:
  tune2fs -c 0 -i 0 -m 0 $INITRD 1> /dev/null 2> /dev/null
  # Mount the initrd-to-be:
  MOUNTPOINT=$(mktemp -d)
  mount -o loop -t ext2 $INITRD $MOUNTPOINT
  # Populate the initrd image:
  ( cd $SOURCE_TREE
    cp -a * $MOUNTPOINT
  )
  # Make sure we have any block devices that might be needed:
  SLOPPY_DEV_LIST=$(cat /proc/partitions)
  for device in $SLOPPY_DEV_LIST ; do
    if [ ! -r $SOURCE_TREE/dev/$device -a -b /dev/$device ]; then
      cp -a /dev/$device $SOURCE_TREE/dev
    fi
  done
  # Unmount the initrd and move it into place:
  umount $INITRD
  rmdir $MOUNTPOINT
  gzip -9 $INITRD
  rm -f $OUTPUT_IMAGE
  mv ${INITRD}.gz $OUTPUT_IMAGE
}

# If --help is given, print_usage and exit:
if echo $* | grep -wq '\--help' ; then
  print_usage
  exit 0
fi

# If -V given, print version and exit:
if echo $* | grep -wq '\-V' ; then
  echo "mkinitrd version $MKINITRD_VERSION"
  exit 0
fi

# Default values if these aren't previously set.
# Might be changed by -s and -o options, too.
SOURCE_TREE=${SOURCE_TREE:-/boot/initrd-tree}
OUTPUT_IMAGE=${OUTPUT_IMAGE:-/boot/initrd.gz}
# Set default kernel to build for to the running one:
KERNEL_VERSION="$(uname -r)"

# Default actions without options:
if [ -z "$1" ]; then
  # If the output tree doesn't exist, create it and then exit:
  if [ ! -d $SOURCE_TREE ]; then
    echo "Nothing found at location $SOURCE_TREE, so we will create an"
    echo -n "initrd directory structure there... "
    create_new_source_tree
    echo "done."
    echo
    echo "Now cd to $SOURCE_TREE and install some modules in your"
    echo "module directory (lib/modules/${KERNEL_VERSION}).  Then see linuxrc"
    echo "for more information (there are a few other files to edit)."
    echo "Finally, run mkinitrd again once the initrd-tree is ready,"
    echo "and $OUTPUT_IMAGE will be created from it."
    echo
    exit 0
  else
    # If the source tree does exist, the default is to build the initrd
    # image from it and then exit:
    build_initrd_image
    echo "$OUTPUT_IMAGE created."
    echo "Be sure to run lilo again if you use it."
    exit 0
  fi
fi # default no-option actions

# Parse options:
while [ ! -z "$1" ]; do
  case $1 in
    -c)
      clear_source_tree
      shift
      ;;
    -f)
      ROOTFS="$2"
      shift 2
      ;;
    -k)
      KERNEL_VERSION="$2"
      shift 2
      ;;
    -m)
      MODULE_LIST="$2"
      shift 2
      ;;
    -o)
      OUTPUT_IMAGE="$2"
      shift 2
      ;;
    -r)
      ROOTDEV="$2"
      shift 2
      ;;
    -s)
      SOURCE_TREE="$2"
      shift 2
      ;;
    *) # unknown, prevent infinite loop
      shift
      ;;
  esac
done

# If there's no $SOURCE_TREE, make one now:
if [ ! -d "$SOURCE_TREE" ]; then
  create_new_source_tree
fi

# If both $ROOTDEV and $ROOTFS are set, write them in the initrd-tree:
if [ ! -z "$ROOTDEV" -a ! -z "$ROOTFS" ]; then
  echo $ROOTDEV > $SOURCE_TREE/rootdev
  echo $ROOTFS > $SOURCE_TREE/rootfs
fi

# Make module directory:
if [ ! -d $SOURCE_TREE/lib/modules/$KERNEL_VERSION ]; then
  mkdir -p $SOURCE_TREE/lib/modules/$KERNEL_VERSION
fi

# If a module list was given, copy the modules into place:
if [ ! -z "$MODULE_LIST" ]; then
  rm -f $SOURCE_TREE/load_kernel_modules
  touch $SOURCE_TREE/load_kernel_modules
  chmod 755 $SOURCE_TREE/load_kernel_modules
  echo "# This is a script used to load the kernel modules." >> $SOURCE_TREE/load_kernel_modules
  echo "# To use it, chmod it 755, and then add the insmod" >> $SOURCE_TREE/load_kernel_modules
  echo "# lines needed to load your modules, like this:" >> $SOURCE_TREE/load_kernel_modules
  echo >> $SOURCE_TREE/load_kernel_modules
  while echo "$MODULE_LIST" | grep -q : ; do
    MODFIELD="$(echo "$MODULE_LIST" | cut -f 1 -d : )"
    MODULE="$(echo "$MODFIELD" | cut -f 1 -d ' ' )"
    # Now find this module:
    SRCMOD=$(find /lib/modules/$KERNEL_VERSION -name "${MODULE}.*")
    if [ -r "$SRCMOD" ]; then
      cp $SRCMOD $SOURCE_TREE/lib/modules/$KERNEL_VERSION
      echo "insmod /lib/modules/\$(uname -r)/$(basename $SRCMOD)" >> $SOURCE_TREE/load_kernel_modules
    else
      echo "WARNING:  Could not find module for \"$MODFIELD\""
    fi
    MODULE_LIST="$(echo "$MODULE_LIST" | cut -f 2- -d : )"
  done
  # One last module left here:
  MODFIELD="$MODULE_LIST"
  MODULE="$(echo "$MODFIELD" | cut -f 1 -d ' ' )"
  # Now find this module:
  SRCMOD=$(find /lib/modules/$KERNEL_VERSION -name "${MODULE}.*")
  if [ -r "$SRCMOD" ]; then
    cp $SRCMOD $SOURCE_TREE/lib/modules/$KERNEL_VERSION
    echo "insmod /lib/modules/\$(uname -r)/$(basename $SRCMOD)" >> $SOURCE_TREE/load_kernel_modules
  else
    echo "WARNING:  Could not find module for \"$MODFIELD\""
  fi
fi

# And finally, build the initrd:
build_initrd_image

