#!/bin/bash
#
# Generate Sysdump
# creates a snapshot of system state for debugging later.
#

set -u

EXT_SUCCESS=0
EXT_GENERAL=1
EXT_LOCKFAIL=2
EXT_RECVSIG=3
EXT_RETRY=4
EXT_TAR_FAILED=5
EXT_PROCFS_SAVE_FAILED=6
EXT_INTERRUPTED=7
EXT_TERMINATED=8
EXT_INVALID_ARGUMENT=10

TIMEOUT_EXIT_CODE=124

TAR=tar
MKDIR=mkdir
RM=rm
LN=ln
GZIP=gzip
CP=cp
MV=mv
GREP=grep
TOUCH=touch
V=
ALLOW_PROCESS_STOP=
NOOP=false
DO_COMPRESS=true
CMD_PREFIX=
SINCE_DATE="@0" # default is set to January 1, 1970 at 00:00:00 GMT
REFERENCE_FILE=/tmp/reference
TECHSUPPORT_TIME_INFO=`mktemp "/tmp/techsupport_time_info.XXXXXXXXXX"`
BASE=sonic_dump_`hostname`_`date +%Y%m%d_%H%M%S`
DUMPDIR=/var/dump
TARDIR=$DUMPDIR/$BASE
TARFILE=$DUMPDIR/$BASE.tar
LOGDIR=$DUMPDIR/$BASE/dump
PLUGINS_DIR=/usr/local/bin/debug-dump
NUM_ASICS=1
HOME=${HOME:-/root}
USER=${USER:-root}
TIMEOUT_MIN="5"
SKIP_BCMCMD=0
SAVE_STDERR=true
RETURN_CODE=$EXT_SUCCESS
DEBUG_DUMP=false
ROUTE_TAB_LIMIT_DIRECT_ITERATION=24000
IS_SUPERVISOR=false

# lock dirs/files
LOCKDIR="/tmp/techsupport-lock"
PIDFILE="${LOCKDIR}/PID"

# Remove lock directory and exit, let user decide if they want to retry
rm_lock_and_exit()
{
    $RM $V -rf ${LOCKDIR}
    exit $EXT_RETRY
}

handle_exit()
{
    ECODE=$?
    echo "Cleaning up working directory $TARDIR"
    $RM -rf $TARDIR
    echo "Removing lock. Exit: $ECODE" >&2
    $RM $V -rf ${LOCKDIR}
    # Echo the filename as the last statement if the generation succeeds
    if [[ -f $TARFILE && ($ECODE == $EXT_SUCCESS || $ECODE == $RETURN_CODE) ]]; then
        echo $TARFILE
    fi
}

handle_sigint()
{
    echo "Generate Dump received interrupt" >&2
    exit $EXT_INTERRUPTED
}

handle_sigterm() {
    echo "Dump generation terminated" >&2
    finalize
    exit $EXT_TERMINATED
}

handle_error() {
    if [ "$1" != "0" ]; then
        echo "ERR: RC:-$1 observed on line $2" >&2
        RETURN_CODE=$EXT_GENERAL
    fi
}

escape_quotes() {
    echo $1 | sed 's/\"/\\\"/g'
}

save_bcmcmd() {
    trap 'handle_error $? $LINENO' ERR
    local start_t=$(date +%s%3N)
    local end_t=0
    local cmd="$1"
    local filename=$2
    local filepath="${LOGDIR}/$filename"
    local do_gzip=${3:-false}
    local timeout_cmd="timeout --foreground ${TIMEOUT_MIN}m"
    local cmd=$(escape_quotes "$cmd")
    if [ ! -d $LOGDIR ]; then
        $MKDIR $V -p $LOGDIR
    fi

    if [ $SKIP_BCMCMD -eq 1 ]; then
        echo "Skip $cmd"
        return 0
    fi
    # eval required here to re-evaluate the $cmd properly at runtime
    # This is required if $cmd has quoted strings that should be bunched
    # as one argument, e.g. vtysh -c "COMMAND HERE" needs to have
    # "COMMAND HERE" bunched together as 1 arg to vtysh -c
    if $NOOP; then
        echo "${timeout_cmd} bash -c \"${cmd}\" &> '${filepath}'"
    else
        ret=0
        eval "${timeout_cmd} bash -c \"${cmd}\" &> '${filepath}'" || ret=$?
        if [ $ret -ne 0 ]; then
            if [ $ret -eq $TIMEOUT_EXIT_CODE ]; then
                echo "Command: $cmd timedout after ${TIMEOUT_MIN} minutes."
            else
                RC=0
                grep "polling socket timeout: Success" ${filepath} &>/dev/null || RC=$?
                if [ $RC -eq 0 ]; then
                    echo "bcmcmd command timeout. Setting SKIP_BCMCMD to true ..."
                    SKIP_BCMCMD=1
                fi
            fi
        fi
    fi
    if $do_gzip; then
        gzip ${filepath} 2>/dev/null
        filepath="${filepath}.gz"
    fi

    end_t=$(date +%s%3N)
    echo "[ save_bcmcmd:$cmd ] : $(($end_t-$start_t)) msec" >> $TECHSUPPORT_TIME_INFO
}

###############################################################################
# Runs a given bcmcmd command in all namesapces in case of multi ASIC platform
# Globals:
#  NUM_ASICS
# Arguments:
#  cmd: The command to run. Make sure that arguments with spaces have quotes
#  filename: the filename to save the output as in $BASE/dump
#  do_gzip: (OPTIONAL) true or false. Should the output be gzipped
# Returns:
#  None
###############################################################################
save_bcmcmd_all_ns() {
    trap 'handle_error $? $LINENO' ERR
    local do_gzip=${3:-false}

    if [[ ( "$NUM_ASICS" > 1 ) ]]; then
        for (( i=0; i<$NUM_ASICS; i++ ))
        do
            local cmd="bcmcmd -n $i $1"
            local file="$2.$i"
            save_bcmcmd "$cmd" "$file" "$do_gzip"
        done
    else
        local cmd="bcmcmd $1"
        save_bcmcmd "$cmd" "$2" "$do_gzip"
    fi
}

###############################################################################
# Runs a comamnd and saves its output to the file.
# Command gets timedout if it runs for more than TIMEOUT_MIN minutes.
# Globals:
#  LOGDIR
#  BASE
#  MKDIR
#  TAR
#  TARFILE
#  DUMPDIR
#  V
#  RM
#  NOOP
# Arguments:
#  cmd: The command to run. Make sure that arguments with spaces have quotes
#  filename: the filename to save the output as in $BASE/dump
#  do_gzip: (OPTIONAL) true or false. Should the output be gzipped
#  cleanup_method: (OPTIONAL) the cleanup method to procress dump file after it generated.
# Returns:
#  None
###############################################################################
save_cmd() {
    trap 'handle_error $? $LINENO' ERR
    local start_t=$(date +%s%3N)
    local end_t=0
    local cmd="$1"
    local filename=$2
    local filepath="${LOGDIR}/$filename"
    local do_gzip=${3:-false}
    local timeout_cmd="timeout --foreground ${TIMEOUT_MIN}m"
    local cleanup_method=${4:-dummy_cleanup_method}
    local redirect='&>'
    local redirect_eval='2>&1'
    if [ ! -d $LOGDIR ]; then
        $MKDIR $V -p $LOGDIR
    fi

    if ! $SAVE_STDERR
    then
        redirect=">"
        redirect_eval=""
    fi

    local cmd=$(escape_quotes "$cmd")
    local cleanup_method_declration=$(declare -f $cleanup_method)
    # eval required here to re-evaluate the $cmd properly at runtime
    # This is required if $cmd has quoted strings that should be bunched
    # as one argument, e.g. vtysh -c "COMMAND HERE" needs to have
    # "COMMAND HERE" bunched together as 1 arg to vtysh -c
    if $do_gzip; then
        filepath="${filepath}.gz"
        # cleanup_method will run in a sub-shell, need declare it first
        local cmds="$cleanup_method_declration; $cmd $redirect_eval | $cleanup_method | gzip -c > '${filepath}'"
        if $NOOP; then
            echo "${timeout_cmd} bash -c \"${cmds}\""
        else
            RC=0
            eval "${timeout_cmd} bash -c \"${cmds}\"" || RC=$?
            if [ $RC -eq $TIMEOUT_EXIT_CODE ]; then
                echo "Command: $cmds timedout after ${TIMEOUT_MIN} minutes."
            elif [ $RC -ne 0 ]; then
                echo "Command: $cmds failed with RC $RC"
            fi
        fi
    else
        local cmds="$cleanup_method_declration; $cmd | $cleanup_method $redirect '$filepath'"
        if $NOOP; then
            echo "${timeout_cmd} bash -c \"${cmds}\""
        else
            RC=0
            eval "${timeout_cmd} bash -c \"${cmds}\"" || RC=$?
            if [ $RC -eq $TIMEOUT_EXIT_CODE ]; then
                echo "Command: $cmds timedout after ${TIMEOUT_MIN} minutes."
            elif [ $RC -ne 0 ]; then
                echo "Command: $cmds failed with RC $RC"
            fi
        fi
    fi

    end_t=$(date +%s%3N)
    echo "[ save_cmd:$cmd ] : $(($end_t-$start_t)) msec" >> $TECHSUPPORT_TIME_INFO
}

###############################################################################
# Save all collected data to tar archive.
# Globals:
#  DUMPDIR
#  TAR
#  TARFILE
#  V
#  BASE
# Arguments:
#  None
# Returns:
#  None
###############################################################################
save_to_tar() {
    trap 'handle_error $? $LINENO' ERR
    local start_t=$(date +%s%3N)
    local end_t=0

    $TAR $V -rhf $TARFILE -C $DUMPDIR "$BASE"

    end_t=$(date +%s%3N)
    echo "[ save_to_tar ] : $(($end_t-$start_t)) msec" >> $TECHSUPPORT_TIME_INFO
}

###############################################################################
# Dummy cleanup method.
# Globals:
#  None
# Arguments:
#  None
# Returns:
#  None
###############################################################################
dummy_cleanup_method() {
    cat
}

###############################################################################
# Runs a given command in all namesapces in case of multi ASIC platform, in
# default (host) namespace in single ASIC platform
# Globals:
#  NUM_ASICS
# Arguments:
#  cmd: The command to run. Make sure that arguments with spaces have quotes
#  filename: the filename to save the output as in $BASE/dump
#  do_gzip: (OPTIONAL) true or false. Should the output be gzipped
#  cleanup_method: (OPTIONAL) the cleanup method to procress dump file after it generated.
# Returns:
#  None
###############################################################################
save_cmd_all_ns() {
    trap 'handle_error $? $LINENO' ERR
    local do_zip=${3:-false}
    local cleanup_method=${4:-dummy_cleanup_method}

    # host or default namespace
    save_cmd "$1" "$2" "$do_zip" $cleanup_method

    if [[ ( "$NUM_ASICS" > 1 ) ]] ; then
      for (( i=0; i<$NUM_ASICS; i++ ))
      do
          local cmd="sonic-netns-exec asic$i $1"
          local file="$2.$i"
          save_cmd "$cmd" "$file" "$do_zip" $cleanup_method
      done
    fi
}

###############################################################################
# Copies a given file from a specified docker to the given target location
# default (host) namespace in single ASIC platform
# Globals:
#  None
# Arguments:
#  docker: docker name
#  filename: the filename to copy
#  destination: destination filename
# Returns:
#  None
###############################################################################
copy_from_docker() {
    trap 'handle_error $? $LINENO' ERR
    local start_t=$(date +%s%3N)
    local end_t=0
    local docker=$1
    local filename=$2
    local dstpath=$3
    local timeout_cmd="timeout --foreground ${TIMEOUT_MIN}m"

    local touch_cmd="sudo docker exec ${docker} touch ${filename}"
    local cp_cmd="sudo docker cp ${docker}:${filename} ${dstpath}"

    if $NOOP; then
        echo "${timeout_cmd} ${touch_cmd}"
        echo "${timeout_cmd} ${cp_cmd}"
    else
        RC=0
        eval "${timeout_cmd} ${touch_cmd}" || RC=$?
        if [ $RC -ne 0 ]; then
            echo "Command: $touch_cmd timedout after ${TIMEOUT_MIN} minutes."
        fi
        eval "${timeout_cmd} ${cp_cmd}" || RC=$?
        if [ $RC -ne 0 ]; then
            echo "Command: $cp_cmd timedout after ${TIMEOUT_MIN} minutes."
        fi
    fi
    end_t=$(date +%s%3N)
    echo "[ copy_from_docker:${docker}:${filename} ] : $(($end_t-$start_t)) msec" \
        >> $TECHSUPPORT_TIME_INFO
}

