#!/bin/sh
# @(#) rcfunctions - A.04.00
# SQL/R common startup/shutdown functions
#
# (C) Copyright Marxmeier Software AG, 2003-2022
#
# Install this file as /opt/sqlr/4.0/etc/rcfunctions
# It is sourced by the sqlr40 startup/shutdown script
#
# History:
# 15.05.2003 - created
# 27.05.2020 - multiple sqlrodbcd instances
# 05.04.2022 - updated to A.04.00

### default ###

# debug logging
sqlr_debug=0

# Binaries
SQLR=${SQLR:=/opt/sqlr/4.0}
ODBCD=${SQLR}/bin/sqlrodbcd

# start SQL/R by default
START_SQLR=1

# require root for startup/shutdown
START_STOP_AS_ROOT=1

# default eloqdb instance
ODBCD_CFG[0]="odbc.cfg"

# location of pid files
pid_dir=/var${SQLR}/run

# create pid dir if it does not exist (optional)
if [ ! -e $pid_dir -a $(id -u) -eq 0 ]; then
   echo "creating pid directory $pid_dir ..."
   mkdir -p -m 755 $pid_dir
   chmod 1777 $pid_dir
fi

if [ ! -d $pid_dir ]; then
   echo "ERROR: Directory $pid_dir does not exist."
fi

# progress messages default to a nop
sqlr_act_msg1=sqlr_nop
sqlr_act_msg2=sqlr_nop
sqlr_stat_msg1=sqlr_nop
sqlr_stat_msg2=sqlr_nop
sqlr_warn_msg=sqlr_nop

# status codes (as defined by LSB)
sqlr_rc_ok=0
sqlr_rc_failed=1
sqlr_rc_badarg=2
sqlr_rc_notimplemented=3
sqlr_rc_perm=4
sqlr_rc_notinstalled=5
sqlr_rc_notconfigured=6
sqlr_rc_notrunning=7

# status codes for status option (as defined by LSB)
sqlr_stat_ok=0
sqlr_stat_deadpid=1
sqlr_stat_varlock=2
sqlr_stat_stopped=3
sqlr_stat_unknown=4

# Some old Linux ps binaries use BSD style, allow to redefine behavior
sqlr_checkproc=sqlr__checkproc
sqlr_child_procs=sqlr__child_procs


### functions ###

typeset -i sqlr_rval=0
sqlr_rmsg=""

# add log message

function sqlr_log_msg
{
   test -n "$1" && sqlr_rmsg="$sqlr_rmsg\n$*"
}

# do nothing

function sqlr_nop
{
   :
}

# add debug message

function sqlr_debug_msg
{
   [ ${sqlr_debug:-0} -gt 1 ] && sqlr_rmsg="$sqlr_rmsg\ndebug: $*"
}

# start process

function sqlr_run
{
   typeset _cmd _msg _rc
   
   _cmd=$1

   if [ ${sqlr_debug:-0} -gt 1 ]; then
      sqlr_rmsg="$sqlr_rmsg\n>> $_cmd"
      _rc=0
   elif [ ${sqlr_debug:-0} -gt 0 ]; then
      sqlr_rmsg="$sqlr_rmsg\n>> $_cmd"
      _msg=$($_cmd 2>&1)
      _rc=$?
      [ $_rc -ne 0 ] && {
         sqlr_rval=1
         sqlr_log_msg "Failed: $_rc"
      }
      sqlr_log_msg "$_msg"
   else
      _msg=$($_cmd 2>&1)
      _rc=$?
      [ $_rc -ne 0 ] && {
         sqlr_rval=1
         sqlr_log_msg ">> $_cmd\nFailed: $_rc"
      }
      sqlr_log_msg "$_msg"
   fi
   return $_rc
}

# sqlr_instance_filter()
# check if current instance should be considered
# usage: sqlr_instance_filter instance

function sqlr_instance_filter
{
   typeset _id

# empty argument
   test -n "$sqlr_arg_list" || return 0

   _id=$1
   for arg_id in $sqlr_arg_list
   do
      if [ "$_id" = $arg_id ]; then
         break;
      fi
   done
   test "$_id" = $arg_id || return 1
   sqlr_accepted_args="$_id $sqlr_accepted_args"
   return 0
}

# iterate defined sqlrodbcd instances

