#!/bin/sh

# mkinitrd
#
# Written by Erik Troan <ewt@redhat.com>
#
# Contributors:
#	Elliot Lee <sopwith@cuc.edu>
#	Miguel de Icaza <miguel@nuclecu.unam.mx>
#	Christian 'Dr. Disk' Hechelmann <drdisk@ds9.au.s.shuttle.de>
#	Michael K. Johnson <johnsonm@redhat.com>
#	Pierre Habraken <Pierre.Habraken@ujf-grenoble.fr>
#	Jakub Jelinek <jj@ultra.linux.cz>
#	Carlo Arenas Belon (carenas@chasqui.lared.net.pe>
#	Keith Owens <kaos@ocs.com.au>
#
# Rewritten by Dmitry V. Levin <ldv@altlinux.org>
#
# Contributors:
#	 Nick S. Grechukh <gns@altlinux.ru>:
#		Aug 15, 2004 improved raid support:
#			- using LVM over raid
#			- arrays created with mdadm (without /etc/raidtab)
#			- loading raid support to initrd can be forced (for autodetect at bootstrap)
#			bugs: raid-over-raid (level 10) will not working this way. however, formerly it wasn't working at all.

PROG=mkinitrd
VERSION=2.9.1
CATCHED=

# override PATH
export PATH="/bin:/sbin:/usr/bin:/usr/sbin"

umask 077

Exit()
{
	local rc=$?
	[ -z "$1" ] || rc="$1"
	CATCHED=1
	exit $rc
}

Fatal()
{
	echo "$PROG: $*" >&2
	Exit 1
}

verbose=
Verbose()
{
	[ -n "$verbose" ] || return 0
	echo "$PROG: $*"
}

debug=
Debug()
{
	[ -n "$debug" ] || return 0
	echo "$PROG: $*"
}

is_yes()
{
	# Test syntax	
	if [ $# = 0 ]; then
		return 2
	fi

	# Check value
	case "$1" in
		yes|Yes|YES|true|True|TRUE|on|On|ON|Y|y|1)
			# true returns zero
			return 0
		;;
		*)
			# false returns one
			return 1
		;;
	esac
}

is_no()
{
	# Test syntax
	if [ $# = 0 ] ; then
		return 2
	fi

	case "$1" in
		no|No|NO|false|False|FALSE|off|Off|OFF|N|n|0)
			# true returns zero
			return 0
			;;
		*)
			# false returns one
			return 1
		;;
	esac
}

WORKDIR=
IMAGE=
MNTDIR=
MNTPOINT=
IMAGESIZE=
INODES=

exit_handler()
{
	local rc=$?
	trap - EXIT
	[ -n "$CATCHED" -o $rc -eq 0 ] ||
		echo "$PROG: unhandled error, exiting..."
	[ -n "$MNTPOINT" ] && umount "$MNTPOINT" >/dev/null 2>&1 ||:
	[ -z "$WORKDIR" ] || rm -rf "$WORKDIR"
	exit $rc
}

signal_handler()
{
	echo 'Interrupted!' >&2
	Exit 1
}

trap exit_handler EXIT
trap signal_handler SIGHUP SIGPIPE SIGINT SIGTERM SIGQUIT

NormalizedName()
{
	echo -n "X$1" | sed -e '1s/^X//' -e 'y/-/_/'
}

USE_COMPRESS=1
TARGET=
KERNEL=
strict=
noscsi=
noide=
noraid=
pause=
MODULES=
MODULES_DIR=
MODULES_CONF=
HAVE_RAID=

PVSCAN=/sbin/pvscan
MDADM=/usr/sbin/mdadm
FORCE_RAID=

FSTAB_FILE=/etc/fstab

BIN_SPLASH=/bin/splash
[ -x "$BIN_SPLASH" ] && ADD_BOOTSPLASH=1 || ADD_BOOTSPLASH=

