#!/usr/bin/env bash
# Filename:      grml2iso
# Purpose:       create a multiboot grml ISO using grml2usb
# Authors:       Michael Prokop <mika@grml.org>,
#                Thorsten Glaser <tg@mirbsd.org>
# Bug-Reports:   see http://grml.org/bugs/
# License:       This file is licensed under the GPL v2 or any later version.
################################################################################

# define function getfilesize before "set -e" {{{
  if stat --help >/dev/null 2>&1; then
    getfilesize='stat -c %s'        # GNU stat
  else
    getfilesize='stat -f %z'        # BSD stat
  fi
# }}}

# adjust variables if necessary through environment {{{
# path to the grml2usb script you'd like to use
  [ -n "$GRML2USB" ] || GRML2USB='grml2usb'
# work directory for creating the filesystem
  [ -n "$TMPDIR" ]   && WRKDIR="${TMPDIR}/grml2iso.tmp"
  [ -n "$WRKDIR" ]   || WRKDIR='/tmp/grml2iso.tmp'
# support mkisofs as well as genisoimage
if which xorriso >/dev/null 2>&1 ; then
  MKISOFS='xorriso -as mkisofs'
elif which mkisofs >/dev/null 2>&1; then
  MKISOFS='mkisofs'
elif which genisoimage >/dev/null 2>&1; then
  MKISOFS='genisoimage'
else
  echo >&2 "Error: neither mkisofs nor genisoimage available - can not create ISO."
  exit 1
fi

if ! which isohybrid >/dev/null 2>&1 ; then
  echo "Error: isohybrid executable not found (install syslinux?)." >&2
  exit 1
fi
# }}}

# helper stuff {{{
  set -e

  usage() {
    echo >&2 "Usage: $0 [OPTIONS] -o target.iso source1.iso [source2.iso ...]"
    echo >&2 "
Options:
     -b Boot Params      Additional boot parameters passed to grml2usb
     -c Directory        Copy files from directory to generated ISO
     -f                  Force overwrite of existing target.iso
     -r BootParam        Remove specified boot params.
                         Can be specified multiple times.
     -p <grml2usb param> Add the specified parameter to the grml2usb
                         commandline. For a list of valid parameters have
                         a look at the grml2usb manpage.
                         Can be specified multiple times.
     -s URI              Generate a small ISO file which downloads the squashfs
                         file from the specified URI. Please note that due to
                         restrictions in the bootprocess only IPs are allowed.
                         Supported protocols are: http and ftp
     -t Directory        Directory that should be used for temporary files
                         during build. Defaults to /tmp/grml2iso.tmp if unset.

     Examples:
     $0 -s http://192.168.23.42:8000/grml/ -o small.iso grml64_2010.12.iso

     Will generate a file small.iso which tries to download the squashfs file from
     http://192.168.23.42:8000/grml/ - the squashfs file is placed in the same
     output directory as the ISO file.
"
    [ -n "$1" ] && exit $1 || exit 1
  }
# }}}

# command line handling {{{
  [[ $# -gt 2 ]] || usage 1

  ISOFILE=''
  DIR=''
  ADD_OPTS=''
  FORCE=''
  URI=''
  typeset -a GRML2USB_OPTS
  while getopts fb:c:o:r:p:s:t: name; do
    case $name in
      o)   ISOFILE="$OPTARG";;
      b)   GRML2USB_OPTS+=(--bootoptions="$OPTARG");;
      c)   DIR="$(readlink -f "$OPTARG")"; [ -n "$DIR" ] || { echo "Could not read $OPTARG - exiting" >&2 ; exit 1 ; } ;;
      f)   FORCE='true';;
      r)   GRML2USB_OPTS+=(--remove-bootoption="$OPTARG");;
      p)   GRML2USB_OPTS+=("$OPTARG");;
      s)   URI="$OPTARG";;
      t)   WRKDIR="$(readlink -f "$OPTARG")";;
      ?)   usage 2;;
    esac
  done

  # test for specified URI
  if [ -n "$URI" ] ; then
    GRML2USB_OPTS+=(--bootoptions="fetch=$URI")
  fi

  if [ -n "$WRKDIR" ] ; then
    GRML2USB_OPTS+=(--tmpdir="$WRKDIR")
  fi

# make sure -o is specified
  [ -n "$ISOFILE" ] || usage 1