###############################################################################
# Copies a given file from a specified docker to the given target location
# default (host) namespace in single ASIC platform
# Globals:
#  NUM_ASICS
# Arguments:
#  docker: docker name
#  filename: the filename to copy
#  destination: destination filename
# Returns:
#  None
###############################################################################
copy_from_masic_docker() {
    trap 'handle_error $? $LINENO' ERR
    local docker=$1
    local filename=$2
    local dstpath=$3

    if [[ ("$NUM_ASICS" > 1) ]]; then
        for (( i=0; i<$NUM_ASICS; i++ ))
        do
            copy_from_docker "$docker$i" "$filename" "$dstpath.$i"
        done
    else
        copy_from_docker "$docker" "$filename" "$dstpath"
    fi
}

###############################################################################
# Returns namespace option to be used with vtysh commmand, based on the ASIC ID.
# Returns empty string if no ASIC ID is provided
# Globals:
#  None
# Arguments:
#  asic_id: (OPTIONAL) ASIC ID
# Returns:
#  vtysh namespace option
###############################################################################
get_vtysh_namespace() {
    trap 'handle_error $? $LINENO' ERR
    local asic_id=${1:-""}
    local ns=""
    if [[ ( $asic_id = "" ) ]] ; then
        ns=""
    else
        ns=" -n  ${asic_id}"
    fi
    echo "$ns"
}

###############################################################################
# Runs a vtysh command in all namesapces for a multi ASIC platform, and in
# default (host) namespace in single ASIC platforms. Saves its output to the
# file.
# Globals:
#  None
# Arguments:
#  cmd: the vtysh command to run. This should NOT include vtysh -c
#  filename: The filename to save the output as.
#  do_gzip: (OPTIONAL) true or false. Should the output be gzipped
# Returns:
#  None
###############################################################################
save_vtysh() {
    trap 'handle_error $? $LINENO' ERR
    local vtysh_cmd=$1
    local filename=$2
    local do_gzip=${3:-false}

    if [[ ( "$NUM_ASICS" == 1 ) ]] ; then
        save_cmd "vtysh -c '${vtysh_cmd}'" "$filename" $do_gzip
    else
        for (( i=0; i<$NUM_ASICS; i++ ))
        do
            ns_cmd=$(get_vtysh_namespace $i)
            local cmd="vtysh $ns_cmd -c '${vtysh_cmd}'"
            local file=$filename.$i
            save_cmd "$cmd" "$file" "$do_gzip"
        done
    fi
}

###############################################################################
# Runs an ip command and saves its output to the file.
# Globals:
#  None
# Arguments:
#  cmd: the ip command to run sans 'ip'
#  filename: Files will be named 'ip.<filename>'
#  do_gzip: (OPTIONAL) true or false. Should the output be gzipped
# Returns:
#  None
###############################################################################
save_ip() {
    trap 'handle_error $? $LINENO' ERR
    local ip_args=$1
    local filename="ip.$2"
    local do_gzip=${3:-false}
    save_cmd_all_ns "ip $ip_args" "$filename" "$do_gzip"
}

###############################################################################
# Runs a bridge command and saves its output to the file.
# Globals:
#  None
# Arguments:
#  cmd: the bridge command to run sans 'bridge'
#  filename: Files will be named 'bridge.<filename>'
#  do_gzip: (OPTIONAL) true or false. Should the output be gzipped
# Returns:
#  None
###############################################################################
save_bridge() {
    trap 'handle_error $? $LINENO' ERR
    local br_args=$1
    local filename="bridge.$2"
    local do_gzip=${3:-false}
    save_cmd_all_ns "bridge $br_args" "$filename" $do_gzip
}

###############################################################################
# Dump the bridge L2 information
# Globals:
#  None
# Arguments:
#  None
# Returns:
#  None
###############################################################################
save_bridge_info() {
    trap 'handle_error $? $LINENO' ERR
    save_bridge "fdb show" "fdb"
    save_bridge "vlan show" "vlan"
}

###############################################################################
# Iterates all neighbors and runs save_vtysh to save each neighbor's
# advertised-routes and received-routes
# On multi ASIC platform, collects information from all namespaces
# Globals:
#  None
# Arguments:
#  Optional arg namespace
# Returns:
#  None
###############################################################################
save_bgp_neighbor() {
    trap 'handle_error $? $LINENO' ERR
    local timeout_cmd="timeout --foreground ${TIMEOUT_MIN}m"
    local asic_id=${1:-""}
    local ns=$(get_vtysh_namespace $asic_id)

    neighbor_list_v4=$(${timeout_cmd} bash -c "vtysh $ns -c 'show ip bgp neighbors' | grep 'BGP neighbor is' | awk -F '[, ]' '{print \$4}' | awk /\\\./")
    if [ ! -z "$neighbor_list_v4" ]; then
        v4_cmd="vtysh "
        for word in $neighbor_list_v4; do
            v4_cmd="${v4_cmd} $ns -Ec 'show bgp ipv4 neighbors $word advertised-routes' "
            v4_cmd="${v4_cmd} $ns -Ec 'show bgp ipv4 neighbors $word routes' "
        done
        save_cmd "$v4_cmd" "ip.bgp.neigh.adv.rcv.routes"
    fi

    neighbor_list_v6=$(${timeout_cmd} bash -c "vtysh $ns -c 'show bgp ipv6 neighbors' | grep 'BGP neighbor is' | awk -F '[, ]' '{print \$4}' | awk /:/")
    if [ ! -z "$neighbor_list_v6" ]; then
        v6_cmd="vtysh "
        for word in $neighbor_list_v6; do
            v6_cmd="${v6_cmd} $ns -Ec 'show bgp ipv6 neighbors $word advertised-routes' "
            v6_cmd="${v6_cmd} $ns -Ec 'show bgp ipv6 neighbors $word routes' "
        done
        save_cmd "$v6_cmd" "ipv6.bgp.neigh.adv.rcv.routes"
    fi

    vrf_list=""
    vrf_output=$(${timeout_cmd} bash -c "vtysh $ns -c 'show vrf'")
    if [ ! -z $vrf_output]; then
        vrf_list= echo $vrf_output | awk -F" " '{print $2}'
    fi

    if [ ! -z "$vrf_list" ]; then
        vrf_cmd="vtysh "
        for vrf in $vrf_list; do
            neighbor_list=`${timeout_cmd} bash -c "vtysh $ns -c 'show ip bgp vrf $vrf neighbors' | grep 'BGP neighbor is' | awk -F '[, ]' '{print \$4}'"`
            for word in $neighbor_list; do
                vrf_cmd="${vrf_cmd} $ns -Ec 'show ip bgp vrf $vrf neighbors $word advertised-routes' "
                vrf_cmd="${vrf_cmd} $ns -Ec 'show ip bgp vrf $vrf neighbors $word routes' "
            done
        done
        save_cmd "$vrf_cmd" "ip.bgp.neigh.vrf.adv.rcv.routes"
    fi
}

###############################################################################
# Iterates all EVPN neighbors and runs save_vtysh to save each neighbor's
# advertised-routes and received-routes
# On multi ASIC platform, collects information from all namespaces
# Globals:
#  None
# Arguments:
#  Optional arg namespace
# Returns:
#  None
###############################################################################
save_bgp_evpn_neighbor() {
    trap 'handle_error $? $LINENO' ERR
    local timeout_cmd="timeout --foreground ${TIMEOUT_MIN}m"
    local asic_id=${1:-""}
    local ns=$(get_vtysh_namespace $asic_id)

    evpn_neighbors=$(${timeout_cmd} bash -c "vtysh -c 'show bgp l2vpn evpn summary' | cut -d ' ' -f1")
    local parse_neighbors=false
    for word in $evpn_neighbors; do
        if [[ $word == "Neighbor" ]]; then
            parse_neighbors=true
            continue
        elif [[ $word == "Total" ]]; then
            parse_neighbors=false
            continue
        fi
        if [ "$parse_neighbors" = true ]; then
            save_cmd "vtysh $ns -c \"show bgp l2vpn evpn neighbors $word advertised-routes\"" "bgp.evpn.neighbor.$word.adv$asic_id"
            save_cmd "vtysh $ns -c \"show bgp l2vpn evpn neighbors $word routes\"" "bgp.evpn.neighbor.$word.rcv$asic_id"
        fi
    done
}

###############################################################################
# Iterates all ASIC namespaces on multi ASIC platform and on default (host)
# namespace on single ASIC platform
# Globals:
#  NUM_ASICS
# Arguments:
#  None
# Returns:
#  None
###############################################################################
save_bgp_neighbor_all_ns() {
    trap 'handle_error $? $LINENO' ERR
    if [[ ( "$NUM_ASICS" == 1 ) ]] ; then
        save_bgp_neighbor
    else
        for (( i=0; i<$NUM_ASICS; i++ ))
        do
            save_bgp_neighbor $i
        done
    fi
}

###############################################################################
# Iterates all ASIC namespaces on multi ASIC platform and on default (host)
# namespace on single ASIC platform
# Globals:
#  NUM_ASICS
# Arguments:
#  None
# Returns:
#  None
###############################################################################
save_bgp_evpn_neighbor_all_ns() {
    trap 'handle_error $? $LINENO' ERR
    if [[ ( "$NUM_ASICS" == 1 ) ]] ; then
        save_bgp_evpn_neighbor
    else
        for (( i=0; i<$NUM_ASICS; i++ ))
        do
            save_bgp_evpn_neighbor $i
        done
    fi
}

###############################################################################
# Dump the nat config, iptables rules and conntrack nat entries
# Globals:
#  None
# Arguments:
#  None
# Returns:
#  None
###############################################################################
save_nat_info() {
    trap 'handle_error $? $LINENO' ERR
    save_cmd_all_ns "iptables -t nat -nv -L" "nat.iptables"
    save_cmd_all_ns "conntrack -j -L" "nat.conntrack"
    save_cmd_all_ns "conntrack -j -L | wc" "nat.conntrackcount"
    save_cmd_all_ns "conntrack -L" "nat.conntrackall"
    save_cmd_all_ns "conntrack -L | wc" "nat.conntrackallcount"
    save_cmd_all_ns "show nat config" "nat.config"
}

###############################################################################
# Dump the BFD information from vtysh
# Globals:
#  None
# Arguments:
#  None
# Returns:
#  None
###############################################################################
save_bfd_info() {
    trap 'handle_error $? $LINENO' ERR
    if $IS_SUPERVISOR; then
        return
    fi
    save_vtysh "show bfd peers" "frr.bfd.peers"
    save_vtysh "show bfd peers counters" "frr.bfd.peers.counters"
    save_vtysh "show bfd peers json" "frr.bfd.peers.json"
    save_vtysh "show bfd peers counters json" "frr.bfd.peers.counters.json"
}

###############################################################################
# Save IP related info
# Globals:
#  None
# Arguments:
#  None
# Returns:
#  None
###############################################################################
save_ip_info() {
    trap 'handle_error $? $LINENO' ERR
    save_ip "link" "link"
    save_ip "addr" "addr"
    save_ip "rule" "rule"
    save_ip "route show table all" "route"
    save_ip "neigh" "neigh"
    save_ip "-s neigh show nud noarp" "neigh.noarp"
    save_ip "-s link" "link.stats"
}

