#!/usr/bin/env bash
#
# Copyright (c) 2007-2010 VMware, Inc.  All rights reserved.
#
# This script manages the services needed to run VMware software

# VMWARE_INIT_INFO_PLACEHOLDER
# Basic support for IRIX style chkconfig
# chkconfig: 235 03 99
# description: Manages the services needed to run VMware software
#!/bin/sh

# A few utility functions used by our shell scripts. These are patched in
# during make.

excludes_file=/etc/vmware-tools/excludes

. /usr/lib/vmware-tools/functions

# Execute a macro in the background
vmware_bg_exec() {
  local msg="$1"  # IN
  local func="$2" # IN
  shift 2

  if [ "$VMWARE_DEBUG" = 'yes' ]; then
    # Force synchronism when debugging
    vmware_exec "$msg" "$func" "$@"
  else
    echo -n '   '"$msg"' (background)'

    # On Caldera 2.2, SIGHUP is sent to all our children when this script exits
    # I wanted to use shopt -u huponexit instead but their bash version
    # 1.14.7(1) is too old
    #
    # Ksh does not recognize the SIG prefix in front of a signal name
    (trap '' HUP; "$func" "$@") 2>&1 | logger -t 'VMware[init]' -p daemon.err &

    vmware_success
    echo
    return 0
  fi
}

# This is a function in case a future product name contains language-specific
# escape characters.
vmware_product_name() {
  echo 'VMware Tools'
  exit 0
}

# This is a function in case a future product contains language-specific
# escape characters.
vmware_product() {
  echo 'tools-for-linux'
  exit 0
}

# They are a lot of small utility programs to create temporary files in a
# secure way, but none of them is standard. So I wrote this --hpreg
make_tmp_dir() {
  local dirname="$1" # OUT
  local prefix="$2"  # IN
  local tmp
  local serial
  local loop

  tmp="${TMPDIR:-/tmp}"

  # Don't overwrite existing user data
  # -> Create a directory with a name that didn't exist before
  #
  # This may never succeed (if we are racing with a malicious process), but at
  # least it is secure
  serial=0
  loop='yes'
  while [ "$loop" = 'yes' ]; do
    # Check the validity of the temporary directory. We do this in the loop
    # because it can change over time
    if [ ! -d "$tmp" ]; then
      echo 'Error: "'"$tmp"'" is not a directory.'
      echo
      exit 1
    fi
    if [ ! -w "$tmp" -o ! -x "$tmp" ]; then
      echo 'Error: "'"$tmp"'" should be writable and executable.'
      echo
      exit 1
    fi

    # Be secure
    # -> Don't give write access to other users (so that they can not use this
    # directory to launch a symlink attack)
    if mkdir -m 0755 "$tmp"'/'"$prefix$serial" >/dev/null 2>&1; then
      loop='no'
    else
      serial=`expr $serial + 1`
      serial_mod=`expr $serial % 200`
      if [ "$serial_mod" = '0' ]; then
        echo 'Warning: The "'"$tmp"'" directory may be under attack.'
        echo
      fi
    fi
  done

  eval "$dirname"'="$tmp"'"'"'/'"'"'"$prefix$serial"'
}

# Checks if the given pid represents a live process.
# Returns 0 if the pid is a live process, 1 otherwise
vmware_is_process_alive() {
  local pid="$1" # IN

  ps -p $pid | grep $pid > /dev/null 2>&1
}

# Check if the process associated to a pidfile is running.
# Return 0 if the pidfile exists and the process is running, 1 otherwise
vmware_check_pidfile() {
  local pidfile="$1" # IN
  local pid

  pid=`cat "$pidfile" 2>/dev/null`
  if [ "$pid" = '' ]; then
    # The file probably does not exist or is empty. Failure
    return 1
  fi
  # Keep only the first number we find, because some Samba pid files are really
  # trashy: they end with NUL characters
  # There is no double quote around $pid on purpose
  set -- $pid
  pid="$1"

  vmware_is_process_alive $pid
}

