#!/bin/sh
#
# mkrescudisk
#
# Written by Yasuyuki Furukawa <furukaw@vinelinux.org>

VERSION=0.2

#
# configration
#
COMMAND_LIST=\
bash:\
ls:ln:cp:rm:mkdir:mv:dd:mknod:chmod:chown:\
tar:gunzip:gzip:\
mke2fs:badblocks:fdisk:\
route:ftp:ifconfig:ping:\
mount:umount:sync:\
ed:grep:more:cat:\
chroot:\
lsmod:insmod:rmmod

# output device or file.
OUTPUT=rescue.img
OUTPUT=/dev/fd0

# progess flags
VERBOSE="yes"
PAUSE=yes

# working dir.
DESTDIR=/tmp/resucue-root-$$

# working ramdisk device
RAMDISK=/dev/ram

#MAX_RAMDISK_SIZE=3072
MAX_RAMDISK_SIZE=4096

# allowed born shells
SHELLS=/bin/sh:/bin/bash:/bin/ash:/bin/bsh:/bin/zsh

#
# runtime environment
#
LANG=C
LC_ALL=C
LANGUAGE=C

unalias -a
PATH=/sbin:/bin:/usr/bin:/usr/sbin
IFS=:

#
# Check nessesary command.
#
REQUIRES=dirname:basename:awk:grep:echo:cat:sed:test:\
ln:cp:du:chmod:install:strip:ldd:tar:dd:gzip:mke2fs:id:strings:sleep

#REQUIRES_PACKAGE=sh-utils:gawk:grep:textutils:\
#sed:fileutils:binutils:tar:gzip:e2fsprogs:binutils

unset ERR
for COMMAND in $REQUIRES; do
    if ! type $COMMAND > /dev/null 2>&1; then
	echo "error: $0 is require command: $COMMAND"
	ERR=1;
    fi
done

[ -n "$ERR" ] && exit 1

#
# Option
#
usage () {
    cat >&2 <<EOF
usage: $(basename $0) [OPTIONS...]
Create root image disk for rescue.

  -q  --quiet            Quiet execution.
  -v  --verbose          Verbose execution.
      --noprompt         Create immediately rescue disk without prompt.

  -d  --device=DEVICE    Output device or regular file. (default: $OUTPUT)
      --ramdisk=DEVICE   Temporary working ram disk device. (default: $RAMDISK)

      --import=FILE      Import command list from FILE.
      --export=FILE      Export command list into FILE and exit.

      --add=CMD1,CMD2,.. Add specified commands to command list.
      --del=CMD1,CMD2,.. Remove specified  commands from command list.

      --version          Display version info and exit.
      --help             Display this message and eixt.

Default command list:
EOF
    COUNT=$((0))
    echo -n "	"
    for COMMAND in ${COMMAND_LIST};do
	[ -z "$COMMAND" ] && continue
	echo -n $COMMAND "	"
	COUNT=$(($COUNT + 1))
	if [ $COUNT = 4 ]; then
	    COUNT=0;
	    echo ""
	    echo -n "	"
	fi
    done
    echo ""
cat <<EOF

This creates only image of root file system that does NOT include
the boot image. You want it also, please use the "mkbootdisk" command.

Report bugs to <furukawa@vinelinux.org>.
EOF
    exit 1
}


unset DEL_LIST
unset ADD_LIST
unset EXPORT_FLAG
unset IMPORT_FLAG