###############################################################################
# Save BGP related info
# Globals:
#  None
# Arguments:
#  None
# Returns:
#  None
###############################################################################
save_bgp_info() {
    trap 'handle_error $? $LINENO' ERR
    if $IS_SUPERVISOR; then
        return
    fi
    save_vtysh "show ip bgp summary" "bgp.summary"
    save_vtysh "show ip bgp neighbors" "bgp.neighbors"
    save_vtysh "show ip bgp" "bgp.table"
    save_vtysh "show bgp ipv6 summary" "bgp.ipv6.summary"
    save_vtysh "show bgp ipv6 neighbors" "bgp.ipv6.neighbors"
    save_vtysh "show bgp ipv6" "bgp.ipv6.table"
    save_vtysh "show ip bgp vrf all" "bgp.vrf.all"
    save_vtysh "show ip bgp vrf all summary json" "bgp.vrf.all.summary.json"
    save_vtysh "show ip bgp vrf all neighbors json" "bgp.vrf.all.neigh.json"
    save_vtysh "show ip bgp vrf all nexthop" "bgp.vrf.all.nexthop"
    save_vtysh "show ip bgp vrf all update-group" "bgp.vrf.all.nexthop"
    save_vtysh "show bgp vrfs json" "bgp.vrf.json"
    save_vtysh "show bgp vrf all ipv4 unicast summary json" "bgp.vrf.all.ipv4uc.summary.json"
    save_vtysh "show bgp vrf all ipv6 unicast summary json" "bgp.vrf.all.ipv6uc.summary.json"
    save_vtysh "show bgp vrf all ipv4 unicast detail json" "bgp.vrf.all.ipv4uc.detail.json"
    save_vtysh "show bgp vrf all ipv6 unicast detail json" "bgp.vrf.all.ipv6uc.detail.json"
    save_vtysh "show bgp vrf all ipv4 unicast update-group" "bgp.vrf.all.ipv4uc.update_group"
    save_vtysh "show bgp vrf all ipv6 unicast update-group" "bgp.vrf.all.ipv6uc.update_group"
    save_vtysh "show bgp vrf all ipv4 unicast route-leak json" "bgp.vrf.all.ipv4uc.route_leak.json"
    save_vtysh "show bgp vrf all ipv6 unicast route-leak json" "bgp.vrf.all.ipv6uc.route_leak.json"
    save_vtysh "show bgp ipv4 labeled-unicast" "bgp.ipv4.labeled_unicast"
    save_vtysh "show bgp ipv6 labeled-unicast" "bgp.ipv6.labeled_unicast"
    save_vtysh "show bgp mac hash" "bgp.mac.hash"
    save_bgp_neighbor_all_ns
}

###############################################################################
# Save EVPN related info
# Globals:
#  None
# Arguments:
#  None
# Returns:
#  None
###############################################################################
save_evpn_info() {
    trap 'handle_error $? $LINENO' ERR
    if $IS_SUPERVISOR; then
        return
    fi
    save_vtysh "show bgp l2vpn evpn" "bgp.l2vpn.evpn"
    save_vtysh "show bgp l2vpn evpn summary json" "bgp.evpn.summary.json"
    save_vtysh "show bgp l2vpn evpn route" "bgp.evpn.route"
    save_vtysh "show bgp l2vpn evpn route detail" "bgp.evpn.route.detail"
    save_vtysh "show bgp l2vpn evpn vni json" "bgp.evpn.vni.json"
    save_vtysh "show bgp l2vpn evpn import-rt json" "bgp.evpn.import-rt.json"
    save_vtysh "show bgp l2vpn evpn vrf-import-rt json" "bgp.evpn.vrf_import-rt.json"
    save_vtysh "show bgp l2vpn evpn update-groups" "bgp.evpn.update_groups"
    save_vtysh "show bgp l2vpn evpn next-hops json" "bgp.evpn.next_hops.json"
    save_vtysh "show bgp vni all" "bgp.vni.all"
    save_vtysh "show bgp vni all detail" "bgp.vni.all.detail"
    save_vtysh "show evpn vni detail json" "evpn.vni.json"
    save_vtysh "show evpn access-vlan json" "evpn.access_vlan.json"
    save_vtysh "show evpn arp-cache vni all" "evpn.arp"
    save_vtysh "show evpn json" "evpn.json"
    save_vtysh "show evpn l2-nh json" "evpn.l2_nh.json"
    save_vtysh "show evpn mac vni all" "evpn.mac.vni"
    save_vtysh "show evpn arp-cache vni all" "evpn.arp_cache.vni"
    save_vtysh "show evpn rmac vni all json" "evpn.rmac.vni.json"
    save_vtysh "show evpn next-hops vni all json" "evpn.next_hops.vni.json"
    save_bgp_evpn_neighbor_all_ns
}
###############################################################################
# Save FRR related info
# Globals:
#  None
# Arguments:
#  None
# Returns:
#  None
###############################################################################
save_frr_info() {
    trap 'handle_error $? $LINENO' ERR
    if $IS_SUPERVISOR; then
        return
    fi
    save_vtysh "show running-config" "frr.running_config"
    save_vtysh "show ip route vrf all nexthop-group" "frr.ip_route.nhg"
    save_vtysh "show ipv6 route vrf all nexthop-group" "frr.ip6_route.nhg"
    save_vtysh "show zebra fpm stats" "frr.fpm.stats"
    save_vtysh "show zebra dplane detailed" "frr.dplane"
    save_vtysh "show interface vrf all" "frr.interfaces"
    save_vtysh "show zebra" "frr.zebra"
    save_vtysh "show zebra client" "frr.zebra.client"
    save_vtysh "show zebra client summary" "frr.zebra.client.summary"
    save_vtysh "show zebra router table summary" "frr.zebra.router.table.summary"
    save_vtysh "show vrf" "frr.vrf"
    save_vtysh "show vrf vni" "frr.vrf.vni"
    save_vtysh "show ip nht vrf all" "frr.ip.nht.vrf.all"
    save_vtysh "show ipv6 nht vrf all" "frr.ipv6.nht.vrf.all"
    save_vtysh "show mpls table" "frr.mpls.table"
    save_vtysh "show mpls fec" "frr.mpls.fec"
    save_vtysh "show nexthop-group rib" "frr.nhg.rib"
    save_vtysh "show route-map" "frr.route_map"
    save_vtysh "show thread cpu" "frr.thread_cpu"
    save_vtysh "show thread poll" "frr.thread_poll"
    save_vtysh "show debugging hashtable" "frr.debugging_hashtable"
    save_vtysh "show work-queues" "frr.work_queues"
    save_vtysh "show memory" "frr.memory"
    save_vtysh "show modules" "frr.modules"
    save_vtysh "show version" "frr.version"
    save_vtysh "show debugging" "frr.debugging"
    save_vtysh "show logging" "frr.logging"
}

###############################################################################
# Save Redis DB contents
# Globals:
#  None
# Arguments:
#  None
# Returns:
#  None
###############################################################################
save_redis_info() {
    trap 'handle_error $? $LINENO' ERR
    save_redis "APPL_DB"
    save_redis "ASIC_DB"
    save_redis "COUNTERS_DB"
    # There are secrets in CONFIG_DB need to be cleanup.
    save_redis "CONFIG_DB" "CONFIG_DB" remove_secret_from_config_db_dump
    save_redis "FLEX_COUNTER_DB"
    save_redis "STATE_DB"
    save_redis "APPL_STATE_DB"
}

###############################################################################
# Given list of proc files, saves proc files to tar.
# Globals:
#  V
#  TARDIR
#  MKDIR
#  CP
#  DUMPDIR
#  TAR
#  RM
#  BASE
#  TARFILE
#  NOOP
# Arguments:
#  *procfiles: variable-length list of proc file paths to save
# Returns:
#  None
###############################################################################
save_proc() {
    trap 'handle_error $? $LINENO' ERR
    local procfiles="$@"
    $MKDIR $V -p $TARDIR/proc
    for f in $procfiles
    do
        if $NOOP; then
            if [ -e $f ]; then
                echo "$CP $V -r $f $TARDIR/proc"
            fi
        else
            ( [ -e $f ] && $CP $V -r $f $TARDIR/proc ) || echo "$f not found" > $TARDIR/$f
        fi
    done

    chmod ugo+rw -R $DUMPDIR/$BASE/proc
}

###############################################################################
# Given list of sys files, saves sys files to tar.
# Globals:
#  V
#  TARDIR
#  MKDIR
#  CP
#  DUMPDIR
#  TAR
#  RM
#  BASE
#  TARFILE
#  NOOP
# Arguments:
#  *sysfiles: variable-length list of sys file paths to save
# Returns:
#  None
###############################################################################
save_sys() {
    trap 'handle_error $? $LINENO' ERR
    local sysfiles="$@"
    local cp_cmd="sudo cp"
    
    $MKDIR $V -p $TARDIR/sys
    for f in $sysfiles
    do
        if $NOOP; then
            if [ -e $f ]; then
                echo "$cp_cmd $V -r -p $f $TARDIR/sys"
            fi
        else
            ( [ -e $f ] && $cp_cmd $V -r -p $f $TARDIR/sys ) || echo "$f not found" > $TARDIR/$f
        fi
    done

    chmod ugo+rw -R $DUMPDIR/$BASE/sys
}

