#!/bin/bash -p
###############################################################################
# BRLTTY - A background process providing access to the Linux console (when in
#          text mode) for a blind person using a refreshable braille display.
#
# Copyright (C) 1995-2004 by The BRLTTY Team. All rights reserved.
#
# BRLTTY comes with ABSOLUTELY NO WARRANTY.
#
# This is free software, placed under the terms of the
# GNU General Public License, as published by the Free Software
# Foundation.  Please see the file COPYING for details.
#
# Web Page: http://mielke.cc/brltty/
#
# This software is maintained by Dave Mielke <dave@mielke.cc>.
###############################################################################

programName="${0##*/}"
programMessage() {
   echo >&2 "${programName}: ${1}"
}
programError() {
   [ -n "${1}" ] && programMessage "${1}"
   exit "${2:-3}"
}
syntaxError() {
   programError "${1}" 2
}

originalImage="boot.iso"
modifiedImage="brltty.iso"
brlttyPattern="brltty-*"
brailleDrivers="all"
textTable="nabcc"
invokeShell=false

showUsage() {
   cat <<EOF
Usage: ${programName} [-option ...]
The following options are supported:
-i file         The original (input) image (default: ${originalImage}).
-o file         The modified (output) image (default: ${modifiedImage}).
-a file         The BRLTTY archive (default: ${brlttyPattern}).
-b driver,...   The braille driver(s) to include (default: ${brailleDrivers}).
-t file         The text translation table to build in (default: ${textTable}).
-s              Invoke an interactive shell to inspect the modified image.
-h              Display usage information and exit.
EOF
   exit 0
}

while getopts ":i:o:a:b:t:sh" option
do
   case "${option}"
   in
      i) originalImage="${OPTARG}";;
      o) modifiedImage="${OPTARG}";;
      a) brlttyPattern="${OPTARG}";;
      b) brailleDrivers="${OPTARG}";;
      t) textTable="${OPTARG}";;
      s) invokeShell=true;;
      h) showUsage;;
     \?) syntaxError "unknown option: -${OPTARG}";;
      :) syntaxError "missing operand: -${OPTARG}";;
      *) syntaxError "unimplemented option: -${option}";;
   esac
done
shift $((OPTIND - 1))
[ "${#}" -gt 0 ] && syntaxError "too many parameters."

[ -f "${originalImage}" ] || programError "original image not found: ${originalImage}"

shopt -s nullglob extglob
set -- $brlttyPattern
[ "${#}" -eq 0 ] && programError "brltty archive not found: ${brlttyPattern}"
[ "${#}" -gt 1 ] && {
   brlttyArchives="$(echo "${*}" | sed -e 's% %,&%g')"
   programError "more than one brltty archive: ${brlttyArchives}"
}
brlttyArchive="${1}"

cleanup() {
   set +e
   cd /
   "${newMounted}" && umount "${newRoot}"
   "${initrdMounted}" && umount "${initrdRoot}"
   "${originalMounted}" && umount "${originalRoot}"
   [ -n "${temporaryDirectory}" ] && rm -r "${temporaryDirectory}"
}
set -e
originalMounted=false
initrdMounted=false
newMounted=false
trap "cleanup" 0

temporaryDirectory="$(mktemp -d "${TMPDIR:-/tmp}/${programName}.XXXXXX")"

originalRoot="${temporaryDirectory}/original"
mkdir "${originalRoot}"
mount -o loop -t iso9660 "${originalImage}" "${originalRoot}"
originalMounted=true

modifiedRoot="${temporaryDirectory}/modified"
mkdir "${modifiedRoot}"
cp -a -- "${originalRoot}/." "${modifiedRoot}"

loaderDirectory=""
for directory in boot/isolinux isolinux
do
   [ -d "${originalRoot}/${directory}" ] && {
      loaderDirectory="${directory}"
      break
   }
done

configurationFile="isolinux.cfg"
sed -n -e '
/^\( *default  *\).*$/s//\1text/
/^\( *timeout  *\).*$/s//\10/
/^ *append.* text/s/$/ nofb/
/^ *append.* rescue/s/$/ text nofb/
p
' <"${originalRoot}/${loaderDirectory}/${configurationFile}" >"${modifiedRoot}/${loaderDirectory}/${configurationFile}"

# bootMessage="boot.msg"
# echo -n -e '\a' >"${modifiedRoot}/${loaderDirectory}/${bootMessage}"
# cat "${originalRoot}/${loaderDirectory}/${bootMessage}" >>"${modifiedRoot}/${loaderDirectory}/${bootMessage}"

initrdImage="${temporaryDirectory}/initrd.ext2"
initrdPath="${modifiedRoot}/${loaderDirectory}/initrd.img"
gunzip -c "${initrdPath}" >"${initrdImage}"

initrdRoot="${temporaryDirectory}/initrd"
mkdir "${initrdRoot}"
mount -o loop -t ext2 "${initrdImage}" "${initrdRoot}"
initrdMounted=true