function sqlr_foreach
{
   typeset fn inst_id inst_cfg_file inst_start 
   typeset inst_service inst_args inst_runpfx inst_odbcd
   typeset -i i inst_cnt

   fn=$1

   inst_cnt=${#ODBCD_CFG[*]}
   sqlr_debug_msg "# inst_cnt = $inst_cnt"

   i=0
   while [ $inst_cnt -gt 0 ]
   do
      # config file

      inst_cfg_file=${ODBCD_CFG[i]:-NOTSET}
      if [ "$inst_cfg_file" = "NOTSET" ]; then
         let i=i+1
         continue
      fi
      sqlr_debug_msg "# inst_cfg_file = $inst_cfg_file"

      inst_start=${ODBCD_START[i]:-1}
      sqlr_debug_msg "# inst_start = $inst_start"

      if [ "${inst_cfg_file##/*}" != "" ]; then
         inst_cfg_file=/etc${SQLR}/$inst_cfg_file
      fi
      sqlr_debug_msg "# inst_cfg_file = $inst_cfg_file"

      if [ ! -s "$inst_cfg_file" ]; then
         sqlr_log_msg "Note: sqlrodbcd[$i]: configuration file does not exist, ignored:"
         sqlr_log_msg $inst_cfg_file
         let inst_cnt=inst_cnt-1 i=i+1
         continue
      fi

      # arguments

      inst_args=${ODBCD_ARGS[i]:-NOTSET}
      if [ "$inst_args" = "NOTSET" ]; then
         inst_args=""
      fi
      sqlr_debug_msg "# inst_args = $inst_args"

      # service

      inst_service=${ODBCD_SERVICE[i]:-NOTSET}
      if [ "$inst_service" = "NOTSET" ]; then
         inst_service=$(grep -i "^[     ]*service[      ]*=" "$inst_cfg_file" | 
                        cut -d= -f2 | head -1 | tr -d ' ')
         [ -z "$inst_service" ] && inst_service="sqlrodbc"
      else
         inst_args="$inst_args -s $inst_service"
      fi
      sqlr_debug_msg "# inst_service = $inst_service"

      # instance id

      inst_id=${ODBCD_ID[i]:-NOTSET}
      if [ "$inst_id" = "NOTSET" ]; then
         inst_id=$inst_service
      fi
      sqlr_debug_msg "# inst_id = $inst_id"

      # wrapper program (eg. mem window on HP-UX)

      inst_runpfx=${ODBCD_RUNPFX[i]:-}
      sqlr_debug_msg "# inst_pfx = $inst_runpfx"

      # binary

      inst_odbcd=$ODBCD
      
      # user

      inst_user="root"
      [ $START_STOP_AS_ROOT = 0 ] && {
         inst_user=$(grep -i "^[        ]*user[  ]*=" "$inst_cfg_file" | 
                     cut -d= -f2 | head -1 | tr -d ' ')
         [ -z "$inst_user" ] && inst_user="sqlr"
         sqlr_debug_msg "# inst_user = $inst_user"
      }

      # callback

      if [ $sqlr_todo = start -o $sqlr_todo = restart ]; then
         if [ $inst_start -eq 0 -a -z "$sqlr_arg_list" ]; then
            let inst_cnt=inst_cnt-1 i=i+1
            continue
         fi
      fi

      $fn "$inst_id" "$inst_cfg_file" "$inst_service" "$inst_args" "$inst_runpfx" "$inst_start" "$inst_user" "$inst_odbcd"
      let inst_cnt=inst_cnt-1 i=i+1
   done
}

# check if user is not root

function sqlr_is_not_root
{
   typeset _uid

   _uid=$(id -u)
   test "$_uid" != 0
}

# check if user matches (or is root)

function sqlr_is_matching_user
{
   typeset user _uid _uid_name

   user=$1
   
   _uid=$(id -u)
   [ "$_uid" = 0 ] && return 0

   _uid_name=$(id -un)
   test "$user" = "$_uid" -o "$user" = "$_uid_name"
}

# make sure a process is terminated

function sqlr_verify_terminated
{
   typeset pid tmp_pid_list
   typeset -i timeout

   sqlr_debug_msg "# sqlr_verify_terminated"

   timeout=10
   while [ $timeout -gt 0 ]
   do
      let timeout=timeout-1
      tmp_pid_list=""

      for pid in $sqlr_pid_list
      do
         kill -0 $pid 2>/dev/null
         if [ $? -eq 0 ]; then
            sqlr_debug_msg "process $pid did not (yet) die"
	    tmp_pid_list="${tmp_pid_list:-} $pid"
         else
            sqlr_debug_msg "process $pid is gone"
         fi
      done
      
      test -z "$tmp_pid_list" && return 0
      sqlr_pid_list=$tmp_pid_list
      sleep 1
   done

# finally, kill remaining processes
   sqlr_log_msg $(kill -KILL $sqlr_pid_list 2>&1)
   return 0
}

# check if process is running
# actual function depends on OS

function sqlr__checkproc
{
   typeset _cmd _pid

   _cmd=${1##*/}
   _pid=$2
   ps -p $_pid 2>/dev/null | fgrep -q $_cmd
}

function sqlr__child_procs
{
   typeset _pid

   _pid=$1
   ps -el | awk "{ if(\$5 == \"$_pid\") print \$4; }"
}

function sqlr__checkproc_bsd
{
   typeset _cmd _pid

   _cmd=${1##*/}
   _pid=$2
   ps p $_pid 2>/dev/null | fgrep -q $_cmd
}

function sqlr__child_procs_bsd
{
   typeset _pid

   _pid=$1
   ps alx | awk "{ if(\$4 == \"$_pid\") print \$3; }"
}

function sqlr__child_procs_rh8
{
   typeset _pid

   _pid=$1
   ps -el -m | awk "{ if(\$5 == \"$_pid\") print \$4; }"
}

# check for extra command line args that were not processed

function sqlr_check_args
{
   typeset _id _ok

   sqlr_bad_args=""
   test -n "$sqlr_arg_list" || return 0

   for _id in $sqlr_arg_list
   do
      for _ok in $sqlr_accepted_args
      do
         if [ "$_id" = "$_ok" ]; then
            break;
         fi
      done
      test "$_id" = "$_ok" || sqlr_bad_args="$_id $sqlr_bad_args"
   done

   if [ ! -z "$sqlr_bad_args" ]; then
      sqlr_log_msg "WARNING: Unknown argument(s) ignored: $sqlr_bad_args"
      sqlr_warn_msg "WARNING: Unknown argument(s) ignored: $sqlr_bad_args"
      [ $sqlr_rval = 0 ] && sqlr_rval=2
   fi
   test -z "$sqlr_bad_args"
}

# read pid from file and verify its sane
# otherwise a user could trick root

function sqlr_get_pid
{
   typeset _pid_file _pid
   
   _pid_file="$*"
   _pid=$(head -n1 "$_pid_file" 2>/dev/null | sed -ne '/^[0-9]*$/p')
   [ "$_pid" -gt 1 ] && echo "$_pid"
}

### sqlrodbcd ###

# sqlr_odbcd_start()
# start an sqlrodbcd instance

function sqlr_odbcd_start
{
   typeset id cfg_file service args pfx
   typeset msg pid_file pid user odbcd
   typeset -i stat

   id=$1
   cfg_file=$2
   service=$3
   args=$4
   pfx=$5
   user=$7
   odbcd=$8

   sqlr_debug_msg "# sqlr_odbcd_start $id"

   test $START_SQLR -eq 0 && return 0
   sqlr_is_matching_user "$user" || return 0
   sqlr_instance_filter "$id" || return 0

   sqlr_log_msg "Starting sqlrodbcd[$id] daemon"
   $sqlr_act_msg1 "Starting sqlrodbcd[$id] daemon"
   stat=$sqlr_rc_ok

   pid_file=$pid_dir/sqlrodbcd_$id.pid
   if [ ! -f "$pid_file" ]; then
      sqlr_run "$pfx $odbcd -c $cfg_file $args -p $pid_file"
      [ $? != 0 ] && stat=$sqlr_rc_failed
   else
      pid=$(sqlr_get_pid "$pid_file")
      test -n "$pid" && $sqlr_checkproc $odbcd $pid
      if [ $? != 0 ]; then
         sqlr_log_msg "Note: sqlrodbcd[$id] process $pid is not active (stale pid file)"
         sqlr_log_msg $(rm -f "$pid_file" 2>&1)
         sqlr_run "$pfx $odbcd -c $cfg_file $args -p $pid_file"
         [ $? != 0 ] && stat=$sqlr_rc_failed
      else
         sqlr_log_msg "sqlrodbcd[$id] process is already active (pid $pid)"
         $sqlr_act_msg2 $stat
         return
      fi
   fi

   # detection of startup problems is not reliable as long as the 
   # binary exists. so we check if the server pid file exists and
   # the process is still there. note there is an obvious race 
   # condition here as there is no guarantee the server process has
   # made sufficient progress ...

   sleep 1
   if [ ! -f "$pid_file" ]; then
      sqlr_log_msg "sqlrodbcd[$id] process is not active (no pid file)"
      stat=$sqlr_rc_failed
      sqlr_rval=1
   else
      pid=$(sqlr_get_pid "$pid_file")
      test -n "$pid" && $sqlr_checkproc $odbcd $pid
      if [ $? != 0 ]; then
         sqlr_log_msg "sqlrodbcd[$id] process $pid is not active (stale pid file)"
         stat=$sqlr_rc_failed
         sqlr_rval=1
      #else
      #   sqlr_log_msg "sqlrodbcd[$id] process is already active (pid $pid)"
      fi
   fi

   $sqlr_act_msg2 $stat
}

# sqlr_odbcd_stop()
# start an sqlrodbcd instance

function sqlr_odbcd_stop
{
   typeset id cfg_file service args pfx
   typeset msg pid_file pid user odbcd
   typeset -i stat

   id=$1
   cfg_file=$2
   service=$3
   args=$4
   pfx=$5
   user=$7
   odbcd=$8

   sqlr_debug_msg "# sqlr_odbcd_stop $id $user"

   sqlr_is_matching_user "$user" || return 0
   sqlr_instance_filter "$id" || return 0

   sqlr_log_msg "Stopping sqlrodbcd[$id] daemon"
   $sqlr_act_msg1 "Stopping sqlrodbcd[$id] daemon"
   stat=$sqlr_rc_notrunning

   pid_file=$pid_dir/sqlrodbcd_$id.pid
   if [ -f "$pid_file" ]; then
      pid=$(sqlr_get_pid "$pid_file")
      test -n "$pid" && $sqlr_checkproc $odbcd $pid
      if [ $? != 0 ]; then
         sqlr_log_msg "Note: sqlrodbcd[$id] process $pid is not active (stale pid file)"
      else
         sqlr_log_msg $(kill -TERM $pid 2>&1)
         child_pid=$($sqlr_child_procs $pid)
         sqlr_pid_list="${sqlr_pid_list:-} $pid $child_pid"
         stat=$sqlr_rc_ok
      fi
      sqlr_log_msg $(rm -f "$pid_file" 2>&1)
   fi

   $sqlr_act_msg2 $stat
}

# sqlr_odbcd_status()
# display status of an sqlrodbcd instance

function sqlr_odbcd_status
{
   typeset id cfg_file service args pfx
   typeset pid_file pid odbcd

   id=$1
   cfg_file=$2
   service=$3
   args=$4
   pfx=$5
   odbcd=$8

   sqlr_instance_filter "$id" || return 0
   pid_file=$pid_dir/sqlrodbcd_$id.pid
   if [ ! -f "$pid_file" ]; then
      $sqlr_stat_msg1 "sqlrodbcd[$id] process is not active"
      $sqlr_stat_msg2 $sqlr_stat_stopped
   else
      pid=$(sqlr_get_pid "$pid_file")
      test -n "$pid" && $sqlr_checkproc $odbcd $pid
      if [ $? != 0 ]; then
         $sqlr_stat_msg1 "sqlrodbcd[$id] process $pid is not active (stale pid file)"
         $sqlr_stat_msg2 $sqlr_stat_deadpid
      else
         $sqlr_stat_msg1 "sqlrodbcd[$id] process is active (pid $pid)"
         $sqlr_stat_msg2 $sqlr_stat_ok
      fi
   fi
}

# sqlr_odbcd_info()
# configuration information for an sqlrodbcd instance

function sqlr_odbcd_info
{
   typeset id cfg_file service args pfx start user odbcd

   id=$1
   cfg_file=$2
   service=$3
   args=$4
   pfx=$5
   start=$6
   user=$7
   odbcd=$8

   sqlr_instance_filter "$id" || return 0
   echo " instance id = $id"
   echo "  configuration file = $cfg_file"
   echo "  service = $service"
   [ -z "$args" ] || echo "  args = $args"
   [ -z "$pfx"  ] || echo "  run prefix = $pfx"
   echo "  automatic start = $start"
   echo "  user = $user"
   #echo "  sqlrodbcd = $odbcd"
}