###############################################################################
# Given list of pstore directories or files, saves subdirectories and files to tar.
# Globals:
#  V
#  TARDIR
#  MKDIR
#  CP
#  DUMPDIR
#  TAR
#  RM
#  BASE
#  TARFILE
#  NOOP
# Arguments:
#  *pstorefiles: variable-length list of pstore file paths to save
# Returns:
#  None
###############################################################################
save_pstore() {
    trap 'handle_error $? $LINENO' ERR
    local pstorefiles="$@"
    PSTORE_DIR_LIST_INFO_FILE="techsupport_pstore_dir_listing_info"
    $MKDIR $V -p $TARDIR/pstore
    
    echo "==================================" > $TARDIR/pstore/$PSTORE_DIR_LIST_INFO_FILE
    echo "/var/lib/systemd/pstore files info" >> $TARDIR/pstore/$PSTORE_DIR_LIST_INFO_FILE
    echo "==================================" >> $TARDIR/pstore/$PSTORE_DIR_LIST_INFO_FILE
    for f in $pstorefiles
    do
        if $NOOP; then
            if [ -e $f ]; then
                echo "$CP $V -r -p $f/* $TARDIR/pstore"
            fi
        else
            ls --time-style='+%d-%m-%Y %H:%M:%S' -last $f >> $TARDIR/pstore/$PSTORE_DIR_LIST_INFO_FILE
            ls --time-style='+%d-%m-%Y %H:%M:%S' -last $f/* >> $TARDIR/pstore/$PSTORE_DIR_LIST_INFO_FILE
            ( [ -e $f ] && $CP $V -r -p $f/* $TARDIR/pstore ) || echo "$f not found" > $TARDIR/$f
        fi
    done

    chmod ugo+rw -R $DUMPDIR/$BASE/pstore
}

###############################################################################
# Dump io stats for processes
# Globals:
#  V
#  TARDIR
#  MKDIR
#  CP
#  DUMPDIR
#  TAR
#  RM
#  BASE
#  TARFILE
#  NOOP
# Arguments:
#  None
# Returns:
#  None
###############################################################################
save_proc_stats() {
    trap 'handle_error $? $LINENO' ERR
    $MKDIR $V -p $TARDIR/proc_stats

    local stats_file=/tmp/stats
    echo > $stats_file
    for pid in /proc/[[:digit:]]*
    do
        pid_num=${pid//[!0-9]/}
        cmdline=`eval ps -p $pid_num -o args --no-headers`
        if test -n "$cmdline"; then
            echo pid $pid
            echo cmdline: $cmdline
            cat $pid/io; echo
        else
            #Dump also internal kernel processes if they perform writes
            write_bytes=$(cat $pid/io | grep -w write_bytes: | cut -b 14-)
            if [ "$write_bytes" != "0" ]; then
                echo pid $pid - `cat $pid/comm`:
                cat $pid/io; echo
            fi
        fi
    done >> $stats_file 2>&1

    if $NOOP; then
        echo "$CP $V -r $stats_file $TARDIR/proc_stats"
    else
        ( $CP $V -r $stats_file $TARDIR/proc_stats ) || echo "$stats_file error" > $TARDIR/$stats_file
    fi

    chmod ugo+rw -R $DUMPDIR/$BASE/proc_stats
}

###############################################################################
# Dumps all fields and values from given Redis DB.
# Arguments:
#  DB name: DB name
#  Filename: Destination filename, if not given then filename would be DB name
#  cleanup_method: (OPTIONAL) the cleanup method to procress dump file after it generated.
# Returns:
#  None
###############################################################################
save_redis() {
    local cleanup_method=${3:-dummy_cleanup_method}
    trap 'handle_error $? $LINENO' ERR
    local db_name=$1
    if [ $# -ge 2 ] && [ -n "$2" ]; then
        local dest_file_name=$2
    else
        local dest_file_name="$db_name"
    fi
    save_cmd_all_ns "sonic-db-dump -n '$db_name' -y" "$dest_file_name.json" false $cleanup_method
}

###############################################################################
# GET ROUTE table size by ASIC id and ip version
# Globals:
#  TIMEOUT_MIN
#  TIMEOUT_EXIT_CODE
# Arguments:
#  asic id
#  IP version
# Returns:
#  Status: 0 success, otherwise failure
###############################################################################
get_route_table_size_by_asic_id_and_ipver() {
    local asic_id="$1"
    local ip_ver="$2"
    local filepath="/tmp/route_summary.txt"
    local ns=""
    RC=0

    if [[ $NUM_ASICS -gt 1 ]] ; then
        ns="-n ${asic_id}"
    fi

    if [ $ip_ver = "ipv4" ]; then
        cmd="vtysh ${ns} -c 'show ip route summary json'"
    elif [ $ip_ver = "ipv6" ]; then
        cmd="vtysh ${ns} -c 'show ipv6 route summary json'"
    else
        echo "Wrong argument $ip_ver."
        return 255
    fi

    local timeout_cmd="timeout --foreground ${TIMEOUT_MIN}m"
    local cmds="$cmd > '$filepath'"

    eval "${timeout_cmd} bash -c \"${cmds}\"" || RC=$?

    if [ $RC -eq $TIMEOUT_EXIT_CODE ]; then
        echo "Command: $cmds timedout after ${TIMEOUT_MIN} minutes."
        return $RC
    elif [ $RC -ne 0 ]; then
        echo "Command: $cmds failed with RC $RC"
        return $RC
    fi

    local route_tab_size=$(python3 -c "\
import json
with open('$filepath') as json_file:
    data = json.load(json_file)
    print(data['routesTotal'])")
    rm $filepath
    echo "$route_tab_size"
}

###############################################################################
# SAI DUMP based on the route table size
# if the route table has more than ROUTE_TAB_LIMIT_DIRECT_ITERATION
# then dump by Redis Save command,
# otherwize, dump it by directly iteration the Redis
#
# Globals:
#  NUM_ASICS
#  ROUTE_TAB_LIMIT_DIRECT_ITERATION
# Arguments:
#  None
# Returns:
#  None
###############################################################################
save_saidump_by_route_size() {
    trap 'handle_error $? $LINENO' ERR

    for (( i=0; i<$NUM_ASICS; i++ ))
    do
        route_size_ipv4=`get_route_table_size_by_asic_id_and_ipver $i ipv4`
        ret=$?

        if [ $ret -ne 0 ]; then
            echo "Get route table's size by asicid $i and ipv4 failed."
            return $ret
        fi

        route_size_ipv6=`get_route_table_size_by_asic_id_and_ipver $i ipv6`
        ret=$?

        if [ $ret -ne 0 ]; then
            echo "Get route table's size by asicid $i and ipv6 failed."
            return $ret
        fi

        route_size=`expr $route_size_ipv4 + $route_size_ipv6`
        echo "The route table's size is $route_size(ipv4 $route_size_ipv4, ipv6 $route_size_ipv6)"

        if [[ $route_size -gt $ROUTE_TAB_LIMIT_DIRECT_ITERATION ]]; then
            echo "Dump by using Redis SAVE."

            if [[ ( "$NUM_ASICS" == 1 ) ]] ; then
                save_cmd "docker exec syncd saidump.sh" "saidump"
            else
                save_cmd "docker exec syncd$i saidump.sh" "saidump$i"
            fi
        else
            echo "Dump by using direct iteration of Redis DB."

            if [[ ( "$NUM_ASICS" == 1 ) ]] ; then
                save_cmd "docker exec syncd saidump" "saidump"
            else
                save_cmd "docker exec syncd$i saidump" "saidump$i"
            fi
        fi
    done
}

###############################################################################
# Save platform related info
# Globals:
#  None
# Arguments:
#  None
# Returns:
#  None
###############################################################################
save_platform_info() {
    trap 'handle_error $? $LINENO' ERR
    PLATFORM=${PLATFORM:-`sonic-cfggen -H -v DEVICE_METADATA.localhost.platform`}
    if [[ ! $PLATFORM =~ .*(mlnx|nvidia).*simx.* ]]; then
        save_cmd "show platform syseeprom" "syseeprom"
        save_cmd "show platform psustatus" "psustatus"
        save_cmd "show platform ssdhealth --vendor" "ssdhealth"
        save_cmd "show platform temperature" "temperature"
        save_cmd "show platform fan" "fan"
    fi
}

###############################################################################
# Runs a comamnd and saves its output to the incrementally built tar.
# Globals:
#  LOGDIR
#  BASE
#  MKDIR
#  TAR
#  TARFILE
#  DUMPDIR
#  V
#  RM
#  NOOP
# Arguments:
#  filename: the full path of the file to save
#  base_dir: the directory in $TARDIR/ to stage the file
#  do_gzip: (OPTIONAL) true or false. Should the output be gzipped
#  do_tar_append: (OPTIONAL) true or false. Should the output be added to final tar archive
# Returns:
#  None
###############################################################################
save_file() {
    trap 'handle_error $? $LINENO' ERR
    local start_t=$(date +%s%3N)
    local end_t=0
    local orig_path=$1
    local supp_dir=$2
    local gz_path="$TARDIR/$supp_dir/$(basename $orig_path)"
    local tar_path="${BASE}/$supp_dir/$(basename $orig_path)"
    local do_gzip=${3:-true}
    local do_tar_append=${4:-false}
    if [ ! -d "$TARDIR/$supp_dir" ]; then
        $MKDIR $V -p "$TARDIR/$supp_dir"
    fi

    if $do_gzip; then
        if [[ -f ${orig_path} ]]; then
          gz_path="${gz_path}.gz"
          tar_path="${tar_path}.gz"

          if $NOOP; then
            echo "gzip -c $orig_path > $gz_path"
          else
            gzip -c $orig_path > $gz_path
          fi
        else
          gz_path="${gz_path}.tar.gz"
          tar_path="${tar_path}.tar.gz"

          if $NOOP; then
              echo "tar -czvf $gz_path -C $(dirname $orig_path) $(basename $orig_path)"
          else
              tar -czvf "$gz_path" -C "$(dirname "$orig_path")" "$(basename "$orig_path")"
          fi
        fi
    else
        if $NOOP; then
            echo "cp $orig_path $gz_path"
        else
            cp $orig_path $gz_path
        fi
    fi

    if $do_tar_append; then
        ($TAR $V -rhf $TARFILE -C $DUMPDIR "$tar_path" \
            || abort "${EXT_PROCFS_SAVE_FAILED}" "tar append operation failed. Aborting to prevent data loss.") \
            && $RM $V -f "$gz_path"
    fi

    end_t=$(date +%s%3N)
    echo "[ save_file:$orig_path] : $(($end_t-$start_t)) msec"  >> $TECHSUPPORT_TIME_INFO
}

###############################################################################
# find_files routine
# Globals:
#  SINCE_DATE: list files only newer than given date
#  REFERENCE_FILE: the file to be created as a reference to compare modification time
# Arguments:
#  directory: directory to search files in
# Returns:
#  None
###############################################################################
find_files() {
    trap 'handle_error $? $LINENO' ERR
    local -r directory=$1
    local -r find_command="find -L $directory -type f -newer ${REFERENCE_FILE}"

    echo $($find_command)
}

###############################################################################
# disable_logrotate routine
# Globals:
#  None
# Arguments:
#  None
# Returns:
#  None
###############################################################################
disable_logrotate() {
    if [ ! -f "/etc/cron.d/logrotate" ]; then
        return
    fi
    sed -i '/\/usr\/sbin\/logrotate/s/^/#/g' /etc/cron.d/logrotate
}

###############################################################################
# enable_logrotate routine
# Globals:
#  None
# Arguments:
#  None
# Returns:
#  None
###############################################################################
enable_logrotate() {
    if [ ! -f "/etc/cron.d/logrotate" ]; then
        return
    fi
    sed -i '/\/usr\/sbin\/logrotate/s/^#*//g' /etc/cron.d/logrotate
}

###############################################################################
# Create a relative symbolic link of an existing file
# Globals:
#  BASE
#  MKDIR
#  TAR
#  TARFILE
#  DUMPDIR
#  V
#  RM
#  NOOP
# Arguments:
#  filename: the full path of the file
#  dest_dir: destination dir where the link is created
#  src_sir: directory under $TARDIR where the actual file exists
# Returns:
#  None
###############################################################################
save_symlink() {
    trap 'handle_error $? $LINENO' ERR
    local start_t=$(date +%s%3N)
    local end_t=0
    local filename=$1
    local dest_dir=$2
    local src_dir=$3
    local do_tar_append=${4:-true}
    local file_basename=$(basename $filename)
    local tar_path="$BASE/$dest_dir/$file_basename"

    $MKDIR $V -p "$TARDIR/$dest_dir"

    ${CMD_PREFIX}pushd $TARDIR/$dest_dir
    ${CMD_PREFIX}ln -s ../$src_dir/$file_basename $file_basename
    ${CMD_PREFIX}popd

    if $do_tar_append; then
        ($TAR $V -rf $TARFILE -C $DUMPDIR "$tar_path" \
            || abort "${EXT_PROCFS_SAVE_FAILED}" "tar append operation failed. Aborting to prevent data loss.") \
            && $RM $V -f "$DUMPDIR/$tar_path"
    fi
    end_t=$(date +%s%3N)
    echo "[ save_symlink:$filename] : $(($end_t-$start_t)) msec"  >> $TECHSUPPORT_TIME_INFO
}

###############################################################################
# Collect Mellanox specific information
# Globals:
#  CMD_PREFIX
# Arguments:
#  None
# Returns:
#  None
###############################################################################
collect_mellanox() {
    trap 'handle_error $? $LINENO' ERR
    local timeout_cmd="timeout --foreground ${TIMEOUT_MIN}m"
    local sai_dump_folder="/tmp/saisdkdump"
    local sai_dump_filename="${sai_dump_folder}/sai_sdk_dump_$(date +"%m_%d_%Y_%I_%M_%p")"
    local platform=$(python3 -c "from sonic_py_common import device_info; print(device_info.get_platform())")
    local platform_folder="/usr/share/sonic/device/${platform}"
    local hwsku=$(python3 -c "from sonic_py_common import device_info; print(device_info.get_hwsku())")
    local is_smartswitch=$(python3 -c "from sonic_py_common import device_info; print(device_info.is_smartswitch())")
    local sku_folder="/usr/share/sonic/device/${platform}/${hwsku}"
    local cmis_host_mgmt_files=(
        "/tmp/nv-syncd-shared/sai.profile"
        "${sku_folder}/pmon_daemon_control.json"
        "${sku_folder}/media_settings.json"
        "${sku_folder}/optics_si_settings.json"
    )

    if [[ "$( docker container inspect -f '{{.State.Running}}' syncd )" == "true" ]]; then
        if [[ x"$(sonic-db-cli APPL_DB EXISTS PORT_TABLE:PortInitDone)" == x"1" ]]; then
            # Run saisdkdump only after the create_switch is known to be successful
            ${CMD_PREFIX}docker exec syncd mkdir -p $sai_dump_folder
            ${CMD_PREFIX}docker exec syncd saisdkdump -f $sai_dump_filename

            if [ $? != 0 ]; then
                echo "Failed to collect saisdkdump."
            fi

            copy_from_docker syncd $sai_dump_folder $sai_dump_folder
            echo "$sai_dump_folder"
            for file in `ls $sai_dump_folder`; do
                save_file ${sai_dump_folder}/${file} sai_sdk_dump true
            done

            ${CMD_PREFIX}rm -rf $sai_dump_folder
            ${CMD_PREFIX}docker exec syncd rm -rf $sai_dump_folder
        fi
    fi

    # collect the sdk dump
    local sdk_dbg_folder="/var/log/sdk_dbg"
    for file in $(find $sdk_dbg_folder -name "sx_sdk_*")
    do
        if [[ $file != *.gz ]]
        then
            save_file $file sai_sdk_dump true
        else
            save_file $file sai_sdk_dump false
        fi
    done

    # run 'hw-management-generate-dump.sh' script and save the result file
    HW_DUMP_FILE=/usr/bin/hw-management-generate-dump.sh
    if [ -f "$HW_DUMP_FILE" ]; then
      ${CMD_PREFIX}${timeout_cmd} /usr/bin/hw-management-generate-dump.sh $ALLOW_PROCESS_STOP
      ret=$?
      if [ $ret -ne 0 ]; then
        if [ $ret -eq $TIMEOUT_EXIT_CODE ]; then
          echo "hw-management dump timedout after ${TIMEOUT_MIN} minutes."
        else
          echo "hw-management dump failed ..."
        fi
      else
        save_file "/tmp/hw-mgmt-dump*" "hw-mgmt" false
        rm -f /tmp/hw-mgmt-dump*
      fi
    else
      echo "HW Mgmt dump script $HW_DUMP_FILE does not exist"
    fi

    save_cmd "get_component_versions.py" "component_versions"
    if [[ $is_smartswitch == "True" ]]; then
        save_cmd "dpuctl dpu-status" "dpu_status"
    fi

    # Save CMIS-host-management related files
    local cmis_host_mgmt_path="cmis-host-mgmt"

    for file in "${cmis_host_mgmt_files[@]}"; do
        if [[ -f "${file}" ]]; then
            ${CMD_PREFIX}save_file "${file}" "$cmis_host_mgmt_path" false true
        fi
    done

    if [[ ! -f "${sku_folder}/pmon_daemon_control.json" && -f "${platform_folder}/pmon_daemon_control.json" ]]; then
        ${CMD_PREFIX}save_file "${platform_folder}/pmon_daemon_control.json" "$cmis_host_mgmt_path" false true
    fi

    save_cmd "show interfaces autoneg status" "autoneg.status"
}

