#! /bin/bash
# Use bash as the interpreter as we need more ulimit options than the default
# shell provides.

# @file sprout.init.d
#
# Copyright (C) Metaswitch Networks 2017
# If license terms are provided to you in a COPYING file in the root directory
# of the source code repository by which you are accessing this code, then
# the license outlined in that COPYING file applies to your use.
# Otherwise no rights are granted except for those provided to you by
# Metaswitch Networks in a separate written agreement.

### BEGIN INIT INFO
# Provides:          sprout
# Required-Start:    $remote_fs $syslog clearwater-infrastructure
# Required-Stop:     $remote_fs $syslog
# Default-Start:     2 3 4 5
# Default-Stop:      0 1 6
# Short-Description: Clearwater Sprout Node
# Description:       Clearwater Sprout SIP Router Node
### END INIT INFO

# Author: Mike Evans <mike.evans@metaswitch.com>
#
# Please remove the "Author" lines above and replace them
# with your own name if you copy and modify this script.

# Do NOT "set -e"

# PATH should only include /usr/* if it runs after the mountnfs.sh script
PATH=/sbin:/usr/sbin:/bin:/usr/bin
DESC="Sprout SIP Router"
NAME=sprout
EXECNAME=sprout
PIDFILE=/var/run/$NAME/$NAME.pid
DAEMON=/usr/share/clearwater/bin/sprout
HOME=/etc/clearwater
log_directory=/var/log/$NAME

# Exit if the package is not installed
[ -x "$DAEMON" ] || exit 0

# Read configuration variable file if it is present
#[ -r /etc/default/$NAME ] && . /etc/default/$NAME

# Load the VERBOSE setting and other rcS variables
. /lib/init/vars.sh

# Define LSB log_* functions.
# Depend on lsb-base (>= 3.2-14) to ensure that this file is present
# and status_of_proc is working.
. /lib/lsb/init-functions

#
# Function to set up environment
#
setup_environment()
{
        export MIBS=""
        ulimit -Hn 1000000
        ulimit -Sn 1000000
        ulimit -c unlimited
        ulimit -r 1 # Allow sprout threads to use realtime priorities.
        # enable gdb to dump a parent sprout process's stack
        echo 0 > /proc/sys/kernel/yama/ptrace_scope
}

#
# Function to pull in settings prior to starting the daemon
#
get_settings()
{
        # Set up defaults and then pull in the settings for this node.
        sprout_rr_level="pcscf"

        # These Sproutlets are enabled by default. They listen on different
        # ports to help ensure that requests have a unambiguous Sproutlet
        # to route to.
        icscf=5052
        bgcf=5053
        scscf=5054
        mmtel=5055

        local_alias_list=""
        remote_alias_list=""
        always_serve_remote_aliases="Y"
        default_session_expires=600
        signaling_dns_server=127.0.0.1
        blacklisted_scscf_uris=""
        scscf_node_uri=""

        # Enable no-ENUM TEL URI translation fallback by default for backwards compatibility
        default_tel_uri_translation="Y"

        # Enable the treatment of originating SIP "user=phone" URIs (that corresponding to global
        # phone numbers) as though they were Tel URIs for Identity purposes by default.
        enable_orig_sip_to_tel_coerce="Y"

        # Set up defaults for user settings then pull in any overrides.
        # Sprout uses blocking look-up services, so must run multi-threaded.
        num_worker_threads=$(($(grep processor /proc/cpuinfo | wc -l) * 250))
        num_http_threads=$(($(grep processor /proc/cpuinfo | wc -l) * 50))

        log_level=2
        authentication=Y
        . /etc/clearwater/config

        if [ -n "$sprout_worker_threads" ];
        then
          num_worker_threads="$sprout_worker_threads"
        fi

        if [ -n "$sprout_http_threads" ];
        then
          num_http_threads="$sprout_http_threads"
        fi

        # Calculate the correct homestead latency:
        #  - if we have sprout_homestead_timeout_ms set, use that
        #  - else:
        #    - if we have diameter_timeout_ms set, use 1000 + double that
        #    - else use 1200
        if [ -z "$sprout_homestead_timeout_ms" ];
        then
          if [ -z "$diameter_timeout_ms" ];
          then
            sprout_homestead_timeout_ms=1200
          else
            sprout_homestead_timeout_ms=$((diameter_timeout_ms * 2 + 1000))
          fi
        fi

        # Work out which features are enabled.
        MMTEL_SERVICES_ENABLED=Y
        if [ -d /etc/clearwater/features.d ]
        then
          for file in $(find /etc/clearwater/features.d -type f)
          do
            [ -r $file ] && . $file
          done
        fi
}

