#! /bin/sh
#
# smartmontools drive database update script
#
# Copyright (C) 2010-16 Christian Franke
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2, or (at your option)
# any later version.
#
# You should have received a copy of the GNU General Public License
# (for example COPYING); If not, see <http://www.gnu.org/licenses/>.
#
# $Id: update-smart-drivedb.in 4224 2016-02-26 20:29:24Z chrfranke $
#

set -e

# Set by config.status
PACKAGE="@PACKAGE@"
VERSION="@VERSION@"
prefix="@prefix@"
exec_prefix="@exec_prefix@"
sbindir="@sbindir@"
datarootdir="@datarootdir@"
datadir="@datadir@"
drivedbdir="@drivedbdir@"

# Download tools
os_dltools="@os_dltools@"

# drivedb.h update branch
BRANCH="@DRIVEDB_BRANCH@"

# Default drivedb location
DRIVEDB="$drivedbdir/drivedb.h"

# Smartctl used for syntax check
SMARTCTL="$sbindir/smartctl"

myname=$0

usage()
{
  cat <<EOF
smartmontools $VERSION drive database update script

Usage: $myname [OPTIONS] [DESTFILE]

  -s SMARTCTL     Use SMARTCTL for syntax check ('-s -' to disable)
                  [default: $SMARTCTL]
  -t TOOL         Use TOOL for download: $os_dltools
                  [default: first one found in PATH]
  -u LOCATION     Use URL of LOCATION for download:
                    sf (Sourceforge code browser via HTTPS)
                    svn (SVN repository via HTTPS) [default]
                    svni (SVN repository via HTTP)
                    trac (Trac code browser via HTTPS)
  --cacert FILE   Use CA certificates from FILE to verify the peer
  --capath DIR    Use CA certificate files from DIR to verify the peer
  --insecure      Don't abort download if certificate verification fails
  --dryrun        Print download commands only
  -v              Verbose output

Updates $DRIVEDB
or DESTFILE from smartmontools SVN repository.
Tries to download first from branch $BRANCH
and then from trunk.
EOF
  exit 1
}

error()
{
  echo "$myname: $*" >&2
  exit 1
}

warning()
{
  echo "$myname: (Warning) $*" >&2
}

selecturl()
{
  case $1 in
    sf)   url='https://sourceforge.net/p/smartmontools/code/HEAD/tree/trunk/smartmontools/drivedb.h?format=raw' ;;
    svn)  url='https://svn.code.sf.net/p/smartmontools/code/trunk/smartmontools/drivedb.h' ;;
    svni) url='http://svn.code.sf.net/p/smartmontools/code/trunk/smartmontools/drivedb.h' ;;
    trac) url='https://www.smartmontools.org/export/HEAD/trunk/smartmontools/drivedb.h' ;;
    *) usage ;;
  esac
}

inpath()
{
  local d rc save
  rc=1
  save=$IFS
  IFS=':'
  for d in $PATH; do
    test -f "$d/$1" || continue
    test -x "$d/$1" || continue
    rc=0
    break
  done
  IFS=$save
  return $rc
}

vecho()
{
  test -n "$q" || echo "$*"
}

# vrun COMMAND ARGS...
vrun()
{
  if [ -n "$dryrun" ]; then
    echo "$*"
  elif [ -n "$q" ]; then
    "$@" 2>/dev/null
  else
    echo "$*"
    "$@"
  fi
}

# vrun2 OUTFILE COMMAND ARGS...
vrun2()
{
  local f err rc
  f=$1; shift
  rc=0
  if [ -n "$dryrun" ]; then
    echo "$* > $f"
  else
    vecho "$* > $f"
    err=`"$@" 2>&1 > $f` || rc=$?
    if [ -n "$err" ]; then
      vecho "$err" >&2
      test $rc != 0 || rc=42
    fi
  fi
  return $rc
}

# download URL FILE
download()
{
  local f u se rc
  u=$1; f=$2
  rc=0

  case $tool in
    curl)
      vrun curl ${q:+-s} -f --max-redirs 0 \
        ${cacert:+--cacert "$cacert"} \
        ${capath:+--capath "$capath"} \
        ${insecure:+--insecure} \
        -o "$f" "$u" || rc=$?
      ;;

    wget)
      vrun wget $q --max-redirect=0 \
        ${cacert:+--ca-certificate="$cacert"} \
        ${capath:+--ca-directory="$capath"} \
        ${insecure:+--no-check-certificate} \
        -O "$f" "$u" || rc=$?
      ;;

    lynx)
      test -z "$cacert" || vrun export SSL_CERT_FILE="$cacert"
      test -z "$capath" || vrun export SSL_CERT_DIR="$capath"
      # Check also stderr as lynx does not return != 0 on HTTP error
      vrun2 "$f" lynx -stderr -noredir -source "$u" || rc=$?
      ;;

    svn)
      vrun svn $q export \
        --non-interactive --no-auth-cache \
        ${cacert:+--config-option "servers:global:ssl-trust-default-ca=no"} \
        ${cacert:+--config-option "servers:global:ssl-authority-files=$cacert"} \
        ${insecure:+--trust-server-cert} \
        "$u" "$f" || rc=$?
      ;;

    fetch) # FreeBSD
      vrun fetch $q --no-redirect \
        ${cacert:+--ca-cert "$cacert"} \
        ${capath:+--ca-path "$capath"} \
        ${insecure:+--no-verify-hostname} \
        -o "$f" "$u" || rc=$?
      ;;

    ftp) # OpenBSD
      vrun ftp \
        ${cacert:+-S cafile="$cacert"} \
        ${capath:+-S capath="$capath"} \
        ${insecure:+-S dont} \
        -o "$f" "$u" || rc=$?
      ;;

    *) error "$tool: unknown (internal error)" ;;
  esac
  return $rc
}

