#!/bin/bash
# Filename:      grml-hwinfo
# Purpose:       get hardware information
# Authors:       grml-team (grml.org), (c) Michael Prokop <mika@grml.org>
# Bug-Reports:   see http://grml.org/bugs/
# License:       This file is licensed under the GPL v2.
################################################################################
# Notice: Some ideas have been taken from
# http://club.black.co.at/david/hwdb/infodump
# by David Schmitt <david@schmitt.edv-bus.at>
################################################################################

# variables
UNAME="$(uname -r)"
PN="$(basename $0)"
[ -n "$WORKING_DIR" -a -d "$WORKING_DIR" ] || WORKING_DIR=$(pwd)
VERSION='0.8.0'

# data collection should not be affected by user locale
export LANG=C
export LC_ALL=C

TIMESTAMP='+%F--%H-%M-%S-%Z'
DATE="$(date $TIMESTAMP)"

echo "$PN ${VERSION} - collect hardware information"

# defaults
GENERATE_FILE='1'
GENERATE_DIRECTORY=''
_opt_output_directory=false
_opt_output_file=false

usage() {
  echo "
  This tool collects information of the hardware it is being executed on.
  It can be executed as normal user to collect some basic information or
  (recommended) with root permissions to collect more system information.
  If executed without any options a file named grml-hwinfo-TIMESTAMP.tar.bz2
  storing all collected information is created in the current working directory.

  Options:

  -b, --both                 Create directory + file grml-hwinfo-TIMESTAMP.tar.bz2
  -d, --directory            Create grml-hwinfo-TIMESTAMP as a directory (no file)
  -f, --file                 Create grml-hwinfo-TIMESTAMP.tar.bz2 [default action]
  -h, --help                 Display this help message
  --output-directory <dir>   Store output files in specified directory
  --output-file <file>       Store output in specified filename (tar.bz2 format)
  "
}

CMDLINE_OPTS=output-directory:,output-file:,both,directory,file,help
_opt_temp=$(getopt --name grml-hwinfo -o +bdfh --long $CMDLINE_OPTS -- "$@")
if [ $? -ne 0 ]; then
  echo "Try 'grml-hwinfo --help' for more information." >&2
  exit 1
fi
eval set -- "$_opt_temp"

while :; do
  case "$1" in
  --help|-h)
    usage ; exit 0
    ;;
  --output-directory)
    shift; OUTDIRNAME="$1"
    GENERATE_DIRECTORY='1'
    _opt_output_directory=true
    $_opt_output_file && GENERATE_FILE='1' || GENERATE_FILE=''
    ;;
  --output-file)
    shift; OUTFILE="$1"
    GENERATE_FILE='1'
    _opt_output_file=true
    $_opt_output_directory && GENERATE_DIRECTORY='1' || GENERATE_DIRECTORY=''
    ;;
  -d|--directory)
    GENERATE_DIRECTORY='1'
    GENERATE_FILE=''
    ;;
  -f|--file)
    GENERATE_DIRECTORY=''
    GENERATE_FILE='1'
    ;;
  -b|--both)
    GENERATE_DIRECTORY='1'
    GENERATE_FILE='1'
    ;;
  --)
    shift; break
    ;;
  *)
    echo "Internal getopt error!" >&2
    exit 1
    ;;
  esac
  shift
done

# Generate output/temporary directory name & path, and output file path
[ -n "$OUTDIRNAME" ] || OUTDIRNAME="grml-hwinfo-${DATE}"
OUTDIR="${WORKING_DIR}/${OUTDIRNAME}"
mkdir "${OUTDIR}" || { echo 'Directory "'${OUTDIR}'" already exists, aborting.'>&2 ; exit 1; }

if [ -n "$GENERATE_FILE" ] ; then
  [ -n "$OUTFILE" ] && OUTFILE_="$OUTFILE" || OUTFILE_="${OUTDIR}.tar.bz2"
  [ -e "${OUTFILE_}" ] && { echo 'File "'${OUTFILE_}'" already exists, aborting.'>&2 ; rm -r "${OUTDIR}"; exit 1; }
  OUTFILE=${OUTFILE_}
  touch "${OUTFILE}"
fi

if [ "$(id -u)" != "0" ] ; then
  NOTROOT=1
  echo "W: Running without root permissions. Not all data will be collected."
fi

# check whether a binary is available and executable
exectest() {
  if [ -z "$1" ] ; then
    echo 'Usage: exectest <binary>'>&2
    return 1
  else
    if test -e "$(which $1)" ; then
      return 0
    else
      grep -q "^$1"'$' missing_tools 2>/dev/null || echo "$1" >> missing_tools
      return 1
    fi
  fi
}

