#!/bin/bash
# Version: 0.5.0.
# Variant: service.
# Copyright (c) 2005-2023:
#   Darren 'Tadgy' Austin <darren (at) afterdark.org.uk>
# Licensed under the terms of the GNU General Public License version 3.

SERVICE_EXEC="/usr/sbin/samba"
SERVICE_ARGS=()
SERVICE_ENV=()
SERVICE_PIDFILE="/run/samba.pid"

# Allow configuration in /etc/default to override.
# Additional available variables:
#   SERVICE_EXTRA_ARGS=()	# Extra arguments passed to $SERVICE_EXEC.  Must be an array.
#   SERVICE_EXTRA_ENV=()	# Extra environment passed to $SERVICE_EXEC.  Must be an array.
#   SLAY_DELAY=""		# Delay between the SIGTERM and SIGKILL on a 'stop'.  Default: 2s.
#   RESTART_DELAY=""		# Delay between stopping and starting on a 'restart'.  Default: 2s.
[[ -e "/etc/default/${0##*rc.}" ]] && {
  # shellcheck disable=SC1090
  source "/etc/default/${0##*rc.}" 2>/dev/null || {
    printf "%s: %s: %s\\n" "${BASH_SOURCE[0]##*/}" "error" "failed sourcing '/etc/default/${0##*rc.}'" >&2
    exit 2
  }
}

# Now all possible variable changes are complete, expand out the embedded variables, if any.
eval "$(declare -p SERVICE_EXEC SERVICE_ARGS SERVICE_ENV SERVICE_PIDFILE SERVICE_EXTRA_ARGS SERVICE_EXTRA_ENV \
    SLAY_DELAY RESTART_DELAY 2>/dev/null | sed -re 's/\\\$/$/g')" || {
  printf "%s: %s: %s\\n" "${BASH_SOURCE[0]##*/}" "error" "failed expanding embedded variables" >&2
  exit 2
}

# This function can be used to perform any pre-start tests; hopfully to insure the daemon can start
# correctly, before actually trying to start it.
checkconfigured() {
  # Returns:
  #   0 - The pre-start tests passed and the service should be started.
  #  >0 - Pre-start tests failed - the service should not be started.
  return 0
}

checkstatus() {
  # Returns:
  #   0 - Service is running.
  #   1 - Service is running, but there is a problem with the .pid file.
  #   2 - Service is not running.
  # shellcheck disable=SC2155
  local RET=0 RUNPIDS="$({ pgrep -f "$SERVICE_EXEC"; pgrep -F "$SERVICE_PIDFILE" 2>/dev/null; } | sort -u)"
  if [[ -n "$RUNPIDS" ]]; then
    printf "%s: %s: %s" "${BASH_SOURCE[0]##*/}" "status" "service running"
    if [[ -n "$SERVICE_PIDFILE" ]]; then
      if [[ ! -e "$SERVICE_PIDFILE" ]]; then
        printf "%s" ", but .pid file does not exist"
        RET=1
      elif ! grep "\<$(<"$SERVICE_PIDFILE")\>" <<<"$RUNPIDS" >/dev/null 2>&1; then
        printf "%s" ", but .pid file is stale"
        RET=1
      fi
    fi
    printf "\\n"
  else
    printf "%s: %s: %s\\n" "${BASH_SOURCE[0]##*/}" "status" "service stopped"
    RET=2
  fi
  return "$RET"
}

startdaemon() {
  # Returns:
  #   0 - Service started successfully.
  #   1 - Sanity or pre-start check failed.
  #   2 - Error code returned when starting service.
  local ERR
  if [[ ! -e "$SERVICE_EXEC" ]]; then
    printf "%s: %s: %s\\n" "${BASH_SOURCE[0]##*/}" "error" "not started - '$SERVICE_EXEC' not found" >&2
    return 1
  elif [[ ! -x "$SERVICE_EXEC" ]]; then
    printf "%s: %s: %s\\n" "${BASH_SOURCE[0]##*/}" "error" "not started - '$SERVICE_EXEC' not executable" >&2
    return 1
  elif ! checkconfigured; then
    printf "%s: %s: %s\\n" "${BASH_SOURCE[0]##*/}" "error" "not started - pre-start checks failed" >&2
    return 1
  fi
  mkdir /run/samba 2>/dev/null
  env "${SERVICE_ENV[@]@Q}" "${SERVICE_EXTRA_ENV[@]@Q}" "$SERVICE_EXEC" "${SERVICE_ARGS[@]@Q}" "${SERVICE_EXTRA_ARGS[@]@Q}" >/dev/null 2>&1
  ERR=$?
  (( ERR != 0 )) && {
    printf "%s: %s: %s\\n" "${BASH_SOURCE[0]##*/}" "error" "failed to start service (error code '$ERR)'" >&2
    return 2
  }
  return 0
}