PRE_SCSI_MODNAMES="scsi_mod sd_mod unknown"
IGNORE_MODNAMES="`NormalizedName "$IGNORE_MODNAMES"`"
IGNORE_MODNAMES=" $IGNORE_MODNAMES ppa imm ide_scsi usb_storage"
LOADED_MODNAMES=

loopDev=
loopFs=
loopFile=
uname_r=`uname -r`

ModuleAlreadyLoaded()
{
	local name="$1" m
	for m in $LOADED_MODNAMES; do
		[ "$m" != "$name" ] || return 0
	done
	LOADED_MODNAMES="$LOADED_MODNAMES
$name"
	return 1
}

IgnoredModule()
{
	local name="$1" m
	for m in off null $IGNORE_MODNAMES; do
		[ "$m" != "$name" ] || return 0
	done
	return 1
}

PreScsiModule()
{
	local name="$1" m
	for m in $PRE_SCSI_MODNAMES; do
		[ "$m" != "$name" ] || return 0
	done
	return 1
}

AddModuleFile()
{
	local path="$1" name="$1"
	name="${name##*/}"
	name="${name%.gz}"
	name="${name%.o}"
	name="${name%.ko}"
	name="`NormalizedName "$name"`"

	if ModuleAlreadyLoaded "$name"; then
		return 0
	fi

	if IgnoredModule "$name"; then
		Debug "Ignoring \"$name\" module"
		return 0
	fi

	Debug "Found module \"$name\" as $path"
	MODULES="$MODULES $path"
}

FindModule()
{
	local skip_errors= name="$1"
	if [ -z "${name##-*}" ]; then
		skip_errors=1
		name="${name#-*}"
	fi
	name="${name%.gz}"
	name="${name%.o}"
	name="${name%.ko}"
	name="`NormalizedName "$name"`"

	Debug "Looking for \"$name\" module"

	local list

	list=`modprobe --kernel-release "$KERNEL" --list-module-files "$name" 2>/dev/null`
	if [ $? -ne 0 ]; then
		if [ -n "$skip_errors" ]; then
			Debug "Ignoring missing \"$name\" module"
			return 0
		fi

		if PreScsiModule "$name"; then
			Debug "Ignoring missing \"$name\" SCSI module"
			return 0
		fi

		echo "No module \"$name\" found for kernel $KERNEL" >&2
		[ -z "$strict" ] && return 1 || Exit 1
	fi

	local m
	for m in $list; do
		[ -z "${m##/lib/modules/*}" ] || continue
		AddModuleFile "$m"
	done
}

FindModules()
{
	local n
	for n in "$@"; do
		FindModule "$n"
	done
}

FindScsiModules()
{
	[ -z "$noscsi" ] || return

	local scsimodules
	scsimodules=`egrep -s '(alias|probeall)[ 	]+scsi_hostadapter' "$MODULES_CONF" |
		grep -v '^[ 	]*#' |
		LC_COLLATE=C sort -u |
		awk '{$1=$2="";print}'`
	
	local n
	for n in $scsimodules; do
		n="`NormalizedName "$n"`"
		if IgnoredModule "$n"; then
			Debug "Ignoring \"$n\" module"
		else
			local m
			for m in $PRE_SCSI_MODNAMES; do
				FindModule "$m"
			done
			FindModule "$n"
		fi
	done
}

FindIdeModules()
{
	[ -z "$noide" ] || return

	local ide
	ide=/proc/ide/ide*
	if [ -n "$ide" ]; then
		FindModule -ide-mod
		FindModule -ide-probe-mod
		FindModule -ide-disk
	fi
}

