#!/bin/ksh
##
## Bootstrap a basic RPM environment for OSSTech software products
## Copyright (c) 2008-2011 OSS Technology, Inc. <http://www.osstech.co.jp/>
## Copyright (c) 2008-2011 SATOH Fumiyasu @ OSS Technology, Inc.
##
## License: GNU General Public License version 2 or later
## Date: 2011-08-24, since 2008-06-10
##

set -u
umask 0022

export LC_ALL="C"

uname=$(uname)

PATH="/bin:/usr/bin:/sbin:/usr/sbin"
case "$uname" in
SunOS)
  PATH="/usr/sfw/bin:/usr/xpg6/bin:/usr/xpg4/bin:/usr/ccs/bin:$PATH"
  arch=$(uname -p)
  ;;
AIX)
  PATH="/usr/sysv/bin:/usr/ccs/bin:$PATH"
  arch=$(uname -p)
  ;;
Linux)
  if [ -f /etc/debian_version ]; then
    arch=$(uname -m)
  fi
  ;;
esac
export PATH="/opt/osstech/bin:$PATH"

pinfo()
{
  { tput bold; tput smul; } 2>/dev/null || :
  echo "${0##*/}: $*"
  tput sgr0 2>/dev/null || :
}

pwarn()
{
  echo "${0##*/}: WARNING: $*" 1>&2
}

perr()
{
  echo "${0##*/}: ERROR: $*" 1>&2
}

pdie()
{
  perr "$*"
  exit 1
}

chroot()
{
  if [ X"$1" = X"/" ]; then
    shift
  else
    set -- command chroot ${1+"$@"}
  fi

  "$@"
  return $?
}

uid_of_user()
{
  /usr/bin/id ${1+"$1"} |/bin/sed -n 's/^[^=]*=\([0-9]*\).*$/\1/p'
}

quote_meta()
{
  sed 's/\([^A-Za-z_0-9]\)/\\\1/g'
}

## This function is ported from rpm2cpio.sh in RPM 4.4.2.3
rpm2cpio()
{
  typeset pkg="$1"
  if [ "$pkg" = "" -o ! -e "$pkg" ]; then
    pdie "No package supplied"
  fi

  typeset leadsize o il dl sigsize hdrsize

  leadsize=96
  o=`expr $leadsize + 8`
  set `od -j $o -N 8 -t u1 "$pkg"`
  il=`expr 256 \* \( 256 \* \( 256 \* $2 + $3 \) + $4 \) + $5`
  dl=`expr 256 \* \( 256 \* \( 256 \* $6 + $7 \) + $8 \) + $9`
  # echo "sig il: $il dl: $dl"

  sigsize=`expr 8 + 16 \* $il + $dl`
  o=`expr $o + $sigsize + \( 8 - \( $sigsize \% 8 \) \) \% 8 + 8`
  set `od -j $o -N 8 -t u1 "$pkg"`
  il=`expr 256 \* \( 256 \* \( 256 \* $2 + $3 \) + $4 \) + $5`
  dl=`expr 256 \* \( 256 \* \( 256 \* $6 + $7 \) + $8 \) + $9`
  # echo "hdr il: $il dl: $dl"

  hdrsize=`expr 8 + 16 \* $il + $dl`
  o=`expr $o + $hdrsize`

  dd if="$pkg" ibs="$o" skip=1 2>/dev/null |gunzip
}

## DEBUG: If the -x option is set, enable trace flag on all functions
[ X"${-#*x}" != X"$-" ] && typeset -ft $(typeset +f)

## Options
## ======================================================================

vendor_nickname="OSSTech"
vendor_id="osstech"
vendor_name_prefix="$vendor_id-"

rpm_basenames_noarch="base"
rpm_basenames_arch="rpm rpm-libs rpm-build"
case "$uname" in
AIX)
  rpm_basenames_arch="$rpm_basenames_arch popt zlib zsh bash"
  ;;
esac

root="/"
ignore_if_bootstrapped=""

cmd_usage="Usage: $0 [OPTIONS] RPMS_DIR

