#!/bin/bash

TIMEOUT_SINGLE_CMD_SEC=60

TIMEOUT_SINGLE_HTTP_REQUEST_SEC=1
AWS_IMDSV2_TOKEN_TTL_SECONDS=21600
AWS_IMDS_TOKEN=

CURL_ARGS=(--silent --max-time $TIMEOUT_SINGLE_HTTP_REQUEST_SEC)
CURL_CODE_PREFIX="CODE:"
CURL_WRITE_OUT=(--write-out "$CURL_CODE_PREFIX%{http_code}")

SCRIPT_GIT_HASH="2fd39b4b25d476910b10b22df56a6a5e61f0ccb6"

args=("$@")

# Parameters.
while [[ $# -gt 0 ]]; do
  opt="$1"
  case "$opt" in
  --preflight)
    # Used for checking sudo target user permissions.
    PREFLIGHT=1
    ;;
  --user)
    USER_FLAG=1
    ;;
  --force-imdsv1)
    # Used for testing purposes.
    FORCE_IMDSV1=1
    ;;
  --low-priority)
    LOW_PRIORITY=1
    ;;
  --stats)
    PRINT_STATS=1
    ;;
  --stats-to-file)
    STATS_TO_FILE=1
    ;;
  --skip-print-file-location)
    SKIP_PRINT_FILE_LOCATION=1
    ;;
  --output)
    OUTPUT_FILE="$2"
    shift
    ;;
  *)
    echo "Unknown parameter: $opt"
    ;;
  esac
  shift
done

if [[ ${EUID} -ne 0 && -z "${SUDO_COMMAND}" ]]; then
  # We are not root and not running as sudo.
  # Try running this script with sudo.
  sudo -n "$0" --preflight &> /dev/null
  if [[ $? -eq 0 ]]; then
    # The preflight is successful.
    # The sudo target user has permissions to run the script.
    # We re-run the script with sudo and propagate the script exit code.
    sudo -n "$0" "${args[@]}"
    exit $?
  fi
  # We keep running the script with the current user.
fi

# This is a preflight. No need to run the rest of the script.
[[ ${PREFLIGHT} -eq 1 ]] && exit

if [[ ${USER_FLAG} -eq 1 ]]; then
  echo >&2 "--user flag is deprecated, root is no longer strictly required."
fi

if [[ ${EUID} -ne 0 ]]; then
  echo >&2 "This script is run with non-root user, it might fail or collect partial data."
fi

shopt -s nullglob

# Apache server config files default locations.
APACHE_CONF_FILES=(
  "/etc/apache2/*.conf"
  "/etc/apache2/*/*.conf"
  "/etc/apache2/*.load"
  "/etc/apache2/*/*.load"
  "/etc/httpd/*.conf"
  "/etc/httpd/*/*.conf"
  "/etc/httpd/*.load"
  "/etc/httpd/*/*.load"
  "/usr/local/apache2/*.conf"
  "/usr/local/apache2/*/*.conf"
  "/usr/local/apache2/*.load"
  "/usr/local/apache2/*/*.load"
  "/usr/local/httpd/*.conf"
  "/usr/local/httpd/*/*.conf"
  "/usr/local/httpd/*.load"
  "/usr/local/httpd/*/*.load"
  "/opt/apache2/*.conf"
  "/opt/apache2/*/*.conf"
  "/opt/apache2/*.load"
  "/opt/apache2/*/*.load"
  "/opt/httpd/*.conf"
  "/opt/httpd/*/*.conf"
  "/opt/httpd/*.load"
  "/opt/httpd/*/*.load"
)

files_dump_targets=(
  "/etc/issue"                                                    # OS issue // man issue.
  "/etc/os-release"                                               # OS identification.
  "/etc/centos-release"                                           # CentOS version.
  "/etc/redhat-release"                                           # RHEL name and version.
  "/etc/hostname"                                                 # Machine's hostname // man hostname.
  "/etc/HOSTNAME"                                                 # Machine's hostname (SuSe).
  "/proc/sys/kernel/hostname"                                     # Machine's hostname.
  "/proc/cpuinfo"                                                 # CPU info.
  "/proc/meminfo"                                                 # Memory info.
  "/sys/class/dmi/id/bios_date"                                   # BIOS date.
  "/sys/class/dmi/id/bios_version"                                # BIOS version.
  "/sys/class/dmi/id/bios_vendor"                                 # BIOS vendor.
  "/sys/class/dmi/id/sys_vendor"                                  # System vendor.
  "/sys/class/dmi/id/product_serial"                              # Serial from SMBIOS (requires root).
  "/etc/hosts"                                                    # domain -> IP translations.
  "/etc/fstab | grep -E '^[^#]' | awk '\$4=\"removed\"'"          # Mounts.
  "/proc/self/mounts | grep -E '^[^#]' | awk '\$4=\"removed\"'"   # Mounts for current PID.
  "/etc/exports"                                                  # NFS exports.
  "/opt/IBM/WebSphere/AppServer/properties/version/installed.xml" # Websphere version (when installed at default).
  "/opt/IBM/WebSphere/AppServer/properties/version/WAS.product"   # Websphere info (when installed at default).
  "/sys/devices/system/node/node*/cpumap"
  "/boot/config-$(uname -r)"                                      # Kernel config.
  ${APACHE_CONF_FILES[@]}
)

# Use the prefix "@outfile filename" to specify the output filename, otherwise the command name will be used.
# To run a command named "@outfile" use "[@outfile filename] -- @outfile [flags]"
commands=(
  "netstat -tuanp"                                                              # Established connections, listening ports.
  "ss -tuanp"                                                                   # Established connections, listening ports.
  "ps -o pid,user,%mem,uid,ppid,comm,args -e"                                   # Running processes.
  "dpkg -l"                                                                     # Installed packages (debian).
  "sestatus"                                                                    # SELinux status tool.
  "lsmod"                                                                       # List Kernel modules.
  "lscpu"                                                                       # CPU.
  "systemctl"                                                                   # systemd services status.
  "lsof /dev/ /"                                                                # List open handles to files and hw devices.
  "ip addr"                                                                     # Show IP addressed assigned to NICs (not available on older Linux distros).
  "lsblk -o NAME,SIZE,TYPE,LABEL,UUID,MOUNTPOINT,TRAN,SUBSYSTEMS -b -P"         # List block devices.
  "@outfile lsblk-fallback lsblk -o NAME,SIZE,TYPE,LABEL,UUID,MOUNTPOINT -b -P" # List block devices - fallback for older Linux distros.
  "@outfile uname.m uname -m"                                                   # Architecture.
  "df -T -B 1024"                                                               # Collect filesystems.
  "zpool get -Hp -o name,value size,free"                                       # Zpoos information.
  "btrfs filesystem show --raw"                                                 # Btrfs information.
  "apache2 -V"                                                                  # Apache version and compile settings.
  "httpd -V"                                                                    # Apache version and compile settings.
  "php -i"                                                                      # PHP information.
  "@outfile php.modules php -m"                                                 # PHP installed modules.
  "@outfile rpm.delimited rpm -qa --qf '%{VENDOR}|%{NAME}|%{VERSION}|%{DIRNAMES}|%{INSTALLTIME}|%{SOURCERPM}|\n'"  # Installed packages (RHEL-like).
  "@outfile lib.modules.kernel find /lib/modules/$(uname -r)/kernel"            # List of Installed Kernel Modules.
  # TODO(b/372851306) skipping these commands until a better solution is found.
  # "@outfile initrd.ramfs lsinitrd /boot/initramfs-$(uname -r).img"            # List of Initramfs or Initrd Image Content (option 1).
  # "@outfile initrd lsinitrd /boot/initrd-$(uname -r)"                         # List of Initramfs or Initrd Image Content (option 2).
  # "@outfile initramf.rd lsinitramfs /boot/initrd.img-$(uname -r)"             # List of Initramfs or Initrd Image Content (option 3).
)

aws_metadata_categories=(
  "instance-id"
)

gce_metadata_keys=(
  "name"
  "id"
)

tmp=$(mktemp -d)

if [[ ${LOW_PRIORITY} -eq 1 ]]; then
  current_priority=$(ps -o ni= -p $$)
  renice -n "$(($current_priority + 5))" -p $$ 1> "$tmp/renice" 2> "$tmp/renice.stderr"
  echo $? > "$tmp/renice.exitcode"
fi

function collect_single_filecommand(){
  filecommand=$1
  file_dumps_dir=$2

  fileglob=$(echo "$filecommand" | cut -d " " -f1)
  command=$(echo "$filecommand" | cut -d " " -f1 --complement -s)
  found=0
  # nullglob is set above, so if this doesn't match a file we will skip the loop entirely.
  for file in $fileglob; do
    found=1
    if [[ ! -f "$file" ]]; then
      echo "$file, is not a file" >>"$tmp/collect.log"
      continue
    fi
    targetDir=$(dirname "$file_dumps_dir/$file")
    mkdir -p "$targetDir"
    "$SHELL" -c "cat $file $command" >"$file_dumps_dir/$file"
  done
  if [[ found -eq 0 ]]; then
    echo "file: $fileglob, not found" >>"$tmp/collect.log"
  fi
}

function collect_file_dumps() {
  file_dumps_dir="$tmp/files"
  mkdir "$file_dumps_dir"

  for filecommand in "${files_dump_targets[@]}"; do
    collect_single_filecommand "$filecommand" "$file_dumps_dir"
  done
}

function collect_command_outputs() {
  command_outputs_dir="$tmp/commands"
  mkdir "$command_outputs_dir"

  for raw_command_line in "${commands[@]}"; do
    # convert command line to array
    command_line_array=($raw_command_line)

    relativeTargetFile=
    # parse the command line, look for exsitence of "@outfile" or "--" prefixes
    while [[ -n "${command_line_array[0]}" ]]; do
      case "${command_line_array[0]}" in
        "@outfile")
          relativeTargetFile=${command_line_array[1]}
          command_line_array=(${command_line_array[@]:2})
        ;;
        "--")
          command_line_array=(${command_line_array[@]:1})
          break
        ;;
        *)
          break
        ;;
      esac
    done

    # no "@outfile" prefix found, use the command name as outfile
    if [[ -z "$relativeTargetFile" ]]; then
      relativeTargetFile=${command_line_array[0]}
    fi

    targetFile="$command_outputs_dir/$relativeTargetFile"

    cmd=${command_line_array[*]}

    echo "- cmd: $cmd -" >>"$tmp/collect.log"
    (time timeout -k 10s ${TIMEOUT_SINGLE_CMD_SEC} "$SHELL" -c "$cmd" 2>"$targetFile.stderr") >"$targetFile" 2>>"$tmp/collect.log"
    echo "$?" >"$targetFile.exitcode"
    cat "$targetFile.stderr" >>"$tmp/collect.log"
  done
}

function collect_nics_information() {
  # Collect the NICs symlinks which indicates which of them are physical.
  "$SHELL" -c 'find /sys/class/net/* -type l -ls' >"$tmp/nics_symlinks" 2>>"$tmp/collect.log"

  # Collect the operstate which indicates the status of the NIC.
  "$SHELL" -c 'grep "" /sys/class/net/*/operstate' >"$tmp/nics_operstate" 2>>"$tmp/collect.log"
}