FindRaidModules()
{
	[ -z "$noraid" ] || return

	if egrep -s '^/dev/md/?[0-9]+[ 	]' "$FSTAB_FILE" | fgrep -qsv noauto \
	    || [ -x $PVSCAN ] && $PVSCAN grep ACTIVE | egrep -q '"/dev/md/?[0-9]"' \
	    || [ -n "$FORCE_RAID" ]; then 
#### detects indirect using of raid arrays (e.g. via LVM) 

		HAVE_RAID=1
		FindModule -md
		if [ -f /etc/raidtab ]; then
		for number in $(grep '^[ 	]*raid-level' /etc/raidtab |
			  awk '{print $2}' |LC_COLLATE=C sort -u); do
			case "$number" in
				[015])
					FindModule "raid$number"
					;;
				4)
					FindModule "raid5"
					;;
				-1|linear)
					FindModule "linear"
					;;
				*)
					echo "raid level $number (in /etc/raidtab) not recognized" >&2
					;;
			esac
		done
		fi

#### detects arrays _without_ raidtab (e.g. created with mdadm). maybe rewrite to /proc/mdstat, heh?
#### of cource, it will detect only running arrays
		if [ -x $MDADM ]; then
			for raidmod in `$MDADM --detail --scan|awk '{print $3}'|awk -F '=' '{print $2}'`; do
				case "$raidmod" in 
					linear|raid[015])
						FindModule "$raidmod"
						;;
					*)
						echo "raid level $raidmod (in output of mdadm) not recognized" >&2
						;;
				esac
			done
		fi

#### and just for perfection: allow to _manually_ specify raid level, even if it is not running now.
		case "$FORCE_RAID" in
			[015])
				FindModule "raid$FORCE_RAID"
				;;
			4)
				FindModule "raid5"
				;;
			-1|linear)
				FindModule "linear"
				;;
			*)
				echo "raid level $FORCE_RAID (in --force-raid) not recognized" >&2
				;;
		esac
	fi

	if grep -s '^/dev/ataraid' "$FSTAB_FILE" |fgrep -qsv noauto; then
		local ataraidmodules
		ataraidmodules=`egrep -s '(alias|probeall)[ 	]+ataraid_hostadapter' "$MODULES_CONF" |
			grep -v '^[ 	]*#' |
			LC_COLLATE=C sort -u |
			awk '{$1=$2="";print}'`
		local n
		for n in $ataraidmodules; do
			FindModule "$n"
		done
	fi
}

FindRootModules()
{
	# In case the root filesystem is modular.
	#rootdev=$(awk '{if (($2 == "/") && ($1 !~ /^[ \t]*#/) {print $1}}' "$FSTAB_FILE")
	local rootfs
	rootfs=$(awk '{if (($2 == "/") && ($1 !~ /^[ \t]*#/)) {print $3}}' "$FSTAB_FILE")
	[ -z "$rootfs" ] || FindModule -"$rootfs"
}

FindLoopModules()
{
	# check to see if we need to set up a loopback filesystem
	local full
	full=$(awk '$2 == "/" && $4 ~ "loop" {print $1}' "$FSTAB_FILE")
	if [ -n "$full" ]; then
		local dir="$full" line=
		while [ -n "$dir" -a -z "$line" ]; do
			dir="${dir%/*}"
			line=$(awk -v "dir=$dir" '$2 == dir {print $0}' "$FSTAB_FILE")	
		done
		[ -n "$line" -a "$dir" != / ] ||
			Fatal "bad fstab, loopback file doesn't belong to any device."
		loopDev=$(echo "$line" |awk '{print $1}')
		loopFs=$(echo "$line" |awk '{print $3}')
		loopFile=$(echo "$full" |sed "s|$dir||")

		FindModule -loop
		FindModule -"$loopFs"
	fi
}

Install()
{
	install -D $debug "$1" "$2" ||
		Fatal "Failed to install \"$1\" file."
}

Cp()
{
	cp $debug "$@"
}

Ln()
{
	ln $debug "$@"
}

Mkdir()
{
	mkdir $debug "$@"
}

Mknod()
{
	file="$1"
	shift
	mknod "$file" "$@" &&
		Debug "Created $file device" ||
		Fatal "Failed to create \"$file\" device."
}

