Blob Blame History Raw
#!/bin/bash
#
# stap-server script for managing the systemtap compile server
#
# Copyright (C) 2008-2011 Red Hat Inc.
#
# This file is part of systemtap, and is free software.  You can
# redistribute it and/or modify it under the terms of the GNU General
# Public License (GPL); either version 2, or (at your option) any
# later version.
#
# This script provides management of systemtap compile servers as a service.
# See stap-server(8) for more information.

if [ -f /etc/rc.d/init.d/functions ]; then
  # Red Hat init functions
  . /etc/rc.d/init.d/functions
else
  # Default init functions
  success () {
    echo -n "OK"
  }
  failure () {
    echo -n "FAILED"
  }
fi

# Systemtap function library
. ${PKGLIBEXECDIR}stap-env

prog=stap-server

# Commands
STAP_START_SERVER=${stap_pkglibexecdir}stap-start-server
STAP_STOP_SERVER=${stap_pkglibexecdir}stap-stop-server
UNAME=/bin/uname

# Default Global Configuration
CONFIG_FILE=$stap_sysconfdir/sysconfig/stap-server
CONFIG_PATH=$stap_sysconfdir/stap-server/conf.d
STAT_PATH=$stap_localstatedir/run/stap-server
LOG_FILE=$stap_localstatedir/log/stap-server/log

# Default option settings
# Optional global config file
OPT_CONFIG_FILE=
OPT_OTHER=

OPT_PORT_IX=0
OPT_LOG_IX=0
OPT_SSL_IX=0
OPT_MAXTHREADS_IX=0
OPT_MAXREQSIZE_IX=0
OPT_MAXCOMPRESSEDREQ_IX=0

echo_usage () {
  echo $"Usage: $prog {start|stop|restart|condrestart|try-restart|force-reload|status} [options]"
  echo $"Options:"
  echo $"	-c configfile	 		: specify additional global configuration file."
  echo $"	-a arch			 	: specify the target architecture."
  echo $"	-r release		 	: specify a kernel release."
  echo $"	-I path			 	: augment the search path for tapsets."
  echo $"	-R path		 		: specify the location of the systemtap runtime."
  echo $"	-B options	 		: specify 'make' options for building systemtap modules."
  echo $"	-D name[=value]			: add a macro definition for building systemtap modules."
  echo $"	-u username 			: specify the user who will run the server(s)."
  echo $"	-i 				: specify a server that handles all installed kernel releases."
  echo $"	-n nickname 			: specify a server configuration by nickname."
  echo $"	-p pid	 			: specify a server or server configuration by process id."
  echo $"	-P			 	: use a password for the server's NSS certificate database."
  echo $"	-k 			 	: keep server temporary files."
  echo $"	--port port	 		: specify the network port to be used by the server."
  echo $"	--log path	 		: specify the location of the server's log file."
  echo $"	--ssl path 			: specify the location of the server's certificate database."
  echo $"	--max-threads threads 		: specify the maximum number of worker threads to handle concurrent requests."
  echo $"	--max-request-size size		: specify the maximum size of an uncompressed client request in bytes."
  echo $"	--max-compressed-request size   : specify the maximum size of an compressed client request in bytes."
  echo $""
  echo $"All options may be specified more than once."
  echo $""
  echo $"If -a is not specified, the default architecture is that of the host"
  echo $"platform."
  echo $""
  echo $"If -r is not specified, the default kernel release is that currently"
  echo $"running on the host platform."
  echo $""
  echo $"If -u is not specified, the default user is 'stap-server'"
  echo $""
  echo $"If --port is not specified, the default is to use a randomly selected port."
  echo $""
  echo $"If --log is not specified, the default is '$stap_localstatedir/log/stap-server/log'."
  echo $""
  echo $"If --ssl is not specified, the default is '$stap_sysconfdir/ssl/server'."
  echo $""
  echo $"If --max-threads is not specified, the default is the number of processors."
  echo $""
  echo $"If --max-threads is specified with a value of 0, all requests are handled in the main thread."
  echo $""
  echo $"If --max-request-size is not specified, the default value is 50000 bytes."
  echo $""
  echo $"If --max-compressed-request is not specified, the default value is 5000 bytes."
  echo $""
  echo $"Each -D, -I and -B option specifies an additional macro, path or option respectively"
  echo $"to be applied to subsequent servers specified."
  echo $""
  echo $"Each --port, --log, --ssl, --max-threads, --max-request-size, and"
  echo $"--max-compressed-request  option is added to an option-specific list"
  echo $"which will be applied, in turn, to each server specified. If more "
  echo $"servers are specified than options in a given list, the default for that"
  echo $"option will be used for subsequent servers."
  echo $""
  echo $"For other options, each new instance overrides the previous setting."
  echo $""
  echo $"The -i option is a shortcut which specifies a server that handles every"
  echo $"release installed in /lib/modules/."
  echo $""
  echo $"The -n option allows the specification of a server configuration by"
  echo $"nickname. When -n is specified, a currently running server with the"
  echo $"given nickname will be searched for. If no currently running server"
  echo $"with the given nickname is found, a server configuration with the"
  echo $"given nickname will be searched for in"
  echo $"$stap_sysconfdir/stap-server/conf.d/*.conf."
  echo $"If a server configuration for the given nickname is found, the -a, -r,"
  echo $"-D, -I, -R, -B and -u options for that server will be used as if they were"
  echo $"specified on the command line. If no configuration with the given"
  echo $"nickname is found, and the action is 'start' (or an action behaving"
  echo $"like 'start' (see below)), the server will be started with the given"
  echo $"nickname. If no configuration with the given nickname is found, and"
  echo $"the action is not 'start' (or an action behaving" "like 'start',"
  echo $"it is an error. If a nickname is not specified for a server, its"
  echo $"nickname will be its process id."
  echo $""
  echo $"The -p option allows the specification of a server configuration by"
  echo $"process id. When -p is specified, a currently running server with the"
  echo $"given process id will be searched for. If no such server is found,"
  echo $"it is an error. If a server with the given pid is found, the -a, -r,"
  echo $"-D, -I, -R, -B and -u options for that server will be used as if they were"
  echo $"specified on the command line."
  echo $""
  echo $"The -k option tells the server to keep the temporary directories it creates"
  echo $"during each transaction with a client."
  echo $""
  echo $"The specified action is performed for the server(s) specified on the"
  echo $"command line. If no servers are specified on the command line, the"
  echo $"behavior is as follows:"
  echo $""
  echo $"  start:        Start the servers configured in $stap_sysconfdir/stap-server/conf.d/*.conf."
  echo $"                If none are configured, start a server for the kernel release"
  echo $"                and architecture of the host platform."
  echo $""
  echo $"  stop:         Stop all currently running servers."
  echo $""
  echo $"  restart:      Restart all currently running servers. If no servers are running,"
  echo $"                behave as if 'start' was specified."
  echo $""
  echo $"  condrestart:  Restart all currently running servers. If no servers are running,"
  echo $"                do nothing."
  echo $""
  echo $"  try-restart:  Same as condrestart."
  echo $""
  echo $"  force-reload: Stop all currently running servers and behave as if 'start'"
  echo $"                was specified."
  echo $""
  echo $"  status:       Report the status of all current running servers."
  echo $""
}