#
# Function to get the arguments to pass to the process
#
get_daemon_args()
{
        # Get the settings
        get_settings

        [ -z "$enum_server" ] || enum_server_arg="--enum=$enum_server"
        [ -z "$enum_suffix" ] || enum_suffix_arg="--enum-suffix=$enum_suffix"
        [ -z "$enum_file" ] || enum_file_arg="--enum-file=$enum_file"
        [ "$default_tel_uri_translation" != "Y" ] || default_tel_uri_translation_arg="--default-tel-uri-translation"

        if [ $MMTEL_SERVICES_ENABLED = Y ]
        then
          [ -z "$xdms_hostname" ] || xdms_hostname_arg="--xdms=$xdms_hostname"
        fi

        [ -z "$ralf_hostname" ] || ralf_arg="--ralf=$ralf_hostname"

        [ "$authentication" != "Y" ] || authentication_arg="--authentication"

        [ "$enforce_user_phone" != "Y" ] || user_phone_arg="--enforce-user-phone"
        [ "$enforce_global_only_lookups" != "Y" ] || global_only_lookups_arg="--enforce-global-only-lookups"
        [ "$override_npdi" != "Y" ] || override_npdi_arg="--override-npdi"
        [ "$force_third_party_reg_body" != "Y" ] || force_3pr_body_arg="--force-3pr-body"
        [ "$sas_use_signaling_interface" != "Y" ] || sas_signaling_if_arg="--sas-use-signaling-interface"
        [ "$disable_tcp_switch" != "Y" ] || disable_tcp_switch_arg="--disable-tcp-switch"
        [ "$apply_fallback_ifcs" != "Y" ] || apply_fallback_ifcs_arg="--apply-fallback-ifcs"
        [ "$reject_if_no_matching_ifcs" != "Y" ] || reject_if_no_matching_ifcs_arg="--reject-if-no-matching-ifcs"
        [ "$http_acr_logging" != "Y" ] || http_acr_logging_arg="--http-acr-logging"
        [ "$enable_orig_sip_to_tel_coerce" != "Y" ] || enable_orig_sip_to_tel_coerce_arg="--enable-orig-sip-to-tel-coerce"

        [ -z "$sprout_target_latency_us" ] || target_latency_us_arg="--target-latency-us=$sprout_target_latency_us"
        [ -z "$sprout_max_tokens" ] || max_tokens_arg="--max-tokens=$sprout_max_tokens"
        [ -z "$sprout_init_token_rate" ] || init_token_rate_arg="--init-token-rate=$sprout_init_token_rate"
        [ -z "$sprout_min_token_rate" ] || min_token_rate_arg="--min-token-rate=$sprout_min_token_rate"
        [ -z "$sprout_max_token_rate" ] || max_token_rate_arg="--max-token-rate=$sprout_max_token_rate"
        [ -z "$exception_max_ttl" ] || exception_max_ttl_arg="--exception-max-ttl=$exception_max_ttl"
        [ -z "$local_site_name" ] || local_site_name_arg="--local-site-name=$local_site_name"
        [ -z "$sprout_impi_store" ] || impi_store_arg="--impi-store=$sprout_impi_store"
        [ -z "$chronos_hostname" ] || chronos_hostname_arg="--chronos-hostname=$chronos_hostname"
        [ -z "$sprout_chronos_callback_uri" ] || sprout_chronos_callback_uri_arg="--sprout-chronos-callback-uri=$sprout_chronos_callback_uri"
        [ -z "$dummy_app_server" ] || dummy_app_server_arg="--dummy-app-server=$dummy_app_server"
        [ -z "$sprout_request_on_queue_timeout" ] || request_on_queue_timeout_arg="--request-on-queue-timeout=$sprout_request_on_queue_timeout"
        [ -z "$alias_list" ] || deprecated_alias_list_arg="--alias=$alias_list"
        [ "$always_serve_remote_aliases" != "Y" ] || always_serve_remote_aliases_arg="--always-serve-remote-aliases"
        [ "$ram_record_everything" != "Y" ] || ram_recording_arg="--ram-record-everything"

        DAEMON_ARGS="
                     --domain=$home_domain
                     --localhost=$local_ip
                     --realm=$home_domain
                     $local_site_name_arg
                     --registration-stores=$sprout_registration_store
                     $impi_store_arg
                     --hss=$hs_hostname
                     --sprout-hostname=$sprout_hostname
                     --scscf-node-uri=$scscf_node_uri
                     $chronos_hostname_arg
                     $sprout_chronos_callback_uri_arg
                     $xdms_hostname_arg
                     $ralf_arg
                     $enum_server_arg
                     $enum_suffix_arg
                     $enum_file_arg
                     $default_tel_uri_translation_arg
                     --sas=$NAME@$public_hostname
                     --dns-server=$signaling_dns_server
                     --worker-threads=$num_worker_threads
                     --http-threads=$num_http_threads
                     --record-routing-model=$sprout_rr_level
                     --default-session-expires=$default_session_expires
                     $target_latency_us_arg
                     $max_tokens_arg
                     $init_token_rate_arg
                     $min_token_rate_arg
                     $max_token_rate_arg
                     $authentication_arg
                     $user_phone_arg
                     $sas_signaling_if_arg
                     $disable_tcp_switch_arg
                     $apply_fallback_ifcs_arg
                     $reject_if_no_matching_ifcs_arg
                     $dummy_app_server_arg
                     $http_acr_logging_arg
                     $global_only_lookups_arg
                     $override_npdi_arg
                     $exception_max_ttl_arg
                     $force_3pr_body_arg
                     $enable_orig_sip_to_tel_coerce_arg
                     $request_on_queue_timeout_arg
                     --http-address=$local_ip
                     --http-port=9888
                     --analytics=$log_directory
                     --log-file=$log_directory
                     --log-level=$log_level
                     $deprecated_alias_list_arg
                     --local-alias-list=$public_ip,$public_hostname,$local_alias_list
                     --remote-alias-list=$remote_alias_list
                     $always_serve_remote_aliases_arg
                     $ram_recording_arg
                     --homestead-timeout=$sprout_homestead_timeout_ms"

        if [ -n "$reg_max_expires" ]
        then
          DAEMON_ARGS="$DAEMON_ARGS --reg-max-expires=$reg_max_expires"
        fi

        if [ -n "$sub_max_expires" ]
        then
          DAEMON_ARGS="$DAEMON_ARGS --sub-max-expires=$sub_max_expires"
        fi

        # TODO improve this so we don't have to have the same parameters
        # repeated for each Sproutlet
        [ "$icscf" = "" ]                         || DAEMON_ARGS="$DAEMON_ARGS --icscf=$icscf"
        [ "$icscf_prefix" = "" ]                  || DAEMON_ARGS="$DAEMON_ARGS --prefix-icscf=$icscf_prefix"
        [ "$icscf_uri" = "" ]                     || DAEMON_ARGS="$DAEMON_ARGS --uri-icscf=$icscf_uri"
        [ "$scscf" = "" ]                         || DAEMON_ARGS="$DAEMON_ARGS --scscf=$scscf"
        [ "$scscf_prefix" = "" ]                  || DAEMON_ARGS="$DAEMON_ARGS --prefix-scscf=$scscf_prefix"
        [ "$scscf_uri" = "" ]                     || DAEMON_ARGS="$DAEMON_ARGS --uri-scscf=$scscf_uri"
        [ "$bgcf" = "" ]                          || DAEMON_ARGS="$DAEMON_ARGS --bgcf=$bgcf"
        [ "$bgcf_prefix" = "" ]                   || DAEMON_ARGS="$DAEMON_ARGS --prefix-bgcf=$bgcf_prefix"
        [ "$bgcf_uri" = "" ]                      || DAEMON_ARGS="$DAEMON_ARGS --uri-bgcf=$bgcf_uri"
        [ "$gemini" = "" ]                        || DAEMON_ARGS="$DAEMON_ARGS --gemini=$gemini"
        [ "$gemini_prefix" = "" ]                 || DAEMON_ARGS="$DAEMON_ARGS --prefix-gemini=$gemini_prefix"
        [ "$gemini_uri" = "" ]                    || DAEMON_ARGS="$DAEMON_ARGS --uri-gemini=$gemini_uri"
        [ "$cdiv" = "" ]                          || DAEMON_ARGS="$DAEMON_ARGS --cdiv=$cdiv"
        [ "$cdiv_prefix" = "" ]                   || DAEMON_ARGS="$DAEMON_ARGS --prefix-cdiv=$cdiv_prefix"
        [ "$cdiv_uri" = "" ]                      || DAEMON_ARGS="$DAEMON_ARGS --uri-cdiv=$cdiv_uri"
        [ "$mmtel" = "" ]                         || DAEMON_ARGS="$DAEMON_ARGS --mmtel=$mmtel"
        [ "$mmtel_prefix" = "" ]                  || DAEMON_ARGS="$DAEMON_ARGS --prefix-mmtel=$mmtel_prefix"
        [ "$mmtel_uri" = "" ]                     || DAEMON_ARGS="$DAEMON_ARGS --uri-mmtel=$mmtel_uri"
        [ "$mangelwurzel" = "" ]                  || DAEMON_ARGS="$DAEMON_ARGS --mangelwurzel=$mangelwurzel"
        [ "$mangelwurzel_prefix" = "" ]           || DAEMON_ARGS="$DAEMON_ARGS --prefix-mangelwurzel=$mangelwurzel_prefix"
        [ "$mangelwurzel_uri" = "" ]              || DAEMON_ARGS="$DAEMON_ARGS --uri-mangelwurzel=$mangelwurzel_uri"
        [ "$external_icscf_uri" = "" ]            || DAEMON_ARGS="$DAEMON_ARGS --external-icscf=$external_icscf_uri"
        [ "$additional_home_domains" = "" ]       || DAEMON_ARGS="$DAEMON_ARGS --additional-domains=$additional_home_domains"
        [ "$sip_blacklist_duration" = "" ]        || DAEMON_ARGS="$DAEMON_ARGS --sip-blacklist-duration=$sip_blacklist_duration"
        [ "$http_blacklist_duration" = "" ]       || DAEMON_ARGS="$DAEMON_ARGS --http-blacklist-duration=$http_blacklist_duration"
        [ "$astaire_blacklist_duration" = "" ]    || DAEMON_ARGS="$DAEMON_ARGS --astaire-blacklist-duration=$astaire_blacklist_duration"
        [ "$sip_tcp_connect_timeout" = "" ]       || DAEMON_ARGS="$DAEMON_ARGS --sip-tcp-connect-timeout=$sip_tcp_connect_timeout"
        [ "$sip_tcp_send_timeout" = "" ]          || DAEMON_ARGS="$DAEMON_ARGS --sip-tcp-send-timeout=$sip_tcp_send_timeout"
        [ "$dns_timeout" = "" ]                   || DAEMON_ARGS="$DAEMON_ARGS --dns-timeout=$dns_timeout"
        [ "$session_continued_timeout_ms" = "" ]  || DAEMON_ARGS="$DAEMON_ARGS --session-continued-timeout=$session_continued_timeout_ms"
        [ "$session_terminated_timeout_ms" = "" ] || DAEMON_ARGS="$DAEMON_ARGS --session-terminated-timeout=$session_terminated_timeout_ms"
        [ "$stateless_proxies" = "" ]             || DAEMON_ARGS="$DAEMON_ARGS --stateless-proxies=$stateless_proxies"
        [ "$max_sproutlet_depth" = "" ]           || DAEMON_ARGS="$DAEMON_ARGS --max-sproutlet-depth=$max_sproutlet_depth"
        [ "$ralf_threads" = "" ]                  || DAEMON_ARGS="$DAEMON_ARGS --ralf-threads=$ralf_threads"
        [ "$non_register_authentication" = "" ]   || DAEMON_ARGS="$DAEMON_ARGS --non-register-authentication=$non_register_authentication"
        [ "$nonce_count_supported" != "Y" ]       || DAEMON_ARGS="$DAEMON_ARGS --nonce-count-supported"
        [ "$listen_port" = "" ]                   || DAEMON_ARGS="$DAEMON_ARGS --listen-port=$listen_port"
        [ "$blacklisted_scscf_uris" = "" ]        || DAEMON_ARGS="$DAEMON_ARGS --blacklisted-scscfs=$blacklisted_scscf_uris"

        for script in /usr/share/clearwater/sprout/plugin_conf.d/*.plugin_conf
        do
          if [ -x "$script" ]
          then
            # Include plugin configuration scripts
            DAEMON_ARGS="$DAEMON_ARGS $("$script")"
          fi
        done
}

#
# Function that starts the daemon/service
#
do_start()
{
        # Return
        #   0 if daemon has been started
        #   1 if daemon was already running
        #   2 if daemon could not be started

        # Allow us to write to the pidfile directory
        install -m 755 -o $NAME -g root -d /var/run/$NAME && chown -R $NAME /var/run/$NAME

        start-stop-daemon --start --quiet --pidfile $PIDFILE --exec $DAEMON --test > /dev/null \
                || return 1

        # daemon is not running, so attempt to start it.
        setup_environment
        get_daemon_args
        /usr/share/clearwater/bin/run-in-signaling-namespace start-stop-daemon --start --quiet --pidfile $PIDFILE --exec $DAEMON --chuid $NAME --chdir $HOME -- $DAEMON_ARGS --daemon --pidfile=$PIDFILE \
                || return 2
        # Add code here, if necessary, that waits for the process to be ready
        # to handle requests from services started subsequently which depend
        # on this one.  As a last resort, sleep for some time.
}

#
# Function that stops the daemon/service
#
do_stop()
{
        # Return
        #   0 if daemon has been stopped
        #   1 if daemon was already stopped
        #   2 if daemon could not be stopped
        #   other if a failure occurred
        start-stop-daemon --stop --quiet --retry=TERM/30/KILL/5 --pidfile $PIDFILE --name $EXECNAME
        RETVAL="$?"
        return "$RETVAL"
}

#
# Function that runs the daemon/service in the foreground
#
do_run()
{
        # Allow us to write to the pidfile directory
        install -m 755 -o $NAME -g root -d /var/run/$NAME && chown -R $NAME /var/run/$NAME

        setup_environment
        get_daemon_args
        /usr/share/clearwater/bin/run-in-signaling-namespace start-stop-daemon --start --quiet --pidfile $PIDFILE --exec $DAEMON --chuid $NAME --chdir $HOME -- $DAEMON_ARGS --pidfile=$PIDFILE \
                || return 2
}

#
# Function that aborts the daemon/service
#
# This is very similar to do_stop except it sends SIGABRT to dump a core file
# and waits longer for it to complete.
#
do_abort()
{
        # Return
        #   0 if daemon has been stopped
        #   1 if daemon was already stopped
        #   2 if daemon could not be stopped
        #   other if a failure occurred
        start-stop-daemon --stop --quiet --retry=ABRT/60/KILL/5 --pidfile $PIDFILE --name $EXECNAME
        RETVAL="$?"
        # If the abort failed, it may be because the PID in PIDFILE doesn't match the right process
        # In this window condition, we may not recover, so remove the PIDFILE to get it running
        if [ $RETVAL != 0 ]; then
          rm -f $PIDFILE
        fi
        return "$RETVAL"
}

#
# Function that sends a SIGHUP to the daemon/service
#
do_reload() {
        #
        # If the daemon can reload its configuration without
        # restarting (for example, when it is sent a SIGHUP),
        # then implement that here.
        #
        start-stop-daemon --stop --signal 1 --quiet --pidfile $PIDFILE --name $EXECNAME
        return 0
}

#
# Sends a SIGQUIT to the daemon/service
#
do_start_quiesce() {
        start-stop-daemon --stop --signal QUIT --quiet --pidfile $PIDFILE --name $EXECNAME
        return 0
}

#
# Sends a SIGQUIT to the daemon/service and waits for it to terminate
#
do_quiesce() {
        # We try to quiesce for 5 minutes and if after that time we still haven't quiesced, we will
        # try to terminate and then kill the sprout process.
        start-stop-daemon --stop --retry QUIT/300/TERM/30/KILL/5 --quiet --pidfile $PIDFILE --name $EXECNAME
        return 0
}

#
# Sends a SIGUSR1 to the daemon/service
#
do_unquiesce() {
        start-stop-daemon --stop --signal USR1 --quiet --pidfile $PIDFILE --name $EXECNAME
        return 0
}

# There should only be at most one sprout process, and it should be the one in /var/run/sprout.pid.
# Sanity check this, and kill and log any leaked ones.
if [ -f $PIDFILE ] ; then
  leaked_pids=$(pgrep -f "^$DAEMON" | grep -v $(cat $PIDFILE))
else
  leaked_pids=$(pgrep -f "^$DAEMON")
fi
if [ -n "$leaked_pids" ] ; then
  for pid in $leaked_pids ; do
    logger -p daemon.error -t $NAME Found leaked sprout $pid \(correct is $(cat $PIDFILE)\) - killing $pid
    kill -9 $pid
  done
fi

case "$1" in
  start)
        [ "$VERBOSE" != no ] && log_daemon_msg "Starting $DESC" "$NAME"
        do_start
        case "$?" in
                0|1) [ "$VERBOSE" != no ] && log_end_msg 0 ;;
                2) [ "$VERBOSE" != no ] && log_end_msg 1 ;;
        esac
        ;;
  stop)
        [ "$VERBOSE" != no ] && log_daemon_msg "Stopping $DESC" "$NAME"
        do_stop
        case "$?" in
                0|1) [ "$VERBOSE" != no ] && log_end_msg 0 ;;
                2) [ "$VERBOSE" != no ] && log_end_msg 1 ;;
        esac
        ;;
  run)
        [ "$VERBOSE" != no ] && log_daemon_msg "Running $DESC" "$NAME"
        do_run
        case "$?" in
                0|1) [ "$VERBOSE" != no ] && log_end_msg 0 ;;
                2) [ "$VERBOSE" != no ] && log_end_msg 1 ;;
        esac
        ;;
  status)
        status_of_proc "$DAEMON" "$NAME" && exit 0 || exit $?
        ;;
  reload|force-reload)
        #
        # If do_reload() is not implemented then leave this commented out
        # and leave 'force-reload' as an alias for 'restart'.
        #
        do_reload
        ;;
  restart)
        #
        # If the "reload" option is implemented then remove the
        # 'force-reload' alias
        #
        log_daemon_msg "Restarting $DESC" "$NAME"
        do_stop
        case "$?" in
          0|1)
                do_start
                case "$?" in
                        0) log_end_msg 0 ;;
                        1) log_end_msg 1 ;; # Old process is still running
                        *) log_end_msg 1 ;; # Failed to start
                esac
                ;;
          *)
                # Failed to stop
                log_end_msg 1
                ;;
        esac
        ;;
  abort)
        log_daemon_msg "Aborting $DESC" "$NAME"
        do_abort
        ;;
  abort-restart)
        log_daemon_msg "Abort-Restarting $DESC" "$NAME"
        do_abort
        case "$?" in
          0|1)
                do_start
                case "$?" in
                        0) log_end_msg 0 ;;
                        1) log_end_msg 1 ;; # Old process is still running
                        *) log_end_msg 1 ;; # Failed to start
                esac
                ;;
          *)
                # Failed to stop
                log_end_msg 1
                ;;
        esac
        ;;
  start-quiesce)
        log_daemon_msg "Start quiescing $DESC" "$NAME"
        do_start_quiesce
        ;;
  quiesce)
        log_daemon_msg "Quiescing $DESC" "$NAME"
        do_quiesce
        ;;
  unquiesce)
        log_daemon_msg "Unquiesce $DESC" "$NAME"
        do_unquiesce
        ;;
  *)
        echo "Usage: $SCRIPTNAME {start|stop|run|status|reload|force-reload|restart|abort|abort-restart|start-quiesce|quiesce|unquiesce}" >&2
        exit 3
        ;;
esac

:
