#!/bin/bash

# usage information
usage() {
    echo 'usage:'
    echo ' -o <output>  - path to output file for compressed kernel image'
    echo ' -a <arch>    - PowerPC sub-architecture'
    echo ' -k <kernel>  - path to kernel image file in ELF format'
    echo ' -s <sysmap>  - path to System.map file'
    echo ' -i <initrd>  - path to initrd image file'
    echo ' -d <objdir>  - path to directory with additional object files'
    echo ' -r <release> - kernel release number'
    echo ' -l <address> - Address where the kernel should be relocated'
    echo ' -n           - do not use an initrd'
    echo ' -z           - assume the initrd image file is already compressed'
    echo ' -u           - assume the initrd image file is not compressed'
    echo ' -v           - verbose operation (print commands)'

    exit 0
}

# echo a shell command if desired, and execute it
do_cmd() {
    if test -n "$verbose"; then
	echo $@
	eval $@
    else
	eval $@ > /dev/null 2>&1
    fi
    return
}

# parse command line
while getopts o:k:s:i:d:r:a:l:nuzv option; do
    case "$option" in
	o) output=$OPTARG ;;
	k) kernel=$OPTARG ;;
	s) sysmap=$OPTARG ;;
	i) initrd=$OPTARG ;;
	d) objdir=$OPTARG ;;
	r) release=$OPTARG ;;
	a) arch=$OPTARG ;;
	l) linkaddr=$OPTARG ;;
	n) noinitrd="Yes" ;;
	z) compressed="Yes" ;;
	u) compressed="No" ;;
	v) verbose="Yes" ;;
	*) usage; exit 0 ;;
    esac
done

# Let's provide some subarch dependent defaults
case $arch in
    apus|miboot)
    	# miboot and apus don't use builtin initrds,
	# and need to stay elf kernels, as we don't use a wrapper.
    	elf="Yes"
    	noinitrd="Yes"
    	;;
esac

# use non-option arguments as release version and kernel image file if needed
shift $(( $OPTIND - 1 ))
if test -z "$release" -a -n "$1"; then
    release=$1
fi
if test -z "$kernel" -a -n "$2"; then
    kernel=$2
fi

# if no sub-architecture was specified, read it from /proc
if test -z "$arch"; then
    case $(grep ^machine /proc/cpuinfo) in
	*PReP*Blackhawk*) arch=ppcbug ;;
	*PReP*) arch=prep ;;
	*CHRP*) arch=chrp ;;
	*Amiga*) arch=apus ;;
	*)
	    case $(grep ^pmac-generation /proc/cpuinfo) in
		*NewWorld) arch=pmac ;;
		*OldWorld) arch=coff ;;
	    esac
	    ;;
    esac
fi

test -z "$verbose" || echo === Building for sub-architecture $arch.

# if no kernel was specified, try to find one
if test -z "$kernel"; then

    # guess the location of a kernel
    if kernel=/boot/vmlinux-$release; test -n "$release" -a -r $kernel; then
	:
    elif kernel=/boot/vmlinux-$(uname -r); test -r $kernel; then
	:
    else
	kernel=""
    fi

    # we couldn't find a kernel, and therefore give up
    if test -z "$kernel"; then
	echo Could not find a kernel image file, please specify one.
	exit 1
    fi

    # sanitize the location of the kernel
    kernel=$(readlink -f $kernel)

fi

test -z "$verbose" || echo === Using kernel image file $kernel.

# if no initrd was specified, try to find one that goes with the kernel
if test -z "$noinitrd" -a -z "$initrd"; then

    # guess the location of an initrd, but don't try too hard
    if initrd=${kernel/vmlinux/initrd.img}; test -r $initrd; then
	:
    else
	initrd=""
    fi

    # sanitize the location of the initrd
    if test -n "$initrd"; then
	initrd=$(readlink -f $initrd)
    fi

fi

test -z "$verbose" -o -z "$initrd" || echo === Using initrd image file $initrd.

# if no release was specified, extract it from the kernel image name
if test -z "$release"; then
    release=$(echo $kernel | sed s/.*vmlinux-//)
fi

test -z "$verbose" || echo === Release version seems to be $release.

if dpkg --compare-versions $release ge 2.6.16 && test "$arch" != "prep" ; then
  post_2_6_16=Yes
else
  post_2_6_16=
fi

if dpkg --compare-versions $release ge 2.6.18 && test "$arch" != "prep" ; then
  post_2_6_18=Yes
else
  post_2_6_18=
fi

if dpkg --compare-versions $release ge 2.6.19 && test "$arch" != "prep" ; then
  post_2_6_19=Yes
else
  post_2_6_19=
fi

if grep -q CONFIG_RD_XZ=y /boot/config-$release ; then
  is_xz_supported=Yes
fi

# if no object file directory was specified, try to find one
if test -z "$objdir"; then

    # try using $STEM-image, then linux-image, then kernel-image
    # in a default location, then use the current directory
    if objdir=/usr/lib/$STEM-image-$release; test -d $objdir; then
	:
    elif objdir=/usr/lib/linux-image-$release; test -d $objdir; then
	:
    elif objdir=/usr/lib/kernel-image-$release; test -d $objdir; then
	:
    # /usr/lib/mkvmlinuz is a copy of arch/powerpc/boot taken from 2.6.17.
    # It is used as a fallback when a kernel package is not present
    # or when it does not provide the tools.
    elif objdir=/usr/lib/mkvmlinuz; test -d $objdir && test "$arch" = "chrp"; then
    	post_2_6_16=Yes
    else
	objdir="$PWD"
    fi
fi
if test \! -d "$objdir"; then
    echo Object directory does not exist.
    exit 1
fi

test -z "$verbose" || echo === Using object files from $objdir.

# if no output file was specified, source the configuration file
if test -z "$output" -a -r /etc/mkvmlinuz/output; then
    . /etc/mkvmlinuz/output
fi

# we absolutely need an output file, and won't attempt guesses
if test -z "$output"; then
    echo Please specify an output file.
    exit 1
fi

test -z "$verbose" || echo === Building a bootable compressed kernel image in $output.

# create a work directory
work=$(mktemp -d)

test -z "$verbose" || echo === Doing build in $work.

# utilities
if test "$is_xz_supported"; then
  XZ="xz --check=crc32 -4"
else
  XZ=false
fi
if test "$post_2_6_16"; then
  ADDNOTE=$objdir/addnote # must be present in mkvmlinuz fallback tools
  if test \! -f "$ADDNOTE"; then
      echo "Missing utility: $ADDNOTE"
      exit 1
  fi
  ADDRAMDISK=$objdir/addRamDisk # unseen in mkvmlinuz fallback tools
  objs=$objdir
  STRIP="strip -s -R .comment"
  GZIP="gzip --force --best"
else
  ADDNOTE=$objdir/utils/addnote # unseen in -prep kernel package, but present in -powerpc kernel package
  if test \! -f "$ADDNOTE" && echo "$release"|grep -q -- '-powerpc$'; then
  # addnote must exist if we use a -powerpc kernel package
      echo "Missing utility: $ADDNOTE"
      exit 1
  fi
  HACKOFF=$objdir/utils/hack-coff # unseen in kernel package tools
  MKNOTE=$objdir/utils/mknote # unseen in kernel package tools
  MKPREP=$objdir/utils/mkprep # must be present in kernel package tools
  if test \! -f "$MKPREP"; then
      echo "Missing utility: $MKPREP"
      exit 1
  fi
  MKBUGBOOT=$objdir/utils/mkbugboot # must be present in kernel package tools
  if test \! -f "$MKBUGBOOT"; then
      echo "Missing utility: $MKBUGBOOT"
      exit 1
  fi
  libs=$objdir/lib
  objs=$objdir/obj
  GZIP=gzip
  STRIP=strip
fi
LD=ld
OBJCOPY=objcopy
MV=mv

case "$arch","$post_2_6_16","$release" in
    apus,Yes,*|miboot,Yes,*|prep,Yes,*|ppcbug,Yes,*)
	;;
    chrp,Yes,*|pmac,Yes,*)
	dummy_kernel=$objs/mkvmlinuz-kernel-vmlinux.strip.o
	dummy_initrd=$objs/mkvmlinuz-kernel-initrd.o
	OBJS="$objs/crt0.o $objs/string.o $objs/prom.o $objs/stdio.o $objs/main.o $objs/div64.o"
	if test "$post_2_6_18"; then
		OBJS="$OBJS $objs/inffast.o $objs/inflate.o $objs/inftrees.o"
	else
		OBJS="$OBJS $objs/infblock.o $objs/infcodes.o $objs/inffast.o $objs/inflate.o $objs/inftrees.o $objs/infutil.o"
	fi
	;;
    apus,,*|miboot,,*|*,Yes,*)
	;;
    prep,,2.6*|ppcbug,,2.6*)
	test -d $objs/simple && objs=$objs/simple
	if test -r $objs/legacy.o; then prepmap=legacy; else prepmap=prepmap; fi
	OBJS="$objs/head.o $objs/relocate.o $objs/$prepmap.o $objs/misc.o $objs/misc-prep.o $objs/mpc10x_memory.o"
	LIBS="$libs/common.a $libs/ppc.a $libs/of.a"
	dummy=$objs/dummy.o
	;;
    pmac,,2.6*)
	test -d $objs/openfirmware && objs=$objs/openfirmware
	OBJS="$objs/start.o $objs/misc.o $objs/common.o $objs/newworldmain.o"
	LIBS="$libs/lib.a $libs/ppc.a $libs/of.a $libs/common.a"
	dummy=$objs/dummy.o
	;;
    *,,2.6*)
	test -d $objs/openfirmware && objs=$objs/openfirmware
	OBJS="$objs/start.o $objs/misc.o $objs/common.o $objs/${arch}main.o"
	LIBS="$libs/lib.a $libs/ppc.a $libs/of.a $libs/common.a"
	dummy=$objs/dummy.o
	;;
    chrp,,2.4*)
	test -d $objs/chrp && objs=$objs/chrp
	OBJS="$objs/start.o $objs/main.o $objs/misc.o"
	OBJS="$OBJS $objs/../common/string.o $objs/../common/ofcommon.o"
	LIBS="$libs/lib.a $libs/zlib.a $libs/of1275.a"
	dummy=$objs/../common/dummy.o
	;;