# echo a list of all disks and their size
# taken from http://cvs.debian.org/fai/lib/disk-info
diskandsize() {
  local isdisk major minor blocks device suffix
  while read major minor blocks device suffix; do
    isdisk=1
    # skip ide cdrom
    [ -f /proc/ide/$device/media ] && grep -q cdrom /proc/ide/$device/media && isdisk=0
    [ "$isdisk" -eq 1 ] && echo "$device $blocks"
  done
}

list_disks() {
  # print only every second entry; used by disk_info
  i=0
  for ent in $@; do
    if [ "$i" -eq 0 ]; then
      echo $ent
      i=1
    else
      i=0
    fi
  done
}

disk_info() {
  # the variable holds a space separated list of devices and their block size
  device_size=`grep -E ' cciss/c.d.$| ida/c.d.$| rd/c.d.$| hd.$| sd.$|/disc$' /proc/partitions | diskandsize`
  # a list of all local disks, without size
  disklist=`list_disks $device_size`
}


cd "${OUTDIR}" || exit 1
(
  [ -n "$GENERATE_FILE" ]      && echo "Output file:      $OUTFILE"
  [ -n "$GENERATE_DIRECTORY" ] && echo "Output directory: $OUTDIR"
  echo
  echo "This might take a few seconds/minutes. Please be patient..."

  # some sysinfo
  date > date
  if [ -r /etc/grml_version ] ; then
     cat /etc/grml_version > grml_version
  fi
  if [ -r /etc/debian_version ] ; then
     cat /etc/debian_version > debian_version
  fi
  uname -a > uname

  # disks / devices
  [ -f /proc/scsi/scsi ] && cat /proc/scsi/scsi > scsi
  exectest lspci && lspci -nn > lspci
  cat /proc/partitions > partitions
  find /proc/ide/ -name geometry -exec grep . {} \; > proc_ide 2>/dev/null
  df -h > df 2>/dev/null
  for i in free lsmod mount lsdev lspnp lsusb ; do
    exectest $i && $i > $i
  done
  swapon -s > swapon 2>swapon.error

  # proc stuff
  for i in cpuinfo interrupts cmdline devices dma fb iomem ioports \
    mdstat meminfo modules mtrr pci version ; do
    [ -r /proc/$i ] && cat /proc/$i > proc_$i
  done
  exectest sysdump  && sysdump > sysdump 2>sysdump.error

  # log
  dmesg > dmesg.cur

  # hwinfo
  exectest discover && discover -v --type-summary --enable-bus all > discover 2> discover.2
  exectest hwinfo   && hwinfo log=hwinfo
  exectest numactl  && numactl --hardware > numactl
  exectest x86info  && x86info > x86info 2>x86info.2

  # net stuff
  exectest ifconfig  && ifconfig -v -a  > ifconfig
  exectest ip        && ip route show   > ip_route
  exectest ip        && ip link show    > ip_link
  exectest route     && route -n        > route

  # software
  if exectest dpkg ; then
    dpkg --get-selections   > dpkg_get_selections
    COLUMNS=300 dpkg --list > dpkg_list
  fi

  # power management
  exectest laptop-detect  && laptop-detect >/dev/null 2>/dev/null && echo "0" > laptop_detected
  exectest acpi_available && acpi_available && cat /proc/acpi/info > acpi_info
  exectest acpi && acpi > acpi 2> acpi.error && acpi -v > acpi.version
  [ -r /proc/apm/ ] && apm > apm

  # kernel stuff
  if [ -r /proc/config.gz ] ; then
    zcat /proc/config.gz > kernelconfig
  else
    [ -r /boot/config-$UNAME ] && cat /boot/config-$UNAME > kernelconfig
  fi

  exectest dpkg && COLUMNS=1000 dpkg -l linux-image-$UNAME 2>running_kernel.error \
           | grep linux-image-$UNAME | tr -s ' ' > running_kernel 2>>running_kernel.error
  dpkg -S /boot/vmlinuz-$(uname -r) >> running_kernel 2>>running_kernel.error

  # X stuff
  if [ -n "${DISPLAY}" ] ; then
    exectest xviddetect  && xviddetect         > xviddetect
    exectest xvidtune    && xvidtune -show     > xdivtune
    exectest xrandr      && xrandr             > xrandr
    exectest xdpyinfo    && xdpyinfo           > xdpyinfo
    X -version > x_version 2>&1
  fi

  for i in Xorg.0.log Xorg.7.log Xorg.8.log XFree86.0.log XFree86.7.log XFree86.8.log dmesg ; do
    cp /var/log/$i log_$i 2>/dev/null
  done

  cp /etc/X11/xorg.conf    xorg.conf    2>/dev/null
  cp /etc/modules          modules      2>/dev/null
  cp /etc/X11/XF86Config-4 XF86Config-4 2>/dev/null

  # as root:
  if [ -n "$NOTROOT" ] ; then
    echo "not running as root" > root
  else
    echo "running as root" > root
    disk_info
    exectest sfdisk     && sfdisk -d > sfdisk 2>sfdisk.error
    exectest dmidecode  && dmidecode > dmidecode

    exectest dconf && dconf -o dconf

    if [ -x /usr/share/doc/lm-sensors/examples/eeprom/decode-dimms.pl ] ; then
      /usr/share/doc/lm-sensors/examples/eeprom/decode-dimms.pl > decode-dimms 2>decode-dimms.error
    fi

    # proxmox
    exectest qm && qm list > qm 2>qm.error
    # libvirt
    exectest virsh && virsh list >virsh 2>virsh.error
    # openvz
    exectest vzlist && vzlist >vzlist 2>vzlist.error
    # vserver
    exectest vserver-stat && vserver-stat >vserver-stat 2>vserver-stat.error

    exectest mdadm && mdadm --detail /dev/md[0-9]* >> mdadm 2>mdadm.error

    # LVM
    exectest pvs && pvs > pvs 2>pvs.error
    exectest vgs && vgs > vgs 2>vgs.error
    exectest lvs && lvs > lvs 2>lvs.error
    exectest lvdisplay && lvdisplay > lvdisplay 2>lvdisplay.error

    exectest dmsetup && dmsetup ls > dmsetup_ls 2>dmsetup_ls.error

    # iSCSI
    if exectest iscsiadm ; then
      iscsiadm -m session > iscsiadm_session 2>iscsiadm_session.error
      iscsiadm -m fw > iscsiadm_fw 2>iscsiadm_fw.error
      iscsiadm -m host > iscsiadm_host 2>iscsiadm_host.error
      iscsiadm -m iface > iscsiadm_iface 2>iscsiadm_iface.error
      iscsiadm -m node > iscsiadm_node 2>iscsiadm_node.error
      iscsiadm -m discovery > iscsiadm_discovery 2>iscsiadm_discovery.error
    fi

    if exectest lsscsi ; then
      lsscsi    > lsscsi 2>lsscsi.error
      lsscsi -t > lsscsi_transport 2>lsscsi_transport.error
    fi

    for disk in $disklist; do
      if exectest smartctl ; then
        echo -e "smartctl -a /dev/${disk}:\n" >> smartctl
        smartctl -a /dev/$disk >> smartctl
        echo -e "\n\n" >> smartctl
      fi

      if exectest hdparm ; then
        echo -e "hdparm -iv /dev/${disk}:\n" >> hdparm
        hdparm -iv /dev/$disk >> hdparm
        echo -e "\n\n" >> hdparm
      fi

      if exectest fdisk ; then
        echo -e "fdisk -lu /dev/${disk}:\n" >> fdisk
        fdisk -lu /dev/$disk >>fdisk 2>>fdisk.error
        echo -e "\n\n" >> fdisk
      fi

      if exectest parted ; then
        echo -e "parted -s /dev/${disk}:\n" >> parted
        parted -s /dev/$disk print >> parted
        echo -e "\n\n" >> parted
      fi

      if exectest sdparm ; then
        echo -e "sdparm --all --long /dev/${disk}:\n" >> sdparm
        sdparm --all --long /dev/$disk >> sdparm
        echo -e "\n\n" >> sdparm
      fi

      file -s /dev/$disk?* | grep -v ": empty" >> file_disk
    done
  fi
)

# get rid of empty files
for file in *.error ; do
  test -s $file || rm $file
done

echo

cd "${WORKING_DIR}"

# create tarball
if [ -n "$GENERATE_FILE" ] ; then
  tar acf "${OUTFILE}" "${OUTDIRNAME}"
  [ -r "$OUTFILE" ] && echo "$OUTFILE ("$(ls -ahl "$OUTFILE" | awk '{print $5}')") has been generated."
fi

# remove (temporary) output directory if needed, else keep it, as it doubles
# as the real output directory.
if [ -z "$GENERATE_DIRECTORY" ] ; then
  rm -r "${OUTDIR}"
else
  [ -r "${OUTDIR}" ] && echo "${OUTDIR} has been generated."
fi

exit 0

## END OF FILE##################################################################