while [ $# -gt 0 ]; do
    case $1 in
	-d|-o)
	    shift
	    OUTPUT=$1
	    ;;
	--dev=*|--device=*|--out=*|--output=*)
	    OUTPUT=$(echo $1 | sed 's/.*=//')
	    ;;
	--ram=*|--ramdisk=*)
	    RAMDISK=$(echo $1 | sed 's/.*=//')
	    if !(echo "$RAMDISK" |grep  /dev/ram > /dev/null) ;then
		echo "error: invalid ramdisk device: $RAMDISK"
		exit 1
	    fi
	    ;;
	--h|--help)
	    usage 0
	    ;;
	-q|--q|--quiet)
	    unset VERBOSE
	    ;;
	-v|--verbose)
	    VERBOSE="more"
	    ;;
	--version)
	    echo "$(basename $0) version $VERSION"
	    exit 0
	    ;;
	--del=*|--delete=*)
	    if [ -z "$DEL_LIST" ]; then
		DEL_LIST=$(echo $1 | sed -e 's/.*=//' -e 's/,/:/g' -e 's/ /:/g')
	    else
		DEL_LIST=${DEL_LIST}:$(echo $1 | sed -e 's/.*=//' -e 's/,/:/g' -e 's/ /:/g')
	    fi
	    ;;
	--add=*)
	    if [ -z "$ADD_LIST" ]; then
		ADD_LIST=$(echo $1 | sed -e 's/.*=//' -e 's/,/:/g' -e 's/ /:/g')
	    else
		ADD_LIST=${DEL_LIST}:$(echo $1 | sed -e 's/.*=//' -e 's/,/:/g' -e 's/ /:/g')
	    fi
	    ;;
	--noprompt)
	    unset PAUSE
	    ;;
	--ex|--exp|--export)
	    EXPORT_FLAG="/dev/stdout"
	    ;;
	--ex=*|--exp=*|--export=*)
	    EXPORT_FLAG=$(echo $1 | sed 's/.*=//')
	    ;;
	--im=*|--imp=*|--import)
	    IMPORT_FLAG="/dev/stdin"
	    ;;
	--im=*|--imp=*|--import=*)
	    IMPORT_FLAG=$(echo $1 | sed 's/.*=//')
	    ;;
	*)
	    echo "error: invalid option: " $1
	    exit 1
    esac
    shift
done

if [ -n "$IMPORT_FLAG" ]; then
    unset COMMAND_LIST
    for COMMAND in $(awk '{printf $1 ":"}' < $IMPORT_FLAG); do
	[ -z "$COMMAND" ] && cotinue
	if [ -z "$COMMAND_LIST" ]; then
	    COMMAND_LIST=$COMMAND
	else
	    COMMAND_LIST=${COMMAND_LIST}:$COMMAND 
	fi
    done
fi

if [ -n "$DEL_LIST" ]; then
    for COMMAND in $DEL_LIST; do
	[ -z $COMMAND ] && continue
	unset FLAG
	for COMMAND2 in $COMMAND_LIST; do
	    [ -z "$COMMAND2" ] && continue;
	    if [ "$COMMAND2" = "$COMMAND" ] ;then
		FLAG="yes"
		break
	    fi
	done
	[ -z "$FLAG" ] && echo "warning: command \"$COMMAND\" isn't already included."
    done
    unset TMP
    for COMMAND in $COMMAND_LIST; do
	[ -z $COMMAND ] && continue
	unset FLAG
	for COMMAND2 in $DEL_LIST; do
	    [ -z "$COMMAND2" ] && continue;
	    if [ "$COMMAND2" = "$COMMAND" ] ;then
		FLAG="yes"
		break
	    fi
	done
	if [ -n "$FLAG" ]; then
	    continue;
	elif [ -n "$TMP" ]; then
	    TMP=${TMP}:$COMMAND
	else
	    TMP=$COMMAND;
	fi
    done
    COMMAND_LIST=$TMP
fi


if [ -n "$ADD_LIST" ]; then
    unset TMP
    for COMMAND in $ADD_LIST; do
	[ -z $COMMAND ] && continue
	unset FLAG
	for COMMAND2 in $COMMAND_LIST; do
	    [ -z "$COMMAND2" ] && continue;
	    if [ "$COMMAND2" = "$COMMAND" ] ;then
		FLAG="yes"
		break
	    fi
	done
	if [ -n "$FLAG" ]; then
	    echo "warning: command \"$COMMAND\" is already included."
	elif [ -z "$TMP" ]; then
	    TMP=$COMMAND
	else
	    TMP=${TMP}:$COMMAND
	fi
    done
    COMMAND_LIST=${COMMAND_LIST}:$TMP
fi


if [ -n "$EXPORT_FLAG" ]; then
    [ "$EXPORT_FLAG" != "/dev/stdout" ] && (rm -f $EXPORT_FLAG || exit 1)
    for COMMAND in $COMMAND_LIST; do
	if [ "$EXPORT_FLAG" = "/dev/stdout" ]; then
	    echo $COMMAND
	else
	    echo $COMMAND >> $EXPORT_FLAG
	fi
    done
    exit
fi

ERR=1
for COMMAND in $COMMAND_LIST; do
    type $COMMAND > /dev/null 2>&1 || contine;
    SRC=$(type -path $COMMAND)
    for SHL in $SHELLS; do
	if [ "$SHL" = "$SRC" ]; then
	    unset ERR
	    break;
	fi
    done
