#!/bin/sh

# Author   : Benoit PAPILLAULT <benoit.papillault@sourcemage.org>
# Creation : 2004-11-25

# Design document:
# http://benoit.papillault.free.fr/sourcemage/cast-binary.php

# Parameters:
# BASE         : directory where the binary cache will be generated
# INSTALL_ROOT : directory where the system is installed (install cache
#                location, install root location, state root location)

# pipefail is not available on all bash version, so removed
# set -o pipefail

# usage: get_sorcery_host

get_sorcery_host()
{
  local config="/etc/sorcery/config"
  if [ -f "${INSTALL_ROOT}/${config}" ]; then
# we start a subshell to avoid polluting our variables
# warning : if INSTALL_ROOT is not / and since "config" will try to
# source other files, we might get into problems.
	if [ "${INSTALL_ROOT}" != "/" ]; then
	  chroot "${INSTALL_ROOT}" /bin/sh -c ". ${config}; echo \${HOST}"
    else
      ( . "${config}"; echo "${HOST}")
    fi
  fi
}

# usage: get_spell_version $spell

get_spell_version()
{
  local spell="$1"

# check if we have a "packages" file, otherwise, we return a empty string

  if [ -f "${INSTALL_ROOT}/var/state/sorcery/packages" ]; then
    grep "^${spell}:" "${INSTALL_ROOT}/var/state/sorcery/packages"|
	cut -d: -f4|head -n 1
  fi
}

# usage: copy_local_depends $SPELL tmpdir
# copy $INSTALL_ROOT/etc/sorcey/local/depends/$SPELL{.p} if any
# to the current directory

copy_local_depends()
{
  local SPELL="$1"
  local tmpdir="$2"

  # copy /etc/sorcery/local/depends files
  local DEPENDS_DIR=/etc/sorcery/local/depends
  local DEPENDS="${DEPENDS_DIR}/${SPELL}"
  if [ -f "${INSTALL_ROOT}/${DEPENDS}" ]; then
    mkdir -p "${tmpdir}/${DEPENDS_DIR}" &&
	cp -a "${INSTALL_ROOT}/${DEPENDS}" "${tmpdir}/${DEPENDS_DIR}"
  fi

  local DEPENDS="${DEPENDS_DIR}/${SPELL}.p"
  if [ -f "${INSTALL_ROOT}/${DEPENDS}" ]; then
    mkdir -p "${tmpdir}/${DEPENDS_DIR}" &&
	cp -a "${INSTALL_ROOT}/${DEPENDS}" "${tmpdir}/${DEPENDS_DIR}"
  fi
}

# usage: copy_aliens $SPELL $tmpdir
# copy all aliens from INSTALL_ROOT to tmpdir

copy_aliens()
{
  local SPELL="$1"
  local tmpdir="$2"

    case "${SPELL}" in
      bash)
        cat <<EOF
/etc/profile
/etc/profile.d/dummy
/etc/skel
/etc/skel/.bash_logout
/etc/skel/.bash_profile
/etc/skel/.bashrc
EOF
        ;;
      hotplug)
        cat <<EOF
/etc/hotplug/blacklist
/etc/hotplug/pnp.distmap
/etc/hotplug/usb.distmap
/etc/hotplug/usb.handmap
/etc/hotplug/usb.usermap
EOF
        ;;
      init.d)
        cat <<EOF
/etc/inittab
/etc/sysconfig/devices
/etc/sysconfig/facilities
/etc/sysconfig/init
/etc/sysconfig/keymap
/etc/sysconfig/shutdown
EOF
        ;;
      Linux-PAM)
        cat <<EOF
/etc/security
/etc/security/time.conf
/etc/security/pam_env.conf
/etc/security/limits.conf
/etc/security/group.conf
/etc/security/access.conf
EOF
        ;;
      linux-pam)
        cat <<EOF
