#!/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>

PROG="${0##*/}"
VERSION=2.8.7-alt
CATCHED=

umask 077

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

Fatal()
{
	echo -E "$@" >&2
	Exit 1
}

verbose=
Verbose()
{
	[ -z "$verbose" ] || echo -E "$@"
}

debug=
Debug()
{
	[ -z "$debug" ] || echo -E "$@"
}

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=
FAKEMOD=
IMAGESIZE=
INODES=

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

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

trap exit_handler EXIT
trap signal_handler SIGHUP SIGPIPE SIGINT SIGTERM SIGQUIT

USE_COMPRESS=1
TARGET=
KERNEL=
strict=
noscsi=
noide=
noraid=
pause=
MODULES=
MODULES_DIR=
MODULES_CONF=
HAVE_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="$IGNORE_MODNAMES ppa imm ide-scsi usb-storage"
TESTED_MODNAMES=

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

TestedModule()
{
	local name="$1" m
	for m in $TESTED_MODNAMES; do
		[ "$m" != "$name" ] || return 0
	done
	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
}

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

	if TestedModule "$name"; then
		return 0
	else
		TESTED_MODNAMES="$TESTED_MODNAMES
$name"
	fi

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

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

	local m
	for m in `/sbin/modprobe -b "$FAKEMOD" -L "$name" 2>/dev/null`; do
		[ "$name" = "$m" ] || FindModule "$m"
	done

	local path
	path=`cd "$MODULES_DIR" &&
		/bin/find -type f \( -name "$name.o" -o -name "$name.o.gz" \) |
			/bin/cut -d/ -f2-`

	if [ ! -f "$MODULES_DIR/$path" ]; 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

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

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

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

	local scsimodules
	scsimodules=`/bin/egrep -s '(alias|probeall)[ 	]+scsi_hostadapter' "$MODULES_CONF" |
		/bin/grep -v '^[ 	]*#' |
		LC_COLLATE=C /bin/sort -u |
		/bin/awk '{$1=$2="";print}'`
	
	local n
	for n in $scsimodules; do
		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 /bin/egrep -s '^/dev/md/?[0-9]+[ 	]' "$FSTAB_FILE" |
		/bin/fgrep -qsv noauto; then
		HAVE_RAID=1
		FindModule -md
		for number in $(/bin/grep '^[ 	]*raid-level' /etc/raidtab |
			  /bin/awk '{print $2}' |LC_COLLATE=C /bin/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

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

FindRootModules()
{
	# In case the root filesystem is modular.
	#rootdev=$(/bin/awk '{if (($2 == "/") && ($1 !~ /^[ \t]*#/) {print $1}}' "$FSTAB_FILE")
	local rootfs
	rootfs=$(/bin/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=$(/bin/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=$(/bin/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" |/bin/awk '{print $1}')
		loopFs=$(echo "$line" |/bin/awk '{print $3}')
		loopFile=$(echo "$full" |/bin/sed "s|$dir||")

		FindModule -loop
		FindModule -"$loopFs"
	fi
}

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

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

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

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

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

SymlinkedCopy()
{
	local from="$1"
	shift
	local to="$1"
	shift
	[ -e "$from" ] || return 0
	[ ! -L "$from" -o "$from" != build ] || return 0
	if [ -d "$from" ]; then
		Mkdir -p "$to"
		local f
		for f in "$from"/*; do
			SymlinkedCopy "$f" "$to/${f##*/}"
		done
	else
		/bin/ln -s "$from" "$to" || Fatal "Failed to create symlink."
	fi
}

CopyModuleTree()
{
	local from="$1"
	shift
	local to="$1"
	shift
	local f
	for f in "$from"/*; do
		[ -e "$f" ] || continue
		if [ -d "$f" ]; then
			SymlinkedCopy "$f" "$to/${f##*/}"
		else
			Cp -p "$f" "$to/${f##*/}" ||
				Fatal "Failed to copy \"$f\" file."
		fi
	done
}

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/losetup"
	Ln -s sh "$MNTDIR/bin/mount"

	local m
	for m in $MODULES; do
		Install "$MODULES_DIR/$m" "$MNTDIR/$MODULES_DIR/$m" ||
			Fatal "Failed to install $m module."
		m="$MNTDIR/$MODULES_DIR/$m"
		if [ -z "${m%%*.gz}" ]; then
			/bin/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

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

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

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

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

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

	if [ -n "$loopDev" ]; then
		Cp -aL "$loopDev" $MNTDIR/safedev
		Cp -aL /dev/loop7 $MNTDIR/safedev
		loopDev=`echo "$loopDev" |/bin/sed 's|/dev/|/safedev/|'`
		/bin/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
		/bin/cat >&2 <<__EOF__

You can now edit initrd manually.
WORKDIR: $WORKDIR

Press ENTER to continue automatic initrd generation.

__EOF__
		read
	fi

	/bin/chmod +x "$RCFILE"

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

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

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

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

	/bin/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.
	/bin/rmdir "$MNTPOINT/lost+found"

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

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

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

MakeImageFile()
{
	if /bin/fgrep -qs romfs "/boot/System.map-$KERNEL"; then
		MakeImageFile_romfs
		Verbose "Created romfs image file"
		return
	fi
	if /bin/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=`/bin/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()
{
	/bin/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=`/bin/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: -- "$@"` || Usage 1
eval set -- "$TEMP"

img_vers=
force=
while :; do
	case "$1" in
		--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."

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

FAKEMOD="$WORKDIR/fakemod"
FAKEPATH="$FAKEMOD/lib/modules/$uname_r"
Mkdir -p "$FAKEPATH" || Fatal "Failed to create modules hierarchy."

Verbose -n "Copying directory tree from $MODULES_DIR: "
Debug
CopyModuleTree "$MODULES_DIR" "$FAKEPATH" &&
	Verbose done. ||
	Fatal "Failed to copy directory tree."

if [ "$uname_r" != "$KERNEL" ]; then
	Ln -s "$uname_r" "$FAKEMOD/lib/modules/$KERNEL" || Fatal "Failed to create symlink."
fi

Verbose -n "Generating module dependencies in $FAKEMOD: "
/sbin/depmod -a -b "$FAKEMOD" -F "/boot/System.map-$KERNEL" "$KERNEL" &&
	Verbose done. ||
	Fatal "Failed to generate module dependencies."

### 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
	/bin/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=`/bin/du -hs $TARGET |/bin/cut -f1`
	echo "Ramdisk size: $size"
fi