esac

# off we go...

# create the compressed initrd image file
if test -n "$initrd"; then
    test -z "$verbose" || echo === Creating compressed initrd image
    if test -z "$compressed"; then
	if test "`xxd -p -l2 $initrd`" = "1f8b"; then
	    test -z "$verbose" || echo === $initrd is already gzip compressed
	    do_cmd cp -p $initrd $work/initrd.gz
	    if test "$is_xz_supported" && test "$arch" != "prep"; then
	      test -z "$verbose" || echo === recompressing to xz
	      zcat $initrd | $XZ - > $work/initrd.xz
	    fi
	elif test "`xxd -p -l6 $initrd`" = "fd377a585a00"; then 
	    test -z "$verbose" || echo === $initrd is already xz compressed
	    do_cmd cp -p $initrd $work/initrd.xz
	else
	    test -z "$verbose" || echo === assuming $initrd was not compressed
	    compressed="No"
	fi
    fi
    case "$compressed" in
    	Yes)
	    do_cmd cp -p $initrd $work/initrd.gz
	    do_cmd ln -s $work/initrd.gz $work/initrd.xz
	    ;;
    	No)
	    do_cmd cp -p $initrd $work/initrd
	    if test "$is_xz_supported" && test "$arch" != "prep"; then
	      do_cmd $XZ $work/initrd
	    else
	      do_cmd $GZIP $work/initrd
	    fi
	    ;;
    esac
fi

# If we are on ARCH=powerpc and 2.6.19 or higher, we just call the provided wrapper script.

if test -n "$post_2_6_19" &&  test "$arch" != "prep"; then
  WRAPPER=$objdir/wrapper
  vmlinuz=$work/vmlinuz.$arch
  if test -n "$initrd"; then
    if test "$is_xz_supported"; then
      INITRD="-i $work/initrd.xz"
    else
      INITRD="-i $work/initrd.gz"
    fi
  fi
  $WRAPPER -c -o $vmlinuz -p $arch $INITRD -D $objdir -W $work $kernel