# Note:
#  . Each daemon must be started from its own directory to avoid busy devices
#  . Each PID file doesn't need to be added to the installer database, because
#    it is going to be automatically removed when it becomes stale (after a
#    reboot). It must go directly under /var/run, or some distributions
#    (RedHat 6.0) won't clean it
#

# Terminate a process synchronously
vmware_synchrone_kill() {
   local pid="$1"    # IN
   local signal="$2" # IN
   local second

   kill -"$signal" "$pid"

   # Wait a bit to see if the dirty job has really been done
   for second in 0 1 2 3 4 5 6 7 8 9 10; do
      vmware_is_process_alive "$pid"
      if [ "$?" -ne 0 ]; then
         # Success
         return 0
      fi

      sleep 1
   done

   # Timeout
   return 1
}

# Kill the process associated to a pidfile
vmware_stop_pidfile() {
   local pidfile="$1" # IN
   local pid

   pid=`cat "$pidfile" 2>/dev/null`
   if [ "$pid" = '' ]; then
      # The file probably does not exist or is empty. Success
      return 0
   fi
   # Keep only the first number we find, because some Samba pid files are really
   # trashy: they end with NUL characters
   # There is no double quote around $pid on purpose
   set -- $pid
   pid="$1"

   # First try a nice SIGTERM
   if vmware_synchrone_kill "$pid" 15; then
      return 0
   fi

   # Then send a strong SIGKILL
   if vmware_synchrone_kill "$pid" 9; then
      return 0
   fi

   return 1
}

# Determine if SELinux is enabled
isSELinuxEnabled() {
   if [ "`cat /selinux/enforce 2> /dev/null`" = "1" ]; then
      echo "yes"
   else
      echo "no"
   fi
}

# Runs a command and retries under the provided SELinux context if it fails
vmware_exec_selinux() {
   local command="$1"
   # XXX We should probably ask the user at install time what context to use
   # when we retry commands.  unconfined_t is the correct choice for Red Hat.
   local context="unconfined_t"
   local retval

   $command
   retval=$?
   if [ $retval -ne 0 -a "`isSELinuxEnabled`" = 'yes' ]; then
      runcon -t $context -- $command
      retval=$?
   fi

   return $retval
}

# Start the blocking file system.  This consists of loading the module and
# mounting the file system.
vmware_start_vmblock() {
   mkdir -p /tmp/VMwareDnD && chmod 1777 /tmp/VMwareDnD
   vmware_exec 'Loading module' vmware_load_module $vmblock
   exitcode=`expr $exitcode + $?`
   vmware_exec_selinux "mount -t vmblock none /proc/fs/vmblock/mountPoint"
}

# Stop the blocking file system
vmware_stop_vmblock() {
   # It's okay for us to not factor the umount command's error code into
   # exitcode since the module unload will fail if the file system remains
   # mounted.
   vmware_exec_selinux "umount /proc/fs/vmblock/mountPoint"

   vmware_unload_module $vmblock
}

is_ESX_running() {
  if [ ! -f "$vmdb_answer_SBINDIR"/vmware-checkvm ] ; then
    echo no
    return
  fi
  if "$vmdb_answer_SBINDIR"/vmware-checkvm -p | grep -q ESX; then
    echo yes
  else
    echo no
  fi
}

#
# Start vmblock only if ESX is not running and the config script
# built/loaded it (kernel is >= 2.4.0 and  product is tools-for-linux).
#
is_vmblock_needed() {

  [ -e $excludes_file ] && grep -q vmblock $excludes_file && echo no && return

  if [ "`is_ESX_running`" = 'yes' ]; then
    echo no
  else
    if [ "$vmdb_answer_VMBLOCK_CONFED" = 'yes' ]; then
      echo yes
    else
      echo no
    fi
  fi
}

vmware_etc_dir=/etc/vmware-tools