###############################################################################
# Collect dfw dumps if any and sai.xml file. Applies to MLNX and NVIDIA Bluefield platform
# Globals:
#  CMD_PREFIX
# Arguments:
#  None
# Returns:
#  None
###############################################################################
collect_nvidia_sdk_dumps() {
    trap 'handle_error $? $LINENO' ERR
    local platform=$(python3 -c "from sonic_py_common import device_info; print(device_info.get_platform())")
    local hwsku=$(python3 -c "from sonic_py_common import device_info; print(device_info.get_hwsku())")
    local def_dump_path="/var/log/mellanox/sdk-dumps"
    local sdk_dump_path=`cat /usr/share/sonic/device/${platform}/${hwsku}/sai.profile|grep "SAI_DUMP_STORE_PATH"|cut -d = -f2`
    local sku_folder="/usr/share/sonic/device/${platform}/${hwsku}"

    if [ -z $sdk_dump_path ]; then
        # If the SAI_DUMP_STORE_PATH is not found in device specific sai profile, check in common sai profile
        sdk_dump_path=`docker exec syncd cat /etc/mlnx/sai-common.profile | grep "SAI_DUMP_STORE_PATH" |cut -d = -f2`
        if [ -z $sdk_dump_path ]; then
            # If the above two mechanisms fail e.g. when syncd is not running , fallback to default sdk dump path
            sdk_dump_path=$def_dump_path
        fi
    fi


    if [[ ! -d $sdk_dump_path ]]; then
       # This would mean the SAI_DUMP_STORE_PATH is not mounted on the host and is only accessible though the container 
       # This is a bad design and not recommended But there is nothing which restricts against it and thus the special handling
       if [[ "$( docker container inspect -f '{{.State.Running}}' syncd )" == "true" ]]; then
            $RM $V -rf /tmp/dfw-sdk-dumps
            $MKDIR $V -p /tmp/dfw-sdk-dumps
            copy_from_docker syncd $sdk_dump_path /tmp/dfw-sdk-dumps
       else
            echo "ERROR: dfw dumps cannot be collected"
       fi
       sdk_dump_path="/tmp/dfw-sdk-dumps"
    fi

    for file in $(find_files "$sdk_dump_path"); do
        if $TAR -tf $TARFILE | grep $BASE/log/$(basename $file); then
            # If this path sits under "/var/log/" dir, the files
            # would've already been collected and thus just add a sym link
            if [ ! -z "${file##*.gz}" ]; then
                # files saved under log/ are zipped with gz
                file=$file.gz
            fi
            ${CMD_PREFIX}save_symlink ${file} sai_sdk_dump log
        else
            if [ ! -z "${file##*.gz}" ]; then
                ${CMD_PREFIX}save_file ${file} sai_sdk_dump true true
            else
                ${CMD_PREFIX}save_file ${file} sai_sdk_dump false true
            fi
        fi
    done

    for file in $(find "$sku_folder" -name "sai_*.xml"); do
        save_file "$file" sai_sdk_dump true true
    done
}

###############################################################################
# Runs a given marvellcmd command in all namesapces in case of multi ASIC platform
# Globals:
#  NUM_ASICS
# Arguments:
#  cmd: The command to run. Make sure that arguments with spaces have quotes
#  filename: the filename to save the output as in $BASE/dump
#  do_gzip: (OPTIONAL) true or false. Should the output be gzipped
# Returns:
#  None
###############################################################################
save_marvell_presteracmd() {
    trap 'handle_error $? $LINENO' ERR

    mkdir -p $LOGDIR/sdkdump
    local cmd="docker exec syncd mrvlcmd -c \"$1\""
    save_cmd "$cmd" "sdkdump/$2"
}

###############################################################################
# Collect Marvell specific information
# Globals:
#  None
# Arguments:
#  None
# Returns:
#  None
###############################################################################
collect_marvell_prestera() {
    trap 'handle_error $? $LINENO' ERR

    save_marvell_presteracmd "show version" "CPSS_version"
    save_marvell_presteracmd "debug-mode;xps-api call xpsDataIntegrityDumpSerInfo all" "SER_table"
    save_marvell_presteracmd "show traffic cpu rx statistic device 0" "CPU_stat"
    save_marvell_presteracmd "show interfaces status all" "INTERFACE_config"
    save_marvell_presteracmd "show port monitor" "PORT_mirror"
    save_marvell_presteracmd "show vlan device 0" "VLAN_table"
    save_marvell_presteracmd "show ip route device 0" "IP_route"
    save_marvell_presteracmd "show ipv6 route device 0" "IPV6_route"
    save_marvell_presteracmd "show ip route_fdb device 0" "IP_forward_route"
    save_marvell_presteracmd "show ipv6 route_fdb device 0" "IPV6_forward_route"
    save_marvell_presteracmd "show ip next-hop device 0" "NH_table"
    save_marvell_presteracmd "show mac address-table device 0" "MAC_forward"
    save_marvell_presteracmd "show mac address-table count device 0" "MAC_count"
    save_marvell_presteracmd "show tail-drop-allocated buffers all" "Tail_drop"
    save_marvell_presteracmd "show policy device 0" "POLICER_table"
    save_marvell_presteracmd "show system policy-tcam utilization device 0" "Policy_count"
    save_marvell_presteracmd "show access-list device 0 pcl-id 0 format ingress_udb_30" "UDB30_acl"
    save_marvell_presteracmd "show access-list device 0 pcl-id 0 format ingress_udb_60" "UDB60_acl"
    save_marvell_presteracmd "debug-mode;show drop counters 0" "Drop_count"
    save_marvell_presteracmd "debug-mode;dump all registers" "REGISTER_table"
    save_marvell_presteracmd "debug-mode;dump all tables0" "HW_table_0"
    save_marvell_presteracmd "debug-mode;dump all tables1" "HW_table_1"
    save_marvell_presteracmd "debug-mode;dump all tables2" "HW_table_2"
    save_marvell_presteracmd "debug-mode;dump all tables3" "HW_table_3"
    save_marvell_presteracmd "debug-mode;dump all tables4" "HW_table_4"
    save_marvell_presteracmd "debug-mode;dump all tables5" "HW_table_5"
    save_marvell_presteracmd "debug-mode;dump all tables6" "HW_table_6"
    save_marvell_presteracmd "debug-mode;dump all tables7" "HW_table_7"
}

###############################################################################
# Collect Broadcom specific information
# Globals:
#  None
# Arguments:
#  None
# Returns:
#  None
###############################################################################
collect_broadcom() {
    trap 'handle_error $? $LINENO' ERR
    local timeout_cmd="timeout --foreground ${TIMEOUT_MIN}m"
    local platform=$(show platform summary --json | python -c 'import sys, json; \
        print(json.load(sys.stdin)["platform"])')
    local hwsku=$(show platform summary --json | python -c 'import sys, json; \
        print(json.load(sys.stdin)["hwsku"])')

    # save SAI configuration files (config.bcm, port_config.ini, sai.profile)
    if [ -d /usr/share/sonic/device/${platform}/${hwsku} ]; then
        # copy all the files in the HWSKU directory
        pushd /usr/share/sonic/device/${platform}/${hwsku} > /dev/null
        for file in $(find . -maxdepth 2 -type f); do
            save_file ${file} sai false
        done
        popd > /dev/null

        if [[ ("$NUM_ASICS" > 1) ]]; then
            for (( i=0; i<$NUM_ASICS; i++ ))
            do
                # config.bcm - copy the one with chip common properties merged
                for file in $(find /var/run/docker-syncd$i -type f -name "*.bcm"); do
                    save_file ${file} sai$i false
                done
                # sai.profile - copy the final sai.profile generated in docker-syncd
                if [ -f /var/run/docker-syncd$i/sai.profile ]; then
                    save_file /var/run/docker-syncd$i/sai.profile sai$i false
                fi
            done
        else
            # config.bcm - copy the one with chip common properties merged
            for file in $(find /var/run/docker-syncd -type f -name "*.bcm"); do
                save_file ${file} sai false
            done
            # sai.profile - copy the final sai.profile generated in docker-syncd
            if [ -f /var/run/docker-syncd/sai.profile ]; then
                save_file /var/run/docker-syncd/sai.profile sai false
            fi
        fi
    else
        echo "'/usr/share/sonic/device/${platform}/${hwsku}' does not exist" > /tmp/error
        save_file /tmp/error sai false
    fi

    save_cmd "cat /proc/bcm/knet/debug" "broadcom.knet.debug"
    save_cmd "cat /proc/bcm/knet/dma" "broadcom.knet.dma"
    save_cmd "cat /proc/bcm/knet/link" "broadcom.knet.link"
    save_cmd "cat /proc/bcm/knet/rate" "broadcom.knet.rate"

    save_bcmcmd_all_ns "-t5 version" "broadcom.version"
    save_bcmcmd_all_ns "-t5 soc" "broadcom.soc"
    save_bcmcmd_all_ns "-t5 ps" "broadcom.ps"
    if [ -e /usr/share/sonic/device/${platform}/platform_asic ]; then
       bcm_family=`cat /usr/share/sonic/device/${platform}/platform_asic`
    else
       echo "'/usr/share/sonic/device/${platform}/platform_asic' does not exist" > /tmp/error
       save_file /tmp/error sai false
       return
    fi

    if [ "$bcm_family" == "broadcom-dnx" ]; then
       supervisor=0
       PLATFORM_ENV_CONF=/usr/share/sonic/device/${platform}/platform_env.conf
       if [ -f "$PLATFORM_ENV_CONF" ]; then
          source $PLATFORM_ENV_CONF
       fi
       if [[ x"$supervisor" != x"1" ]]; then

          save_bcmcmd_all_ns "\"l2 show\"" "l2.summary"
          save_bcmcmd_all_ns "\"field group list\"" "fpgroup.list.summary"
          total_fp_groups=34
          for (( fp_grp=0; fp_grp<$total_fp_groups; fp_grp++ ))
          do
            save_bcmcmd_all_ns "\"field group info group=$fp_grp\"" "fpgroup$fp_grp.info.summary"
          done
          save_bcmcmd_all_ns "\"dbal table dump table=IPV4_UNICAST_PRIVATE_LPM_FORWARD\"" "l3.ipv4.lpm.summary"
          save_bcmcmd_all_ns "\"dbal table dump table=IPV6_UNICAST_PRIVATE_LPM_FORWARD\"" "l3.ipv6.lpm.summary"
          save_bcmcmd_all_ns "\"dbal table dump table=IPV4_UNICAST_PRIVATE_HOST\"" "l3.ipv4.host.summary"
          save_bcmcmd_all_ns "\"dbal table dump table=IPV6_UNICAST_PRIVATE_HOST\"" "l3.ipv6.host.summary"
          save_bcmcmd_all_ns "\"dbal table dump table=SUPER_FEC_1ST_HIERARCHY\"" "l3.egress.fec.summary"
          save_bcmcmd_all_ns "\"dbal table dump table=ECMP_TABLE\"" "ecmp.table.summary"
          save_bcmcmd_all_ns "\"dbal table dump table=ECMP_GROUP_PROFILE_TABLE\"" "ecmp.group.summary"
          save_bcmcmd_all_ns "\"dbal table dump table=ING_VSI_INFO_DB\"" "ing.vsi.summary"
          save_bcmcmd_all_ns "\"dbal table dump table=L3_MY_MAC_DA_PREFIXES\"" "l3.mymac.summary"
          save_bcmcmd_all_ns "\"dbal table dump table=INGRESS_VLAN_MEMBERSHIP\"" "ing.vlan.summary"
          save_bcmcmd_all_ns "\"dbal table dump table=LOCAL_SBC_IN_LIF_MATCH_INFO_SW\"" "sbc.inlif.summary"
          save_bcmcmd_all_ns "\"dbal table dump table=SNIF_COMMAND_TABLE\"" "snif.command.summary"
          save_bcmcmd_all_ns "\"port mgmt dump full\"" "port.mgmt.summary"
          save_bcmcmd_all_ns "\"tm lag\"" "tm.lag.summary"
          save_bcmcmd_all_ns "\"pp info fec\"" "pp.fec.summary"
          save_bcmcmd_all_ns "\"nif sts\"" "nif.sts.summary"
          save_bcmcmd_all_ns "\"tm ing q map\"" "tm.ingress.qmap.summary"
          save_bcmcmd_all_ns "\"tm ing vsq resources\"" "tm.ing.vsq.res.summary"
          for group in {a..f}
          do
             save_bcmcmd_all_ns "\"tm ing vsq non g=$group\"" "tm.ing.vsq.non.group-$group.summary"
          done
       fi
       save_bcmcmd_all_ns "\"port pm info\"" "port.pm.summary"
       save_bcmcmd_all_ns "\"conf show\"" "conf.show.summary"
       save_bcmcmd_all_ns "\"show counters\"" "show.counters.summary"
       save_bcmcmd_all_ns "\"diag counter g\"" "diag.counter.summary"
       save_bcmcmd_all_ns "\"fabric connectivity\"" "fabric.connect.summary"
       save_bcmcmd_all_ns "\"port status\"" "port.status.summary"
    else
       save_bcmcmd_all_ns "\"l3 nat_ingress show\"" "broadcom.nat.ingress"
       save_bcmcmd_all_ns "\"l3 nat_egress show\"" "broadcom.nat.egress"
       save_bcmcmd_all_ns "\"ipmc table show\"" "broadcom.ipmc"
       save_bcmcmd_all_ns "\"multicast show\"" "broadcom.multicast"
       save_bcmcmd_all_ns "\"conf show\"" "conf.summary"
       save_bcmcmd_all_ns "\"fp show\"" "fp.summary"
       save_bcmcmd_all_ns "\"pvlan show\"" "pvlan.summary"
       save_bcmcmd_all_ns "\"l2 show\"" "l2.summary"
       save_bcmcmd_all_ns "\"l3 intf show\"" "l3.intf.summary"
       save_bcmcmd_all_ns "\"l3 defip show\"" "l3.defip.summary"
       save_bcmcmd_all_ns "\"l3 l3table show\"" "l3.l3table.summary"
       save_bcmcmd_all_ns "\"l3 egress show\"" "l3.egress.summary"
       save_bcmcmd_all_ns "\"l3 ecmp egress show\"" "l3.ecmp.egress.summary"
       save_bcmcmd_all_ns "\"l3 multipath show\"" "l3.multipath.summary"
       save_bcmcmd_all_ns "\"l3 ip6host show\"" "l3.ip6host.summary"
       save_bcmcmd_all_ns "\"l3 ip6route show\"" "l3.ip6route.summary"
       save_bcmcmd_all_ns "\"mc show\"" "multicast.summary"
       save_bcmcmd_all_ns "\"cstat *\"" "cstat.summary"
       save_bcmcmd_all_ns "\"mirror show\"" "mirror.summary"
       save_bcmcmd_all_ns "\"mirror dest show\"" "mirror.dest.summary"
       save_bcmcmd_all_ns "\"port *\"" "port.summary"
       save_bcmcmd_all_ns "\"d chg my_station_tcam\"" "mystation.tcam.summary"
    fi

    copy_from_masic_docker "syncd" "/var/log/diagrun.log" "/var/log/diagrun.log"
    copy_from_masic_docker "syncd" "/var/log/bcm_diag_post" "/var/log/bcm_diag_post"

    # run 'hw-management-generate-dump.sh' script and save the result file
    HW_DUMP_FILE=/usr/bin/hw-management-generate-dump.sh
    if [ -f "$HW_DUMP_FILE" ]; then
      ${CMD_PREFIX}${timeout_cmd} /usr/bin/hw-management-generate-dump.sh $ALLOW_PROCESS_STOP
      ret=$?
      if [ $ret -ne 0 ]; then
        if [ $ret -eq $TIMEOUT_EXIT_CODE ]; then
          echo "hw-management dump timedout after ${TIMEOUT_MIN} minutes."
        else
          echo "hw-management dump failed ..."
        fi
      else
        save_file "/tmp/hw-mgmt-dump*" "hw-mgmt" false
        rm -f /tmp/hw-mgmt-dump*
      fi
    else
      echo "HW Mgmt dump script $HW_DUMP_FILE does not exist"
    fi  
}