stopdaemon() {
  # Returns:
  #   0 - The service was stopped sucessfully with a SIGTERM.
  #   1 - The service was stopped sucessfully with a SIGKILL.
  #   2 - An error occured while stopping service.
  local ERR RET=0 T="${SLAY_DELAY:-2}"
  kill -TERM "$(pgrep -f "$SERVICE_EXEC" | tr $'\n' ' ')" >/dev/null 2>&1
  sleep 0.5
  T="$(awk -v T="$T" 'BEGIN {print T - 0.5}')"
  checkstatus >/dev/null
  ERR=$?
  if (( ERR <= 1 )); then
    [[ -n "$SERVICE_PIDFILE" ]] && [[ -e "$SERVICE_PIDFILE" ]] && {
      kill -TERM "$(<"$SERVICE_PIDFILE")" >/dev/null 2>&1
      sleep 0.5
      T="$(awk -v T="$T" 'BEGIN {print T - 0.5}')"
      checkstatus >/dev/null
      ERR=$?
      (( ERR <= 1 )) && sleep "$T"
    }
  elif (( ERR == 0 )); then
    if kill -KILL "$({ pgrep -f "$SERVICE_EXEC"; cat "$SERVICE_PIDFILE" 2>/dev/null; } | sort -u | tr $'\n' ' ')" >/dev/null 2>&1; then
      printf "%s: %s: %s\\n" "${BASH_SOURCE[0]##*/}" "warning" "failed to stop service gracefully - slayed" >&2
      RET=1
    else
      RET=2
    fi
  fi
  return "$RET"
}

RET=0
case "$1" in
  'start')
    checkstatus >/dev/null
    ERR=$?
    if (( ERR <= 1 )); then
      printf "%s: %s: %s\\n" "${BASH_SOURCE[0]##*/}" "error" "service running" >&2
      printf "%s\\n" "Try: ${BASH_SOURCE[0]} status" >&2
      RET=1
    elif (( ERR == 2 )); then
      startdaemon
      ERR=$?
      if (( ERR == 1 )); then
        RET=3
      elif (( ERR == 2 )); then
        RET=5
      fi
    fi
    ;;
  'stop')
    checkstatus >/dev/null
    ERR=$?
    if (( ERR <= 1 )); then
      stopdaemon
      ERR=$?
      (( ERR == 2 )) && {
        sleep 0.1
        checkstatus >/dev/null
        ERR=$?
        (( ERR <= 1 )) && {
          printf "%s: %s: %s\\n" "${BASH_SOURCE[0]##*/}" "error" "service failed to stop" >&2
          RET=5
        }
      }
    elif (( ERR == 2 )); then
      printf "%s: %s: %s\\n" "${BASH_SOURCE[0]##*/}" "error" "service stopped" >&2
      printf "%s\\n" "Try: ${BASH_SOURCE[0]} status" >&2
      RET=1
    fi
    ;;
  'restart')
    checkstatus >/dev/null
    ERR=$?
    (( ERR <= 1 )) && {
      stopdaemon
      ERR=$?
      if (( ERR <= 1 )); then
        sleep "${RESTART_DELAY:-2}"
        RET=-1
      elif (( ERR == 2 )); then
        sleep 0.1
        checkstatus >/dev/null
        ERR=$?
        (( ERR <= 1 )) && {
          printf "%s: %s: %s\\n" "${BASH_SOURCE[0]##*/}" "error" "service failed to stop" >&2
          RET=5
        }
      fi
    }
    (( RET <= 0 )) && {
      startdaemon
      ERR=$?
      if (( ERR == 0 )); then
        RET=0
      elif (( ERR == 1 )); then
        RET=3
      elif (( ERR == 2 )); then
        RET="$(( RET + 5 ))"
      fi
    }
    ;;
  'status')
    checkstatus
    ERR=$?
    if (( ERR == 1 )); then
      RET=4
    elif (( ERR == 2 )); then
      RET=5
    fi
    ;;
  *)
    printf "%s\\n" "Usage: ${BASH_SOURCE[0]} <start|stop|restart|status>" >&2
    printf "%s\\n" "Error codes:" >&2
    printf "  %s: %s\\n" "0" "For 'start', 'restart' and 'stop': Requested action succeeded." >&2
    printf "     %s\\n" "For 'status': Service is running." >&2
    printf "  %s: %s\\n" "1" "Usage error." >&2
    printf "  %s: %s\\n" "2" "A general error occurred." >&2
    printf "  %s: %s\\n" "3" "Sanity or pre-start check failed." >&2
    printf "  %s: %s\\n" "4" "For 'start', 'restart' and 'stop': Partial failure to complete action." >&2
    printf "     %s\\n" "For 'status': Service is running, but a problem exists with the .pid file." >&2
    printf "  %s: %s\\n" "5" "For 'start', 'restart' and 'stop': Total failure to complete action." >&2
    printf "     %s\\n" "For 'status': Service is not running." >&2
    RET=1
    ;;
esac

exit "$RET"