done
if [ -n "$ERR" ]; then
    echo "error: command list doesn't include born shell"
    exit 1
fi

if [ `id -u` != 0 ]; then
    echo "please run by root again."
    exit 1
fi

if [ -n "$VERBOSE" -a -n "$PAUSE" -a -b $OUTPUT ] ; then
    echo "Insert a disk in $OUTPUT. Any information on the disk will be lost."
    echo -n "Press <Enter> to continue or ^C to abort: "
    read aline
    echo "-----"
fi

#
# Cleaning working directory.
#
umount $RAMDISK 2> /dev/null

rm -fr  $DESTDIR
mkdir $DESTDIR || {
    echo "Failed to create $DESTDIR" >&2
    exit 1
}
[ -d $DESTDIR ] || {
    echo "$DESTDIR is not a directory!" >&2
    exit 1
}

#
# Create basic dirs
#
[ -n "$VERBOSE" ] && echo "Createing temporary rescue image tree: $DESTDIR"

BASEDIR_LIST=\
/bin:/dev:/etc:/lib:/proc:/tmp:/usr:/root:\
/mnt/floppy:\
/mnt/image:\

for DIR in $BASEDIR_LIST; do
    if [ ! -d  $DESTDIR/$DIR ]; then
	mkdir -p $DESTDIR/$DIR
    fi
done

chmod 1777 $DESTDIR/tmp
[ -d $DESTDIR/usr/sbin ] && rm -fr  $DESTDIR/usr/sbin
[ -d $DESTDIR/usr/sbin ] && rm -fr  $DESTDIR/usr/bin
ln bin -sf $DESTDIR/sbin
ln -s ../bin $DESTDIR/usr/sbin
ln -s ../bin $DESTDIR/usr/bin


#
# Install commands
#

mycp() {
#    EXEC="echo"
    if [ -d $2 ]; then
	_DEST_=$2/$(basename $1)
    else
	_DEST_=$2
    fi

    [ -f $_DEST_ ] && return

    if [ -h $1 ]; then
	$EXEC cp -d $1 $2
	_LNKTO_=$(dirname $1)/$(ls -l $1  | sed 's/.*->[ ]*//')
#	$EXEC mycp $_LNKTO_ $2 2> /dev/null
	$EXEC install -s $_LNKTO_ $2 2> /dev/null
    else
	$EXEC install -s $1 $2 2> /dev/null
    fi
}


install_command() {
    if (dirname $1 | grep -w lib > /dev/null) ; then
	_DESTDIR_=$DESTDIR/lib
    else
	_DESTDIR_=$DESTDIR/bin
    fi
    mycp $1 $_DESTDIR_

    if [ -x $1 ]; then
	if ldd $1 > /dev/null 2>&1; then
	    LIBS=$(ldd $1 2> /dev/null |awk '{printf $3":"}')
	    for LIB in $LIBS; do
		LIBS2=$(ldd $LIB  2> /dev/null |awk '{printf $3":"}')
		for LIB2 in $LIBS2; do
		    mycp $LIB $DESTDIR/lib
		done;
		mycp $LIB $DESTDIR/lib
	    done
	fi
    fi
}



for COMMAND in ${COMMAND_LIST};do
    [ -z "$COMMAND" ] && continue
    if ! type $COMMAND > /dev/null 2>&1; then
	echo "warning: missing command: $COMMAND"
    else
	SRC=$(type -path $COMMAND)
	[ x$VERBOSE = "xmore" ] && echo "Installing $SRC..."
	install_command $SRC
    fi
done

#
# install etc files
#
[ x$VERBOSE = "xmore" ] && echo "Installing /etc files..."

cat > $DESTDIR/etc/services <<EOF
ftp             21/tcp
telnet          23/tcp
smtp            25/tcp          mail
name            42/udp          nameserver
domain          53/tcp
domain          53/udp
bootps          67/udp
bootpc          68/udp
tftp            69/udp
sunrpc          111/tcp
sunrpc          111/tcp         portmapper
shell           514/tcp         cmd
printer         515/tcp         spooler
nfs             2049/udp
nfs             2049/tcp
EOF

touch $DESTDIR/etc/mtab
cat > $DESTDIR/etc/protocols <<EOF
ip      0       IP
icmp    1       ICMP
tcp     6       TCP
udp     17      UDP
EOF

