#!/bin/sh
#
# mkinitrd - make an initrd image
#
# Copyright (C) 2001-2002 Herbert Xu <herbert@debian.org>
#
# 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
#
# $Id: mkinitrd,v 1.69 2002/10/18 23:01:27 herbert Exp $

set -e

defaults() {
	MODULES=most
	DELAY=0
	ROOT=probe
	UMASK=022
	MKIMAGE='mkcramfs %s %s > /dev/null'
}

cleanse() {
	case "$MODULES" in
	all | most | none)
		;;
	*)
		echo "$PROG: MODULES: Unknown value $MODULES" >&2
		exit 1
		;;
	esac

	if [ ${PROBE+1} ]; then
		echo "$PROG: warning: PROBE is obsolete" >&2
		if [ "$PROBE" != on ] && [ "$ROOT" = probe ]; then
			# Check conffile again.
			ROOT="$(
				unset ROOT
				. "$CONFDIR"/mkinitrd.conf
				echo "$ROOT"
			)"
		fi
	fi

	if [ -z "${DELAY##*[!0-9]*}" ]; then
		echo "$PROG: $DELAY: Illegal delay value" >&2
		exit 1
	fi
}

usage() {
	 revision='$Id: mkinitrd,v 1.69 2002/10/18 23:01:27 herbert Exp $'
	 cat >&2 << EOF

$revision

Usage: $PROG [OPTION]... <-o outfile> [moduledir]

Options:
  -d confdir  Specify an alternative configuration directory.
  -k          Keep temporary directory used to make the image.
  -m command  Set the command to make an initrd image.
  -o outfile  Write to outfile.
  -r root     Override ROOT setting in mkinitrd.conf.

See mkinitrd(8) for further details.
EOF
	exit 1
}

parseraidtab() {
	awk '
		$1 == "raiddev" {
			if ($2 == "'"$1"'") {
				start = 1
			} else if (start) {
				exit
			}
		}
		!start {
			next
		}
		$1 == "persistent-superblock" {
			sb = $2
			next
		}
		$1 == "raid-level" {
			raidlevel = $2
		}
		$1 == "device" {
			print $2 >> "/dev/fd/4"
		}
		END {
			if (start) {
				if (!raidlevel && !sb) {
					print "old"
				} else {
					print "new"
				}
			} else {
				print "bad"
			}
		}
	' /etc/raidtab
}

