#!/bin/bash
#
#  @file rtm-naming
#  @brief OpenRTM-aist name server launcher
#  @author Noriaki Ando <n-ando@aist.go.jp>
#
#  Copyright (C) 2003-2020
#      National Institute of
#          Advanced Industrial Science and Technology (AIST), Japan
#      All rights reserved.
#

COSNAMES="/usr/bin/omniNames"
ORB="omniORB"
PNAME=`basename $COSNAMES`
HOSTNAME=`hostname`
OSNAME=`uname -s`
LOGDIR="/tmp"
DEBUG="YES"

usage()
{
    cat<<EOF

  Usage:
    $(basename $0) [-p port_number] [-f] [-w password] [h]

  Example:
    $(basename $0) -p 8888 -f
      Force running naming service on port 8888

  Options:
    -p         Port number of naming service. (default 2809)
    -f         Force kill current running naming service.
    -k         Stop existing naming service.
    -w         Current user's passoword to restart naming service.
    -h         Print this help.

EOF
}

get_opt()
{
    debug "get_opt()"
    # Global variables
    FORCEKILL=""
    PASSWORD=""
    PORT=2809
    if test $# -eq 0 ; then
        PORT=2809
    fi

    while getopts p:fkw:h OPT
    do
        case $OPT in
            p) PORT=$OPTARG
                ;;
            f) FORCEKILL="YES"
                ;;
            k) STOPNAMESERVICE="YES"
                ;;
            w) PASSWORD=$OPTARG
                ;;
            h) usage
                exit 1
                ;;
        esac
    done
    debug "Option related variables:"
    debug "  PORT " $PORT
    debug "  FORCEKILL " $FORCEKILL
    debug "  STOP" $STOPNAMESERVICE
    debug "  PASSWORD" $PASSWORD
}
#
# debug <text>
#
# Debug print. If variable "DEBUG" is defined, text will be printed.
#
# @param text to be printed.
#
debug()
{
    if test "x$DEBUG" != "x"; then
        echo $* >> /tmp/rtm-naming-${USER}_debug.log
    fi
}

#
# get_pid_of_nsport <port_number>
#
# @param  pid_of_nsport: Process ID of a process which is using the port.
# @param  pname_of_nsport: Process name which is using the port is set
# @param  pid_of_nsport: Process ID which is using the port is set
# @return 0 process that is using the port was found
#         1 process not found
#
# Linux: netstat -tlnp
#        t: TCP
#        l: listening
#        n: numeric IP address
#        p: print program and PID
#
get_pid_of_nsport()
{
    debug "get_pid_of_nsport()"
    _port=$1
    debug "netstat -tlnp 2> /dev/null | grep $_port | awk '{print $7;}'"
    netstat_tanp=`netstat -tlnp`
    if test "x$PASSWORD" = "x"; then
        _netstat=`sudo netstat -tlnp 2> /dev/null | grep $_port | awk '{if($7!="-"){print $7;}}'`
    else
        _netstat=`echo $PASSWORD | sudo -S netstat -tlnp 2> /dev/null | grep $_port | awk '{if($7!="-"){print $7;}}'`
    fi
    debug "A possible process that is using $_port port:" $_netstat
    if test "x$_netstat" = "x"; then
        echo "No process using port number ${_port} on the system."
        debug "No process using port number ${_port} on the system."
        return 1
    fi

    _pid=`echo $_netstat | awk 'BEGIN{FS="/";}{print $1;}'`
    pname_of_nsport=`echo $_netstat | awk 'BEGIN{FS="/";}{print $2;}'`
    pid_of_nsport=$_pid
    debug "$_port port is used by $pname_of_nsport (pid = $_pid)."
    return 0
}
#
# get_pid_of_nsport <port_number> for macOS
#
# @param  pid_of_nsport: Process ID of a process which is using the port.
# @param  pname_of_nsport: Process name which is using the port is set
# @param  pid_of_nsport: Process ID which is using the port is set
# @return 0 process that is using the port was found
#         1 process not found
#
# macOS: lsof
#        -n: numeric IP address (not hostname)
#        -P: numeric Port number (not service name)
#        -i: specifying address/port, :<port> specifies port
#
get_pid_of_nsport_macos()
{
    debug "get_pid_of_nsport_macos()"
    _port=$1
    debug "sudo -S lsof -n -P -i :$_port | grep LISTEN"
    if test "x$PASSWORD" = "x"; then
        _netstat=`sudo -S lsof -n -P -i :$_port | grep LISTEN`
    else
        _netstat=`echo $PASSWORD | sudo -S lsof -n -P -i :$_port | grep LISTEN`
    fi
    debug "A possible process that is using $_port port:" $_netstat
    if test "x$_netstat" = "x"; then
        echo "No process using port number ${_port} on the system."
        debug "No process using port number ${_port} on the system."
        return 1
    fi

    _pid=`echo $_netstat | awk '{print $2;}'`
    pname_of_nsport=`echo $_netstat | awk '{print $1;}'`
    pid_of_nsport=$_pid
    debug "$_port port is used by $pname_of_nsport (pid = $_pid)."
    return 0
}

#
# is_launch_from_init <prog_name> (<pid>)
#
# @param prog_name The name of the program
# @param pid The pid of the program
# @param init_script A path to init script is set
# @return 0: The program might be started from init script
#         1: The program might not be started from init script
# 
is_started_from_init()
{
    debug "number of args" $#
    if test $# -eq 2; then
	_pname=$1
	_pid=$2
    elif test $# -eq 1; then
	debug "pid is not specified. It is obtained from pid file."
	_pname=$1
	_pid=`cat /var/run/$_pname.pid`
    elif test $# -eq 0; then
	debug "No program name specified."
    fi

    if test -f /var/run/$_pname.pid; then
	debug "pid file: /var/run/$_pname.pid found"
	_var_run_pid=`cat /var/run/$_pname.pid`

	debug "specified pid:        " $_pid
	debug "/var/run/$_pname.pid: " $_var_run_pid 

	if test $_pid -eq $_var_run_pid; then
	    debug "The process $_pname ($_pid) might be started " \
		"from init script."
	    debug "searching init script: $COSNAMES"
	    init_script=`grep $COSNAMES /etc/init.d/* | awk 'BEGIN{FS=":";}{if(FNR==1){print $1;}}'`

	    if test "x$init_script" = "x"; then

		echo "Init script not found. Aborting"
		exit 1
	    fi
	    debug "Init script found: $init_script"
	    return 0
	fi

	debug "The process $_pname ($_pid) might not be started " \
	    "from init script."
	return 1
    fi
    debug "/var/run/$_pname.pid not found."
    return 1
}


#
# check_cosname
#
# Checking if global variable "cosname" is set.
#
check_cosname()
{
    debug "check_cosname()"
    if test ! -f $COSNAMES ; then
	echo "Name service program ($COSNAMES) not found."
	echo "Please install or chech rtm-naming script."
	exit 1
    fi
}

delete_omninames_files()
{
    debug "delete_omninames_files()"
    _logfiles=(
        $LOGDIR/omninames-$HOSTNAME.log
        $LOGDIR/omninames-$HOSTNAME.bak
        $LOGDIR/omninames-$HOSTNAME.dat
        )
    if test "x$PASSWORD" = "x" ; then
        for file in ${_logfiles[@]} ; do
            if test -w $file ; then
                rm -f $file
            else
                debug "log files are not writable. delete by sudo."
                sudo -S rm -f $file
            fi
        done
    else
        for file in ${_logfiles[@]} ; do
            echo $PASSWORD | sudo -S rm -f $file
        done
    fi
    if test $? != 0 ; then
        echo "Delete log files failed!!"
        debug "Delete log files failed!!"
    fi
    debug "omninames-$HOSTNAME files deleted."
}

#
# specified_port_used_check
#
# Find a process which is using specified port.
#
specified_port_used_check()
{
    debug "specified_port_used_check()"
    if test "x$OSNAME" = "xDarwin" ; then
        get_pid_of_nsport_macos $PORT
    else
        get_pid_of_nsport $PORT
    fi
    if test $? -eq 0; then
        debug "The Process information using the port could be obtained."
        # If "port" is used by other program -> abort

        pids=`pgrep $PNAME`
        matchflag=0
        for p in $pids; do
            if test "x$pid_of_nsport" = "x$p"; then
                matchflag=1
            fi
        done
        if test $matchflag -eq 0; then
            echo "$pname_of_nsport (not $PNAME) is using the port."
            echo "Starting $PNAME aborted. Please use the other port."
            exit 1
        fi
    else
        debug "The process information using the port could not be obtained."
        exit 1
    fi
}

#
# stop omninames by init script
#
# Process is started from the init script, it tries to stop it
# by the script.
#
stop_omninames_by_init_script()
{
    debug "stop_omninames_by_init_script()"
    echo "omniNames might be started $init_script."

    if test "x$FORCEKILL" = "x" && test "x$STOPNAMESERVICE" = "x" ; then
        echo "Stop it (password for sudo is required.)"
        read -p "and start omniNames by rtm-naming? (y/N)" startns
        if test "x$startns" != "xy" -a "x$startns" != "xY" ; then
            echo "Aborted."
            exit 1
        fi
    fi
    echo "Stopping omniNames by $init_script."
    if test "x$PASSWORD" = "x" ; then
       sudo $init_script stop
       sudo rm -f /var/run/$PNAME.pid
    else
        echo $PASSWORD | sudo -S $init_script stop
        echo $PASSWORD | sudo -S rm -f /var/run/$PNAME.pid
    fi
    debug "$init_script stop"
    debug "/var/run/$PNAME.pid  are deleted"
    delete_omninames_files
    return 0

}