#-----------------------------------------------------------------
# Helper functions
#-----------------------------------------------------------------
log () { # message
  echo `LC_ALL=en date +"%b %e %T"`": $1" >> "$LOG_FILE"
}
clog () { # message [-n]
  echo $2 "$1"
  log "$1"
}
slog () { # message
  logger "$1" # if syslogd is running, this message will be sent to syslog.
  log "$1"
}
logex () { # command
  eval log \"Exec: "$@"\"
  "$@" >> "$LOG_FILE" 2>&1
  return $?
}
do_failure () { # message
  slog "Error: $1"
  failure "$1"
}
do_success () { # message
  log "Pass: $1"
  success "$1"
}

#------------------------------------------------------------------
# Parameter parsing and setup options
#------------------------------------------------------------------
parse_args () { # arguments
  local rc=0
  while [ -n "$1" ]; do
    case "$1" in
      -k | -P)
        OPT_OTHER="$OPT_OTHER $1"
        shift 1
        ;;
      -a)
	SERVER_CMDS+=("ARCH=\"`quote_for_cmd "$2"`\"")
        shift 1
	;;
      -B)
	SERVER_CMDS+=("BUILD+=(\"`quote_for_cmd "$2"`\")")
        shift 1
	;;
      -c)
        OPT_CONFIG_FILE="$2"
        shift 1
        ;;
      -i)
        process_i
	;;
      -D)
	SERVER_CMDS+=("DEFINE+=(\"`quote_for_cmd "$2"`\")")
        shift 1
	;;
      -I)
	SERVER_CMDS+=("INCLUDE+=(\"`quote_for_cmd "$2"`\")")
        shift 1
	;;
      -n)
	process_n "$2"
        shift 1
	;;
      -p)
	process_p "$2"
	test $? = 0 || rc=1
        shift 1
	;;
      -r)
	process_r "$2"
	test $? = 0 || rc=1
        shift 1
	;;
      -R)
	SERVER_CMDS+=("RUNTIME=\"`quote_for_cmd "$2"`\"")
        shift 1
	;;
      -u)
	SERVER_CMDS+=("USER=\"`quote_for_cmd "$2"`\"")
        shift 1
	;;
      --log)
	OPT_LOG+=("$2")
        shift 1
	;;
      --port)
	OPT_PORT+=("$2")
        shift 1
	;;
      --ssl)
	OPT_SSL+=("$2")
        shift 1
	;;
      --max-threads)
	OPT_MAXTHREADS+=("$2")
        shift 1
	;;
      --max-request-size)
	OPT_MAXREQSIZE+=("$2")
        shift 1
	;;
      --max-compressed-request)
	OPT_MAXCOMPRESSEDREQ+=("$2")
        shift 1
	;;
      --)
        ;;
      *)
	rc=1
        ;;
    esac
    shift 1
  done

  # Add options from any lists and an EXEC command to the end if any server options were specified
  if test -n "$SERVER_CMDS"; then
      add_distributed_options
      SERVER_CMDS+=("EXEC")
  fi

  test $rc != 0 && echo_usage
  return $rc
}

# Some options accumulate into lists when specified more than once. The items in the each list
# are the applied to each specified server in turn. If a list runs out, then the default for that
# option is applied.
add_distributed_options () {
    # The --log option
    if test -n "${OPT_LOG[$OPT_LOG_IX]}"; then
	SERVER_CMDS+=("LOG=\"`quote_for_cmd "${OPT_LOG[$OPT_LOG_IX]}"`\"")
	OPT_LOG_IX=$(($OPT_LOG_IX + 1))
    else
	SERVER_CMDS+=("LOG=\"`quote_for_cmd "$LOG_FILE"`\"")
    fi
    # The --port option
    if test -n "${OPT_PORT[$OPT_PORT_IX]}"; then
	SERVER_CMDS+=("PORT=\"`quote_for_cmd "${OPT_PORT[$OPT_PORT_IX]}"`\"")
	OPT_PORT_IX=$(($OPT_PORT_IX + 1))
    else
	SERVER_CMDS+=("PORT=\"\"")
    fi
    # The --ssl option
    if test -n "${OPT_SSL[$OPT_SSL_IX]}"; then
	SERVER_CMDS+=("SSL=\"`quote_for_cmd "${OPT_SSL[$OPT_SSL_IX]}"`\"")
	OPT_SSL_IX=$(($OPT_SSL_IX + 1))
    else
	SERVER_CMDS+=("SSL=\"\"")
    fi
    # The --max-threads option
    if test -n "${OPT_MAXTHREADS[$OPT_MAXTHREADS_IX]}"; then
	SERVER_CMDS+=("MAXTHREADS=\"`quote_for_cmd "${OPT_MAXTHREADS[$OPT_MAXTHREADS_IX]}"`\"")
	OPT_MAXTHREADS_IX=$(($OPT_MAXTHREADS_IX + 1))
    else
	SERVER_CMDS+=("MAXTHREADS=\"\"")
    fi
    # The --max-request-size option
    if test -n "${OPT_MAXREQSIZE[$OPT_MAXREQSIZE_IX]}"; then
	SERVER_CMDS+=("MAXREQSIZE=\"`quote_for_cmd "${OPT_MAXREQSIZE[$OPT_MAXREQSIZE_IX]}"`\"")
	OPT_MAXREQSIZE_IX=$(($OPT_MAXREQSIZE_IX + 1))
    else
	SERVER_CMDS+=("MAXREQSIZE=\"\"")
    fi
    # The --max-compressed-request option
    if test -n "${OPT_MAXCOMPRESSEDREQ[$OPT_MAXCOMPRESSEDREQ_IX]}"; then
	SERVER_CMDS+=("MAXCOMPRESSEDREQ=\"`quote_for_cmd "${OPT_MAXCOMPRESSEDREQ[$OPT_MAXCOMPRESSEDREQ_IX]}"`\"")
	OPT_MAXCOMPRESSEDREQ_IX=$(($OPT_MAXCOMPRESSEDREQ_IX + 1))
    else
	SERVER_CMDS+=("MAXCOMPRESSEDREQ=\"\"")
    fi
}