/etc/securetty
/etc/security
/etc/security/access.conf
/etc/security/group.conf
/etc/security/limits.conf
/etc/security/pam_env.conf
/etc/security/time.conf
EOF
        ;;
      linux)
        cat <<EOF
/etc/sorcery/local/kernel.config
EOF
        ;;
      net-tools)
        cat <<EOF
/etc/sysconfig/dhcpcd
/etc/sysconfig/mountnetwork
EOF
        ;;
      nfs-utils)
        cat <<EOF
/etc/sysconfig/nfs
EOF
        ;;
      openssh)
        cat <<EOF
/etc/pam.d/openssh
/etc/pam.d/sshd
/etc/ssh
/etc/ssh/moduli
/etc/ssh/ssh_config
/etc/ssh/ssh_host_dsa_key
/etc/ssh/ssh_host_dsa_key.pub
/etc/ssh/ssh_host_key
/etc/ssh/ssh_host_key.pub
/etc/ssh/ssh_host_rsa_key
/etc/ssh/ssh_host_rsa_key.pub
/etc/ssh/sshd_config
/var/empty
EOF
        ;;
      shadow)
        cat <<EOF
/etc/limits
/etc/login.access
/etc/login.defs
/etc/login.defs.linux
/etc/pam.d/chfn
/etc/pam.d/chsh
/etc/pam.d/groupadd
/etc/pam.d/groupdel
/etc/pam.d/login
/etc/pam.d/passwd
/etc/pam.d/shadow
/etc/pam.d/su
/etc/pam.d/useradd
EOF
        ;;
      udev)
        cat <<EOF
/etc/dev.d/snd/controlC0/alsa.dev
/etc/udev/udev.conf
/etc/udev/udev.missing
/etc/udev/udev.rules
/etc/udev/udev.rules.devfs
/etc/udev/udev.rules.example
EOF
        ;;
      util-linux)
        cat <<EOF
/etc/sysconfig/hwclock
EOF
        ;;
    esac | sed 's:^\(/*\)::' |
    tar -cf - -C "${INSTALL_ROOT}" -T - | tar -xpf - -C "${tmpdir}"
}

# usage: get_packages $spell
# output one line from the /var/state/sorcery/packages

get_packages()
{
  local spell="$1"

  local packages="${INSTALL_ROOT}/var/state/sorcery/packages"
  if [ -f "${packages}" ]; then
	grep "^${spell}:" "${packages}"
  fi
}

# usage: get_depends $spell
# output one line for each dependency. each dependency is a spell name

get_depends()
{
  local spell="$1"

# check if we have a "depends" file
# warning, some spells depends on generic provider and the spell name field
# is for instance : util-linux(UTIL-LINUX)
# we remove the () part.

  local depends="${INSTALL_ROOT}/var/state/sorcery/depends"
  if [ -f "${depends}" ]; then
    grep "^${spell}:" "${depends}"
	true
  fi
}

# usage: get_spell_dir $spell
# returns the directory containing spell files
# something like /var/lib/sorcery/codex/stable/libs/glibc for the glibc spell

get_spell_dir()
{
  local spell="$1"

  if [ ! -f "${INSTALL_ROOT}/etc/sorcery/local/grimoire" ]; then
    echo "missing /etc/sorcery/local/grimoire"
    exit 1
  fi

  # define an array GRIMOIRE_DIR
  . "${INSTALL_ROOT}/etc/sorcery/local/grimoire"

  for grimoire in "${GRIMOIRE_DIR[@]}";
  do
    DIR=`find "${INSTALL_ROOT}/${grimore}" -mindepth 2 -maxdepth 2 \
      -type d -name "${spell}" | head -n 1`
    if [ -n "${DIR}" ]; then
      echo "${dir}"
      return 0
    fi
  done
}

# usage: get_build_api $spell_dir
# this returns the BUILD_API specified in the first spell found in
# /var/lib/sorcery/codex