# Since this script is installed, our main database should be installed too and
# should contain the basic information
vmware_db="$vmware_etc_dir"/locations
if [ ! -r "$vmware_db" ]; then
    echo 'Warning: Unable to find '"`vmware_product_name`""'"'s main database '"$vmware_db"'.'
    echo

    exit 1
fi

#!/bin/sh

#
# Manage an installer database
#

# Add an answer to a database in memory
db_answer_add() {
  local dbvar="$1" # IN/OUT
  local id="$2"    # IN
  local value="$3" # IN
  local answers
  local i

  eval "$dbvar"'_answer_'"$id"'="$value"'

  eval 'answers="$'"$dbvar"'_answers"'
  # There is no double quote around $answers on purpose
  for i in $answers; do
    if [ "$i" = "$id" ]; then
      return
    fi
  done
  answers="$answers"' '"$id"
  eval "$dbvar"'_answers="$answers"'
}

# Remove an answer from a database in memory
db_answer_remove() {
  local dbvar="$1" # IN/OUT
  local id="$2"    # IN
  local new_answers
  local answers
  local i

  eval 'unset '"$dbvar"'_answer_'"$id"

  new_answers=''
  eval 'answers="$'"$dbvar"'_answers"'
  # There is no double quote around $answers on purpose
  for i in $answers; do
    if [ "$i" != "$id" ]; then
      new_answers="$new_answers"' '"$i"
    fi
  done
  eval "$dbvar"'_answers="$new_answers"'
}

# Load all answers from a database on stdin to memory (<dbvar>_answer_*
# variables)
db_load_from_stdin() {
  local dbvar="$1" # OUT

  eval "$dbvar"'_answers=""'

  # read doesn't support -r on FreeBSD 3.x. For this reason, the following line
  # is patched to remove the -r in case of FreeBSD tools build. So don't make
  # changes to it. -- Jeremy Bar
  while read -r action p1 p2; do
    if [ "$action" = 'answer' ]; then
      db_answer_add "$dbvar" "$p1" "$p2"
    elif [ "$action" = 'remove_answer' ]; then
      db_answer_remove "$dbvar" "$p1"
    fi
  done
}

# Load all answers from a database on disk to memory (<dbvar>_answer_*
# variables)
db_load() {
  local dbvar="$1"  # OUT
  local dbfile="$2" # IN

  db_load_from_stdin "$dbvar" < "$dbfile"
}

# Iterate through all answers in a database in memory, calling <func> with
# id/value pairs and the remaining arguments to this function
db_iterate() {
  local dbvar="$1" # IN
  local func="$2"  # IN
  shift 2
  local answers
  local i
  local value

  eval 'answers="$'"$dbvar"'_answers"'
  # There is no double quote around $answers on purpose
  for i in $answers; do
    eval 'value="$'"$dbvar"'_answer_'"$i"'"'
    "$func" "$i" "$value" "$@"
  done
}

# If it exists in memory, remove an answer from a database (disk and memory)
db_remove_answer() {
  local dbvar="$1"  # IN/OUT
  local dbfile="$2" # IN
  local id="$3"     # IN
  local answers
  local i

  eval 'answers="$'"$dbvar"'_answers"'
  # There is no double quote around $answers on purpose
  for i in $answers; do
    if [ "$i" = "$id" ]; then
      echo 'remove_answer '"$id" >> "$dbfile"
      db_answer_remove "$dbvar" "$id"
      return
    fi
  done
}

# Add an answer to a database (disk and memory)
db_add_answer() {
  local dbvar="$1"  # IN/OUT
  local dbfile="$2" # IN
  local id="$3"     # IN
  local value="$4"  # IN

  db_remove_answer "$dbvar" "$dbfile" "$id"
  echo 'answer '"$id"' '"$value" >> "$dbfile"
  db_answer_add "$dbvar" "$id" "$value"
}