cat > $DESTDIR/etc/fstab <<EOF
/dev/ram        /               ext2    defaults
/proc           /proc           proc    defaults
/dev/fd0        /mnt/floppy     ext2    defaults
EOF

cat > $DESTDIR/etc/hosts <<EOF
127.0.0.1	localhost.localdomain	localhost
EOF

grep -e "^root" \
    -e "^bin" \
    -e "^daemon" \
    -e "^mail" \
    -e "^man" \
    -e "^nobody" /etc/passwd > $DESTDIR/etc/passwd

if [ -f /etc/passwd ] ; then
    grep -e "^root" -e "^bin" -e "^daemon" \
	 -e "^mail" -e "^man" -e "^nobody" /etc/passwd > $DESTDIR/etc/passwd
else
    echo "warning: failed to create /etc/passwd file."
fi

if [ -f /etc/group ] ; then
    grep -e "^root" -e "^bin" -e "^daemon" \
	 -e "^mail" -e "^man" -e "^nobody" /etc/group > $DESTDIR/etc/group
else
    echo "warning: failed to create /etc/group file."
fi

cat > $DESTDIR/etc/termcap <<EOF
linux|linux console:\\
	:am:eo:mi:ms:xn:xo:\\
	:it#8:\\
	:AL=\\E[%dL:DC=\\E[%dP:DL=\\E[%dM:IC=\\E[%d@:K2=\\E[G:\\
	:ae=\\E[10m:al=\\E[L:as=\\E[11m:bl=^G:cd=\\E[J:ce=\\E[K:\\
	:cl=\\E[H\\E[J:cm=\\E[%i%d;%dH:cr=^M:cs=\\E[%i%d;%dr:\\
	:ct=\\E[3g:dc=\\E[P:dl=\\E[M:do=^J:ec=\\E[%dX:ei=\\E[4l:ho=\\E[H:\\
	:ic=\\E[@:im=\\E[4h:k1=\\E[[A:k2=\\E[[B:k3=\\E[[C:k4=\\E[[D:\\
	:k5=\\E[[E:k6=\\E[17~:k7=\\E[18~:k8=\\E[19~:k9=\\E[20~:\\
	:kD=\\E[3~:kI=\\E[2~:kN=\\E[6~:kP=\\E[5~:kb=\\177:kd=\\E[B:\\
	:kh=\\E[1~:kl=\\E[D:kr=\\E[C:ku=\\E[A:le=^H:mb=\\E[5m:md=\\E[1m:\\
	:me=\\E[0;10m:mh=\\E[2m:mr=\\E[7m:nd=\\E[C:nw=^M^J:rc=\\E8:\\
	:sc=\\E7:se=\\E[27m:sf=^J:so=\\E[7m:sr=\\EM:st=\\EH:ta=^I:\\
	:ue=\\E[24m:up=\\E[A:us=\\E[4m:vb=200\\E[?5h\\E[?5l:\\
	:ve=\\E[?25h:vi=\\E[?25l:
EOF

if [ -f $DESTDIR/bin/bash ]; then
cat > $DESTDIR/etc/bashrc <<EOF
alias which="type -path"
alias ls='ls -F --color=auto'
alias ll='ls -la --color=auto'
alias la='ls -a --color=auto'
alias rm='rm -i'
alias mv='mv -i'
alias cp='cp -i'
alias ln='ln -i'
set -o ignoreeof
EOF
fi

#if [ -f /etc/resolv.conf ]; then
#    cp /etc/resolv.conf $DESTDIR/etc/resolv.conf
#fi

if [ -f /etc/nsswitch.conf ]; then
LIBNSS=$(ls -1 /lib/libnss_files.so.[0-9] |tail -1)
install_command $LIBNSS
LIBNSS=$(ls -1 /lib/libnss_dns.so.[0-9] |tail -1)
install_command $LIBNSS

cat > $DESTDIR/etc/nsswitch.conf <<EOF
hosts:      files dns
protocols:  files
services:   files
EOF
fi

#
# Install dev files
#
[ x$VERBOSE = "xmore" ] && echo "Installing device files..."
(cd /;
tar c   dev/hd[a-d]* \
	dev/sd[a-f]* \
	dev/scd[0-2] \
	dev/fd0* \
	dev/tty[0-2] dev/console \
	dev/ram[0-2] dev/ramdisk \
	dev/null dev/systty dev/zero | tar x -C $DESTDIR
)


#
# Install misc files
#
[ x$VERBOSE = "xmore" ] && echo "Installing terminfo files..."
TERMINFO_PATH=/usr/lib/terminfo:/usr/share/terminfo
for DIR2 in $TERMINFO_PATH ; do
    if [ -d $DIR2 ]; then
	mkdir -p $DESTDIR/$DIR2/l/
	cp $DIR2/l/linux  $DESTDIR/$DIR2/l/linux
	break;
    fi
done

[ x$VERBOSE = "xmore" ] && echo "Installing rpmrc files..."
RPMPATH=/usr/lib:/usr/lib/rpm:/etc
for DIR in $RPMPATH ; do
    if [ -f $DIR/rpmrc ]; then
	mkdir -p $DESTDIR/$DIR
	cp $DIR/rpmrc  $DIR/rpmpopt  $DESTDIR/$DIR/
	break
    fi
done


#
# Create some command alias
#
if [ ! -f $DESTDIR/bin/sh  ] ; then
    for SHL in $SHELLS; do
	if [ -f $DESTDIR/$SHL ] ; then
	    ln -s $SHL $DESTDIR/bin/sh
	    break;
	fi
    done
fi

if [ ! -f $DESTDIR/bin/sh  ] ; then
    echo "warning: /bin/sh was not installed!"
fi

[ -f $DESTDIR/bin/more -a ! -f $DESTDIR/bin/less ] && ln -s more $DESTDIR/bin/less
[ -f $DESTDIR/bin/less -a ! -f $DESTDIR/bin/more ] && ln -s less $DESTDIR/bin/more

[ -f $DESTDIR/bin/more -a ! -f $DESTDIR/bin/cat ] && ln -s more $DESTDIR/bin/cat
[ -f $DESTDIR/bin/cat -a ! -f $DESTDIR/bin/more ] && ln -s cat $DESTDIR/bin/more

#
# Create misc directroy.
#
DIRS=$(strings $DESTDIR/bin/* | grep "^/var/" | awk '{printf $0":"}')
for DIR in $DIRS; do
  if ! (echo $DIR |grep "/"$ > /dev/null);  then
    DIR=$(dirname $DIR)
  fi
  [ -d $DESTDIR/$DIR ] || mkdir -p $DESTDIR/$DIR
done

SIZE=$(du -sb $DESTDIR| awk '{print $1}')
[ -n "$VERBOSE" ] && echo "Root Size: $SIZE bytes"

if [ $SIZE -gt $(($MAX_RAMDISK_SIZE*1024)) ]; then
    echo "Error: Root size is too big. (ramdisk size: $(($MAX_RAMDISK_SIZE*1024)) bytes)"
    rm -fr $DESTDIR
    exit 1
fi

#
# Copy to ramdisk
#
stty intr ^2

[ -n "$VERBOSE" ] && echo "Copy ext2fs rescue image to ramdisk '$RAMDISK'..."
mke2fs -q -m0 $RAMDISK $MAX_RAMDISK_SIZE

(cd $DESTDIR; tar zcf $DESTDIR.tgz *)
mount $RAMDISK $DESTDIR
rm -fr $DESTDIR/lost+found/

unset ERR
tar zxf $DESTDIR.tgz -C $DESTDIR 2>/dev/null || ERR="yes"
rm -fr $DESTDIR.tgz

sync;sync;sync
sleep 3

if [ -n "$ERR" ]; then
    echo "Error: No space left on ramdisk device. (ramdisk size: $(($MAX_RAMDISK_SIZE*1024)) bytes)"
    umount $RAMDISK
    rm -fr $DESTDIR
    stty intr ^C
    exit 1;
fi

#
# Create image file.
#

[ -n "$VERBOSE" ] && echo "Create compressed ext2fs image to '$OUTPUT'..."

unset ERR
dd if=$RAMDISK bs=1k count=$MAX_RAMDISK_SIZE 2>/dev/null  |gzip -9 |dd of=$OUTPUT conv=sync || ERR="yes"

umount $RAMDISK
stty intr ^C

#
# Done.
#
rm -fr $DESTDIR

if [ -n "$ERR" ]; then
    echo "error: failed to write $OUTPUT."
    exit 1
else
    [ -b $OUTPUT ] || ls -l $OUTPUT | awk '{print "Image Size: "  $5 " bytes" }'
    [ -n "$VERBOSE" ] && echo "Done."
fi

if type eject > /dev/null 2>&1; then
    [ -b $OUTPUT ] && eject $OUTPUT > /dev/null 2>&1
fi

exit 0