linuxrcPath="${initrdRoot}/linuxrc"
initPath="$(readlink "${linuxrcPath}")"
rm "${linuxrcPath}"
ln -s /bin/brltty "${linuxrcPath}"

deviceDirectory="${initrdRoot}/dev"
shopt -u nullglob
for device in /dev/vcs*
do
   deviceName="${device##*/}"
   [ -e "${deviceDirectory}/${deviceName}" ] && continue
   cp -a "${device}" "${deviceDirectory}"
done

case "${brlttyArchive}"
in
   *.tar.gz|*.tgz)
      tar x -z -C "${temporaryDirectory}" -f "${brlttyArchive}"
      ;;
   *.tar)
      tar x -C "${temporaryDirectory}" -f "${brlttyArchive}"
      ;;
   *)
      programError "unsupported brltty archive name: ${brlttyArchive}"
      ;;
esac

shopt -s nullglob
set -- ${temporaryDirectory}/brltty*
[ "${#}" -eq 0 ] && programError "brltty build directory not found."
brlttyBuildRoot="${1}"

brlttyInstallRoot="${temporaryDirectory}/install"
(
   set -e
   trap "" 0
   cd "${brlttyBuildRoot}"

   ./autogen.sh
   ./configure --quiet --with-install-root="${brlttyInstallRoot}" \
	       --with-init-path="${initPath}" \
	       --with-braille-driver="${brailleDrivers}" \
	       --with-text-table="${textTable}" \
	       --disable-speech-support --without-libbraille --without-curses --without-x \
	       --disable-pcm-support --disable-fm-support --disable-midi-support \
               --enable-standalone-programs --disable-api --disable-gpm
   make -s -C Programs all
   strip Programs/brltty
   make -s -C Programs install-programs install-help install-tables
) || exit "${?}"

set -- $(stat -f -- "${initrdRoot}" | grep '^ *Blocks:')
initrdAvailableBlocks="${7}"
initrdBlockSize="${9}"
set -- $(du --block-size="${initrdBlockSize}" -s "${brlttyInstallRoot}")
brlttyInstallBlocks="${1}"
set -- $(stat -- "${initrdImage}" | grep '^ *Size:')
initrdImageBytes="${2}"
initrdImageBlocks=$(( (initrdImageBytes + initrdBlockSize - 1) / initrdBlockSize ))
initrdPadBlocks=$(( brlttyInstallBlocks - initrdAvailableBlocks ))
(( (initrdPadBlocks += 100) > 0 )) && {
   initrdMounted=false
   umount "${initrdRoot}"

   dd bs="${initrdBlockSize}" count="${initrdPadBlocks}" if="/dev/zero" of="${initrdImage}" seek="${initrdImageBlocks}"
   e2fsck -y -f "${initrdImage}" && resize2fs "${initrdImage}" || {
      newImage="${temporaryDirectory}/new.ext2"
      cp -a -- "${initrdImage}" "${newImage}"
      mke2fs -q -b "${initrdBlockSize}" -F "${newImage}"

      newRoot="${temporaryDirectory}/new"
      mkdir "${newRoot}"

      mount -o loop -t ext2 "${newImage}" "${newRoot}"
      newMounted=true

      mount -o loop -t ext2 "${initrdImage}" "${initrdRoot}"
      initrdMounted=true
      cp -a -- "${initrdRoot}/." "${newRoot}"
      initrdMounted=false
      umount "${initrdRoot}"

      newMounted=false
      umount "${newRoot}"

      cp -- "${newImage}" "${initrdImage}"
   }

   mount -o loop -t ext2 "${initrdImage}" "${initrdRoot}"
   initrdMounted=true
}

(
   set -e
   cd "${brlttyInstallRoot}"
   cp --parents -- $(find . -type f -printf '%P ') "${initrdRoot}"
) || exit 1

"${invokeShell}" && (
   set +e
   trap "" 0

   cd "${temporaryDirectory}"
   "${SHELL:-/bin/sh}"
   exit 0
)

initrdMounted=false
umount "${initrdRoot}"
gzip -c -9 "${initrdImage}" >"${initrdPath}"

isoVolumeIdentifier="$(isoinfo -d -i "${originalImage}" | grep '^Volume id:' | cut -d' ' -f3-)"
isoApplicationIdentifier="$(isoinfo -d -i "${originalImage}" | grep '^Application id:' | cut -d' ' -f3-)"
mkisofs -quiet -J -R -T -boot-load-size 4 -boot-info-table -no-emul-boot \
	-A "${isoApplicationIdentifier}" -V "${isoVolumeIdentifier}" \
	-b "${loaderDirectory}/isolinux.bin" -c "${loaderDirectory}/boot.cat" \
	-o "${modifiedImage}" "${modifiedRoot}"
exit 0