# Add a file to a database on disk
# 'file' is the file to put in the database (it may not exist on the disk)
# 'tsfile' is the file to get the timestamp from, '' if no timestamp
db_add_file() {
  local dbfile="$1" # IN
  local file="$2"   # IN
  local tsfile="$3" # IN
  local date

  if [ "$tsfile" = '' ]; then
    echo 'file '"$file" >> "$dbfile"
  else
    date=`date -r "$tsfile" '+%s' 2> /dev/null`
    if [ "$date" != '' ]; then
      date=' '"$date"
    fi
    echo 'file '"$file$date" >> "$dbfile"
  fi
}

# Add a directory to a database on disk
db_add_dir() {
  local dbfile="$1" # IN
  local dir="$2"    # IN

  echo 'directory '"$dir" >> "$dbfile"
}
# END_OF_DB_DOT_SH

db_load 'vmdb' "$vmware_db"

# This comment is a hack to prevent RedHat distributions from outputing
# "Starting <basename of this script>" when running this startup script.
# We just need to write the word daemon followed by a space --hpreg.

# This defines echo_success() and echo_failure() on RedHat
if [ -r "$vmdb_answer_INITSCRIPTSDIR"'/functions' ]; then
    . "$vmdb_answer_INITSCRIPTSDIR"'/functions'
fi

# This defines $rc_done and $rc_failed on S.u.S.E.
if [ -f /etc/rc.config ]; then
   # Don't include the entire file: there could be conflicts
   rc_done=`(. /etc/rc.config; echo "$rc_done")`
   rc_failed=`(. /etc/rc.config; echo "$rc_failed")`
else
   # Make sure the ESC byte is literal: Ash does not support echo -e
   rc_done='[71G done'
   rc_failed='[71Gfailed'
fi

#
# Global variables
#
vmmemctl="vmmemctl"
vmxnet="vmxnet"
vmhgfs="vmhgfs"
vmdesched="vmdesched"
subsys=vmware-tools
vmblock="vmblock"

#
# Utilities
#

# Are we running in a VM?
vmware_inVM() {
  "$vmdb_answer_SBINDIR"/vmware-checkvm >/dev/null 2>&1
}

vmware_hwVersion() {
  "$vmdb_answer_SBINDIR"/vmware-checkvm -h | grep hw | cut -d ' ' -f 5
}

# Build a Linux kernel integer version
kernel_version_integer() {
  echo $(((($1 * 256) + $2) * 256 + $3))
}

# Get the running kernel integer version
get_version_integer() {
  local version_uts
  local v1
  local v2
  local v3

  version_uts=`uname -r`

  # There is no double quote around the back-quoted expression on purpose
  # There is no double quote around $version_uts on purpose
  set `IFS='.'; echo $version_uts`
  v1="$1"
  v2="$2"
  v3="$3"
  # There is no double quote around the back-quoted expression on purpose
  # There is no double quote around $v3 on purpose
  set `IFS='-ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyz'; echo $v3`
  v3="$1"

  kernel_version_integer "$v1" "$v2" "$v3"
}

#
# Note:
#  . Each daemon must be started from its own directory to avoid busy devices
#  . Each PID file doesn't need to be added to the installer database, because
#    it is going to be automatically removed when it becomes stale (after a
#    reboot). It must go directly under /var/run, or some distributions
#    (RedHat 6.0) won't clean it
#

vmware_start_daemon() {
   cd "$vmdb_answer_SBINDIR" && "$vmdb_answer_SBINDIR"/vmware-$1 \
      --background "/var/run/vmware-$1.pid"
}

vmware_stop_daemon() {
   local pidfile="/var/run/vmware-$1.pid"
   if vmware_stop_pidfile $pidfile; then
     rm -f $pidfile
   fi
}

vmware_daemon_status() {
   echo -n "vmware-$1 "
   if vmware_check_pidfile "/var/run/vmware-$1.pid"; then
      echo 'is running'
   else
      echo 'is not running'
      exitcode=$(($exitcode + 1))
   fi
}

# Start the virtual ethernet kernel service
vmware_start_vmxnet() {
   # only load vmxnet if it's not already loaded
   if [ "`isLoaded "$vmxnet"`" = 'no' ]; then
     vmware_load_module $vmxnet
   fi
}