getroot() {
	device=$(readlink -f "$1")

	case "$device" in
	/dev/md*)
		if [ ! -f /etc/raidtab ]; then
			echo "$PROG: RAID support requires raidtools2" >&2
			exit 1
		fi
		{
			# raid* requires /dev/md0 or /dev/md/0 to exist.
			echo "${device%%[0-9]}0"
			echo "$device"
			echo "/etc/raidtab"
		} >&4
		{
			set "$device"
			echo "set \"$device\""
			[ -z "${1#/dev/md/*}" ] || {
				set "/dev/md/${device#/dev/md}"
				echo "[ -b \"\$1\" ] || set \"$1\""
			}
		} >&5
		case $(parseraidtab "$device") in
		new)
			echo 'raidstart "$1"' >&5
			echo /sbin/raidstart >&6
			;;
		old)
			echo 'raid0run "$1"' >&5
			echo /sbin/raid0run >&6
			;;
		*)
			echo "$PROG: $device: Device not listed in raidtab" >&2
			exit 1
			;;
		esac 4>&1 | tee $dir/getroot >&4
		eval $(sed 's/^/getroot /' $dir/getroot)
		;;
	/dev/sd* | /dev/scd* | /dev/scsi/*)
		if [ ! -d /proc/scsi ]; then
			echo "$PROG: Cannot determine SCSI module" >&2
			exit 1
		fi
		find /proc/scsi -type d -mindepth 1 -maxdepth 1 \
			-printf "%f\n" | sed '
				s/^am53c974$/AM53C974/; t
				s/^eata2x$/eata/; t
				s/^isp1020$/qlogicisp/; t
				s/^isp2x00$/qlogicfc/; t
				s/^ncr53c7xx$/53c7,8xx/; t
				s/^A2091$/a2091/; t
				s/^A3000$/a3000/; t
				s/^Amiga7xx$/amiga7xx/; t
				s/^Atari$/atari_scsi/; t
				s/^BVME6000$/bvme6000/; t
				s/^cpqfcTS$/cpqfc/; t
				s/^dtc3x80$/dtc/; t
				s/^GVP11$/gvp11/; t
				s/^INI9100U$/ini9100u/; t
				s/^INIA100$/inia100/; t
				s/^mac5380$/mac_scsi/; t
				s/^MVME147$/mvme147/; t
				s/^MVME16x$/mvme16x/; t
				s/^SGIWD93$/sgiwd93/; t
				s/^Sun3 5380 SCSI$/sun3_scsi/; t
				s/^esp-blz1230$/blz1230/; t
				s/^esp-blz2060$/blz2060/; t
				s/^esp-cyberstorm$/cyberstorm/; t
				s/^esp-cyberstormII$/cyberstormII/; t
				s/^esp$/dec_esp\
esp\
jazz_esp\
mac_esp\
mca_53c9x\
sun3x_esp/; t
				s/^esp-fastlane$/fastlane/; t
				s/^GVP11$/gvp11/; t
				s/^53c94$/mac53c94/; t
				s/^esp-oktagon$/octagon_esp/
			' >&3
		;;
	/dev/ida/*)
		echo cpqarray >&3
		;;
	/dev/rd/c*)
		echo DAC960 >&3
		;;
	/dev/cciss/*)
		echo cciss >&3
		;;
	/dev/ataraid/*)
		echo ide-probe-mod >&3
		echo ide-disk >&3
		echo hptraid >&3
		echo pdcraid >&3
		;;
	/dev/[fh]d* | /dev/ide/* | /dev/dasd* | /dev/dasd/*)
		;;
	*)
		{
			echo "$PROG: $device: Unknown root device"
			printf "%$((${#PROG} + 2))s%s\n" \
				' ' 'Please refer to the manual page.'
		} >&2
		exit 1
		;;
	esac
}

probe() {
	set -f
	set +f -- $ROOT
	device=$1
	type=$2
	if [ "$device" = probe ]; then
		script='
			/^#/ { next }
			$2 == "/" { print $1 " " $3 " " $4; exit }
		'
		root=$(awk "$script" /etc/fstab)
		if [ -z "$root" ]; then
			echo "$PROG: Cannot determine root device" >&2
			exit 1
		fi
		set -f
		set +f -- $root
		device=$1
	fi

	if [ -z "$type" ]; then
		set -- $(awk -F '	' '!$1 { print $2 }' /proc/filesystems)
	else
		IFS=,
		set -f
		set +f -- $type
		unset IFS
	fi

	if [ $# -eq 0 ]; then
		echo "$PROG: Cannot determine root file system" >&2
		exit 1
	fi

	FSTYPES=
	noext3=
	cramfs=

	for fs; do
		case $fs in
		ext2)
			if [ $noext3 ]; then
				FSTYPES=${FSTYPES:+$FSTYPES,}ext2
			else
				FSTYPES=${FSTYPES:+$FSTYPES,}ext3,ext2
				noext3=yes
			fi
			;;
		ext3)
			if ! [ $noext3 ]; then
				FSTYPES=${FSTYPES:+$FSTYPES,}ext3
				noext3=yes
			fi
			;;
		cramfs)
			cramfs=yes
			;;
		*)
			FSTYPES=${FSTYPES:+$FSTYPES,}$fs
			;;
		esac
	done

	if [ $cramfs ]; then
		FSTYPES=${FSTYPES:+$FSTYPES,}cramfs
	fi

	getroot "$device"
}

xldd() {
	i=$1

	set +e
	x=$(ldd $i)
	err=$?
	set -e

	case $err in
	0)
		;;
	1)
		[ -n "$x" ] || return
		set -f
		set +f -- $x
		[ "$*" = 'not a dynamic executable' ]
		return
		;;
	*)
		return $err
		;;
	esac
	echo "$x"
}

gendir() {
	dir=$1

	if [ ! -d "$MODULEDIR" ]; then
		mkdir $dir/empty
		MODULEDIR=$dir/empty
	fi

	exec \
		3> $dir/modules 4> $dir/files 5> $dir/script 6> $dir/exe

	[ -z "$ROOT" ] || probe

	exec 3>&- 4>&- 5>&- 6>&- 7>&-

	mkdir $dir/initrd

	shell=/bin/dash
	[ -f $shell ] || shell=/bin/ash

	{
		set "$CONFDIR/files"
		[ -f "$1" ] || set --
		cat "$@" $dir/files
		case $MODULES in
		all)
			find "$MODULEDIR/" -type f
			;;
		most)
			find "$MODULEDIR/" -maxdepth 1 -name 'modules.*'
			if [ -d "$MODULEDIR/boot" ]; then
				find "$MODULEDIR/boot" -type f
				set "$MODULEDIR"/kernel/drivers/video/fb*.o
				[ -e "$1" ] && printf '%s\n' "$@"
			fi
			if [ -d "$MODULEDIR/kernel" ]; then
				for i in \
					"$MODULEDIR/kernel/drivers/block" \
					"$MODULEDIR/kernel/drivers/ide" \
					"$MODULEDIR/kernel/drivers/md" \
					"$MODULEDIR/kernel/drivers/scsi" \
					"$MODULEDIR/kernel/fs" \
					"$MODULEDIR/kernel/net/unix"
				do
					[ -d "$i/" ] || continue
					find "$i/" -type f
				done > $dir/tmp1
				< $dir/tmp1 awk '
					!/fs\/binfmt_[^\/]*$/ &&
					!/fs\/autofs.*\/[^\/]*$/ &&
					!/fs\/coda\/[^\/]*$/ &&
					!/fs\/intermezzo\/[^\/]*$/ &&
					!/fs\/lockd\/[^\/]*$/ &&
					!/fs\/ncpfs.*\/[^\/]*$/ &&
					!/fs\/nfs.*\/[^\/]*$/ &&
					!/fs\/nls\/[^\/]*$/ &&
					!/fs\/ramfs\/[^\/]*$/ &&
					!/fs\/smbfs\/[^\/]*$/ &&
					!/drivers\/block\/loop\.o$/ &&
					!/drivers\/block\/nbd\.o$/ &&
					!/drivers\/block\/paride\/[^\/]*$/ &&
					!/drivers\/ide\/ide-cs\.o$/ &&
					!/drivers\/ide\/ide-tape\.o$/ &&
					!/drivers\/scsi\/ide-scsi\.o$/ &&
					!/drivers\/scsi\/osst\.o$/ &&
					!/drivers\/scsi\/pcmcia\/[^\/]*$/ &&
					!/drivers\/scsi\/ppa\.o$/ &&
					!/drivers\/scsi\/scsi_debug\.o$/ &&
					!/drivers\/scsi\/s[gt]\.o$/ {
						print
					}
				'
			else
				for i in \
					"$MODULEDIR/block" \
					"$MODULEDIR/fs" \
					"$MODULEDIR/scsi"
				do
					[ -d "$i/" ] || continue
					find "$i/" -type f
				done
				[ -f "$MODULEDIR/misc/unix.o" ] &&
					echo "$MODULEDIR/misc/unix.o"
			fi
			;;
		none)
			find "$MODULEDIR/" -maxdepth 1 -name 'modules.*'
			;;
		esac
		set "$CONFDIR/exe"
		[ -f "$1" ] || set --
		for i in \
			$([ $DELAY -gt 0 ] && echo /bin/sleep) \
			/sbin/modprobe /sbin/insmod $shell \
			/bin/echo /bin/mount /bin/umount \
			/sbin/pivot_root /bin/cat /bin/mknod \
			/usr/sbin/chroot \
			`cat "$@" $dir/exe`
		do
			echo $i
			xldd $i >&3
		done 3> $dir/tmp2
		< $dir/tmp2 sed 's/.*=>[[:blank:]]*\([^[:blank:]]*\).*/\1/'
		echo /dev/console
		echo /dev/null
		echo /etc/modules.conf
	} > $dir/tmp3
	< $dir/tmp3 sort -u | cpio -pLd --quiet $dir/initrd

	ln -s ${shell##*/} $dir/initrd/bin/sh
	install /usr/share/initrd-tools/init $dir/initrd/sbin
	install /usr/share/initrd-tools/linuxrc $dir/initrd
	{
		echo DELAY=$DELAY
		[ -z "$ROOT" ] || echo "FSTYPES=$FSTYPES"
	} > $dir/initrd/linuxrc.conf
	egrep -v '^(#.*)?$' "$CONFDIR"/modules | cat -n - $dir/modules |
		sort -u -k 2,2 | sort -n -k 1,1 |
		awk '{ print "modprobe -k " $2 }' > $dir/initrd/loadmodules
	mv $dir/script $dir/initrd
	cd $dir/initrd
	mkdir -p dev2 etc mnt proc scripts tmp
	cd - > /dev/null

	INITRDDIR=$dir/initrd MODULEDIR="$MODULEDIR" \
		run-parts "$CONFDIR"/scripts
}