# Process the -i flag.
process_i () {
  cd /lib/modules
  local release
  for release in `ls`; do
    process_r $release
  done
  return 0
}

# Process the -n flag.
process_n () {
  local target_NICKNAME="$1"

  # Is there a running server with this nickname?
  local pid=`get_server_pid_by_nickname "$target_NICKNAME"`
  if [ -n "$pid" ]; then
      # Read the configuration and add it to the configuration commands.
      interpret_server_status "$STAT_PATH/$pid.stat" || continue
      add_server_commands
      return
  fi

  # Is there a server configuration with this nickname?
  for f in "$CONFIG_PATH"/*.conf; do
    if [ -f "$f" ]; then
      interpret_server_config "$f" || continue
      test "X$NICKNAME" = "X$target_NICKNAME" || continue
      add_server_commands
      return
    fi
  done

  # No server configuration could be found for this nickname. Add a
  # NICKNAME_NOT_FOUND=... command to the configuration commands.
  SERVER_CMDS+=("NICKNAME_NOT_FOUND=\"`quote_for_cmd "$target_NICKNAME"`\"")
}

# Process the -p flag.
process_p () {
  local pid="$1"

  # Are we managing a server with the given pid?
  test ! -f "$STAT_PATH/$pid.stat" && echo "No stap-server running as pid $pid" && \
      exit 1

  # Add the configuration of the server running as $pid to SERVER_CMDS
  interpret_server_status "$STAT_PATH/$pid.stat" || exit 1
  add_server_commands
  return 0
}

# Process the -r flag.
process_r () {
    local first_char="${1:1:1}"

    if test "$first_char" = "/"; then # fully specified path
        local kernel_build_tree="$1"
        local version_file_name="$kernel_build_tree/include/config/kernel.release"
        # The file include/config/kernel.release within the kernel
        # build tree is used to pull out the version information
	local kernel_release=`cat $version_file_name 2>/dev/null`
	if test "X$kernel_release" = "X"; then
	    echo "Missing $version_file_name"
	    return 1
	fi
	SERVER_CMDS+=("RELEASE+=(\"`quote_for_cmd "$1"`\")") # pass the path as-is
	return 0
    fi

    # kernel release specified directly
    SERVER_CMDS+=("RELEASE+=(\"`quote_for_cmd "$1"`\")")
    return 0
}

load_config () {
  # Include configs
  if [ -f "$CONFIG_FILE" ]; then
      interpret_config_file "$CONFIG_FILE"
  fi
  if [ -f "$OPT_CONFIG_FILE" ]; then
    interpret_config_file "$OPT_CONFIG_FILE"
  fi
}

# Default to the currently running kernel release
get_release () {
    $UNAME -r
}

# Default to the currently running kernel release
get_arch () {
    stap_get_arch
}

add_server_commands () {
    # Add commands based on the current state to SERVER_CMDS
    SERVER_CMDS+=("ARCH=\"`quote_for_cmd "$ARCH"`\"")
    SERVER_CMDS+=("RELEASE=()")
    for r in "${RELEASE[@]}"; do
	SERVER_CMDS+=("RELEASE+=(\"`quote_for_cmd "$r"`\")")
    done
    SERVER_CMDS+=("RUNTIME=\"`quote_for_cmd "$RUNTIME"`\"")
    SERVER_CMDS+=("DEFINE=()")
    for d in "${DEFINE[@]}"; do
	SERVER_CMDS+=("DEFINE+=(\"`quote_for_cmd "$d"`\")")
    done
    SERVER_CMDS+=("INCLUDE=()")
    for i in "${INCLUDE[@]}"; do
	SERVER_CMDS+=("INCLUDE+=(\"`quote_for_cmd "$i"`\")")
    done
    SERVER_CMDS+=("BUILD=()")
    for b in "${BUILD[@]}"; do
	SERVER_CMDS+=("BUILD+=(\"`quote_for_cmd "$b"`\")")
    done
    SERVER_CMDS+=("USER=\"`quote_for_cmd "$USER"`\"")
    SERVER_CMDS+=("NICKNAME=\"`quote_for_cmd "$NICKNAME"`\"")
    SERVER_CMDS+=("LOG=\"`quote_for_cmd "$LOG"`\"")
    test -n "$PORT" && SERVER_CMDS+=("PORT=\"`quote_for_cmd "$PORT"`\"")
    test -n "$SSL" && SERVER_CMDS+=("SSL=\"`quote_for_cmd "$SSL"`\"")
    test -n "$MAXTHREADS" && SERVER_CMDS+=("MAXTHREADS=\"`quote_for_cmd "$MAXTHREADS"`\"")
    test -n "$MAXREQSIZE" && SERVER_CMDS+=("MAXREQSIZE=\"`quote_for_cmd "$MAXREQSIZE"`\"")
    test -n "$MAXCOMPRESSEDREQ" && SERVER_CMDS+=("MAXCOMPRESSEDREQ=\"`quote_for_cmd "$MAXCOMPRESSEDREQ"`\"")
}

echo_server_options () {
    # Echo the current state.
    echo -n "-a \"`quote_for_cmd "$ARCH"`\""
    for r in "${RELEASE[@]}"; do
	echo -n " -r \"`quote_for_cmd "$r"`\""
    done
    test -n "$RUNTIME" && echo -n " -R \"`quote_for_cmd "$RUNTIME"`\""
    for d in "${DEFINE[@]}"; do
	echo -n " -D \"`quote_for_cmd "$d"`\""
    done
    for i in "${INCLUDE[@]}"; do
	echo -n " -I \"`quote_for_cmd "$i"`\""
    done
    for b in "${BUILD[@]}"; do
	echo -n " -B \"`quote_for_cmd "$b"`\""
    done
    echo -n " -u \"`quote_for_cmd "$USER"`\""
    test -n "$NICKNAME" && echo -n " -n \"`quote_for_cmd "$NICKNAME"`\""
    echo -n " --log \"`quote_for_cmd "$LOG"`\""
    test -n "$PORT" && echo -n " --port \"`quote_for_cmd "$PORT"`\""
    test -n "$SSL" && echo -n " --ssl \"`quote_for_cmd "$SSL"`\""
    test -n "$MAXTHREADS" && echo -n " --max-threads \"`quote_for_cmd "$MAXTHREADS"`\""
    test -n "$MAXREQSIZE" && echo -n " --max-request-size \"`quote_for_cmd "$MAXREQSIZE"`\""
    test -n "$MAXCOMPRESSEDREQ" && echo -n " --max-compressed-request \"`quote_for_cmd "$MAXCOMPRESSEDREQ"`\""
    echo
}

prepare_stat_dir () {
  if [ ! -d "$STAT_PATH" ]; then
    logex mkdir -p "$STAT_PATH"
    [ $? -ne 0 ] && return 1
  fi
  return 0
}

prepare_certs () {
    if [ "$USER" != "`id -un`" ]; then
        if ! runuser -s /bin/bash - $USER -c 'test -f $HOME/.systemtap/ssl/server/stap.cert'; then
            runuser -s /bin/bash - $USER -c ${PKGLIBEXECDIR}stap-gen-cert >/dev/null
        fi
    else
        if ! test -f $HOME/.systemtap/ssl/server/stap.cert; then
            ${PKGLIBEXECDIR}stap-gen-cert
        fi
    fi
}


prepare_log_dir () {
  local log_path=`dirname "$1"`
  if [ ! -d "$log_path" ]; then
      mkdir -p "$log_path"
      [ $? -ne 0 ] && return 1
  fi
  return 0
}

init_server_opts () {
  ARCH=`get_arch`
  unset RELEASE
  unset DEFINE
  unset BUILD
  unset INCLUDE
  NICKNAME=
  NICKNAME_NOT_FOUND=
  RUNTIME=
  USER=$STAP_USER
  test -z "$USER" && USER=`id -un`
  LOG=$LOG_FILE
  PORT=
  SSL=
  MAXTHREADS=
  MAXREQSIZE=
  MAXCOMPRESSEDREQ=
}

# Double quotes, backslashes within generated command
# arguments must be quoted.
quote_for_cmd () {
    echo "$1" | \
	sed -e 's/\\/\\\\/g' \
	    -e 's/"/\\"/g'
}

# Interpret the contents of a global config file.
interpret_config_file () {
    local config_file="$1"

    # Save the results locally first, in case there is an error.
    local local_CONFIG_PATH=
    local local_STAT_PATH=
    local local_LOG_FILE=
    local local_STAP_USER=

    local input
    while read -r -u3 input
    do
	case "$input" in
	    CONFIG_PATH=*)
              local_CONFIG_PATH="${input:12}"
	      ;;
	    STAT_PATH=*)
              local_STAT_PATH="${input:10}"
	      ;;
	    LOG_FILE=*)
              local_LOG_FILE="${input:9}"
	      ;;
	    STAP_USER=*)
              local_STAP_USER="${input:10}"
	      ;;
	    \#*)
	      ;; # Comment, do nothing
	    "")
	      ;; # Empty line, do nothing
	    *)
	      echo $"Invalid input, \"$input\", in $config_file" >&2
	      exit 1
	      ;;
	esac
    done 3< "$config_file"

    # Now set the results globally
    test -n "$local_CONFIG_PATH" && CONFIG_PATH="$local_CONFIG_PATH"
    test -n "$local_STAT_PATH"   && STAT_PATH="$local_STAT_PATH"
    test -n "$local_LOG_FILE"    && LOG_FILE="$local_LOG_FILE"
    test -n "$local_STAP_USER"   && STAP_USER="$local_STAP_USER"
}

# Interpret the contents of a server config file.
interpret_server_config () {
    local config_file="$1"

    # Save the results locally first, in case there is an error.
    local local_ARCH=
    local local_RELEASE=()
    local local_DEFINE=()
    local local_BUILD=()
    local local_INCLUDE=()
    local local_RUNTIME=
    local local_USER=
    local local_NICKNAME=
    local local_LOG=
    local local_PORT=
    local local_SSL=
    local local_MAXTHREADS=
    local local_MAXREQSIZE=
    local local_MAXCOMPRESSEDREQ=

    local input
    while read -r -u3 input
    do
	case "$input" in
	    ARCH=*)
              local_ARCH="${input:5}"
	      ;;
	    RELEASE=*)
	      if [ -z "${input:8}" -o "${input:8}" = "()" ]; then
		  local_RELEASE=()
	      else
		  local_RELEASE=("${input:8}")
	      fi
	      ;;
	    RELEASE+=*)
	      test -z "${input:9}" -o "${input:9}" = "()" || \
	          local_RELEASE+=("${input:9}")
	      ;;
	    DEFINE=*)
	      if [ -z "${input:7}" -o "${input:7}" = "()" ]; then
		  local_DEFINE=()
	      else
		  local_DEFINE=("${input:7}")
	      fi
	      ;;
	    DEFINE+=*)
	      test -z "${input:8}" -o "${input:8}" = "()" || \
		  local_DEFINE+=("${input:8}")
	      ;;
	    BUILD=*)
	      if [ -z "${input:6}" -o "${input:6}" = "()" ]; then
		  local_BUILD=()
	      else
		  local_BUILD=("${input:6}")
	      fi
	      ;;
	    BUILD+=*)
	      test -z "${input:7}" -o "${input:7}" = "()" || \
		  local_BUILD+=("${input:7}")
	      ;;
	    INCLUDE=*)
	      if [ -z "${input:8}" -o "${input:8}" = "()" ]; then
		  local_INCLUDE=()
	      else
		  local_INCLUDE=("${input:8}")
	      fi
	      ;;
	    INCLUDE+=*)
	      test -z "${input:8}" -o "${input:8}" = "()" || \
		  local_INCLUDE+=("${input:9}")
	      ;;
	    RUNTIME=*)
              local_RUNTIME="${input:8}"
	      ;;
	    USER=*)
              local_USER="${input:5}"
	      ;;
	    NICKNAME=*)
              local_NICKNAME="${input:9}"
	      ;;
	    LOG=*)
              local_LOG="${input:4}"
	      ;;
	    PORT=*)
              local_PORT="${input:5}"
	      ;;
	    SSL=*)
              local_SSL="${input:4}"
	      ;;
	    MAXTHREADS=*)
              local_MAXTHREADS="${input:11}"
	      ;;
	    MAXREQSIZE=*)
              local_MAXREQSIZE="${input:11}"
	      ;;
	    MAXCOMPRESSEDREQ=*)
              local_MAXCOMPRESSEDREQ="${input:17}"
	      ;;
	    \#*)
	      ;; # Comment, do nothing
	    "")
	      ;; # Empty line, do nothing
	    *)
	      echo $"Invalid input, \"$input\", in $config_file" >&2
	      exit 1
	      ;;
	esac
    done 3< "$config_file"

    # Now set the results globally. All variables are expected to be set, even
    # if to nothing.
    ARCH="$local_ARCH"
    RELEASE=("${local_RELEASE[@]}")
    DEFINE=("${local_DEFINE[@]}")
    BUILD=("${local_BUILD[@]}")
    INCLUDE=("${local_INCLUDE[@]}")
    RUNTIME="$local_RUNTIME"
    USER="$local_USER"
    NICKNAME="$local_NICKNAME"
    LOG="$local_LOG"
    PORT="$local_PORT"
    SSL="$local_SSL"
    MAXTHREADS="$local_MAXTHREADS"
    MAXREQSIZE="$local_MAXREQSIZE"
    MAXCOMPRESSEDREQ="$local_MAXCOMPRESSEDREQ"
}

# Interpret the contents of a server status file.
interpret_server_status () {
    # The contents of the server status files are currently the same as
    # that of server config files.
    interpret_server_config "$1"
}

# Load the default server config and add the results to SERVER_CMDS.
load_server_config () {
  for f in "$CONFIG_PATH"/*.conf; do
    if [ -f "$f" ]; then
      # Obtain a configuration from each config file.
      # Ensure that we get the correct defaults for items not specified.
      local ARCH=
      local DEFINE=()
      local BUILD=()
      local INCLUDE=()
      local RUNTIME=
      local USER=
      local RELEASE=()
      local LOG=
      local PORT=
      local SSL=
      local MAXTHREADS=
      local MAXREQSIZE=
      local MAXCOMPRESSEDREQ=
      interpret_server_config "$f" || continue
      # Other options default to empty. These ones don't.
      [ -z "$ARCH" ]     && ARCH=`get_arch`
      [ -z "$RELEASE" ]  && RELEASE+=(`get_release`)
      [ -z "$USER" ]     && USER=$STAP_USER
      [ -z "$USER" ]     && USER=`id -un`
      [ -z "$LOG" ]      && LOG="$LOG_FILE"
      add_server_commands
      SERVER_CMDS+=("EXEC")
    fi
  done
}

server_still_running () { # PID
    (ps -e | grep stap-serverd | grep -q $1) && return 0 # Still running

    rm -f "$STAT_PATH/$1.stat"
    return 1 # Not running
}

get_server_pid_by_config () {
    # Need to save the config, since the process of checking the running
    # servers alters it.
    local target_ARCH="$ARCH"
    local target_RELEASE=("${RELEASE[@]}")
    local target_RUNTIME="$RUNTIME"
    local target_INCLUDE=("${INCLUDE[@]}")
    local target_BUILD=("${BUILD[@]}")
    local target_DEFINE=("${DEFINE[@]}")
    local target_USER="$USER"
    local target_NICKNAME="$NICKNAME"
    local target_LOG="$LOG"
    local target_PORT="$PORT"
    local target_SSL="$SSL"
    local target_MAXTHREADS="$MAXTHREADS"
    local target_MAXREQSIZE="$MAXREQSIZE"
    local target_MAXCOMPRESSEDREQ="$MAXCOMPRESSEDREQ"

    # Check the status file for each running server to see if it matches
    # the one currently configured. We're checking for a given configuration,
    # so don't compare the nickname.
    for f in "$STAT_PATH"/*.stat; do
	test ! -e "$f" && continue
	interpret_server_status "$f" || continue
	test "X$ARCH"         = "X$target_ARCH"         || continue
	test "X$RELEASE"      = "X$target_RELEASE"      || continue
	test "X$INCLUDE"      = "X$target_INCLUDE"      || continue
	test "X$RUNTIME"      = "X$target_RUNTIME"      || continue
	test "X$BUILD"        = "X$target_BUILD"        || continue
	test "X$DEFINE"       = "X$target_DEFINE"       || continue
	test "X$USER"         = "X$target_USER"         || continue
	test "X$LOG"          = "X$target_LOG"          || continue
	test "X$PORT"         = "X$target_PORT"         || continue
	test "X$SSL"          = "X$target_SSL"          || continue
	test "X$MAXTHREADS"   = "X$target_MAXTHREADS"   || continue
	test "X$MAXREQSIZE"   = "X$target_MAXREQSIZE"   || continue
	test "X$MAXCOMPRESSEDREQ"   = "X$target_MAXCOMPRESSEDREQ"   || continue
	echo `basename "$f" | sed 's/.stat//'` # Server has a pid
	return
    done

    ARCH="$target_ARCH"
    RELEASE=("${target_RELEASE[@]}")
    RUNTIME="$target_RUNTIME"
    INCLUDE=("${target_INCLUDE[@]}")
    BUILD=("${target_BUILD[@]}")
    DEFINE=("${target_DEFINE[@]}")
    USER="$target_USER"
    NICKNAME="$target_NICKNAME"
    LOG="$target_LOG"
    PORT="$target_PORT"
    SSL="$target_SSL"
    MAXTHREADS="$target_MAXTHREADS"
    MAXREQSIZE="$target_MAXREQSIZE"
    MAXCOMPRESSEDREQ="$target_MAXCOMPRESSEDREQ"
}

get_server_pid_by_nickname () {
    # No need to save the current configuration. This function is not called
    # in a context requiring it.
    local target_NICKNAME="$1"

    # Check the status file for each running server to see if the nickname
    # matches the one we want.
    for f in "$STAT_PATH"/*.stat; do
	test ! -e "$f" && continue
	interpret_server_status "$f" || continue
	test "X$NICKNAME" = "X$target_NICKNAME" || continue
	echo `basename "$f" | sed 's/\.stat//'` # Server with nickname was found
	return
    done
}

managed_servers () {
  if [ ! -d "$STAT_PATH" ]; then
      echo ""
      return 1
  fi
  cd "$STAT_PATH"
  local list=`ls | sed 's/\.stat//'`
  if [ -z "$list" ]; then
      echo ""
      return 1
  fi

  echo "$list"
}

eval_server_command () {
    local cmd="$1"
    eval $cmd
}

start_server () {
    clog $"Starting $prog `echo_server_options`"

    # Is there already a server running for the requested kernel release
    # and arch?
    local server_pid=`get_server_pid_by_config`
    if test -n "$server_pid"; then
	if server_still_running $server_pid; then
	    do_success $"$prog start `echo_server_options`"
	    return 0 # Success
	fi
    fi

    # Create certificates for this server
    prepare_certs
    if [ $? -ne 0 ]; then
	echo $"Failed to make certificates ($USER .systemtap/ssl/server/stap.cert)" >&2
	exit 1
    fi

    # Create the log directory for this server
    prepare_log_dir "$LOG"
    if [ $? -ne 0 ]; then
	echo $"Failed to make log directory (`dirname "$LOG"`)" >&2
	exit 1
    fi

    # Construct the server start command.
    local server_cmd="$STAP_START_SERVER -a \"`quote_for_cmd "$ARCH"`\" $OPT_OTHER"
    for r in "${RELEASE[@]}"; do
        server_cmd="$server_cmd -r \"`quote_for_cmd "$r"`\""
    done
    for b in "${BUILD[@]}"; do
	server_cmd="$server_cmd -B \"`quote_for_cmd "$b"`\""
    done
    for i in "${INCLUDE[@]}"; do
	server_cmd="$server_cmd -I \"`quote_for_cmd "$i"`\""
    done
    for d in "${DEFINE[@]}"; do
	server_cmd="$server_cmd -D \"`quote_for_cmd "$d"`\""
    done
    test -n "$RUNTIME" && server_cmd="$server_cmd -R \"`quote_for_cmd "$RUNTIME"`\""
    server_cmd="$server_cmd --log=\"`quote_for_cmd "$LOG"`\""
    test -n "$PORT" && server_cmd="$server_cmd --port \"`quote_for_cmd "$PORT"`\""
    test -n "$SSL" && server_cmd="$server_cmd --ssl \"`quote_for_cmd "$SSL"`\""
    test -n "$MAXTHREADS" && server_cmd="$server_cmd --max-threads \"`quote_for_cmd "$MAXTHREADS"`\""
    test -n "$MAXREQSIZE" && server_cmd="$server_cmd --max-request-size \"`quote_for_cmd "$MAXREQSIZE"`\""
    test -n "$MAXCOMPRESSEDREQ" && server_cmd="$server_cmd --max-compressed-request \"`quote_for_cmd "$MAXCOMPRESSEDREQ"`\""

    # Start the server here.
    local pid
    if [ "$USER" != "`id -un`" ]; then
	pid=`runuser -s /bin/bash - $USER -c "$server_cmd"`
    else
	pid=`eval $server_cmd`
    fi
    if [ $? != 0 -o -z "$pid" ]; then
	if [ -n "$pid" ]; then
	    rm -f "$STAT_PATH/$pid.stat"
	fi
	do_failure $"$prog start `echo_server_options`"
	return 1 # Failure
    fi

    # Nickname defaults to the pid.
    test -z "$NICKNAME" && NICKNAME="$pid"

    # Write the configuration to the status file.
    local server_status_file="$STAT_PATH/$pid.stat"
    echo "ARCH=$ARCH" > "$server_status_file"
    echo "USER=$USER" >> "$server_status_file"
    for b in "${BUILD[@]}"; do
	echo "BUILD+=$b" >> "$server_status_file"
    done
    for i in "${INCLUDE[@]}"; do
	echo "INCLUDE+=$i" >> "$server_status_file"
    done
    for d in "${DEFINE[@]}"; do
	echo "DEFINE+=$d" >> "$server_status_file"
    done
    echo "NICKNAME=$NICKNAME" >> "$server_status_file"
    echo "RUNTIME=$RUNTIME" >> "$server_status_file"
    for r in "${RELEASE[@]}"; do
        echo "RELEASE+=$r" >> "$server_status_file"
    done
    echo "LOG=$LOG" >> "$server_status_file"
    echo "PORT=$PORT" >> "$server_status_file"
    echo "SSL=$SSL" >> "$server_status_file"
    echo "MAXTHREADS=$MAXTHREADS" >> "$server_status_file"
    echo "MAXREQSIZE=$MAXREQSIZE" >> "$server_status_file"
    echo "MAXCOMPRESSEDREQ=$MAXCOMPRESSEDREQ" >> "$server_status_file"

    do_success $"$prog start `echo_server_options`"
}

start () { # server-cmds
  prepare_stat_dir
  if [ $? -ne 0 ]; then
    do_failure $"Failed to make stat directory ($STAT_PATH)"
    return 1
  fi

  # Start the specified servers
  local server_cmds=("$@")

  # If none specified, start the configured servers
  if [ -z "$server_cmds" ]; then
    unset SERVER_CMDS
    load_server_config
    server_cmds=("${SERVER_CMDS[@]}")
    # If none configured, start the default servers
    if test -z "$server_cmds"; then
	add_distributed_options
	server_cmds=("${SERVER_CMDS[@]}")
	server_cmds+=("EXEC")
    fi
  fi

  # Start each requested server in turn
  local rc=0
  local first=1
  init_server_opts
  local cmd
  local prevCmd
  for cmd in "${server_cmds[@]}"; do
      prevCmd="$cmd"
      # Evaluate commands until the EXEC command is found.
      if test "$cmd" != "EXEC"; then
	  eval_server_command "$cmd"
	  # A specified nickname only sticks if it is the final command.
	  # Otherwise, we have a configuration based on a nicknamed
	  # configuration.
	  echo "$cmd" | grep -q "^NICKNAME=" || NICKNAME=""
	  continue
      fi
      # If a nickname was specified, but the corresponding config was not found,
      # then it is the nickname for this new configuration.
      if test -n "$NICKNAME_NOT_FOUND"; then
	  NICKNAME="$NICKNAME_NOT_FOUND"
	  NICKNAME_NOT_FOUND=
      fi

      # Start the configured server
      test $first = 0 && echo
      first=0
      start_server || rc=1

      # Don't use the same nickname for the next server.
      NICKNAME=
  done

  return $rc
}

stop () { # server-cmds
  local first=1
  local server_list=
  local server_cmds=("$@")
  if [ -n "$server_cmds" ]; then
      # Get the pids of all the requested servers.
      init_server_opts
      local cmd
      local prevCmd
      for cmd in "${server_cmds[@]}"; do
	  prevCmd="$cmd"
          # Evaluate commands until the EXEC command is found.
	  if test "$cmd" != "EXEC"; then
	      eval_server_command "$cmd"
	      # A specified nickname only sticks if it is the final command.
	      # Otherwise, we have a configuration based on a nicknamed
	      # configuration.
	      echo "$cmd" | grep -q "^NICKNAME=" || NICKNAME=""
	      continue
	  fi
          # If a nickname was specified, but the corresponding config was not
	  # found, it is an error.
	  if test -n "$NICKNAME_NOT_FOUND"; then
	      clog "No configuration found for the nickname '$NICKNAME_NOT_FOUND'"
	      NICKNAME="$NICKNAME_NOT_FOUND"
	      do_failure $"$prog stop `echo_server_options`"
	      NICKNAME_NOT_FOUND=
	      rc=1
	      continue
	  fi

          # Get the pid for this server, if it's running
	  local server_pid=`get_server_pid_by_config`
	  if test -n "$server_pid"; then
	      server_list="$server_list $server_pid"
	      continue
	  fi

	  # This server is not running, but give a success stop status anyway.
	  test $first = 0 && echo
	  first=0
	  clog $"Stopping $prog `echo_server_options`"
	  do_success $"$prog stop `echo_server_options`"
      done
  else
      server_list=`managed_servers`
      if [ -z "$server_list" ]; then
	  clog $"Stopping $prog: " -n
	  do_success $"$prog: No managed servers to stop"
	  return 0
      fi
  fi

  # Stop each server in turn
  local rc=0
  local pid
  for pid in $server_list; do
      if ! interpret_server_status "$STAT_PATH/$pid.stat"; then
	  rc=1
	  continue
      fi

      test $first = 0 && echo
      first=0
      clog $"Stopping $prog `echo_server_options`"

      local this_rc=0
      if server_still_running $pid; then
	  if [ "$USER" != "`id -un`" ]; then
	      runuser -s /bin/bash - $USER -c "$STAP_STOP_SERVER $pid"
	  else
	      eval $STAP_STOP_SERVER $pid
	  fi
	  if [ $? != 0 ]; then
	      do_failure $"$prog stop `echo_server_options`"
	      this_rc=1
	      rc=1
	  fi
      fi
      if [ $this_rc = 0 ]; then
	  rm -f "$STAT_PATH/$pid.stat"
	  do_success $"$prog stop `echo_server_options`"
      fi
  done

  return $rc
}

status () { # server-list
  local rc=0

  # Report status for the specified servers or all running servers, if none
  # specified.
  local server_list=
  local server_cmds=("$@")
  if [ -n "$server_cmds" ]; then
      # Get the pids of all the requested servers.
      init_server_opts
      local cmd
      local prevCmd
      for cmd in "${server_cmds[@]}"; do
	  prevCmd="$cmd"
          # Evaluate commands until the EXEC command is found.
	  if test "$cmd" != "EXEC"; then
	      eval_server_command "$cmd"
	      # A specified nickname only sticks if it is the final command.
	      # Otherwise, we have a configuration based on a nicknamed
	      # configuration.
	      echo "$cmd" | grep -q "^NICKNAME=" || NICKNAME=""
	      continue
	  fi
          # If a nickname was specified, but the corresponding config was not
	  # found, say so.
	  if test -n "$NICKNAME_NOT_FOUND"; then
	      echo "No configuration found for the nickname '$NICKNAME_NOT_FOUND'"
	      NICKNAME_NOT_FOUND=
	      rc=3
	      continue
	  fi

          # Get the pid for this server, if it's running
	  local server_pid=`get_server_pid_by_config`
	  if test -n "$server_pid"; then
	      server_list="$server_list $server_pid"
	      continue
	  fi
	  # This server is not running
	  echo "stap-server `echo_server_options` is not running"
	  rc=3
      done
  else
      server_list=`managed_servers`
      if [ -z "$server_list" ]; then
	  echo "No managed stap-server is running"
	  return 3
      fi
  fi

  # Get status of each server in turn
  local pid
  for pid in $server_list; do
      if ! interpret_server_status "$STAT_PATH/$pid.stat"; then
	  rc=1
	  continue
      fi
      if ! server_still_running $pid; then
	  echo "stap-server `echo_server_options` started as PID $pid is no longer running"
	  rc=1
	  continue
      fi
      echo "stap-server `echo_server_options` running as PID $pid"
  done

  return $rc
}

# Restart or start if not running
function restart () { # server-cmds
  # Restart the specified servers or all servers, if none specified.
  local rc=0
  local server_cmds=("$@")
  if [ -z "$server_cmds" ]; then
      local server_list=`managed_servers`
      local pid
      for pid in $server_list; do
	  if ! interpret_server_status "$STAT_PATH/$pid.stat"; then
	      rc=1
	      continue
	  fi
	  unset SERVER_CMDS
	  add_server_commands
	  server_cmds+=("${SERVER_CMDS[@]}")
	  server_cmds+=("EXEC")
      done
  fi

  # Stop the specified servers, or all if none specified
  stop "${server_cmds[@]}" || rc=1
  echo

  # Restart the same servers. If none were specified then
  # start the configured or default server(s)).
  start "${server_cmds[@]}"
  local this_rc=$?
  [ $this_rc != 0 ] && rc=$this_rc

  return $rc
}

# Restart only if running
function condrestart () { # server-list
  # Restart the specified servers or all servers, if none specified,
  # but only if they are already running.
  local rc=0
  local server_cmds=("$@")
  if [ -z "$server_cmds" ]; then
      local server_list=`managed_servers`
      local pid
      for pid in $server_list; do
	  if ! interpret_server_status "$STAT_PATH/$pid.stat"; then
	      rc=1
	      continue
	  fi
	  unset SERVER_CMDS
	  add_server_commands
	  server_cmds+=("${SERVER_CMDS[@]}")
	  server_cmds+=("EXEC")
      done
      # No server specified or running?
      if [ -z "$server_cmds" ]; then
	  clog "No managed stap-server is running" -n
	  do_success "No managed stap-server is running"
	  return 0
      fi
  fi

  # For each server in the list, stop it if it is running
  local first=1
  local this_server_cmds
  local cmd
  for cmd in "${server_cmds[@]}"; do
      # Execute and collect commands until the EXEC command is found.
      this_server_cmds+=("$cmd")
      if test "$cmd" != "EXEC"; then
	  eval_server_command "$cmd"
	  continue
      fi

      test $first = 0 && echo
      first=0

      # Now see if this server is running
      if ! status "${this_server_cmds[@]}" >/dev/null 2>&1; then
	clog $"$prog `echo_server_options` is not running"
	do_success "$prog `echo_server_options` is not running"
	unset this_server_cmds
	continue
      fi

      start_cmds+=("${this_server_cmds[@]}")

      stop "${this_server_cmds[@]}"
      this_rc=$?
      [ $this_rc != 0 ] && rc=$this_rc

      unset this_server_cmds
  done

  # Now restart the servers that were running
  if [ -n "$start_cmds" ]; then
      echo
      start "${start_cmds[@]}"
      local this_rc=$?
      [ $this_rc != 0 ] && rc=$this_rc
  fi

  return $rc
}

#------------------------------------------------------------------
# Mainline script
#------------------------------------------------------------------
CMD=$1
shift 1

prepare_log_dir "$LOG_FILE"
if [ $? -ne 0 ]; then
    echo $"Failed to make log directory (`dirname $LOG_FILE`)" >&2
    exit 1
fi

OPTS=`getopt -s bash -u --options 'a:B:c:D:iI:n:p:kPr:R:u:' \
                        --longoptions 'log:' \
                        --longoptions 'port:' \
                        --longoptions 'ssl:' \
                        --longoptions 'max-threads:' \
                        --longoptions 'max-request-size:' \
                        --longoptions 'max-compressed-request:' \
                        -- "$@"`
if [ $? -ne 0 ]; then
  echo "Error: Argument parse error: $@" >&2
  echo_usage
  exit 2
fi

# Initialize server specs
parse_args "$@" || exit 2
load_config

RETVAL=0

case $CMD in
  start) # Start specified servers. If none specified, start configured servers
    start "${SERVER_CMDS[@]}"
    RETVAL=$?
    ;;
  stop) # Stop specified servers
    stop "${SERVER_CMDS[@]}"
    RETVAL=$?
    ;;
  restart) # Restart specified servers
    restart "${SERVER_CMDS[@]}"
    RETVAL=$?
    ;;
  condrestart|try-restart) # Restart specified servers if they are running
    condrestart "${SERVER_CMDS[@]}"
    RETVAL=$?
    ;;
  status) # Give status on specified servers
    status "${SERVER_CMDS[@]}"
    exit $?
    ;;
  reload) # Reloading config without stop/restart is not supported
    RETVAL=3
    ;;
  force-reload) # Reload config with stop/start
    # stop all running servers
    stop
    echo
    # Restart specified servers
    # If none specified, restart configured servers
    start "${SERVER_CMDS[@]}"
    RETVAL=$?
    ;;
  usage|*)
    echo_usage
    RETVAL=0
    ;;
esac

echo
exit $RETVAL