###############################################################################
# Collect Barefoot specific information
# Globals:
#  None
# Arguments:
#  None
# Returns:
#  None
###############################################################################
collect_barefoot() {
    local bf_logs="/tmp/bf_logs"
    $( rm -rf ${bf_logs} )
    $( mkdir  ${bf_logs} )
    unset array
    array=( $(docker exec -ti syncd ls -1 | grep bf_drivers.log) )
    for y in "${array[@]}"; do
        itstr=`echo ${y} | tr -d "\r\n"`
        copy_from_masic_docker "syncd" "/${itstr}" "${bf_logs}"
    done

    for file in $(find /tmp/bf_logs -type f); do
        save_file "${file}" log true
    done
} 

###############################################################################
# Collect Cisco-8000 specific information
# Globals:
#  None
# Arguments:
#  None
# Returns:
#  None
###############################################################################
collect_cisco_8000() {
    trap 'handle_error $? $LINENO' ERR
    local platform=$(show platform summary --json | python -c 'import sys, json; \
        print(json.load(sys.stdin)["platform"])')

    if [ -d /usr/share/sonic/device/${platform} ]; then
        pushd /usr/share/sonic/device/${platform} > /dev/null
        for file in $(find . -path "./*plugin*" -prune -o -path "./*.xml" -prune -o -path "./*.yaml" -prune -o -print); do
            if [ -f ${file} ]; then
                 save_file ${file} sai false
            fi
        done
        popd > /dev/null
    else
        echo "'/usr/share/sonic/device/${platform}' does not exist" > /tmp/error
        save_file /tmp/error sai false
    fi

    save_cmd "show platform versions" "platform.versions"

    # run 'hw-management-generate-dump.sh' script and save the result file
    HW_DUMP_FILE=/usr/bin/hw-management-generate-dump.sh
    if [[ -x $HW_DUMP_FILE ]]; then
      ${CMD_PREFIX} $HW_DUMP_FILE $ALLOW_PROCESS_STOP
      ret=$?
      if [[ $ret -ne 0 ]]; then
        if [[ $ret -eq $TIMEOUT_EXIT_CODE ]]; then
          echo "hw-management dump timedout after ${TIMEOUT_MIN} minutes."
        else
          echo "hw-management dump failed ..."
        fi
      else
        save_file "/tmp/hw-mgmt-dump*" "hw-mgmt" false
        rm -f /tmp/hw-mgmt-dump*
      fi
    else
      echo "HW Mgmt dump script $HW_DUMP_FILE does not exist"
    fi
}

##############################################################################
# collect_marvell_teralynx
# Globals:
#   None
# Arguments:
#   None
# Retuens:
#   None
##############################################################################
collect_marvell_teralynx() {
    save_cmd "ivmcmd 'show techsupport -i /innovium/show_techsupport_infile'" "show_techsupport_op_ifcs.log"
    save_cmd "ivmcmd 'show techsupport -i /innovium/show_techsupport_infile_iSAI'" "show_techsupport_op_iSAI.log"
}

###############################################################################
# Collect Nvidia Bluefield specific information
# Globals:
#  CMD_PREFIX
# Arguments:
#  None
# Returns:
#  None
###############################################################################
collect_nvidia_bluefield() {
    trap 'handle_error $? $LINENO' ERR
    local timeout_cmd="timeout --foreground ${TIMEOUT_MIN}m"
    local sai_dump_folder="/root/saisdkdump"
    local sai_dump_filename="${sai_dump_folder}/sai_sdk_dump_$(date +"%m_%d_%Y_%I_%M_%p")"

    ${CMD_PREFIX}docker exec syncd mkdir -p $sai_dump_folder
    ${CMD_PREFIX}docker exec syncd saisdkdump -f $sai_dump_filename

    if [ $? != 0 ]; then
        echo "Failed to collect saisdkdump."
    fi

    copy_from_docker syncd $sai_dump_folder $sai_dump_folder
    echo "$sai_dump_folder"
    for file in `ls $sai_dump_folder`; do
        save_file ${sai_dump_folder}/${file} sai_sdk_dump true
    done

    ${CMD_PREFIX}rm -rf $sai_dump_folder
    ${CMD_PREFIX}docker exec syncd rm -rf $sai_dump_folder

    DUMP_FILE=/usr/bin/platform-dump.sh
    if [ -f "$DUMP_FILE" ]; then
      ${CMD_PREFIX}${timeout_cmd} ${DUMP_FILE} $ALLOW_PROCESS_STOP
      ret=$?
      if [ $ret -ne 0 ]; then
        if [ $ret -eq $TIMEOUT_EXIT_CODE ]; then
          echo "platform dump timedout after ${TIMEOUT_MIN} minutes."
        else
          echo "platform dump failed ..."
        fi
      else
        save_file "/tmp/platform-dump*" "platform-dump" false
        rm -f /tmp/platform-dump*
      fi
    else
      echo "Platform dump script $DUMP_FILE does not exist"
    fi

    save_cmd "get_component_versions.py" "component_versions"
}

###############################################################################
# Collect Pensando specific information
# Globals:
#  MKDIR
#  V
#  NOOP
#  RM
# Arguments:
#  None
# Returns:
#  None
###############################################################################
collect_pensando() {
    trap 'handle_error $? $LINENO' ERR
    platform=$(grep 'onie_platform=' /host/machine.conf | cut -d '=' -f 2)
    pipeline=`cat /usr/share/sonic/device/${platform}/default_pipeline`
    if [ ${pipeline} = "polaris" ]; then
        dpu_container_name="polaris"
    else
        dpu_container_name="dpu"
    fi
    local dpu_dump_folder="/root/dpu_dump"
    $MKDIR $V -p $dpu_dump_folder
    if $NOOP; then
        echo "docker exec ${dpu_container_name} /nic/tools/collect_techsupport.sh"
    else
        output=$(docker exec ${dpu_container_name} /nic/tools/collect_techsupport.sh 2>&1)
        if echo "${output}" | grep -q "Techsupport collected at"; then
            file_path=$(echo "${output}" | grep -oP '(?<=Techsupport collected at ).*')
            file_name=$(basename "${file_path}")
            copy_from_docker ${dpu_container_name} ${file_path} ${dpu_dump_folder}
            save_file ${dpu_dump_folder}/${file_name} ${dpu_container_name}_techsupport false
        else
            echo "Failed to collect ${dpu_container_name} container techsupport..."
        fi
    fi
    $RM $V -rf $dpu_dump_folder
}