# we don't to override any files by accident
  if [ -e "$ISOFILE" -a ! -n "$FORCE" ]; then
    echo "Error: target file $ISOFILE exists already." >&2
    exit 1
  fi

  if [ ! -z "$DIR" -a ! -d "$DIR" ] ; then
     echo "Error: specified parameter for -c is not a directory" >&2
     exit 1
  fi
# }}}

# we need root permissions for executing grml2usb {{{
  if [[ $(id -u) != 0 ]]; then
    echo >&2 "Error: please run $0 as root."
    exit 1
  fi
# }}}

# check for grml2usb {{{
  if [ ! -x "$(which $GRML2USB)" ] && [ ! -x "$GRML2USB" ] ; then
    echo "Error: Could not find grml2usb executable. Is /usr/sbin missing in PATH?" >&2
    echo "Tip: run GRML2USB=/usr/sbin/grml2usb grml2iso ... as workaround" >&2
    if [ -x "./$GRML2USB" ] ; then
      echo >&2 "If you executed grml2iso from the grml2usb repository use"
      echo >&2 "GRML2USB=./grml2usb $0 $*"
    fi
    exit 1
  fi
# }}}

# variables {{{
  ORIG_DIR="$(pwd)"

# normalise path
  case $ISOFILE in
    /*) ;;
    *) ISOFILE=$ORIG_DIR/$ISOFILE ;;
  esac
# }}}

# create necessary stuff under WRKDIR {{{
  [ -d "$WRKDIR" ] && WRKDIR_EXISTED='true' || WRKDIR_EXISTED='false'
  rm -rf "$WRKDIR/cddir" "$WRKDIR/grub_tmp"
  mkdir -p "$WRKDIR/cddir"
# }}}}

# execute grml2usb with all ISOs you'd like to install {{{
  # remove all parameters
  shift $(($OPTIND - 1))

  $GRML2USB "${GRML2USB_OPTS[@]}" "$@" "$WRKDIR/cddir"
# }}}

# move syslinux to isolinux {{{
  mv "$WRKDIR"/cddir/boot/syslinux "$WRKDIR"/cddir/boot/isolinux
  echo "menu label ^Isolinux prompt" > "$WRKDIR"/cddir/boot/isolinux/promptname.cfg
  echo "include hd.cfg" >> "$WRKDIR"/cddir/boot/isolinux/grmlmain.cfg
# }}}

# change to $WRKDIR {{{
  # make sure $WRKDIR is an absolute path, otherwise accessing files
  # in it will fail later in the code path if user provided a
  # relative directory
  WRKDIR=$(realpath $WRKDIR)
  cd "$WRKDIR/cddir"
# }}}

# efi boot {{{
  # default, independent of UEFI support
  BOOT_ARGS="-no-emul-boot -boot-load-size 4 -boot-info-table -b boot/isolinux/isolinux.bin -c boot/isolinux/boot.cat"

  case "$MKISOFS" in
    xorriso*)
      echo "Using xorriso for ISO generation."
      if ! dpkg --compare-versions $(dpkg-query -W -f='${Version}\n' xorriso 2>/dev/null) gt-nl 1.1.6-1 ; then
        echo "Disabling (U)EFI boot support since xorriso version is not recent enough."
      else
        echo "xorriso with -eltorito-alt-boot support present"

        if ! [ -r "${WRKDIR}/cddir/boot/efi.img" ] ; then
          echo "File /boot/efi.img not found, not extending boot arguments for (U)EFI boot."
        else
          echo "/boot/efi.img found, extending boot arguments for (U)EFI boot."
          BOOT_ARGS="$BOOT_ARGS -boot-info-table -eltorito-alt-boot -e boot/efi.img -no-emul-boot"
        fi
      fi
      ;;
  esac
# }}}

# adjust ISO for small output if necessary {{{
  if [ -n "$URI" ] ; then
     bootloader_files=$(find . -name "*.cfg" -type f)
     bootloader_files+=" "
     bootloader_files+=$(find . -name "*.lst" -type f)
     output_dir=$(dirname "$ISOFILE")
     for squashfs in $(find . -name *.squashfs) ; do
        media_path="$(dirname "$squashfs")"
        filename="$(basename "$squashfs")"
        target="$output_dir/$filename"
        if [ -f "$target" ] && [ ! -n "$FORCE" ] ; then
           echo >&2 "Warning: $target already exists, and -force not specified, not overwriting"
        else
           mv $squashfs $target
           OUTPUT_FILES+=("$target")
        fi
        sed -i -e "s#^\(^.*$media_path.*\)\($URI\)\(.*$\)#\1$URI/$filename\3#g" $bootloader_files

   done
  fi
# }}}

# copy specified directory to cd {{{
  if [ -n "$DIR" ] ; then
     echo >&2 "Copying ${DIR} to generated ISO"
     for param in GRML_NAME VERSION RELEASENAME DATE SHORT_NAME \
         VERSION BOOTID RELEASE_INFO ; do
       EXCLUDE_PARAM="$EXCLUDE_PARAM --exclude **%${param}%**"
     done
     rsync -a ${DIR}/ $EXCLUDE_PARAM .
  fi

  # adjust files from overlay directory
  for GRML_VERSION_FILE in $(find . -name grml-version) ; do
    GRML_NAME=$(awk '{print $1}' "$GRML_VERSION_FILE")
    VERSION=$(awk '{print $2}' "$GRML_VERSION_FILE")
    RELEASENAME=$(sed 's/.*- \(.*\).*\[.*/\1/' "$GRML_VERSION_FILE")
    DATE=$(sed 's/.*\[\(.*\)].*/\1/' "$GRML_VERSION_FILE")
    SHORT_NAME="$(echo $GRML_NAME | tr -d ',./;\- ')"
    RELEASE_INFO="$GRML_NAME $VERSION - $RELEASENAME"
    BOOTID=$(cat conf/bootid.txt)

    for param in GRML_NAME VERSION RELEASENAME DATE SHORT_NAME \
        RELEASE_INFO BOOTID  ; do
      value="$(eval echo '$'"$param")"

      # copy parameterized files from the overlay directory
      for file in $(find ${DIR} -name "*%$param%*") ; do
        file=${file##$DIR/}
        target_dir="$(dirname ${file})"
        mkdir -p "$target_dir" || true
        cp -r ${DIR}/${file} ./${target_dir}/"$(basename ${file/\%${param}\%/$value})"
      done

      # adjust config files
      for file in ./boot/isolinux/*.cfg ./boot/isolinux/*.msg \
        ./boot/grub/*.cfg ; do
        sed -i "s/%$param%/$value/g" ${file} 2>/dev/null || true
      done
    done
  done
# }}}

# generate the CD/DVD ISO {{{
  $MKISOFS -V 'grml-multiboot' -l -r -J -no-pad $BOOT_ARGS \
    -o "$ISOFILE" .
# }}}

# pad the output ISO to multiples of 256 KiB for partition table support {{{
  siz=$($getfilesize "$ISOFILE")
  cyls=$(($siz / 512 / 32 / 16 + 1))  # C=$cyls H=16 S=32
  ofs=$(($cyls * 16 * 32 * 512 - 1))  # padding offset (size - 1)
  dd if=/dev/zero bs=1 count=1 seek=$ofs of="$ISOFILE" 2>/dev/null
# }}}

# make ISO dd-able {{{
  if isohybrid --help | grep -q -- --uefi ; then
    echo "isohybrid version supports --uefi option, enabling"
    ISOHYBRID_OPTIONS=--uefi
 else
    echo "isohybrid version does NOT support --uefi option, disabling"
  fi

  echo "Creating dd-able ISO using isohybrid"
  isohybrid $ISOHYBRID_OPTIONS "$ISOFILE"
# }}}

# cleanup {{{
  cd "$ORIG_DIR"
  sync
  rm -rf "$WRKDIR/cddir" "$WRKDIR/grub_tmp"
  [[ $WRKDIR_EXISTED = 'false' ]] && rmdir "$WRKDIR"
  echo "Generated $ISOFILE"
  if [ -n "$URI" ] ; then
     echo "
Information:
==============
You requested to generate a small ISO image. Your generated
ISO image $ISOFILE does _not_ contain the squashfs files from
the source ISO images.

You have to provide the extracted squashfs files under $URI.

ISO image: $ISOFILE
Squashfs files: ${OUTPUT_FILES[@]}
URI: $URI
"
  fi
# }}}

## EOF #########################################################################
# vim:foldmethod=marker ts=2 ft=sh ai expandtab tw=80 sw=3