Options and Arguments:
 --root ROOTDIR
    Target root filesystem (default: $root)
 --ignore-if-bootstrapped
    Warn and exit with status 0 instead of 1
    if the environment was already bootstrapped.
 RPMS_DIR
    Directory that contains $vendor_nickname RPM packages for
    bootstrapping. The following RPM packages are required:"

rpm_basenames_noarch=$(echo " $rpm_basenames_noarch" |sed 's/ / '"$vendor_name_prefix"'/g')
for rpm_basename in $rpm_basenames_noarch; do
  cmd_usage="$cmd_usage
        $rpm_basename-X.X.X-X.noarch.rpm"
done
rpm_basenames_arch=$(echo " $rpm_basenames_arch" |sed 's/ / '"$vendor_name_prefix"'/g')
for rpm_basename in $rpm_basenames_arch; do
  cmd_usage="$cmd_usage
        $rpm_basename-X.X.X-X.$arch.rpm"
done
rpm_basenames="$rpm_basenames_noarch $rpm_basenames_arch"

## ======================================================================

getopts_want_arg()
{
  if [ $# -lt 2 ]; then
    pdie "Option requires an argument: $1"
  fi
  if [ $# -ge 3 ]; then
    if expr X"$2" : X"$3\$" >/dev/null; then
      : OK
    else
      pdie "Invalid value for option : $1 $2"
    fi
  fi
}

while test "$#" -gt 0; do
  OPT="$1"; shift
  case "$OPT" in
  -r|--root)
    getopts_want_arg "$OPT" ${1+"$1"} ${1+"/.*"}
    root="$1"; shift
    [ -d "$root" ] || pdie "Root directory not found: $root"
    ;;
  --ignore-if-bootstrapped)
    ignore_if_bootstrapped="yes"
    ;;
  --)
    break
    ;;
  -*)
    pdie "Invalid option: $OPT"
    exit 1
    ;;
  *)
    set -- "$OPT" ${1+"$@"}
    break
    ;;
  esac
done