###############################################################################
# Save log file
# Globals:
#  TAR, TARFILE, DUMPDIR, BASE, TARDIR, TECHSUPPORT_TIME_INFO
# Arguments:
#  None
# Returns:
#  None
###############################################################################
save_log_files() {
    trap 'handle_error $? $LINENO' ERR
    disable_logrotate
    trap enable_logrotate HUP INT QUIT TERM KILL ABRT ALRM

    start_t=$(date +%s%3N)
    log_dir_1="/var/log/"
    log_dir_2="/var/log.tmpfs/"
    file_list=""

    if [ -d "$log_dir_1" ]; then
        file_list_1=$(find_files ${log_dir_1})
        file_list="${file_list} ${file_list_1}"
    fi

    if [ -d "$log_dir_2" ]; then
        file_list_2=$(find_files ${log_dir_2})
        file_list="${file_list} ${file_list_2}"
    fi

    # save log files creation info
    files_ts_info=/tmp/files.timestamp.info
    ls --time-style='+%d-%m-%Y %H:%M:%S' -last /var/log/* > $files_ts_info
    save_file $files_ts_info "log" true
    rm -f $files_ts_info
    
    # gzip up all log files individually before placing them in the incremental tarball
    for file in $file_list; do
        dest_dir="log"
        if [[ $file == *"tmpfs"* ]]; then
            dest_dir="log.tmpfs"
        fi
        # ignore the sparse file lastlog
        if [ "$file" = "/var/log/lastlog" ]; then
            continue
        fi
        # don't gzip already-gzipped log files :)
        # do not append the individual files to the main tarball
        if [ -z "${file##*.gz}" ]; then
            save_file $file $dest_dir false
        else
            save_file $file $dest_dir true
        fi
    done

    end_t=$(date +%s%3N)
    echo "[ TAR /var/log Files ] : $(($end_t-$start_t)) msec" >> $TECHSUPPORT_TIME_INFO

    enable_logrotate
}

###############################################################################
# Save warmboot files
# Globals:
#  TARDIR, TARFILE, TAR, DUMPDIR, TECHSUPPORT_TIME_INFO, NOOP
# Arguments:
#  None
# Returns:
#  None
###############################################################################
save_warmboot_files() {
    # Copy the warmboot files
    trap 'handle_error $? $LINENO' ERR
    start_t=$(date +%s%3N)
    if $NOOP; then
        echo "$CP $V -rf /host/warmboot $TARDIR"
    else
        mkdir -p $TARDIR
        $CP $V -rf /host/warmboot $TARDIR
        chmod ugo+rw -R $DUMPDIR/$BASE/warmboot
    fi
    end_t=$(date +%s%3N)
    echo "[ Warm-boot Files ] : $(($end_t-$start_t)) msec" >> $TECHSUPPORT_TIME_INFO
}

###############################################################################
# Save crash files
# Globals:
#  None
# Arguments:
#  None
# Returns:
#  None
###############################################################################
save_crash_files() {
    # archive core dump files
    trap 'handle_error $? $LINENO' ERR
    if [ -d /var/core/ ]; then
        for file in $(find_files "/var/core/"); do
            # don't gzip already-gzipped log files :)
            if [ -z "${file##*.gz}" ]; then
                save_file $file core false true
            else
                save_file $file core true true
            fi
        done
    fi

    # archive kernel dump files
    if [ -d /var/crash/ ]; then
        for file in $(find_files "/var/crash/"); do
            # don't gzip already-gzipped dmesg files :)
            if [ ! ${file} = "/var/crash/kexec_cmd" -a ! ${file} = "/var/crash/export" ]; then
                if [[ ${file} == *"kdump."* ]]; then
                    save_file $file kdump false true
                else
                    save_file $file kdump true true
                fi
            fi
        done
    fi
}

###############################################################################
# Collect SAI failure dump files under /var/log/sai_failure_dump/. These files are
#  created because of the orchagent abort triggered by SAI programming failure
# Globals:
#  None
# Arguments:
#  None
# Returns:
#  None
###############################################################################
save_sai_failure_dump(){
    if [ ! -d "/var/log/sai_failure_dump/" ]; then
        return
    fi
    for file in $(find_files "/var/log/sai_failure_dump/"); do
        if $TAR -tf $TARFILE | grep $BASE/log/$(basename $file); then
            # if the files are already collected under the log/ dir
            # just add a symbolic link
            if [ ! -z "${file##*.gz}" ]; then
                # files saved under log/ are zipped with gz
                file=$file.gz
            fi
            ${CMD_PREFIX}save_symlink ${file} sai_failure_dump log
        else
            if [ ! -z "${file##*.gz}" ]; then
                ${CMD_PREFIX}save_file ${file} sai_failure_dump true true
            else
                ${CMD_PREFIX}save_file ${file} sai_failure_dump false true
            fi
        fi
        #Clean up the file once its part of tech support
        rm -f $file
    done
}

###############################################################################
# Get number of ASICs in the platform
# Globals:
#  None
# Arguments:
#  None
# Returns:
#  ASIC Count
###############################################################################
get_asic_count() {
    trap 'handle_error $? $LINENO' ERR
    local redirect_eval="2>&1"
    if ! $SAVE_STDERR
    then
         redirect_eval=""
    fi
    local cmd="python -c 'from sonic_py_common.multi_asic import get_num_asics; print(get_num_asics())'"
    echo `eval ${cmd} ${redirect_eval}`
}

###############################################################################
# Get counter snapshot
# Globals:
#  None
# Arguments:
#  asic_name: Name of the asic vendor
#  index: Index of counter snapshot
# Returns:
#  None
###############################################################################
save_counter_snapshot() {
    trap 'handle_error $? $LINENO' ERR
    local asic_name="$1"
    local idx=$2
    counter_t=$(date +'%d/%m/%Y %H:%M:%S:%6N')

    save_cmd "echo $counter_t" "date.counter_$idx"
    save_cmd "show interface counters" "interface.counters_$idx"
    if ! $IS_SUPERVISOR; then
       save_cmd "show queue counters" "queue.counters_$idx"
    fi
    save_redis "COUNTERS_DB" "COUNTERS_DB_$idx"

    if [ "$asic_name" = "broadcom" ]; then
        save_cmd "cat /proc/bcm/knet/dstats" "broadcom.knet_drop.counters_$idx"
        save_cmd "cat /proc/bcm/knet/stats" "broadcom.knet_filter.counters_$idx"
        if [ -e /usr/local/bin/softnet_stat.sh ]; then
            save_cmd "softnet_stat.sh" "softnet_queue.counters_$idx"
        fi
        if [ -e /proc/bcm/knet/rx_drop ]; then
            save_cmd "cat /proc/bcm/knet/rx_drop" "broadcom.knet_queue.counters_$idx"
        fi
    fi
    save_cmd_all_ns "netstat -i" "netstat.counters_$idx"
    save_cmd_all_ns "ifconfig -a" "ifconfig.counters_$idx"
}

###############################################################################
# save the debug dump output
###############################################################################
save_dump_state_all_ns() {
    trap 'handle_error $? $LINENO' ERR
    MODULES="$(dump state -s | sed '1d;2d' | awk '{print $1}')"
    local UVDUMP="unified_view_dump"
    echo "DEBUG DUMP: Modules Available to Generate Debug Dump Output"
    echo $MODULES
    $MKDIR $V -p $LOGDIR/$UVDUMP

    for addr in $MODULES;
    do
            save_cmd "dump state $addr all --key-map" "$UVDUMP/$addr"
            if [[ ( "$NUM_ASICS" > 1 ) ]] ; then
                for (( i=0; i<$NUM_ASICS; i++ ))
                do
                        local cmd="dump state $addr all --key-map --namespace asic$i"
                        local file="$UVDUMP/$addr.asic$i"
                        save_cmd "$cmd" "$file"
                done
            fi
    done
}

###############################################################################
# Save important files that are present in container storage for better debugging
# Files will be saved under dump/<container_name>/
# Types of Files Saved:
# 1) rsyslogd.conf
###############################################################################
save_container_files() {
    trap 'handle_error $? $LINENO' ERR
    local CONTAINER_FDUMP="container_dumps"
    # Get the running container names
    container_names=$(docker ps --format '{{.Names}}' --filter status=running)
    for name in $container_names; do
        $MKDIR $V -p $LOGDIR/$CONTAINER_FDUMP/$name
        copy_from_docker $name "/etc/rsyslog.conf" $LOGDIR/$CONTAINER_FDUMP/$name/rsyslog.conf
    done
}

###############################################################################
# Main generate_dump routine
# Globals:
#  All of them.
# Arguments:
#  None
# Returns:
#  None
###############################################################################
main() {
    trap 'handle_error $? $LINENO' ERR
    local start_t=0
    local end_t=0
    NUM_ASICS=$(get_asic_count)
    ${CMD_PREFIX}renice +5 -p $$ >> /dev/null
    ${CMD_PREFIX}ionice -c 2 -n 5 -p $$ >> /dev/null

    # Created file as a reference to compare modification time
    $TOUCH --date="${SINCE_DATE}" "${REFERENCE_FILE}"
    $MKDIR $V -p $TARDIR

    # Start with this script so its obvious what code is responsible
    $LN $V -s /usr/local/bin/generate_dump $TARDIR
    $TAR $V -chf $TARFILE -C $DUMPDIR $BASE
    $RM $V -f $TARDIR/sonic_dump

    # Start populating timing data
    echo $BASE > $TECHSUPPORT_TIME_INFO
    start_t=$(date +%s%3N)

    # Capture /proc state early
    save_proc /proc/buddyinfo /proc/cmdline /proc/consoles \
        /proc/cpuinfo /proc/devices /proc/diskstats /proc/dma \
        /proc/interrupts /proc/iomem /proc/ioports /proc/kallsyms \
        /proc/loadavg /proc/locks /proc/meminfo /proc/misc \
        /proc/modules /proc/self/mounts /proc/self/net \
        /proc/pagetypeinfo /proc/partitions /proc/slabinfo \
        /proc/softirqs /proc/stat /proc/swaps /proc/sysvipc /proc/timer_list \
        /proc/uptime /proc/version /proc/vmallocinfo /proc/vmstat \
        /proc/zoneinfo &
    save_proc_stats &
    end_t=$(date +%s%3N)
    echo "[ Capture Proc State ] : $(($end_t-$start_t)) msec" >> $TECHSUPPORT_TIME_INFO
    wait

    # capture /sys info - include acpi info and pre-process pstore info
    save_sys /sys/firmware/acpi/tables /sys/fs/pstore &
    end_t2=$(date +%s%3N)
    echo "[ Capture Sys info ] : $(($end_t2-$end_t)) msec" >> $TECHSUPPORT_TIME_INFO
    wait

    # capture /var/lib/systemd/pstore info
    start_t=$(date +%s%3N)
    save_pstore /var/lib/systemd/pstore &
    end_t=$(date +%s%3N)
    echo "[ Capture pstore info ] : $(($end_t-$start_t)) msec" >> $TECHSUPPORT_TIME_INFO
    wait
    
    # Save all the processes within each docker
    save_cmd "show services" services.summary &

    # Save reboot cause information
    save_cmd "show reboot-cause" reboot.cause &
    save_cmd "show reboot-cause history" "reboot.cause.history" &
    wait

    local asic="$(/usr/local/bin/sonic-cfggen -y /etc/sonic/sonic_version.yml -v asic_type)"
    local device_type=`sonic-db-cli CONFIG_DB hget 'DEVICE_METADATA|localhost' type`
    # 1st counter snapshot early. Need 2 snapshots to make sense of counters trend.
    save_counter_snapshot $asic 1

    save_cmd "show asic-sdk-health-event received" "asic.sdk.health.event" &

    save_cmd "systemd-analyze blame" "systemd.analyze.blame" &
    save_cmd "systemd-analyze dump" "systemd.analyze.dump" &
    save_cmd "systemd-analyze plot" "systemd.analyze.plot.svg" &
    wait

    save_platform_info &
    save_cmd "show vlan brief" "vlan.summary" &
    save_cmd "show version" "version" &
    save_cmd "show platform summary" "platform.summary" &
    wait

    save_cmd "cat /host/machine.conf" "machine.conf" &
    save_cmd "cat /boot/config-$(uname -r)" "boot.conf" &
    save_cmd "docker stats --no-stream" "docker.stats" &
    wait

    save_cmd "sensors" "sensors" &
    save_cmd "lspci -vvv -xx" "lspci" &
    save_cmd "lsusb -v" "lsusb" &
    save_cmd "sysctl -a" "sysctl" &
    wait

    save_ip_info &
    save_bridge_info &
    wait

    save_frr_info &

    save_bgp_info &
    save_evpn_info &
    wait

    save_cmd "show interface status -d all" "interface.status" &
    save_cmd "show interface transceiver presence" "interface.xcvrs.presence" &
    save_cmd "show interface transceiver eeprom --dom" "interface.xcvrs.eeprom" &
    save_cmd "show ip interface -d all" "ip.interface" &
    save_cmd "sfputil show eeprom-hexdump" "interface.xcvrs.eeprom.raw" &
    wait

    save_cmd "lldpctl" "lldpctl" &
    if [[ ( "$NUM_ASICS" > 1 ) ]]; then
        for (( i=0; i<$NUM_ASICS; i++ ))
        do
            save_cmd "docker exec lldp$i lldpcli show statistics" "lldp$i.statistics" &
            save_cmd "docker logs bgp$i" "docker.bgp$i.log" &
            save_cmd "docker logs swss$i" "docker.swss$i.log" &
        done
    else
        save_cmd "docker exec lldp lldpcli show statistics" "lldp.statistics" &
        save_cmd "docker logs bgp" "docker.bgp.log" &
        save_cmd "docker logs swss" "docker.swss.log" &
    fi
    wait

 	save_cmd "stpctl all" "stp.log"
    save_cmd "show spanning-tree" "stp.show"
    save_cmd "show spanning-tree statistics" "stp.stats"
    save_cmd "show spanning-tree bpdu_guard" "stp.bg"
    save_cmd "show spanning-tree root_guard" "stp.rg"

    save_cmd "ps aux" "ps.aux" &
    save_cmd "top -b -n 1" "top" &
    save_cmd "free" "free" &
    wait
    save_cmd "vmstat 1 5" "vmstat" &
    save_cmd "vmstat -m" "vmstat.m" &
    save_cmd "vmstat -s" "vmstat.s" &
    wait
    save_cmd "mount" "mount" &
    save_cmd "df" "df" &
    save_cmd "dmesg" "dmesg" &
    wait

    save_nat_info &
    save_bfd_info &
    wait
    save_redis_info &

    save_container_files &

    if $DEBUG_DUMP
    then
        save_dump_state_all_ns &
    fi
    wait

    save_cmd "docker ps -a" "docker.ps" &
    save_cmd "docker top pmon" "docker.pmon" &

    if [[ -d ${PLUGINS_DIR} ]]; then
        local -r dump_plugins="$(find ${PLUGINS_DIR} -type f -executable)"
        for plugin in $dump_plugins; do
            # save stdout output of plugin and gzip it
            save_cmd "$plugin" "$(basename $plugin)" true &
        done
    fi
    wait

    save_cmd "dpkg -l" "dpkg" &
    save_cmd "who -a" "who" &
    save_cmd "swapon -s" "swapon" &
    wait
    save_cmd "hdparm -i /dev/sda" "hdparm" &
    save_cmd "ps -AwwL -o user,pid,lwp,ppid,nlwp,pcpu,pri,nice,vsize,rss,tty,stat,wchan:12,start,bsdtime,command" "ps.extended" &
    wait

    save_saidump_by_route_size

    if [ "$asic" = "barefoot" ]; then
        collect_barefoot
    fi

    if [[ "$asic" = "mellanox" ]]; then
        collect_mellanox
    fi

    if [ "$asic" = "broadcom" ]; then
        collect_broadcom
    fi

    if [ "$asic" = "cisco-8000" ]; then
        collect_cisco_8000
    fi

    if [[ "$asic" = "nvidia-bluefield" ]]; then
        collect_nvidia_bluefield
    fi

    if [ "$asic" = "marvell-teralynx" ]; then
        collect_marvell_teralynx
    fi

    if [ "$asic" = "marvell-prestera" ]; then
        collect_marvell_prestera
    fi

    if [ "$asic" = "pensando" ]; then
        collect_pensando
    fi


    # 2nd counter snapshot late. Need 2 snapshots to make sense of counters trend.
    save_counter_snapshot $asic 2

    # Copying the /etc files to a directory and then tar it
    $CP -r /etc $TARDIR/etc
    rm_list=$(find -L $TARDIR/etc -maxdepth 5 -type l)
    if [ ! -z "$rm_list" ]
    then
        rm -f $rm_list
    fi

    # Remove secret from /etc files before tar
    remove_secret_from_etc_files $TARDIR

    # Remove unecessary files
    $RM $V -rf $TARDIR/etc/alternatives $TARDIR/etc/passwd* \
    $TARDIR/etc/shadow* $TARDIR/etc/group* $TARDIR/etc/gshadow* \
    $TARDIR/etc/ssh* $TARDIR/etc/mlnx $TARDIR/etc/mft \
    $TARDIR/etc/ssl/certs/* $TARDIR/etc/ssl/private/*
    rm_list=$(find -L $TARDIR -type f \( -iname \*.cer -o -iname \*.crt -o \
        -iname \*.pem -o -iname \*.key -o -iname \*snmpd.conf\* -o -iname \*get_creds\* \))
    if [ ! -z "$rm_list" ]
    then
        rm $rm_list
    fi

    save_log_files &
    save_crash_files &
    save_warmboot_files &
    wait

    save_to_tar

    save_sai_failure_dump 

    if [[ "$asic" = "mellanox" ]] || [[ "$asic" = "nvidia-bluefield" ]]; then
        collect_nvidia_sdk_dumps
    fi

    finalize
}

###############################################################################
# Finalize dump generation
###############################################################################
finalize() {
    # Save techsupport timing profile info
    save_file $TECHSUPPORT_TIME_INFO log false true

    if $DO_COMPRESS; then
        RC=0
        $GZIP $V $TARFILE || RC=$?
        if [ $RC -eq 0 ]; then
            TARFILE="${TARFILE}.gz"
        else
            echo "WARNING: gzip operation appears to have failed." >&2
        fi
        # sometimes gzip takes more than 20 sec to finish, causing file time create validation
        # to fail. touching the tarfile created to refresh modify time.
        touch ${TARFILE}
    fi

    # Invoke the TechSupport Cleanup Hook
    setsid python3 /usr/local/bin/techsupport_cleanup.py ${TARFILE} &> /tmp/techsupport_cleanup.log &

    if ! $SAVE_STDERR
    then
    	exit $RETURN_CODE
    fi
}


###############################################################################
# Remove secret from pipeline input and output result to pipeline.
# Globals:
#  None
# Arguments:
#  None
# Returns:
#  None
###############################################################################
remove_secret_from_config_db_dump() {
    # Remove tacacs & radius passkey and snmp community from config DB
    sed -E 's/\"passkey\"\s*:\s*\"([^\"]*)\"/\"passkey\":\"****\"/g; /SNMP_COMMUNITY/,/\s{2,4}\},/d'
}


###############################################################################
# Remove secret from file.
###############################################################################
remove_secret_from_config_db_dump_file() {
    local dumpfile=$1
    if [ -e ${dumpfile} ]; then
        cat $dumpfile | remove_secret_from_config_db_dump > $dumpfile.temp
        mv $dumpfile.temp $dumpfile
    fi
}

###############################################################################
# Remove secret from dump files.
# Globals:
# Arguments:
#  dumppath: the dump file path.
# Returns:
#  None
###############################################################################
remove_secret_from_etc_files() {
    local dumppath=$1
    echo "Remove secret from etc files."
    # Remove tacacs passkey from tacplus_nss.conf
    local secret_regex='s/(secret=)([^,|\S]*)(.*)/\1****\3/g'
    sed -i -E $secret_regex $dumppath/etc/tacplus_nss.conf

    # Remove radius passkey from radius_nss.conf
    sed -i -E $secret_regex $dumppath/etc/radius_nss.conf

    # Remove tacacs passkey from common-auth-sonic
    sed -i -E 's/(secret=)(\S*)/\1****/g' $dumppath/etc/pam.d/common-auth-sonic

    # Remove tacacs passkey from pam_radius_auth.conf
    sed -i -E 's/^([^#]\S*\s*)(\S*)/\1****/g' $dumppath/etc/pam_radius_auth.conf

    # Remove radius passkey from per-server conf file /etc/pam_radius_auth.d/{ip}_{port}.conf
    for filename in $dumppath/etc/pam_radius_auth.d/*.conf; do
        sed -i -E 's/^([^#]\S*\s*)(\S*)/\1****/g' $filename
    done

    # Remove snmp community string from snmp.yml
    sed -i -E 's/(\s*snmp_\S*community\s*:\s*)(\S*)/\1****/g' $dumppath/etc/sonic/snmp.yml

    # Remove secret from /etc/sonic/config_db.json
    remove_secret_from_config_db_dump_file $dumppath/etc/sonic/config_db.json

    # Remove secret from /etc/sonic/golden_config_db.json
    remove_secret_from_config_db_dump_file $dumppath/etc/sonic/golden_config_db.json

    # Remove secret from /etc/sonic/old_config/

    # Remove snmp community string from old_config/snmp.yml
    local oldsnmp=${dumppath}/etc/sonic/old_config/snmp.yml
    if [ -e ${oldsnmp} ]; then
        sed -i -E 's/(\s*snmp_\S*community\s*:\s*)(\S*)/\1****/g' $oldsnmp
    fi

    # Remove secret from /etc/sonic/config_db.json
    remove_secret_from_config_db_dump_file ${dumppath}/etc/sonic/old_config/config_db.json

    # Remove secret from /etc/sonic/golden_config_db.json
    remove_secret_from_config_db_dump_file ${dumppath}/etc/sonic/old_config/golden_config_db.json
}

