#!/bin/bash
#
#   Copyright 2005-2009, 2019, 2022 VMware, Inc.  All rights reserved.
#
#   Support program for VMware products on Linux hosts.
#
#   Collects various configuration and log files
#   for use when troubleshooting VMware products on Linux hosts.
#


#   Function: usage prints how to use this script

function usage {
   echo ""
   echo "Usage: $0 [-h] [-w output directory] [-q] [-f VM directory]"
   echo "  -h prints this usage statement"
   echo "  -q runs in quiet mode"
   echo "  -w <dir> sets the working directory used for the output files"
   echo "  -v VMX filename(s) or dir path(s) separated by :"
   echo "                   Collects logs only for the specified VMs"
   echo "                   Example: vm-support -v \"path1/file1 : path2/file2\""
   echo "  -f <VMdir> collects the Record/Replay and FT logs from the VM directory "
   exit
}


TARFILE=vm-$(date -I).$$.tgz
VER=0.90
OUTPUT_DIR=vm-support.$$

#   Function: banner prints any number of strings padded with
#   newlines before and after.

function banner {
   echo
   for option in "$@"
   do
      echo $option
   done
   echo
}


function UpdateSpinner {
   case $SPINNER in
      "|")
           SPINNER="/"
      ;;

      "/")
           SPINNER="-"
      ;;

      "-")
           SPINNER="\\"
      ;;

      "\\")
           SPINNER="|"
      ;;

      *)
           SPINNER="|"
      ;;
   esac
   echo -en "\rPreparing Files: $SPINNER"
}

# Function: checkOutputDir checks for
# a self contained output directory for later archiving
# and creates it if needed

function checkOutputDir {
   DIR="$1"

   if [ ! -d "${OUTPUT_DIR}$DIR" ]; then
      mkdir -p "${OUTPUT_DIR}$DIR"

      if [ $? != 0 ]; then
         banner "Could not create ./${OUTPUT_DIR}$DIR... " \
                "Have you run out of disk space?" "Continuing"
         return -1
      fi
   fi
   return 0
}

# Function: addArchive copies whatever files and directories you give it to
#   Function: addtar copies whatever files and directories you give it to
#   a self contained output directory for later tar'ing
#   Working on copies could slow this down with VERY large files but:
#   1) We don't expect VERY large files
#   2) Since /proc files can be copied this preserves the tree without
#      having to cat them all into a file.
#   3) tar barfs on open files like logs if it changes while it's tar'ing.
#     Copying file first makes sure tar doesn't complain