if [ $# -ne 1 ]; then
  echo "$cmd_usage"
  exit 1
fi

rpms_dir="$1"; shift
rpms_dir_abs=$(cd "$rpms_dir" && pwd)

tmp_dir_tmpl="${root%/}/tmp/${0##*/}.tmp.XXXXXX"

prefix="${root%/}/opt/$vendor_id"
bindir="$prefix/bin"
datadir="$prefix/share"
localstatedir="$prefix/var"
localstatedir_new="${root%/}/var/opt/$vendor_id"

base_pkg_name="${vendor_name_prefix}base"
base_os_pkg_name="${vendor_name_prefix}base-os"
base_os_pkg_ksh="$datadir/${vendor_name_prefix}base/${vendor_name_prefix}base-os.ksh"

## Check environment
## ======================================================================

if [ X"`uid_of_user`" != X"0" ]; then
  pdie "You must be root to bootstrap"
fi

if chroot "$root" "$bindir/rpm" -qi $rpm_basenames >/dev/null 2>&1; then
  msg="This environment was already bootstrapped"
  if [ -n "$ignore_if_bootstrapped" ]; then
    pwarn "$msg"
    pinfo "Done."
    exit 0
  else
    pdie "$msg"
  fi
fi

shift $#
for rpm_basename in $rpm_basenames_noarch; do
  rpm=$(ls "$rpms_dir/$rpm_basename"-[0-9]*-[0-9]*."noarch.rpm" 2>/dev/null)
  [ -f "$rpm" ] || pdie "Required RPM package not found: $rpm_basename-*.noarch.rpm"
  set -- ${1+"$@"} "$rpm"
done
for rpm_basename in $rpm_basenames_arch; do
  rpm=$(ls "$rpms_dir/$rpm_basename"-[0-9]*-[0-9]*."$arch.rpm" 2>/dev/null)
  [ -f "$rpm" ] || pdie "Required RPM package not found: $rpm_basename-*.$arch.rpm"
  set -- ${1+"$@"} "$rpm"
done

## Migration: Local state directory
## ======================================================================

if [ -d "$localstatedir" ] && [ ! -h "$localstatedir" ]; then
  if [ X"$root" != X"/" ]; then
    ## FIXME
    pdie "Migration with --root ROOTDIR option is not supported"
  fi

  pinfo "Old local state directory found: $localstatedir"
  pinfo "Preparing to migrate ..."

  if [ -d "$localstatedir_new" ]; then
    pdie "New local state directory already exist: $localstatedir_new"
  fi

  localstatedir_re=$(echo "$localstatedir" |quote_meta)
  localstatedir_fs=$(df "$localstatedir" |sed 's/ .*//')
  (fuser -c "$localstatedir_fs"; echo) 2>/dev/null \
  |sed 's/^ *//;s/  */ /g' \
  |tr ' ' '\n' \
  |xargs pfiles 2>/dev/null \
  |nawk '
    $0 ~ /^[1-9][0-9]*:/ {
      pid=$1
      sub(/:/, "", pid)
    }
    $0 ~ /'"$localstatedir_re"'/ {
      print pid " " $0
    }
  ' \
  |egrep " +$localstatedir_re(/.*)*\$" \
  |sed 's/ .*//' \
  |sort -nu \
  |(
    file_opened=0
    while read pid; do
      if [ "$file_opened" -eq 0 ]; then
        perr "Old local state directory is used by the following process(es)"
      fi
      file_opened="1"
      echo -n "  PID $pid: "
      ps -p "$pid" -o args |tail -n +2
    done
    exit "$file_opened"
  )
  [ $? -ne 0 ] && exit 1

  pinfo "Migrating local state directory: $localstatedir_new ..."
  (
    find "$localstatedir" -type s |xargs rm -f
    mkdir -p "$localstatedir_new"
    mv "$localstatedir"/{.??*,*} "$localstatedir_new/"
    rmdir "$localstatedir" \
  ) || pdie "Migrating local state directory failed"
fi

## Installation
## ======================================================================

pinfo "Extracting RPM packages by rpm2cpio and cpio ..."

if [ X"$uname" = X"AIX" ]; then
  ## We use own GNU cpio binary because AIX /usr/bin/cpio is NOT
  ## compatible with SYSV cpio format and /usr/sysv/bin/cpio is broken.
  cpio="$rpms_dir_abs/cpio"
else
  cpio=cpio
fi

for rpm in "$@"; do
  echo "${rpm##*/}"
  rpm2cpio "$rpm" |(cd "$root" && $cpio -idu)
  if [ $? -ne 0 ]; then
    pdie "Extracting $rpm failed"
  fi
done

## ----------------------------------------------------------------------

if ! tmp_dir=$(mktemp -d "$tmp_dir_tmpl"); then
  pdie "Cannot create a temporary directory";
fi
trap 'rm -rf "$tmp_dir"' 0 INT

## ----------------------------------------------------------------------

pinfo "Building $base_os_pkg_name package ..."

ksh "$base_os_pkg_ksh" "$tmp_dir" || exit 1

base_os_rpm=$(echo "$tmp_dir/$base_os_pkg_name"-*.rpm)

## ----------------------------------------------------------------------

pinfo "Initializing RPM database ..."

chroot "$root" "$bindir/rpm" --initdb
if [ $? -ne 0 ]; then
  pdie "Initializing RPM database failed"
fi

## ----------------------------------------------------------------------

if [ X"$root" != X"/" ]; then
  cp -p "$rpms_dir"/*.rpm "$tmp_dir/" || exit 1
  shift $#
  for rpm in "$tmp_dir"/*.rpm; do
    set -- ${1+"$@"} "${rpm#$root}"
  done
else
  set -- "$rpms_dir"/*.rpm "$base_os_rpm"
fi

pinfo "Installing RPM packages by rpm ..."
chroot "$root" "$bindir/rpm" -Uv "$@"
if [ $? -ne 0 ]; then
  pdie "Installing RPM packages failed"
fi

## ----------------------------------------------------------------------

pinfo "Done."
echo "Thank you!"