get_build_api()
{
  local spell_dir="$1"

  if [ -z "${spell_dir}" ]; then
    echo "cannot find spell ${spell} in grimoires"
    exit 1
  fi

  local build_api

# spell level
  local spell_config="${spell_dir}/DETAILS"
  if [ -f "${spell_config}" ]; then
    build_api=`. "${spell_config}"; echo "${BUILD_API}"`
  fi

# section level (if not defined at the spell level)
  if [ -z "${build_api}" ]; then
    local section_config="${spell_dir}/../API_VERSION"
    if [ -f "${section_config}" ]; then
	  build_api=`. "${section_config}"; echo "${BUILD_API}"`
    fi
  fi

# grimoire level (if not already defined)
  if [ -z "${build_api}" ]; then
    local grimoire_config="${spell_dir}/../../API_VERSION"
    if [ -f "${grimoire_config}" ]; then
      build_api=`. "${grimoire_config}"; echo "${BUILD_API}"`
    fi
  fi

# default value if not already defined
  if [ -z "${build_api}" ]; then
    build_api=1
  fi

  echo "${build_api}"
}

# usage: get_postinst $spell
# output the postinst file from either POST_INSTALL (BUILD_API=1)
# or FINAL (BUILD_API=2)

get_postinst()
{
  local spell="$1"

  local spell_dir=`get_spell_dir "${spell}"`
  local build_api=`get_build_api "${spell_dir}"`

  case "${build_api}" in
    1)
      if [ -f "${spell_dir}/POST_INSTALL" ]; then
		cat "${spell_dir}/POST_INSTALL"
      fi
	  ;;
    2)
      if [ -f "${spell_dir}/FINAL" ]; then
        cat "${spell_dir}/FINAL"
      fi
	  ;;
  esac
}

# usage: package_depends spell
# stdin is read for dependencies information
# lines have the same format as /var/state/sorcery/depends
# call spell-binary-make for all dependencies

package_depends()
{
  local SPELL="$1"

# we ignore errors from grep (for instrance, where there is no match)

  (grep "^${SPELL}:.*:on:.*" || true) |
  cut -d: -f2 | cut -d'(' -f1|
  while read dep_spell;
  do
	spell-binary-make "${dep_spell}" || exit 1
  done
}

# usage: make_binary_cache spell version
# the resulting cache file name is written on stdout

make_binary_cache()
{
  local SPELL="$1"
  local VERSION="$2"

  local INSTALL="${INSTALL_ROOT}/var/log/sorcery/install/${SPELL}-${VERSION}"
  if [ ! -f "${INSTALL}" ]; then
    exit 1
  fi

  # canonicalize file names (needed for xorg) and remove all leadings /

  rm -rf "/tmp/ls.${SPELL}" &&
  cat "${INSTALL}" |
  while read file;
  do
	file="/${file}"
	# we ouput the real name in all cases
	dir=`dirname "${file}"` &&
    if [ -L "${dir}" ]; then
      echo "warning: ${file} is in a symlink dir" >&2
      while [ -L "${dir}" ];
      do
        ldir=`readlink "${INSTALL_ROOT}/${dir}"`
        if [ "${ldir:0:1}" = "/" ]; then
          dir="${ldir}"
        else
          dir=`dirname "${dir}"`
          dir="${dir}/${ldir}"
        fi
      done
      base=`basename "${file}"`
      echo "${dir}/${base}"
    else
      echo "${file}"
    fi
  done |
  # remove /./
  sed 's:/\./:/:g' |
  # remove leading /
  sed 's:^\(/*\)::' | sort -u > "/tmp/ls.${SPELL}"

  local CACHE="/tmp/${SPELL}-${VERSION}-iso.tar.bz2"

  tar jcf "${CACHE}" -C "${INSTALL_ROOT}" \
    --no-recursion -T "/tmp/ls.${SPELL}" &&
  echo "${CACHE}"

  rm "/tmp/ls.${SPELL}"
}