MakeMountDir()
{
	MNTDIR="$WORKDIR/tree"
	RCFILE="$MNTDIR/linuxrc"

	Mkdir -p $MNTDIR/{etc,dev,safedev,loopfs} ||
		Fatal "Failed to create directories."

	Install /lib/mkinitrd/busybox "$MNTDIR/bin/sh"
	Ln -s sh "$MNTDIR/bin/echo"
	Ln -s sh "$MNTDIR/bin/insmod"
	Ln -s sh "$MNTDIR/bin/modprobe"

	local m
	for m in $MODULES; do
		Install "$m" "$MNTDIR/$m" ||
			Fatal "Failed to install $m module."
		m="$MNTDIR/$m"
		if [ -z "${m%%*.gz}" ]; then
			gunzip $debug "$m" ||
				Fatal "Failed to uncompress \"$m\" module."
		fi
	done

	Mknod "$MNTDIR/dev/console" c 5 1
	Mknod "$MNTDIR/dev/null" c 1 3
	Mknod "$MNTDIR/dev/ram" b 1 1
	Mknod "$MNTDIR/dev/systty" c 4 0
	Mknod "$MNTDIR/dev/tty1" c 4 1

	cat >"$RCFILE" <<EOF
#!/bin/sh
EOF

	for m in $MODULES; do
		m="${m%.gz}"
		local n
		n="${m##*/}"
		n="${n%.o}"
		n="${n%.ko}"

		options=`sed -ne "s/^options[ 	]\\+$n[ 	]\\+//p" "$MODULES_CONF"`

		Debug "Loading module \"$m\" with options \"$options\""
		cat >>"$RCFILE" <<EOF
/bin/insmod -f $m $options
EOF
	done

	if [ -n "$HAVE_RAID" ]; then
		Mknod "$MNTDIR/safedev/md255" b 9 255
		Ln -s sh "$MNTDIR/bin/raidautorun"
		cat >>"$RCFILE" <<EOF
/bin/raidautorun /safedev/md255
EOF
	fi

	if [ -n "$loopDev" ]; then
		Ln -s sh "$MNTDIR/bin/mount"
		Ln -s sh "$MNTDIR/bin/losetup"
		Cp -aL "$loopDev" $MNTDIR/safedev
		Cp -aL /dev/loop7 $MNTDIR/safedev
		loopDev=`echo "$loopDev" |sed -e 's|/dev/|/safedev/|'`
		cat >>"$RCFILE" <<EOF
echo Mounting device containing loopback root filesystem
/bin/mount -t $loopFs $loopDev /loopfs
echo Setting up loopback file $loopFile
/bin/losetup /safedev/loop7 /loopfs/$loopFile
EOF
	fi

	if [ -n "$pause" ]; then
		cat >&2 <<__EOF__

You can now edit initrd manually.
WORKDIR: $WORKDIR

Press ENTER to continue automatic initrd generation.

__EOF__
		read
	fi

	chmod +x "$RCFILE"

	if [ -n "$verbose" ]; then
		echo "Contents of linuxrc:"
		cat "$RCFILE"
	fi

	INODES=`find "$MNTDIR" |wc -l` ||
		Fatal "Failed to calculate inodes."
	INODES=$[INODES+20]
	Verbose "Inode count: $[INODES]"
	IMAGESIZE=`du -ks --block-size=4096 "$MNTDIR" |cut -f1` ||
		Fatal "Failed to calculate image size."
	# Add more 20%
	IMAGESIZE=$[IMAGESIZE*6/5]
	Verbose "Image size: $[IMAGESIZE*4]K"
}