# Start the guest virtual memory manager
vmware_start_vmmemctl() {
  vmware_load_module $vmmemctl
}

# Stop the guest virtual memory manager
vmware_stop_vmmemctl() {
  vmware_unload_module $vmmemctl
}

# Load the module and start the guest vmdesched service
vmware_start_vmdesched() {
   local ret='0'
   vmware_exec 'Loading vmdesched driver module:' vmware_load_module $vmdesched
   ret=$(($ret + $?))
   if [ "$ret" -eq 0 ]; then
      # XXX Should do this in the service binary
      rm -f /dev/$vmdesched
      local major=$(awk "/"$vmdesched/" {print \$1}" /proc/devices)
      mknod /dev/$vmdesched c $major 0
      vmware_exec 'Guest descheduled time accounting daemon: ' vmware_start_daemon $vmdesched
   fi
   return $ret
}

# Stop the guest vmdesched service and unload the module
vmware_stop_vmdesched() {
  local ret='0'
  vmware_exec 'Guest descheduled time accounting daemon:' vmware_stop_daemon $vmdesched
  ret=$(($ret + $?))
  vmware_exec 'Unloading vmdesched driver module:' vmware_unload_module $vmdesched
  ret=$(($ret + $?))
  rm -f /dev/$vmdesched
  return $ret
}

# Mount all hgfs filesystems
vmware_mount_vmhgfs() {
  vmware_exec_selinux "mount -t vmhgfs .host:/ /mnt/hgfs"
  vmware_exec_selinux "mount -a -t $vmhgfs"
}

# Start the guest filesystem driver and mount it
vmware_start_vmhgfs() {
  # only load vmhgfs if it's not already loaded
  if [ "`isLoaded "$vmhgfs"`" = 'no' ]; then
    vmware_load_module $vmhgfs
  fi
}

# Unmount all hgfs filesystems left mounted
vmware_unmount_vmhgfs() {
  vmware_exec_selinux "umount -a -t $vmhgfs"
}

# Stop the guest filesystem driver
vmware_stop_vmhgfs() {
  vmware_unload_module $vmhgfs
}

is_vmhgfs_needed() {

  [ -e $excludes_file ] && grep -q vmhgfs $excludes_file && echo no && return

  local min_kver=`kernel_version_integer '2' '4' '0'`
  local run_kver=`get_version_integer`
  if [ "`is_ESX_running`" = 'yes' ]; then
    echo no
  else
    if [ $min_kver -le $run_kver -a "$vmdb_answer_VMHGFS_CONFED" = 'yes' ]; then
      echo yes
    else
      echo no
    fi
  fi
}

is_vmxnet_needed() {

  # First try vmxnet's vendor/device ID's
  cat /proc/bus/pci/devices | grep -qi "^[0-9a-f]*	15ad0720	"
  if [ "$?" -eq 0 -a "$vmdb_answer_VMXNET_CONFED" = 'yes' ]; then
    echo yes
  else
    # Now try pcnet32's vendor/device ID's...see bug 79352
    # We only accept pcnet32 if the HW version of the VM is ws50 or later
    local hwver=`vmware_hwVersion`
    cat /proc/bus/pci/devices | grep -qi "^[0-9a-f]*	10222000	"
    if [ "$?" -eq 0 -a "$vmdb_answer_VMXNET_CONFED" = 'yes' -a \
	 $hwver -ge 4 ]; then
      echo yes
    else
      echo no
    fi
  fi
}

is_vmmemctl_needed() {

  [ -e $excludes_file ] && grep -q vmmemctl $excludes_file && echo no && return

  if [ "$vmdb_answer_VMMEMCTL_CONFED" = 'yes' ]; then
    echo yes
  else
    echo no
  fi
}