else 
  # create the compressed kernel image file
  test -z "$verbose" || echo === Creating compressed kernel image vmlinux.gz...
  if test -n "$elf" || test -n "$post_2_6_16"; then
      do_cmd $STRIP $kernel -o $work/vmlinux
  else
      do_cmd $OBJCOPY -O binary $kernel $work/vmlinux
  fi
  do_cmd $GZIP $work/vmlinux
  
  # create the raw ELF image file; this is not needed for the miBoot image
  if test -z "$elf"; then
      test -z "$verbose" || echo === Putting everything into ELF image file image.o...
      if test -n "$post_2_6_16"; then
        do_cmd $OBJCOPY $dummy_kernel $work/dummy_kernel.o \
      	--add-section=.kernel:vmlinux.strip=$work/vmlinux.gz \
  	--set-section-flags=.kernel:vmlinux.strip=contents,alloc,load,readonly,data
        if test -n "$initrd"; then
  	do_cmd $OBJCOPY $dummy_initrd $work/dummy_initrd.o \
  	    --add-section=.kernel:initrd=$work/initrd.gz \
  	    --set-section-flags=.kernel:initrd=contents,alloc,load,readonly,data
        fi
      else
        do_cmd $OBJCOPY -R .comment $dummy $work/image.o
        do_cmd $OBJCOPY $work/image.o $work/image.o \
  	--add-section=.image=$work/vmlinux.gz \
  	--set-section-flags=.image=contents,alloc,load,readonly,data
        if test -n "$sysmap"; then
  	do_cmd $OBJCOPY $work/image.o $work/image.o \
  	    --add-section=.sysmap=$sysmap \
  	    --set-section-flags=.sysmap=contents,alloc,load,readonly,data
        fi
        if test -n "$initrd"; then
  	do_cmd $OBJCOPY $work/image.o $work/image.o \
  	    --add-section=.ramdisk=$work/initrd.gz \
  	    --set-section-flags=.ramdisk=contents,alloc,load,readonly,data
        fi
      fi
  else
  	do_cmd $MV $work/vmlinux.gz $work/vmlinuz.$arch.tmp
  fi

  # link everything into the final image file and make it bootable
  vmlinuz=$work/vmlinuz.$arch
  if test "$post_2_6_16"; then
    LD_ARGS="-m elf32ppc -T $objs/zImage.lds"
  else
    LD_ARGS="-T $objdir/boot/ld.script"
  fi
  OBJCOPY_ARGS="-R .comment"
  test -z "$verbose" || echo === Creating bootable kernel image file vmlinuz.$arch...
  case "$arch","$post_2_6_16","$release" in
      chrp,Yes,*|pmac,Yes,*)
  	if test -n "$initrd"; then
  		do_cmd $LD $LD_ARGS -o $vmlinuz $OBJS $work/dummy_kernel.o $work/dummy_initrd.o
  	else
  		do_cmd $LD $LD_ARGS -o $vmlinuz $OBJS $work/dummy_kernel.o 
  	fi
  	do_cmd $ADDNOTE $vmlinuz
      	;;
      chrp,,2.6*)
      	if [ -z "$linkaddr" ]; then linkaddr=0x00800000; fi
  	LD_ARGS="$LD_ARGS -e _start -Ttext $linkaddr"
  	do_cmd $LD -o $vmlinuz $LD_ARGS $objs/crt0.o $OBJS $work/image.o $LIBS
  	do_cmd $OBJCOPY $OBJCOPY_ARGS $vmlinuz $vmlinuz
  	do_cmd cp -p $objdir/boot/note $work
  	do_cmd $ADDNOTE $vmlinuz
  	;;
      chrp,,2.4*)
      	if [ -z "$linkaddr" ]; then linkaddr=0x00800000; fi
  	LD_ARGS="$LD_ARGS -e _start -Ttext $linkaddr"
  	do_cmd $LD -o $vmlinuz $LD_ARGS $objs/../common/crt0.o $OBJS $work/image.o $LIBS
  	do_cmd $OBJCOPY $OBJCOPY_ARGS $vmlinuz $vmlinuz
  	do_cmd $ADDNOTE $vmlinuz
  	;;
      coff,,2.6*)
      	if [ -z "$linkaddr" ]; then linkaddr=0x00500000; fi
  	LD_ARGS="$LD_ARGS -e _start -Ttext $linkaddr -Bstatic"
  	OBJCOPY_ARGS="-O aixcoff-rs6000 -R .stab -R .stabstr $OBJCOPY_ARGS"
  	do_cmd $LD -o $vmlinuz $LD_ARGS $objs/coffcrt0.o $OBJS $work/image.o $LIBS
  	do_cmd $OBJCOPY $OBJCOPY_ARGS $vmlinuz $vmlinuz
  	do_cmd $HACKOFF $vmlinuz
  	;;
      pmac,,2.6*)
      	if [ -z "$linkaddr" ]; then linkaddr=0x01000000; fi
  	LD_ARGS="$LD_ARGS -e _start -Ttext $linkaddr"
  	OBJCOPY_ARGS="--add-section=.note=$work/note $OBJCOPY_ARGS"
  	do_cmd $LD -o $vmlinuz $LD_ARGS $objs/crt0.o $OBJS $LIBS $work/image.o
  	do_cmd $MKNOTE > $work/note
  	do_cmd $OBJCOPY $OBJCOPY_ARGS $vmlinuz $vmlinuz
  	;;
      ppcbug,*,2.6*)
      	if [ -z "$linkaddr" ]; then linkaddr=0x00800000; fi
  	LD_ARGS="$LD_ARGS -Ttext $linkaddr -Bstatic"
  	OBJCOPY_ARGS="-O elf32-powerpc $OBJCOPY_ARGS -R .stab -R .stabstr -R .sysmap"
  	do_cmd $LD -o $vmlinuz $LD_ARGS $OBJS $work/image.o $LIBS
  	do_cmd $OBJCOPY $OBJCOPY_ARGS $vmlinuz $vmlinuz.tmp
  	do_cmd $MKBUGBOOT $vmlinuz.tmp $vmlinuz
  	;;
      prep,*,2.6*)
      	if [ -z "$linkaddr" ]; then linkaddr=0x00800000; fi
  	LD_ARGS="$LD_ARGS -Ttext $linkaddr -Bstatic"
  	OBJCOPY_ARGS="-O elf32-powerpc $OBJCOPY_ARGS -R .stab -R .stabstr -R .sysmap"
  	do_cmd $LD -o $vmlinuz $LD_ARGS $OBJS $work/image.o $LIBS
  	do_cmd $OBJCOPY $OBJCOPY_ARGS $vmlinuz $vmlinuz.tmp
  	do_cmd $MKPREP -pbp $vmlinuz.tmp $vmlinuz
  	;;
      miboot,,2.6*|apus,,2.6*)
          do_cmd $MV $vmlinuz.tmp $vmlinuz
  	;;
      *)
  	echo Sorry, I do not know how to handle PowerPC sub-architecture $arch in version $release.
  esac
fi

# move bootable kernel image to its final location and creating symlinks.
if test -e $vmlinuz; then
  if test -n "$linkname"; then
    oldlink="`readlink $linkname`"
  fi
  test -z "$verbose" || echo === Moving bootable kernel image file to $output...
  if test -e $output; then
    echo Output file $output exists, attempting to back it up...
    mv $output $output.old 2> /dev/null
    if test -n "$linkname" && test "$oldlink" = "`basename $output`"; then
      oldlink=$output.old
    fi
  fi
  cat $vmlinuz > $output
  if test -n "$linkname"; then
    test -z "$verbose" || echo Creating symlink : $linkname -\> $output
    if test -L "$linkname"; then
      unlink $linkname
    fi
    ln -sf "`basename $output`" $linkname
    if test -n "$oldlink"; then
      test -z "$verbose" || echo Creating backup symlink : $linkname.old -\> $oldlink
      if test -L "$linkname.old"; then
    	unlink $linkname.old
      fi
    ln -sf $oldlink $linkname.old
    fi
  fi
fi

# clean up
test -z "$verbose" || echo === Cleaning up...
rm -rf $work