MakeImageFile_ext2()
{
	dd if=/dev/zero of="$IMAGE" bs=4k count="$IMAGESIZE" 2>/dev/null &&
		Verbose "Created image file" ||
		Fatal "Failed to create image file."

	mke2fs -q -m 0 -F -N "$INODES" "$IMAGE" &&
		Verbose "Created filesystem for ramdisk" ||
		Fatal "Failed to create filesystem."

	mount $verbose -t ext2 "$IMAGE" "$MNTPOINT" -o loop,noexec,nosuid,nodev ||
		Fatal "Failed to mount loopback device."

	# We don't need this directory, so let's save space.
	rmdir "$MNTPOINT/lost+found"

	(cd "$MNTDIR"; tar cf - .) | (cd "$MNTPOINT"; tar xf -) &&
		Verbose "Installed directory tree: $MNTDIR --> $MNTPOINT" ||
		Fatal "Failed to copy directory tree."

	umount "$MNTPOINT" || Fatal "Failed to unmount loopback device."
}

MakeImageFile_romfs()
{
	genromfs -f "$IMAGE" -d "$MNTDIR" &&
		Verbose "Created image from tree: $MNTDIR --> $IMAGE" ||
		Fatal "Failed to create romfs image."
}

MakeImageFile()
{
	if fgrep -qs romfs "/boot/System.map-$KERNEL"; then
		MakeImageFile_romfs
		Verbose "Created romfs image file"
		return
	fi
	if fgrep -qs ext2 "/boot/System.map-$KERNEL"; then
		MakeImageFile_ext2
		Verbose "Created ext2 image file"
		return
	fi
	Fatal "Failed to create image file: neither romfs nor ext2 support found in your kernel"
}

AddBootSplash()
{
	local config="/etc/sysconfig/bootsplash"
	local fbresolution=
	[ -s "$config" ] &&
		. "$config" &&
		is_yes "$SPLASH" &&
		[ -n "$THEME" ] &&
		fbresolution=`fbresolution` ||:
	local themefile
	themefile="/usr/share/splash/themes/$THEME/config/bootsplash-$fbresolution.cfg"
	if [ -f "$themefile" ]; then
		"$BIN_SPLASH" -f -s "$themefile" >>"$TARGET" &&
			Verbose "Added bootsplash \"$themefile\" to the $TARGET" ||
			Verbose "Failed to add bootsplash \"$themefile\" to the $TARGET."
			
	fi
}

Usage()
{
	cat >&2 <<EOF
mkinitrd - creates an initial ramdisk image for preloading modules.

mkinitrd is free software, covered by the GNU General Public License.
mkinitrd comes with ABSOLUTELY NO WARRANTY, see license for details.

Usage: $PROG [options] <initrd-image> <kernel-version>

Valid options are:
--fstab FILENAME                use FILENAME instead of /etc/fstab.
--preload MODULENAME		load MODULENAME before all found automatically.
--with MODULENAME               load MODULENAME after all found automatically.
--omit-scsi-modules             do not load any SCSI modules.
--omit-ide-modules              do not load any IDE modules.
--omit-raid-modules             do not load any raid modules.
--pause                         pause for manual initrd editing.
--nocompress                    do not compress initrd image.
--nobootsplash                  do not add bootsplash to the initrd image.
--strict                        abort on errors.
--image-version                 make image name based on kernel name.
--ifneeded                      create initrd image only if needed.
--version                       print version number and exit.
-f, --force                     force initrd image creation.
-v, --verbose                   be more verbose.
-d, --debug                     print debug information.
-h, --help                      show this text.

Example: $PROG /boot/initrd-$uname_r.img $uname_r

EOF
	[ -n "$1" ] && Exit "$1" || Exit
}

TEMP=`getopt -n "$PROG" -o fhvd -l help,version,verbose,debug,force,ifneeded,omit-scsi-modules,omit-ide-modules,omit-raid-modules,pause,image-version,nocompress,nobootsplash,strict,fstab:,before:,preload:,with:,after:,force-raid: -- "$@"` || Usage 1
eval set -- "$TEMP"