# Collect information on running tomcat servers, including java args,
# installation homes, server and applications configurations.
function collect_tomcat_information() {
  output_dir="$tmp/tomcat"
  mkdir "$output_dir"

  count=0
  while read -r line; do
    count=$(($count + 1))
    pid=$(echo "$line" | cut -d" " -f1)

    # Server process info.
    server_dir=$output_dir/$count
    mkdir -p "$server_dir"
    ps -o pid,user,%mem,uid,comm,args -p "$pid" >"$server_dir/ps"

    # Get java version.
    java_path=$(readlink "/proc/$pid/exe")
    # Java -version output going to stderr.
    "$java_path" -version 2>"$server_dir"/java_version

    # Get tomcat path from catalina.base/home, prefer base.
    catalina_home=$(echo "$line" | grep -Po "catalina.home=\K[^ ]*")
    catalina_path=$(echo "$line" | grep -Po "catalina.base=\K[^ ]*")
    if [[ $catalina_path != *[^[:space:]]* ]]; then
      catalina_path=$catalina_home
    fi

    # Get tomcat version, might be in catalina_home only.
    tomcat_version=$(java -cp "$catalina_path"/lib/catalina.jar org.apache.catalina.util.ServerInfo)
    if [[ $? != 0 ]]; then
      tomcat_version="$(java -cp "$catalina_home"/lib/catalina.jar org.apache.catalina.util.ServerInfo)"
    fi
    echo "$tomcat_version" >"$server_dir"/tomcat_version

    # Server config files.
    conf_dir=$server_dir/conf
    mkdir -p "$conf_dir"
    cp "$catalina_path/conf/server.xml" "$conf_dir"/

    # Server xml without comments and line breaks,
    # If one search on the xml avoid search elements that comment out, and easier to work with grep without line breaks.
    clean_server_xml=$(sed 's/<!--/\x0<!--/g;s/-->/-->\x0/g' "$catalina_path/conf/server.xml" | grep -zv '^<!--' | tr -d '\0' | tr '\n' ' ')

    # Server release notes, might be in catalina_home only.
    cp "$catalina_home"/RELEASE-NOTES "$server_dir"
    cp "$catalina_path"/RELEASE-NOTES "$server_dir"

    # Application can be directory or war.
    # We run on all the appBase using the Host tag.
    # app_base can be absolute or relative to the catalina_path.
    apps_paths=""
    while read -r line; do
      if [[ "$line" = /* ]]; then
        app_base_path=$line # Absolute.
      else
        app_base_path=$catalina_path/$line # Relative.
      fi
      find -L "$app_base_path" -maxdepth 1 -mindepth 1 -type d -o -name "*.war" | xargs realpath >>"$server_dir/applications_list"
    done < <(echo "$clean_server_xml" | grep -Po "<Host(.*)</Host>" | grep -Po "appBase=\K[^ ]*" | tr -d '"' | sort --unique)

    # Copy setenv.sh.
    cp "$catalina_path/bin/setenv.sh" "$server_dir/setenv.sh"

    # List web xmls.
    web_xmls=$server_dir/web_xmls.txt
    find -L "$catalina_path" -name web.xml >"$web_xmls"

    # Copy web xmls.
    web_xmls_dir=$server_dir/web_xmls
    mkdir -p "$web_xmls_dir"
    web_xmls_count=0
    while read -r web_xml; do
      web_xmls_count=$(($web_xmls_count + 1))
      cat "$web_xml" >"$web_xmls_dir/$web_xmls_count"
    done <"$web_xmls"
  done < <(ps -o pid,user,%mem,uid,comm,args -e | grep -i catalina | grep -v grep)

  # Store the results in a zip file for loading as a single artifact.
  tar -C "$tmp" -cf "$tmp/tomcat.tar" "tomcat"
  rm -rf "$output_dir"
}

# Collect information on running jboss servers, including java args,
# installation homes, server and applications configurations.
function collect_jboss_information() {
  output_dir="$tmp/jboss"
  mkdir "$output_dir"

  count=0
  while read -r line; do
    count=$(($count + 1))
    pid=$(echo "$line" | cut -d" " -f1)

    # Server process info.
    server_dir=$output_dir/$count
    mkdir -p "$server_dir"
    ps -o pid,user,%mem,uid,comm,args --no-headers -p "$pid" >"$server_dir/jboss-ps"

    # Get java version.
    java_path=$(readlink "/proc/$pid/exe" || cat "/proc/$pid/cmdline" | xargs -0 --max-args=1 | head -n1 )

    # Java -version output going to stderr.
    echo "$java_path" > "$server_dir"/jboss-java-path
    "$java_path" -version 2> "$server_dir"/jboss-java-version

    # Extract the JBoss configuration xml file.
    jboss_server_base_dir=$(echo "$line" | grep -Po "jboss.server.base.dir=\K[^ ]*")
    jboss_config_file=$(echo "$line" | grep -Po "\-c \K[^ ]*")
    if [[ "$jboss_config_file" == "" ]]; then
      echo "$line" | grep -iq standalone && jboss_config_file=standalone.xml
    fi
    jboss_config_file_full="$jboss_server_base_dir/configuration/$jboss_config_file"
    cat "$jboss_config_file_full" >"$server_dir"/jboss-config.xml

    # Extract version by running the original command with "--version".
    jbossargs=$(ps -o args -p "$pid" | tail -n1)
    # Remove -Xms and -Xmx to reduce memory footprint when running "--version".
    low_memory_args=$(echo "$jbossargs" | sed --expression='s/-Xm[x,s][[:digit:]]*[k,m,g]//g')
    # shellcheck disable=SC2086
    timeout -k 10s 10s $low_memory_args --version > "$server_dir"/jboss-version

    # Extract the JBoss internal state using "jboss-cli.sh".
    jboss_home_dir=$(echo "$line" | grep -Po "jboss.home.dir=\K[^ ]*")
    jboss_bind_address=$(echo "$line" | grep -Po "\-b \K[^ ]*")

    timeout -k 10s 10s "$jboss_home_dir/bin/jboss-cli.sh" --connect --bind="$jboss_bind_address" "/:read-resource(recursive=true)" > "$server_dir/jboss-cli-resource"
    timeout -k 10s 10s "$jboss_home_dir/bin/jboss-cli.sh" --connect --bind="$jboss_bind_address" "ls deployment" > "$server_dir/jboss-deployment"

    # Extract process information from /proc/PID directory.
    cat "/proc/$pid/environ" | tr '\0' '\n' > "$server_dir/jboss-env"
    ls -l "/proc/$pid/fd" > "$server_dir/jboss-fd"
    cat "/proc/$pid/limits" > "$server_dir/jboss-limits"
    cat "/proc/$pid/status" > "$server_dir/jboss-status"
    cat "/proc/$pid/cmdline" > "$server_dir/jboss-cmdline"

  done < <(ps -o pid,user,%mem,uid,comm,args -e | grep -i "jboss.home.dir" | grep java | grep -v grep)
}

# Collect WebSphere Application Servers information.
function collect_websphere_information() {
  output_dir="$tmp/websphere"
  mkdir "$output_dir"

  liberty_output_dir="$output_dir/liberty"
  mkdir "$liberty_output_dir"

  liberty_installation_path_patterns=(
    "/opt/ol/wlp"
    "/usr/ol/wlp"
    "/etc/ol/wlp"
    "/opt/ibm/wlp"
    "/opt/IBM/wlp"
    "/usr/ibm/wlp"
    "/usr/IBM/wlp"
    "/etc/ibm/wlp"
    "/etc/IBM/wlp"
    "/opt/ibm/WebSphere/Liberty"
    "/opt/IBM/WebSphere/Liberty"
    "/usr/ibm/WebSphere/Liberty"
    "/usr/IBM/WebSphere/Liberty"
    "/etc/ibm/WebSphere/Liberty"
    "/etc/IBM/WebSphere/Liberty"
    "/opt/*/ol/wlp",
    "/usr/*/ol/wlp",
    "/etc/*/ol/wlp",
    "/opt/*/ibm/wlp",
    "/opt/*/IBM/wlp",
    "/usr/*/ibm/wlp",
    "/usr/*/IBM/wlp",
    "/etc/*/ibm/wlp",
    "/etc/*/IBM/wlp",
    "/opt/*/ibm/WebSphere/Liberty",
    "/opt/*/IBM/WebSphere/Liberty",
    "/usr/*/ibm/WebSphere/Liberty",
    "/usr/*/IBM/WebSphere/Liberty",
    "/etc/*/ibm/WebSphere/Liberty",
    "/etc/*/IBM/WebSphere/Liberty",
  )

  count=0
  for pattern in "${liberty_installation_path_patterns[@]}"; do
    for installation_path in $pattern; do
      if [[ -d "$installation_path" ]]; then

        mkdir -p "$liberty_output_dir/$count"
        echo "$installation_path" > "$liberty_output_dir/$count/installation-path"

        version_file_pattern="$installation_path/lib/versions/*.properties"
        for version_file in $version_file_pattern; do
          cat "$version_file" > "$liberty_output_dir/$count/version"
        done

        count=$(($count + 1))

      fi
    done
  done

  traditional_output_dir="$output_dir/traditional"
  mkdir "$traditional_output_dir"

  traditional_installation_path_patterns=(
    "/opt/ibm/WebSphere/AppServer"
    "/opt/IBM/WebSphere/AppServer"
    "/usr/ibm/WebSphere/AppServer"
    "/usr/IBM/WebSphere/AppServer"
    "/opt/*/ibm/WebSphere/AppServer"
    "/opt/*/IBM/WebSphere/AppServer"
    "/usr/*/ibm/WebSphere/AppServer"
    "/usr/*/IBM/WebSphere/AppServer"
  )

  count=0
  for pattern in "${traditional_installation_path_patterns[@]}"; do
    for installation_path in $pattern; do
      if [[ -d "$installation_path" ]]; then

        mkdir -p "$traditional_output_dir/$count"
        echo "$installation_path" > "$traditional_output_dir/$count/installation-path"

        version_file_pattern="$installation_path/properties/version/WAS.product"
        for version_file in $version_file_pattern; do
          cat "$version_file" > "$traditional_output_dir/$count/version"
        done

        count=$(($count + 1))

      fi
    done
  done

}

# Collect information on wordpress servers.
function collect_wordpress_information() {
  output_dir="$tmp/wordpress"
  mkdir "$output_dir"

  for fileglob in "${APACHE_CONF_FILES[@]}"; do
    # nullglob is set above, so if this doesn't match a file we will skip the loop entirely.
    for file in $fileglob; do
      while read -r line; do
        collect_single_filecommand "$line/wp-includes/version.php" "$output_dir"
        ls "$line/wp-content/plugins/" > "$output_dir/$line/plugin-list"
      done < <(cat "$file" | grep -i documentRoot | cut -f2 -d" ")
    done
  done
}

function collect_common_metadata() {
  full_collect_date="$(date --iso-8601=sec -u | sed 's/+00:00/.000Z/g')"
  hostname >"$tmp"/machinename
  echo "$full_collect_date" >"$tmp"/timestamp
  echo Linux >"$tmp"/ostype
  echo "$SCRIPT_GIT_HASH" >"$tmp"/version
  echo "full" >"$tmp"/collection_mode
  whoami | tr -d '\n' >"$tmp"/whoami
}

# Extracts status code and response body from curl result.
# Returns 0 and echos response's body only if HTTP status code is 200.
function validate_http_200() {
  if [[ $# -ne 1 ]]; then
    echo "validate_http_200: expected one argument " >>"$tmp/collect.log"
    return 1
  fi

  http_code_with_prefix=$(echo "${1}" | grep -Eo "${CURL_CODE_PREFIX}[[:digit:]]{3}")
  http_code="${http_code_with_prefix//${CURL_CODE_PREFIX}/}"
  http_response="${1//${http_code_with_prefix}/}"

  echo "HTTP code: $http_code" >>"$tmp/collect.log"
  if [[ "$http_code" != "200" ]]; then
    return 1
  else
    echo "$http_response"
    return 0
  fi
}

# Returns 0 only if was able to get AWS IMDS identity document using a token.
function check_aws_imdsv2() {
  RESPONSE=$(curl "${CURL_ARGS[@]}" "${CURL_WRITE_OUT[@]}" -X PUT http://169.254.169.254/latest/api/token -H "X-aws-ec2-metadata-token-ttl-seconds: $AWS_IMDSV2_TOKEN_TTL_SECONDS")
  AWS_IMDS_TOKEN=$(validate_http_200 "$RESPONSE")
  if [[ $? -ne 0 ]] || [[ -z "$AWS_IMDS_TOKEN" ]]; then
    return 1
  fi

  RESPONSE=$(curl "${CURL_ARGS[@]}" "${CURL_WRITE_OUT[@]}" -H "X-aws-ec2-metadata-token: $AWS_IMDS_TOKEN" http://169.254.169.254/latest/dynamic/instance-identity/document)
  IDENTITY=$(validate_http_200 "$RESPONSE")
}

# Returns 0 only if was able to get AWS IMDS identity document without a token.
function check_aws_imdsv1() {
  RESPONSE=$(curl "${CURL_ARGS[@]}" "${CURL_WRITE_OUT[@]}" http://169.254.169.254/latest/dynamic/instance-identity/document)
  IDENTITY=$(validate_http_200 "$RESPONSE")
}

# Tries checking supported AWS IMDS version.
# If at least one is supported, collects all aws_metadata_categories with it and the AWS IMDS identity document.
function collect_aws_metadata() {
  if check_aws_imdsv2; then
    echo "- IMDSv2 is available -" >>"$tmp/collect.log"
    header="-H \"X-aws-ec2-metadata-token: $AWS_IMDS_TOKEN\""
  elif check_aws_imdsv1; then
    echo "- IMDSv1 is available -" >>"$tmp/collect.log"
    header=
  else
    return
  fi

  if [[ $FORCE_IMDSV1 -eq 1 ]]; then
    echo "- forcing IMDSv1 -" >>"$tmp/collect.log"
    header=
  fi

  aws_dir="$tmp/aws"
  mkdir "$aws_dir"

  echo "$IDENTITY" >>"$aws_dir/instance_identity_document.json"

  for category in "${aws_metadata_categories[@]}"; do
    target_file="$aws_dir/$category"
    category_url="http://169.254.169.254/latest/meta-data/$category"
    echo "- aws metadata: $category -" >>"$tmp/collect.log"
    "$SHELL" -c "curl ${CURL_ARGS[*]} $header $category_url" >"$target_file" 2>>"$tmp/collect.log"
    echo "$?" >"$target_file.exitcode"
  done
}

function check_azure_metadata() {
  RESPONSE=$(curl "${CURL_ARGS[@]}" "${CURL_WRITE_OUT[@]}" "http://169.254.169.254/metadata/instance/compute/vmId?api-version=2017-04-02&format=text")
  METADATA_KEYS=$(validate_http_200 "$RESPONSE")
}

function collect_azure_metadata() {
  if ! check_azure_metadata; then
    return
  fi

  azure_dir="$tmp/azure"
  mkdir "$azure_dir"
  "$SHELL" -c "curl -H Metadata:true \"http://169.254.169.254/metadata/instance/compute/vmId?api-version=2017-04-02&format=text\"" > "$azure_dir/vm_id" 2>>"$tmp/collect.log"
}

# Returns 0 only if was able to get Compute Engine instance metadata keys.
function check_gce_metadata() {
  RESPONSE=$(curl "${CURL_ARGS[@]}" "${CURL_WRITE_OUT[@]}" "http://metadata.google.internal/computeMetadata/v1/instance/" -H "Metadata-Flavor: Google")
  METADATA_KEYS=$(validate_http_200 "$RESPONSE")
}

# Tries collecting Compute Engine metadata.
function collect_gce_metadata() {
  if ! check_gce_metadata; then
    return
  fi

  gce_dir="$tmp/gce"
  mkdir "$gce_dir"

  for key in "${gce_metadata_keys[@]}"; do
    target_file="$gce_dir/$key"
    metadata_url="http://metadata.google.internal/computeMetadata/v1/instance/$key"
    echo "- Compute Engine metadata: $key -" >>"$tmp/collect.log"
    "$SHELL" -c "curl ${CURL_ARGS[*]} -H \"Metadata-Flavor: Google\" $metadata_url" >"$target_file" 2>>"$tmp/collect.log"
    echo "$?" >"$target_file.exitcode"
  done
}

function pack_collected_data() {
  collect_date="$(date +%Y-%m-%d-%H-%M)"
  if [[ -z "$OUTPUT_FILE" ]]; then
    result_filename="mcdc-collect-$(hostname)-$collect_date.tar"
  else
    out_dir=$(dirname "$OUTPUT_FILE")
    mkdir -p "$out_dir"
    result_filename="$OUTPUT_FILE"
  fi

  # Tar command runs as the current user (which will be root if the script elevated via sudo).
  # This ensures it has permission to read all collected files in $tmp.
  tar czf "$result_filename" -C "$tmp" .
  tar_exit_code=$?

  if [[ $tar_exit_code -ne 0 ]]; then
    echo "Failed to create $result_filename (tar exit code: $tar_exit_code)" >>"$tmp/collect.log"
    echo >&2 "Couldn't create $result_filename"
    exit 1
  fi

  # If the script is running as root (EUID is 0),
  # explicitly set the permissions of the created tar file to be world-readable (644).
  # The original non-root user will then be able to download it via scp.
  # If the script is not running as root (e.g. sudo failed), the file is already owned by the user.
  # In that case, chmod 644 also ensures the owner can read it.
  echo "Setting permissions for $result_filename to be readable by owner and others (644)" >>"$tmp/collect.log"
  chmod 644 "$result_filename"
  if [[ $? -ne 0 ]]; then
    echo "Warning: Failed to chmod $result_filename. Download might still fail." >>"$tmp/collect.log"
  fi

  if [[ -z "$SKIP_PRINT_FILE_LOCATION" ]]; then
    echo "Collected info saved to: $result_filename"
  fi
  # $tmp/collect.log file might not exist by the time this fails
  # so make sure it's completely silent and error free.
  rm -rf "$tmp" >/dev/null 2>&1 || true
}

function print_stats() {
  local stage
  stage="$1"

  # Check if statistics collection is enabled
  if [[ -z "$PRINT_STATS" && -z "$STATS_TO_FILE" ]]; then
    return
  fi

  # Get CPU and memory usage of the script. See `man 5 proc` for fields definitions.
  proc_stat=($(sed -E 's/\([^)]+\)/X/' "/proc/$$/stat"))
  utime=${proc_stat[15]} # user space time
  stime=${proc_stat[16]} # kernel space time
  clock_tick=$(getconf CLK_TCK)
  let cpu_time="($utime + $stime) / $clock_tick"
  memory_usage=$(grep "VmPeak" /proc/$$/status | awk '{print $2}')
  stat_line="ScriptStats: $(date '+%Y-%m-%d %H:%M:%S'),$stage,$cpu_time,$memory_usage"

  # Print stats to console or log to file
  if [[ -n "$PRINT_STATS" ]]; then
    echo "$stat_line"
  elif [[ -n "$STATS_TO_FILE" ]]; then
    local log_file
    log_file=$(dirname "$OUTPUT_FILE")/script-stats.log
    echo "$stat_line" >> "$log_file"
  fi
}

main() {
  print_stats "start"
  echo "--- file dumps -----" >>"$tmp/collect.log"
  (collect_file_dumps) 2>>"$tmp/collect.log"
  print_stats "file_dumps"
  echo "--- commands -----" >>"$tmp/collect.log"
  (collect_command_outputs) 2>>"$tmp/collect.log"
  print_stats "commands"
  echo "--- nics information -----" >>"$tmp/collect.log"
  (collect_nics_information) 2>>"$tmp/collect.log"
  print_stats "nics_information"
  echo "--- tomcat information -----" >>"$tmp/collect.log"
  (collect_tomcat_information) 2>>"$tmp/collect.log"
  print_stats "tomcat_information"
  echo "--- jboss information -----" >>"$tmp/collect.log"
  (collect_jboss_information) 2>>"$tmp/collect.log"
  print_stats "jboss_information"
  echo "--- websphere information -----" >>"$tmp/collect.log"
  (collect_websphere_information) 2>>"$tmp/collect.log"
  print_stats "websphere_information"
  echo "--- wordpress information -----" >>"$tmp/collect.log"
  (collect_wordpress_information) 2>>"$tmp/collect.log"
  print_stats "wordpress_information"
  echo "--- common metadata -----" >>"$tmp/collect.log"
  (collect_common_metadata) 2>>"$tmp/collect.log"
  print_stats "common_metadata"
  echo "--- AWS metadata -----" >>"$tmp/collect.log"
  (collect_aws_metadata) 2>>"$tmp/collect.log"
  print_stats "aws_metadata"
  echo "--- Azure metadata -----" >>"$tmp/collect.log"
  (collect_azure_metadata) 2>>"$tmp/collect.log"
  print_stats "azure_metadata"
  echo "--- Compute Engine metadata -----" >>"$tmp/collect.log"
  (collect_gce_metadata) 2>>"$tmp/collect.log"
  print_stats "gce_metadata"
  echo "--- pack data -----" >>"$tmp/collect.log"
  pack_collected_data 2>>"$tmp/collect.log"
  print_stats "pack_data"
}

main
