#!/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@alt-linux.org>

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

umask 077

Exit()
{
	[ -n "$1" ] && RETVAL="$1" || RETVAL=$?
	CATCHED=1
	exit $RETVAL
}

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

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

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

WORKDIR=
IMAGE=
MNTDIR=
MNTPOINT=
FAKEMOD=
IMAGESIZE=
INODES=
exit_handler()
{
	RETVAL=$?
	trap '' EXIT
	[ -n "$CATCHED" -o $RETVAL -eq 0 ] ||
		echo "$PROG: something odd happened, exiting..."
	[ -n "$MNTPOINT" ] && /bin/umount "$MNTPOINT" &>/dev/null ||:
	[ -z "$WORKDIR" ] || /bin/rm -rf "$WORKDIR"
	exit $RETVAL
}

Signal()
{
	echo 'Interrupted!'
	Exit 1
}

trap "exit_handler " EXIT
trap "Signal " SIGHUP SIGPIPE SIGINT SIGTERM SIGQUIT

USE_COMPRESS=1
TARGET=
KERNEL=
strict=
noscsi=
noide=
noraid=
pause=
MODULES=

MODULES_CONF=/etc/modules.conf
FSTAB_FILE=/etc/fstab
MODULES_DIR=

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

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

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

FindModule()
{
	local skip_errors=
	local 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 module $name"

	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=`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=`/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=/proc/ide/ide*
	if [ -n "$ide" ]; then
		FindModule -ide-mod
		FindModule -ide-probe-mod
		FindModule -ide-disk
	fi
}

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

	if /bin/grep -s '^/dev/md' "$FSTAB_FILE" |/bin/fgrep -v noauto >/dev/null 2>&1; then
		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
				[0145])
					FindModule "raid$number"
					;;
				*)
					echo "raid level $number (in /etc/raidtab) not recognized" >&2
					;;
			esac
		done
	fi

	if /bin/grep -s '^/dev/ataraid' "$FSTAB_FILE" |/bin/fgrep -v noauto >/dev/null 2>&1; then
		local 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=$(/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=$(/bin/awk '$2 == "/" && $4 ~ "loop" {print $1}' "$FSTAB_FILE")
	if [ -n "$full" ]; then
		local dir=$full
		local 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="${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 "$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:"
		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."
	IMAGESIZE=$[IMAGESIZE*11/10]
	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"
}

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."

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
	[ -f "/etc/sysconfig/bootsplash" ] && . /etc/sysconfig/bootsplash ||:
	[ -n "$SPLASH" -a -n "$THEME" ] && fbresolution=`/bin/fbresolution` ||:
	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
fi

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