###############################################################################
# Terminates generate_dump early just in case we have issues.
# Globals:
#  None
# Arguments:
#  retcode: 0-255 return code to exit with. default is 1
#  msg: (OPTIONAL) msg to print to standard error
# Returns:
#  None
###############################################################################
abort() {
    local exitcode=${1:-1}
    local msg=${2:-Error. Terminating early for safety.}
    echo "$msg" >&2
    exit $exitcode
}

###############################################################################
# Prints usage to stdout.
# Globals:
#  None
# Arguments:
#  None
# Returns:
#  None
###############################################################################
usage() {
    cat <<EOF
$0 [-xnvh]

Create a SONiC system dump for support/debugging. Requires root privileges.

OPTIONS
    -x
        Enable bash debug mode.
    -h
        The usage information you are reading right now
    -v
        Enable verbose mode. All commands (like tar, mkdir, rm..) will have -v
        passed to them
    -n
        Noop mode. Don't actually create anything, just echo what would happen
    -a
        Allow any process stop. This allows for collection of platform HW register
        status, which may require potential system interruption
    -z
        Don't compress the tar at the end.
    -s DATE
        Collect logs since DATE;
        The argument is a mostly free format human readable string such as
        "24 March", "yesterday", etc.
    -t TIMEOUT_MINS
        Command level timeout in minutes
    -r
        Redirect any intermediate errors to STDERR
    -d
        Collect the output of debug dump cli
EOF
}


while getopts ":xnvhzas:t:r:d" opt; do
    case $opt in
        x)
            # enable bash debugging
            PS4="+(${BASH_SOURCE}:${LINENO}): ${FUNCNAME[0]:+${FUNCNAME[0]}(): }"
            set -x
            ;;
        h)
            usage
            exit $EXT_SUCCESS
            ;;
        v)
            # echo commands about to be run to stderr
            V="-v"
            ;;
        n)
            TAR="echo tar"
            MKDIR="echo mkdir"
            RM="echo rm"
            LN="echo ln"
            GZIP="echo gzip"
            CMD_PREFIX="echo "
            MV="echo mv"
            CP="echo cp"
            TOUCH="echo touch"
            NOOP=true
            ;;
        z)
            DO_COMPRESS=false
            ;;
        a)
            ALLOW_PROCESS_STOP="-a"
            ;;
        s)
            SINCE_DATE="${OPTARG}"
            # validate date expression
            date --date="${SINCE_DATE}" &> /dev/null || abort "${EXT_INVALID_ARGUMENT}" "Invalid date expression passed: '${SINCE_DATE}'"
            ;;
        t)
            if ! [[ ${OPTARG} =~ ^[0-9]+$ ]]; then
                echo "Invalid timeout value: ${OPTARG}, Please enter a numeric value."
                exit $EXT_GENERAL
            fi
            TIMEOUT_MIN="${OPTARG}"
            ;;
        r)
            SAVE_STDERR=false
            ;;
        d)
            DEBUG_DUMP=true
            ;;
        /?)
            echo "Invalid option: -$OPTARG" >&2
            exit $EXT_GENERAL
            ;;
    esac
done

# Check permissions before proceeding further
if [ `whoami` != root ] && ! $NOOP;
then
    echo "$0: must be run as root (or in sudo)" >&2
    exit $EXT_INVALID_ARGUMENT
fi

##
## Attempt Locking
##
platform_name=$(show platform summary --json | python -c 'import sys, json; print(json.load(sys.stdin)["platform"])')
supervisor=0
PLATFORM_ENV_CONF=/usr/share/sonic/device/${platform_name}/platform_env.conf
if [ -f "$PLATFORM_ENV_CONF" ]; then
    source $PLATFORM_ENV_CONF
fi
if [[ x"$supervisor" == x"1" ]]; then
    IS_SUPERVISOR=true
fi
    
if $MKDIR "${LOCKDIR}" &>/dev/null; then
    trap 'handle_exit' EXIT
    echo "$$" > "${PIDFILE}"
    # This handler will exit the script upon receiving these interrupts
    # Trap configured on EXIT will be triggered by the exit from handle_signal function
    trap 'handle_sigterm' SIGHUP SIGQUIT SIGTERM
    trap 'handle_sigint' SIGINT
    echo "Lock succesfully accquired and installed signal handlers"
    # Proceed with the actual code
    if [[ ! -z "${V}" ]]; then
        set -v
    fi
    main
else
    # lock failed, check if the other PID is alive
    PID_PROG="$(cat "${PIDFILE}")"

    if [ $? != 0 ]; then
        # Another instance is probably about to remove the lock or PIDfile doesn't exist
        rm_lock_and_exit
    fi

    if ! kill -0 $PID_PROG &>/dev/null; then
        # Lock is stale
        echo "Removing stale lock of nonexistant PID ${PID_PROG}"
        rm_lock_and_exit
    else
        # Lock is valid and the other instance is active. Exit Now
        echo "Another instance of techsupport running with PID: ${PID_PROG}, please retry after some time..." >&2
        exit $EXT_LOCKFAIL
    fi
fi