function addtar {
   FILE=$1

   if [ -z "$FILE" ]; then
       return
   fi

   DIR=$(dirname "$FILE")
   checkOutputDir "$DIR"
   if [ $? != 0 ]; then
      return
   fi

   # Ignore stdout and handle errors.
   if [ $quiet_mode -eq 0 ]; then
      UpdateSpinner
   fi
   cp -pr "$FILE" "${OUTPUT_DIR}$DIR" 2>/dev/null

   # We could have failed to copy for several reasons
   # If we path had a shell special character (* ? .)
   # or if the file is in /proc
   if [ $? != 0 ]; then
      FILENAME=${FILE##*/}
      for line in "$DIR"/$FILENAME
      do
         if [ -e "$line" ]; then
            # Ignore stdout and handle errors.
            if [ $quiet_mode -eq 0 ]; then
               UpdateSpinner
            fi
            if [ `expr "$line" : '.*state.*log'` -eq 0 ]
            then
                cp -pr "$line" "${OUTPUT_DIR}$DIR" 2>/dev/null
            fi
         fi # does file exist
      done # for each file in the list
   fi # if copy failed

}

#   Function: addtar_smallfile calls the addtar
#   command on the given file(s) only if the file
#   size if less than 30K.

function addtar_smallfiles {
   for file in "$@"
   do
      if [ -e "$file" ]; then
         size=`stat --format="%s" "$file"`
         if [ $size -lt 30000 ]; then
            addtar "$file"
         fi
      fi # does file exist
   done # for each file in the list
}

#   Function: runcmd executes the command ($1)
#   redirected to a file ($2)

function runcmd {
   DIR=`dirname "$2"`
   checkOutputDir "$DIR"
   if [ $? != 0 ]; then
      return
   fi

   $1 > "${OUTPUT_DIR}$2" 2>/dev/null

   if [ $? != 0 ]; then
      banner "Either could not run $1 or could not write to ${OUTPUT_DIR}$2" \
             "Do you have a full disk?" "Continuing..."
   fi
   UpdateSpinner
}

function addteam {
   teamfile=$1

   sed -n -e "s/<URL type=\"string\">//Ip" "$line" | \
      sed -n -e "s/<\/URL>//Ip" | while read vmline
   do
      addvm "$vmline"
   done
}

function addvm {
   cfgfile=${1//\"/}

   # No config file... nothing to do here.
   if ! [ -e "$cfgfile" ]; then
      return
   fi

   # See if this is a team file
   suffix=${cfgfile##*.}
   if [ "$suffix" = "vmtm" ];then
      addteam "$cfgfile"
   fi

   [ -e "$OUTPUT_DIR$cfgfile" ] && return
   addtar "$cfgfile"
   dir=$(dirname "$cfgfile")
   addtar "$dir/*.log"
   addtar "$dir/*.vmx"
   addtar "$dir/*.vmxf"
   addtar "$dir/*.vmtm"
   addtar "$dir/*.vmpl"
   addtar_smallfiles "$dir"/*.vmsn
   addtar_smallfiles "$dir"/*.vmdk
   addtar "$dir/vmmcores*"
   addtar "$dir/gmmcore*"
   addtar "$dir/core"
   addtar "$dir/core.*"
   addtar "$dir/debug.vmss"

   #collecting the file list in the VM's folder
   ls -al "$dir" > /"$dir"/VMFolderList.txt
   addtar "$dir/VMFolderList.txt"

   # monitor stats, kstats, callstack profiling
   addtar "$dir/stats"
}

#
# Get support information about hostd
#

function snarf_hostd_state {
   #   Parse /etc/vmware/hostd/vmInventory.xml and add the virtual
   #   machine configuration and log files to the
   #   list of files

   INVENTORY_LOCATION="/etc/vmware/hostd/vmInventory.xml"
   NEW_LOCATION=`sed -n -e "s/^\s*<vmInventory>\s*\(\.*\)/\1/pg" /etc/vmware/hostd/config.xml | sed -n -e "s/\s*<\/vmInventory>//pg"`
   if [ -n "$NEW_LOCATION" ]; then
      INVENTORY_LOCATION=$NEW_LOCATION
   fi
   # Grab the inventory file itself, in case it is configured differently
   addtar $INVENTORY_LOCATION
   CFGLIST=`sed -n -e "s/^\s*<vmxCfgPath>\s*\(\.*\)/\1/g" -e "s/<\/vmxCfgPath>//pg" $INVENTORY_LOCATION`
   IFS=$'\n'
   for CFGNAME in $CFGLIST; do
      addvm "$CFGNAME"
   done
   unset IFS

   # Grab a dump of all hostd properties export through the VIM API.
   # XXX: Cant do this outside ESX because we cant assume the python environment is
   # setup correctly in non-ESX products. See bug 119163
   # runcmd "/usr/bin/vmware-vimdump -o /tmp/vimdump.$$.txt"
}

quiet_mode=0
working_dir=
list_VMs_mode=0
list_VMs=

# main()
while getopts hqw:v: arg ; do
   case "$arg" in
      "q") quiet_mode=1
           ;;
      "w") working_dir=$OPTARG
           ;;
      "v") list_VMs_mode=1
           list_VMs=$OPTARG
           ;;
      "h") usage ;;
        *) usage ;;
   esac
done

#   Start message

banner "Support program for VMware products on Linux hosts - Version $VER"

#   Check for root privilege

if [ $(id -u) != "0" ]; then
   banner "You are not root, some system information can't be collected."
fi

# Source /etc/profile.  If we can't find it, it's the users problem to get
# their paths straight.

if [ -f /etc/profile ]; then
   . /etc/profile
fi

# Protect against non-default values of $IFS (Not all scripts in /etc/profile.d/
# are good citizens).
unset IFS

#       Set the working directory if specified

if [ -n "$working_dir" ]; then
   if ! cd "$working_dir"; then
      banner "Could not set working directory to '$working_dir'."
      exit
   fi
fi

# In quiet mode, print this here since the spinner won't be printed/updated
if [ $quiet_mode -eq 1 ]; then
    banner "Preparing files ..."
fi

# Set umask to make diagnostic information unreadable to other users to avoid
# possible information leakage.
umask 0077

# Clear up temporary files if the process is killed midway.
trap "rm -rf ${OUTPUT_DIR}; exit 1" HUP INT QUIT TERM ABRT

#   make a subdir to put all your files in.  die if it does not create
mkdir $OUTPUT_DIR

if [ $? != 0 ]; then
   banner "Could not create ./${OUTPUT_DIR}... Exiting..." \
          "Please cd to a directory to which you can write"
   exit
fi

if [ $list_VMs_mode -eq 1 ]; then
   echo $list_VMs | sed -e "s/ *: */\n/g" | \
      while read line
   do
      addvm "$line"
   done
else
   #   Parse /etc/vmware/vm-list and add the virtual
   #   machine configuration and log files to the
   #   list of files

   if [ -e ~/.vmware/inventory.vmls ]; then
      # This picks up only the user registered VM's
      sed -n -e "s/.*config *= \"//Ip" ~/.vmware/inventory.vmls | \
         sed -n -e "s/\"//Ip" | \
         while read line
      do
            addvm "$line"
      done
   fi

   #       Pick up the user's vmlist for GSX installs.

   if [ -e ~/.vmware/vmlist ]; then
      # This picks up only the user registered VM's
      sed -n -e "s/.*config *= \"//Ip" ~/.vmware/vmlist | \
         sed -n -e "s/\"//Ip" | \
         while read line
      do
         addvm "$line"
      done
   fi

   if [ $(id -u) == "0" ] && [ -e "/etc/vmware/vm-list" ]; then
      # This picks up the VM in /etc/vmware/vm-list
      sed -n -e "s/config \"//Ip" /etc/vmware/vm-list | \
         sed -n -e "s/\"//Ip" | \
         while read line
      do
         addvm "$line"
      done

   fi

   if [ -e ~/.vmware/preferences ]; then
       sed -n -e "s/.*openedObj.\+\.file *= \"//Ip" ~/.vmware/preferences | \
          sed -n -e "s/\"//Ip" | \
          while read line
       do
          addvm "$line"
      done
   fi
fi

# Try to collect bootloader config
if [ -e /etc/lilo.conf ]; then
   addtar "/etc/lilo.conf"
fi

# And for grub we are not sure about the exact default location so collect them
# all.
if [ -e /boot/grub/grub.conf ]; then
   addtar "/boot/grub/grub.conf"
fi
if [ -e /boot/grub/menu.lst ]; then
   addtar "/boot/grub/menu.lst"
fi
if [ -e /etc/grub.conf ]; then
   addtar "/etc/grub.conf"
fi

# Add virtual machines spotted from hostd if present
if [ -e /etc/vmware/hostd/config.xml ]; then
   snarf_hostd_state
fi

if [ -e /var/log/vmware/webAccess ]; then
   addtar "/var/log/vmware/webAccess"
fi

addtar "/etc/crontab"
addtar "/etc/cron.daily"
addtar "/etc/cron.hourly"
addtar "/etc/cron.monthly"
addtar "/etc/cron.weekly"
addtar "/etc/modules.conf"
addtar "/etc/ntp.conf"
addtar "/etc/security/*"

# Add VMware configuration sans sensitive files
(
   IFS=$'\n'
   for f in `find /etc/vmware -type f | egrep -v '/ssl/|/license-'`; do
      addtar "$f"
   done
)

# Add UI logs
addtar "/tmp/vmware*"

# Add services
addtar "/etc/services"

addtar "$HOME/.vmware/*"
addtar "/var/log/boot*"
addtar "/var/log/secure*"
addtar "/var/log/messages*"
addtar "/etc/xinetd.d/vmware-authd"
addtar "/var/log/vmware/*"
addtar "/var/lib/vmware/hostd/*"

# Add normal user readable files in the top-level of /proc.
# stderr redirected to /dev/null.
for procfile in `find /proc -maxdepth 1 -type f -perm '/o=r' 2>/dev/null | grep -v -e kcore -e kmsg -e acpi -e pagemap -e kpagecount -e kpageflags -e use-gss-proxy -e smaps`
do
   addtar "$procfile"
done

#   Commands to run ($1) and redirect to logs ($2) for
#   inclusion.

runcmd "echo vm-support version: $VER" "/tmp/vm-support-version.$$.txt"
runcmd "lspci -M -vvv -nn -xxxx" "/tmp/lspci1.$$.txt"
runcmd "lspci -t -v -nn -F ${OUTPUT_DIR}/tmp/lspci1.$$.txt" "/tmp/lspci2.$$.txt"
runcmd "lspci -vvv -nn" "/tmp/lspci3.$$.txt"
runcmd "/sbin/lsmod" "/tmp/modules.$$.txt"
runcmd "uname -a" "/tmp/uname.$$.txt"
runcmd "df" "/tmp/df.$$.txt"
runcmd "cat /etc/issue" "/tmp/issue.$$.txt"
runcmd "ifconfig -a" "/tmp/ifconfig.$$.txt"
# Don't assume rpm exists (debian/ubuntu don't provide it)
which rpm > /dev/null 2>&1
if [ $? -eq 0 ]; then
   runcmd "rpm -qa" "/tmp/rpm-qa.$$.txt"
else
   which dpkg > /dev/null 2>&1
   if [ $? -eq 0 ]; then
      runcmd "dpkg -l" "/tmp/dpkg-l.$$.txt"
   fi
fi
runcmd "netstat -lan" "/tmp/netstat-lan.$$.txt"
runcmd "route" "/tmp/route.$$.txt"
runcmd "mount" "/tmp/mount.$$.txt"
runcmd "vmware -v" "/tmp/vmware.$$.txt"
runcmd "dmesg" "/tmp/dmesg.$$.txt"
runcmd "free" "/tmp/free.$$.txt"
runcmd "uptime" "/tmp/uptime.$$.txt"
runcmd "date" "/tmp/date.$$.txt"
runcmd "ps auwwx" "/tmp/ps-auwwx.$$.txt"
runcmd "ulimit -a" "/tmp/ulimit-a.$$.txt"
runcmd "umask" "/tmp/umask.$$.txt"

#   Terminate the spinner
echo
echo

#   Perform the tar ('S' for sparse core files)

if [ $quiet_mode -eq 0 ]; then
   tar -czSvf $TARFILE $OUTPUT_DIR
else
   tar -czSf $TARFILE $OUTPUT_DIR
fi

if [ $? != 0 ]; then
   banner "The tar did not successfully complete!" \
          "If tar reports that a file changed while reading, please attempt to rerun this script."
else
   size=`stat -t $TARFILE | cut -f2 -d " "`
   if [ $size -gt 10000000 ]; then
      banner "File: $(pwd | sed s/[\/]$//)/$TARFILE" \
             "NOTE: $TARFILE is greater than 10 MB." \
             "Please do not attach this file when submitting an incident report." \
             "Please contact VMware support for an ftp site." \
             "To file a support incident, go to http://www.vmware.com/support/sr/sr_login.jsp"
   else
      banner "File: $(pwd | sed s/[\/]$//)/$TARFILE" \
             "Please attach this file when submitting an incident report." \
             "To file a support incident, go to http://www.vmware.com/support/sr/sr_login.jsp"
   fi
fi


#   Clean up temporary files

rm -rf $OUTPUT_DIR

if [ $? != 0 ]; then
   banner "$OUTPUT_DIR was not successfully removed.  Please remove manually."
fi

#   End