is_vmdesched_needed() {

  [ -e $excludes_file ] && grep -q vmdesched $excludes_file && echo no && return

  local min_kver=`kernel_version_integer '2' '4' '0'`
  local run_kver=`get_version_integer`
  if [ "$vmdb_answer_VMDESCHED_CONFED" = 'yes' ] && \
    ( ! /bin/uname -v | grep -q "SMP" ) && \
    [ $min_kver -le $run_kver ]; then
    echo yes
  else
    echo no
  fi
}


# See how we were called.
case "$1" in
   start)
      exitcode='0'
      if vmware_inVM; then
         if [ "`is_vmhgfs_needed`" = 'yes' ]; then
            vmware_exec 'Guest filesystem driver:' vmware_start_vmhgfs
            exitcode=$(($exitcode + $?))
            vmware_exec 'Mounting HGFS shares:' vmware_mount_vmhgfs
	    # Ignore the exitcode. The mount may fail if HGFS is disabled
	    # in the host, in which case requiring a rerun of the Tools
	    # configurator is useless.
         fi

         if [ "`is_vmmemctl_needed`" = 'yes' ]; then
            vmware_exec 'Guest memory manager:' vmware_start_vmmemctl
            exitcode=$(($exitcode + $?))
         fi

         if [ "`is_vmxnet_needed`" = 'yes' ]; then
            vmware_exec 'Guest vmxnet fast network device:' vmware_start_vmxnet
            exitcode=$(($exitcode + $?))
         fi

         if [ "`is_vmdesched_needed`" = 'yes' ]; then
            vmware_start_vmdesched
            exitcode=$(($exitcode + $?))
         fi

         if [ "`is_vmblock_needed`" = 'yes' ] ; then
            vmware_exec 'Blocking file system:' vmware_start_vmblock
            exitcode=$(($exitcode + $?))
         fi

         vmware_exec 'Guest operating system daemon:' vmware_start_daemon guestd
         exitcode=$(($exitcode + $?))
      fi

   [ -d /var/lock/subsys ] || mkdir -p /var/lock/subsys
   touch /var/lock/subsys/"$subsys"
   ;;

   stop)
      exitcode='0'

      if vmware_inVM; then
         echo 'Stopping VMware Tools services in the virtual machine:'
         vmware_exec 'Guest operating system daemon:' vmware_stop_daemon guestd
         exitcode=$(($exitcode + $?))

         if [ "`is_vmblock_needed`" = 'yes' ] ; then
           vmware_exec 'Blocking file system:' vmware_stop_vmblock
           exitcode=$(($exitcode + $?))
	 fi

         if [ "`is_vmhgfs_needed`" = 'yes' ]; then
           vmware_exec 'Unmounting HGFS shares:' vmware_unmount_vmhgfs
           exitcode=$(($exitcode + $?))
           vmware_exec 'Guest filesystem driver:' vmware_stop_vmhgfs
           exitcode=$(($exitcode + $?))
         fi

         if [ "`is_vmmemctl_needed`" = 'yes' ]; then
           vmware_exec 'Guest memory manager:' vmware_stop_vmmemctl
           exitcode=$(($exitcode + $?))
         fi

        if [ "`is_vmdesched_needed`" = 'yes' ]; then
          vmware_stop_vmdesched
          exitcode=$(($exitcode + $?))
        fi
      else
         echo -n 'Skipping VMware Tools services shutdown on the host:'
         vmware_success
         echo
      fi
      if [ "$exitcode" -gt 0 ]; then
         exit 1
      fi

      rm -f /var/lock/subsys/"$subsys"
      ;;

   status)
      exitcode='0'

      vmware_daemon_status guestd
      exitcode=$(($exitcode + $?))

      if [ "`is_vmdesched_needed`" = 'yes' ]; then
         vmware_daemon_status vmdesched
         exitcode=$(($exitcode + $?))
      fi

      if [ "$exitcode" -ne 0 ]; then
         exit 1
      fi
      ;;

   restart)
      "$0" stop && "$0" start
   ;;

   *)
      echo "Usage: `basename "$0"` {start|stop|status|restart}"
      exit 1
esac

exit 0