# usage: test_cache cache.tar.bz2
# returns 0 if the cache is OK. Used to test xorg/xfree86 cache which
# are not usable.

test_cache()
{
  local CACHE="$1"
  local TMPDIR="/tmp/cache-$$"
  local RESULT=0

  rm -rf "${TMPDIR}" && mkdir -p "${TMPDIR}" &&
  if ! tar jxf "${CACHE}" -C "${TMPDIR}" 2>/dev/null; then
    echo "${CACHE} can not be used"
	RESULT=1
  fi &&
  rm -rf "${TMPDIR}" &&
  return "${RESULT}"
}

main()
{
  if [ $# -ne 1 ]; then
    echo "usage: $0 spell"
    exit 1
  fi

  local SPELL="$1"

# default values
  if [ -z "${BASE}" ]; then
    BASE=.
  fi

  if [ -z "${INSTALL_ROOT}" ]; then
    INSTALL_ROOT=/
  fi

  local VERSION=`get_spell_version "${SPELL}"`
  if [ -z "${VERSION}" ]; then
    echo "${SPELL} is not installed in ${INSTALL_ROOT}"
    exit 1
  fi

  echo "spell ${SPELL} version ${VERSION} is being investigated"

  local HOST=`get_sorcery_host`
  if [ -z "${HOST}" ]; then
    echo "sorcery architecture is unknown, check your sorcery settings"
    exit 1
  fi

# compute canonic name (replace _ by -)

  local C_SPELL=`echo "${SPELL}" | sed s/_/-/g`
  local C_VERSION=`echo "${VERSION}" | sed s/_/-/g`
  local C_HOST=`echo "${HOST}" | sed s/_/-/g`
  local PKG="${C_SPELL}_${C_VERSION}_${C_HOST}.tar.bz2"

# check if the package already exist (it may already exist since we
# are checking dependencies recursively)

  if [ -f "${BASE}/${PKG}" ]; then
	 echo "spell ${SPELL} version ${VERSION} is already packaged"
     exit 0
  fi

# look for the precompiled binary cache

  CACHE="${INSTALL_ROOT}/var/cache/sorcery/${SPELL}-${VERSION}-${HOST}.tar.bz2"
  if [ ! -f "${CACHE}" ] || ! test_cache "${CACHE}"; then
	# we need to make a cache ourselves (TO BE IMPLEMENTED)
	CACHE=`make_binary_cache "${SPELL}" "${VERSION}"`
	if [ -z "${CACHE}" ]; then
     echo "no binary cache found and cannot make one"
     exit 1
    fi
  fi

# package dependencies first
  get_depends "${SPELL}" | package_depends "${SPELL}" || exit 1

# create a temporary directory

  local tmpdir="/tmp/spell-binary-$$"
  rm -rf "${tmpdir}" && mkdir -p "${tmpdir}" &&
  tar jxf "${CACHE}" -C "${tmpdir}" &&
  mkdir -p "${tmpdir}/install" &&
  copy_local_depends "${SPELL}" "${tmpdir}" &&
  copy_aliens "${SPELL}" "${tmpdir}" &&
  get_packages "${SPELL}" > "${tmpdir}/install/packages" &&
  get_depends  "${SPELL}" > "${tmpdir}/install/depends" &&
  get_postinst "${SPELL}" > "${tmpdir}/install/postinst" &&
  if [ -s "${tmpdir}/install/postinst" ]; then
    chmod a+x "${tmpdir}/install/postinst"
  else
    rm "${tmpdir}/install/postinst"
  fi &&
  echo "spell ${SPELL} version ${VERSION} is being packaged" &&
  mkdir -p "${BASE}" &&
  tar jcf "${BASE}/${PKG}" -C "${tmpdir}" . &&
  echo "spell ${SPELL} version ${VERSION} is packaged" &&
  rm -rf "${tmpdir}"
}

main "$@"