#
# stop_existing_ns
#
# This function try to stop existing nameing service.
#
# 1. It tries to find a process which is using specified port.  If the
#    process is not same as naming-service to be started by this
#    script, script will be aborted.
#
# 2. Next, it checks if the process is started from init script. If
#    the process is started from the init script, it tries to stop it
#    by the script.
#
# 3. The process is not started by init script, it tries to stop the
#    process by pkill command.
#
stop_existing_ns()
{
    debug "stop_existing_ns()"
    debug "---stop_existing_ns---"

    # Find a process which is using specified port.
    specified_port_used_check

    # Started form /etc/init.d/ script?
    is_started_from_init $pname_of_nsport $pid_of_nsport

    # omniNames is not started by init script
    if test $? -ne 0; then
        debug "$PNAME might not be started from init script."
        if test "x$pid_of_nsport" != "x" ; then
            echo "$PNAME (pid: $pid_of_nsport) is running"

            # Restart naming service
            if test "x$FORCEKILL" = "x" ; then
                if test "x$STOPNAMESERVICE" = "x" ; then
		            read -p "Kill anyway and start $PNAME again? (y/N)" killns
	            else
                    read -p "Kill anyway? (y/N)" killns
		        fi
                if test "x$killns" != "xy" -a  "x$killns" != "xY"; then
                    echo "Aborting"
                    exit 1
                fi
	        fi
            if test "x$PASSWORD" = "x" ; then
                debug "Killing name service. PID = " $pid_of_nsport
                sudo kill -9 $pid_of_nsport
            else
                debug "Killing name service. PID = " $pid_of_nsport
                echo $PASSWORD | sudo -S kill -9 $pid_of_nsport
            fi
            echo "$PNAME (pid: $pid_of_nsport) is killed"
            debug "$PNAME (pid: $pid_of_nsport) is killed"
            delete_omninames_files
            return 0
        fi
        echo "No running $PNAME found. The process using the port $PORT "
        echo "cannot be estimated. Arboting"
        exit 1
    fi
    # omniNames is started by init script
    stop_omninames_by_init_script
    return 0
}

#------------------------------------------------------------
# omniNames specific functions
#------------------------------------------------------------
#
# start_omninames
#
# This function starts omniNames and checks it is properly started.
# If omniNames could not be started properly, this function returns 1.
#
start_omninames()
{
    debug "start_omninames()"
    debug "---start_omninames---"
    echo  'Starting omniORB omniNames: '$HOSTNAME':'$PORT
    debug 'Starting omniORB omniNames: '$HOSTNAME':'$PORT
    delete_omninames_files

    debug "$COSNAMES -start $PORT -logdir $LOGDIR"
    $COSNAMES -start $PORT -logdir $LOGDIR &
    if test $? != 0; then
        debug "omniNames start failed."
        return 1
    else
        debug "omniNames started."
    fi

    pid=$!
    debug "PID: " $!
    debug "Return: " $?

    # After omniNames started, if another omniNames already working in
    # the same port, omniNames fails and terminated after a few seconds.
    # So one second sleep is necessary.
    sleep 1

    # check if the omniNames strill working
    omnipid=`ps $pid | grep $pid | grep -v grep | awk '{print $1;}'`
    debug   "omnipid: "$omnipid
    if test "x$omnipid" != "x" ; then
        debug "omniNames properly started"
        echo  "omniNames properly started"
        exit 0
    else
        debug "omniNames start failed."
        echo  "omniNames start failed."
    fi
    return 1 
}

omniname()
{
    debug "omniname()"
    # Check cosname variable is properly set
    check_cosname

    # Starting omniNames 
    if test "x$STOPNAMESERVICE" = "x" ; then
        start_omninames
        echo "omniNames was not properly started."
    fi

    # Stopping existing name service
    stop_existing_ns

    # Try to start omniNames again
    if test "x$STOPNAMESERVICE" = "x" ; then
        start_omninames
        echo "omniNames was not properly started."
    fi
}

#------------------------------------------------------------
# TAO Naming_Service specific functions
#------------------------------------------------------------
#
# start_taonames
#
# This function starts TAO Naming_Service and checks it is properly started.
# If TAO Naming_Service could not be started properly, this function returns 1.
#
start_taonames()
{
    debug "start_taonames()"
    if test ! -f $COSNAMES ; then
	echo "TAO Naming_Service not found. Aborting."
	exit 1
    fi
    echo 'Starting TAO Naming_Service: '$HOSTNAME':'$PORT
    $COSNAMES -m 0 -ORBListenEndpoints iiop://:$PORT &
    ret=$!
    sleep 1
    debug "return code of TAO Naming_Service:" $ret
    omnip=`ps $! | wc -l`
    if test $omnip -gt 1; then
	echo "TAO Naming_Service properly started"
	exit 0
    fi
    return 1

}

taonames()
{
    debug "taonames()"
    # Check cosname variable is properly set
    check_cosname

    # Starting omniNames
    start_taonames
    echo "TAO Naming_Service was not properly started."

    # Stopping existing name service
    stop_existing_ns

    # Try to start omniNames again
    start_taonames
    echo "TAO Naming_Service was not properly started."
}



#------------------------------------------------------------
# main
#------------------------------------------------------------
get_opt $@

case $ORB in
	omniORB)
		omniname
		;;
	TAO)
		taonames
		;;
	*)
		usage
		;;
esac