img_vers=
force=
while :; do
	case "$1" in
		--force-raid)
			shift
			FORCE_RAID=$1
			shift
			;;
		--fstab)
			shift
			FSTAB_FILE=$1
			shift
			;;
		--before|--preload)
			shift
			PRELOAD_MODNAMES="$PRELOAD_MODNAMES $1"
			shift
			;;
		--after|--with)
			shift
			POSTLOAD_MODNAMES="$POSTLOAD_MODNAMES $1"
			shift
			;;
		--strict)
			strict=1
			shift
			;;
		--ifneeded)
			ifneeded=1
			shift
			;;
		--nocompress)
			USE_COMPRESS=
			shift
			;;
		--nobootsplash)
			ADD_BOOTSPLASH=
			shift
			;;
		--omit-scsi-modules)
			noscsi=1
			shift
			;;
		--omit-ide-modules)
			noide=1
			shift
			;;
		--omit-raid-modules)
			noraid=1
			shift
			;;
		--pause)
			pause=1
			shift
			;;
		--image-version)
			img_vers=1
			shift
			;;
		-v|--verbose)
			verbose=-v
			shift
			;;
		-d|--debug)
			verbose=-v
			debug=-v
			shift
			;;
		-f|--force)
			force=1
			shift
			;;
		--version)
			echo "$PROG: version $VERSION"
			exit 0
			;;
		-h|--help)
			Usage 0
			;;
		--)
			shift
			break
			;;
		*)
			Fatal "$PROG: unrecognized option: $1"
			;;
	esac
done

TARGET=$1
[ -n "$TARGET" ] || Fatal "Target image not specified."
shift

KERNEL=$1
[ -n "$KERNEL" ] || Fatal "Kernel version not specified."
shift

if [ -n "$img_vers" ]; then
	TARGET="$TARGET-$KERNEL.img"
fi

[ -n "$force" -o ! -f "$TARGET" ] || Fatal "$TARGET already exists."

MODULES_DIR="/lib/modules/$KERNEL"
[ -d "$MODULES_DIR" ] ||
	Fatal "Directory \"$MODULES_DIR\" doesn't exist or not accessible."

WORKDIR=`mktemp -td initrd.XXXXXXXXXX` ||
	Fatal "Failed to create working directory."

Verbose "Generating module dependencies..."
depmod -a -F "/boot/System.map-$KERNEL" "$KERNEL" &&
	Verbose "...done." ||
	Fatal "Failed to generate module dependencies."

MODULES_CONF="$WORKDIR/modules.conf"
modprobe -c >"$MODULES_CONF" ||
	Fatal "Failed to parse modutils configuration."

### Begin module lookup.

FindModules $PRELOAD_MODNAMES

FindScsiModules

FindIdeModules

FindRaidModules

FindRootModules

FindLoopModules

FindModules $POSTLOAD_MODNAMES

### End module lookup.

if [ -n "$ifneeded" -a -z "$MODULES" ]; then
	Verbose "No modules are needed - not building initrd image."
	exit 0
fi

Verbose "Using modules: $MODULES"

MakeMountDir

MNTPOINT="$WORKDIR/mnt"
Mkdir -p "$MNTPOINT" || Fatal "Failed to create mount point."

IMAGE="$WORKDIR/img"
:>"$IMAGE" || Fatal "Failed to create image file."

MakeImageFile

if [ -n "$USE_COMPRESS" ]; then
	gzip -9n <"$IMAGE" >"$TARGET" &&
		Verbose "Installed ramdisk into $TARGET" ||
		Fatal "Failed to install ramdisk into $TARGET."
else
	Cp "$IMAGE" "$TARGET" &&
		Verbose "Installed ramdisk into $TARGET" ||
		Fatal "Failed to install ramdisk into $TARGET."
fi

if [ -n "$ADD_BOOTSPLASH" ]; then
	AddBootSplash
fi

if [ -n "$verbose" ]; then
	size=`du -hs $TARGET |cut -f1`
	echo "Ramdisk size: $size"
fi