ORIGDIR=`pwd`
PROG="$0"

CONFDIR=/etc/mkinitrd
unset keep croot cmkimage out || :

while getopts "d:km:o:r:" flag; do
	case $flag in
	d)
		CONFDIR="$OPTARG"
		if [ ! -d "$CONFDIR" ]; then
			echo "$PROG: $CONFDIR: Not a directory" >&2
			exit 1
		fi
		[ -z "${CONFDIR##/*}" ] || CONFDIR="$ORIGDIR/$CONFDIR"
		;;
	k)
		keep=yes
		;;
	m)
		cmkimage=$OPTARG
		;;
	o)
		exec > "$OPTARG"
		out=yes
		;;
	r)
		croot=$OPTARG
		;;
	*)
		usage
		;;
	esac
done
shift $(($OPTIND - 1))

if ! [ $out ] || [ $# -gt 1 ]; then
	usage
fi

MODULEDIR="${1-/lib/modules/$(uname -r)}"
[ -z "${MODULEDIR##/*}" ] || MODULEDIR="$ORIGDIR/$MODULEDIR"

if [ ! -f "$CONFDIR"/mkinitrd.conf ]; then
	echo "$PROG: $CONFDIR/mkinitrd.conf: Configuration file not found" >&2
	exit 1
fi

defaults
. "$CONFDIR"/mkinitrd.conf
cleanse

ROOT=${croot-$ROOT}
MKIMAGE=${cmkimage-$MKIMAGE}

DSIG=
exittrap='if [ $DSIG ]; then trap - "$DSIG"; kill -s $DSIG $$; fi'
trap "$exittrap" EXIT
for i in HUP INT TERM; do
	trap "DSIG=$i; exit" $i
done

workdir=${TMPDIR:-/tmp}/mkinitrd.$$
eval $(
	trap '' HUP INT TERM
	if ! mkdir $workdir; then
		echo "$PROG: $workdir: Cannot create temporary directory" >&2
		echo exit 1
		exit
	fi
	if [ $keep ]; then
		echo "$PROG: The working directory $workdir will be kept." >&2
	else
		echo trap "'rm -r $workdir; $exittrap'" EXIT
	fi
)

umask "$UMASK"
gendir $workdir
eval "$(printf "$MKIMAGE" $workdir/initrd /dev/fd/3)" 3>&1 >&2