# Parse options
smtctl=$SMARTCTL
tool=
url=
q="-q"
dryrun=
cacert=
capath=
insecure=

while true; do case $1 in
  -s)
    shift; test -n "$1" || usage
    smtctl=$1 ;;

  -t)
    shift
    case $1 in *\ *) usage ;; esac
    case " $os_dltools " in *\ $1\ *) ;; *) usage ;; esac
    tool=$1 ;;

  -u)
    shift; selecturl "$1" ;;

  -v)
    q= ;;

  --dryrun)
    dryrun=t ;;

  --cacert)
    shift; test -n "$1" || usage
    cacert=$1 ;;

  --capath)
    shift; test -n "$1" || usage
    capath=$1 ;;

  --insecure)
    insecure=t ;;

  -*)
    usage ;;

  *)
    break ;;
esac; shift; done

case $# in
  0) DEST=$DRIVEDB ;;
  1) DEST=$1 ;;
  *) usage ;;
esac

if [ -z "$tool" ]; then
  # Find download tool in PATH
  for t in $os_dltools; do
    if inpath "$t"; then
      tool=$t
      break
    fi
  done
  test -n "$tool" || error "found none of: $os_dltools"
fi

test -n "$url" || selecturl "svn"

# Check option compatibility
case "$tool:$url" in
  svn:http*://svn.code.sf.net*) ;;
  svn:*) error "'-t svn' requires '-u svn' or '-u svni'" ;;
esac
case "$tool:${capath:+set}" in
  svn:set) warning "'--capath' is ignored if '-t svn' is used" ;;
esac
case "${insecure:-f}:$url" in
  t:http:*) insecure= ;;
  ?:https:*) ;;
  *) error "'-u svni' requires '--insecure'" ;;
esac
case "$tool:$insecure" in
  lynx:t) warning "'--insecure' is ignored if '-t lynx' is used" ;;
esac

# Try possible branch first, then trunk
errmsg=
errmsg2=
for location in "branches/$BRANCH" "trunk"; do
  test -z "$errmsg" || errmsg2=$errmsg
  vecho "Download from $location with $tool"

  # Adjust URL
  case $location in
    trunk) src=$url ;;
    *)     src=`echo "$url" | sed "s,/trunk/,/$location/,"` ;;
  esac

  # Download
  test -n "$dryrun" || rm -f "$DEST.new" || exit 1
  rc=0
  download "$src" "$DEST.new" || rc=$?
  test -z "$dryrun" || continue

  errmsg=
  if [ $rc != 0 ]; then
    errmsg="download from $location failed ($tool: exit $rc)"
    continue
  fi

  # Check file contents
  case `sed 1q "$DEST.new"` in
    /*) ;;
    \<*)
      errmsg="download from $location failed (HTML error message)"
      continue ;;
    *)
      errmsg="download from $location failed (Unknown file contents)"
      continue ;;
  esac

  # Check file size
  size=`wc -c < "$DEST.new"`
  if [ "$size" -lt 10000 ]; then
    errmsg="download from $location failed (too small file size $size bytes)"
    continue
  fi
  if [ "$size" -gt 1000000 ]; then
    errmsg="download from $location failed (too large file size $size bytes)"
    break
  fi

  break
done

test -z "$dryrun" || exit 0

if [ -n "$errmsg" ]; then
  rm -f "$DEST.new"
  test -z "$errmsg2" || echo "$myname: $errmsg2" >&2
  error "$errmsg"
fi

# Adjust timestamp and permissions
touch "$DEST.new"
chmod 0644 "$DEST.new"

if [ "$smtctl" != "-" ]; then
  # Check syntax
  rm -f "$DEST.error"
  if "$smtctl" -B "$DEST.new" -P showall >/dev/null; then
    test -n "$q" || echo "$smtctl: syntax OK"
  else
    mv "$DEST.new" "$DEST.error"
    echo "$DEST.error: rejected by $smtctl, probably no longer compatible" >&2
    exit 1
  fi
fi

# Keep old file if identical, ignore missing Id keyword expansion in new file
rm -f "$DEST.lastcheck"
if [ -f "$DEST" ]; then
  if    cmp "$DEST" "$DEST.new" >/dev/null 2>/dev/null \
     || cat "$DEST" | sed 's|\$''Id''[^$]*\$|$''Id''$|' \
        | cmp - "$DEST.new" >/dev/null 2>/dev/null; then
    rm -f "$DEST.new"
    touch "$DEST.lastcheck"
    echo "$DEST is already up to date"
    exit 0
  fi
  mv "$DEST" "$DEST.old"
fi

mv "$DEST.new" "$DEST"

echo "$DEST updated from $location"

