#!/bin/bash
#
# SpinetiX system report
#
# Copyright (c) 2011 SpinetiX S.A. All rights reserved.
#
# Considerably inspired by the sysreport tool from Red Hat Inc.
#
# WARNING: this utility will output to stdout a tar.gz file with the
# report, so be careful about output anything to stdout, stderr is OK.
#
# If the first argument is "-x" an extended report is generated,
# when running chroot'ed extended mode is automatically enabled

umask 0077
PATH="/usr/bin:/bin:/usr/sbin:/sbin"
DATE=$(/bin/date -u +%Y%m%d%H%M%S | /usr/bin/tr -d ' ')

# Actual FFSTYPE set by inithwinfo()
FFSTYPE=auto

if [ $UID != 0 ]; then
  echo "ERROR: you must be root to use this utility" >&2
  exit 1
fi

# Limit the maximum running CPU time to 5 minutes (gzip may take long)
# to avoid problems with runaway sub-processes
ulimit -S -t 300 # soft, sends SIGXCPU
ulimit -H -t 320 # hard, sends SIGKILL

# Test if running inside chroot or not
if [ -e /proc/1 ]>/dev/null 2>&1 && ! [ -e /proc/1/root ]>/dev/null 2>&1; then
    IN_CHROOT="yes"
else
    IN_CHROOT=''
fi

# Test if we should run in extended mode or not
if [ "$1" = "-x" -o "$IN_CHROOT" = "yes" ]; then
    DO_EXTENDED="yes"
else
    DO_EXTENDED=''
fi

# When running in chroot /tmp is the large read-write fs
# but otherwise space on /tmp is limited so prefer /var/tmp
if [ "$IN_CHROOT" = "yes" ]; then
    TMPDIR=/tmp
else
    TMPDIR=/var/tmp
fi
export TMPDIR

#
# FUNCTIONS
#

is_mounted() {
    local dev mpt fstype opts dump pass junk

    while read dev mpt fstype opts dump pass junk; do
	[ "$mpt" = "$1" ] && return 0 # found
    done < /proc/mounts

    return 1 # not found
}

inithwinfo() {
    hwtype=
    while read tag sep val; do
	if [ "$tag" = "Hardware" ]; then
	    hwtype="$val"
	    break;
	fi
    done < /proc/cpuinfo

    case "$hwtype" in
	Sakura)
	    MTDTYPE=nand
	    FFSTYPE=jffs2
	    ;;
	Bonsai)
	    MTDTYPE=nor
	    FFSTYPE=cramfs
	    ;;
    esac
}

function reassign_stdout()
{
    exec 3>&1
    exec > $1
}

function restore_stdout()
{
    exec 1>&3 3>&-
}

function catiffile()
{
    if [ -f "$1" ]; then
	cat "$1" 2>&1
    else
	echo "ERROR: file '$1' not found or not a regular file"
    fi
}

function catifpath()
{
    if [ -e "$1" ]; then
	cat "$1" 2>&1
    else
	echo "ERROR: path '$1' not found"
    fi
}

function catifexec()
{
    "$@" 2>&1
    RET=$?
    if [ $RET -ne 0 ]; then
	echo "ERROR: command '$1' failed (exit code: $RET)"
    fi
    return $RET
}

function section_start()
{
    echo
    echo "=== $@"
}

function mkimagedev()
{
    local tmp
    local ret
    tmp="$(mktemp -t mtd.XXXXXXXX)"
    if [ $? -ne 0 ]; then
	return 1
    fi
    if [ "$MTDTYPE" = "nand" ]; then
	nanddump -b -o "$1" > "$tmp"
    elif [ "$MTDTYPE" = "nor" ]; then
	cp "$1" "$tmp"
    fi
    if [ $? -ne 0 ]; then
	rm -f "$tmp"
	return 1
    fi
    mkimage -l -i "$tmp"
    ret=$?
    rm -f "$tmp"
    return $ret
}

#
# MAIN
#

# When running in chroot, meaning from recovery console, the bind
# mounts in /etc/fstab might be missing if running from an old
# recovery console, so we mount any missing bind mounts
if [ "$IN_CHROOT" = "yes" ]; then
    while read dev mpt fstype opts dump pass junk; do

	[ "${dev:0:1}" = "#" ] && continue # comment line 
	[ "$opts" = "bind" ] || continue # only bind mounts
	[ "${dev:0:5}" = "/dev/" ] && continue # a true device
	[ "${dev:0:1}" != "/" ] && continue # not a path
	[ -d "$dev" ] || continue # the source of the bind does not exist

	is_mounted "$mpt" || mount "$mpt"

    done < /etc/fstab > /dev/null 2>&1
fi

ROOT=$(mktemp -d /tmp/spxreport-XXXXXXXX)
if [ $? -ne 0 ]; then
    echo "ERROR: could not create temp dir" >&2
    exit 1
fi

trap "rm -rf $ROOT" EXIT

inithwinfo

# === date ===

echo "$DATE" > $ROOT/date

# === system data ===

# From here stdout redirected to file
reassign_stdout $ROOT/sys-data

section_start "Base system release"
catiffile /etc/spinetix-release

section_start "Updater status"
catiffile /var/lib/updater/status

section_start "MVL system release"
catiffile /etc/mvl-release

section_start "Date"
catifexec date +"%a %F %T [%Z / UTC%z]"

section_start "Timezone file"
catifexec readlink -q -f /etc/localtime

section_start "Uptime"
catifexec uptime

section_start "Power-on time counters"
for f in /sys/bus/i2c/drivers/Bonsai-EEPROM/*/poweron_hours; do
    if [ -f "$f" -a -r "$f" ]; then
	section_start "${f#/sys/bus/i2c/drivers/Bonsai-EEPROM/}"
	catiffile "$f"
    fi
done

section_start "CPU and serial"
catiffile /proc/cpuinfo

section_start "Memory"
catiffile /proc/meminfo

section_start "Running processes and threads"
catifexec ps -e H \
    -o euser,pid,ppid,tid,ni,cls,rtprio,priority,tty,%cpu,%mem,rss,vsz,start_time,time,state,wchan,cmd

section_start "Process tree"
catifexec pstree -A -l -p

section_start "Memory statistics"
catifexec memstat -w

section_start "Hostname"
catifexec hostname

section_start "Network links"
catifexec ip link

section_start "Network addresses"
catifexec ip addr

section_start "Network routes"
catifexec ip route

section_start "Network link stats"
catifexec netstat -i -e

section_start "Network protocol stats"
catifexec netstat -s

section_start "Listening network endpoints"
catifexec netstat -n -l -e -p

section_start "EMAC link"
catiffile /proc/net/emac_link

section_start "EMAC stats"
catiffile /proc/net/emac_rfc2665_stats

section_start "Multicast groups"
catifexec ip maddr

section_start "IGMP state"
catiffile /proc/net/igmp

section_start "IGMP multicast filter"
catiffile /proc/net/mcfilter

section_start "IGMPv6 state"
catiffile /proc/net/igmp6

section_start "IGMPv6 multicast filter"
catiffile /proc/net/mcfilter6

section_start "NTP statistics"
catifexec ntpq -n -c 'timeout 300' -c peers

section_start "NTP system variables"
catifexec ntpq -n -c 'timeout 300' -c rv

section_start "NTP current offsets"
catifexec ntpdate -q -t 0.4 127.0.0.1 `ntpq -n -c 'timeout 300' -c peers | sed -n '/^.[^[:space:]=]/{s/^.//;s/[[:space:]].*//;p}'`

section_start "NTP saved drift"
catiffile /var/lib/spx-ntp/drift

section_start "Startup services"
catifexec initdconfig --list

section_start "IPC"
catifexec ipcs -a

section_start "Disk"
catifexec df -al

section_start "Partitions"
catiffile /proc/partitions

section_start "MMC/SD partition tables"
catifexec sfdisk -l -uS -x /dev/mmcblk[0-9]

section_start "IDE/ATA partition tables"
catifexec sfdisk -l -uS -x /dev/hd[a-d]

section_start "IDE/ATA drive data"
for f in /proc/ide/ide*/*{,/*}; do
    if [ -f "$f" -a -r "$f" ]; then
	section_start "$f"
	catiffile "$f"
    fi
done

section_start "IDE/ATA extended drive data"
for d in /dev/hd[a-d]; do
    if [ -b "$d" ]; then
	section_start "$d"
	catifexec hdparm -adkmruiI "$d"
    fi
done

section_start "USB/SCSI partition tables"
catifexec sfdisk -l -uS -x /dev/sd[a-d]

section_start "USB/SCSI extended drive data"
for d in /dev/sd[a-d]; do
    if [ -b "$d" ]; then
	section_start "$d"
	catifexec hdparm "$d"
    fi
done

section_start "Partition tables"
pl=$(awk '{if ($2 == 0 && $4 !~ /^mtdblock/) print $4}' /proc/partitions)
if [ $? -eq 0 ]; then
    for p in $pl; do
	catifexec sfdisk -l -uS -x /dev/$p
    done
else
    echo "ERROR: could not recover list of partitions"
fi

section_start "Flash partitions"
catiffile /proc/mtd

# These requires U-Boot tools (u-tools) to be installed
section_start "U-Boot version"
bootloader="$(LC_ALL=C tr -c '[ -~]' '\n' < /dev/mtd0ro | grep 'U-Boot [0-9]')"
if [ $? -eq 0 ]; then
    echo "$bootloader"
else
    echo "ERROR: could not recover bootloader version"
fi

section_start "U-Boot environment"
catifexec fw_printenv

if [ -n "$DO_EXTENDED" ]; then
    section_start 'U-Boot image "kernel"'
    mtd=$(sed -e '/"kernel"$/{s,:.*,,;p};d' /proc/mtd)
    catifexec mkimagedev /dev/"${mtd}"ro

    section_start 'U-Boot image "failsafe-kernel"'
    mtd=$(sed -e '/"failsafe-kernel"$/{s,:.*,,;p};d' /proc/mtd)
    [ -n "$mtd" ] && catifexec mkimagedev /dev/"${mtd}"ro

    section_start 'U-Boot image "failsafe-fs"'
    mtd=$(sed -e '/"failsafe-fs"$/{s,:.*,,;p};d' /proc/mtd)
    [ -n "$mtd" ] && catifexec mkimagedev /dev/"${mtd}"ro

    section_start 'U-Boot image "failsafe-img"'
    mtd=$(sed -e '/"failsafe-img"$/{s,:.*,,;p};d' /proc/mtd)
    [ -n "$mtd" ] && catifexec mkimagedev /dev/"${mtd}"ro

    section_start 'Filesystem "failsafe-res" contents'
    mtdblock="$(sed -e '/"failsafe-res"$/{s,\([0-9]\+\):.*,block\1,;p};d' /proc/mtd)"
    if [ -n "$mtdblock" ]; then
	mpt="$(mktemp -d /tmp/mnt.XXXXXXXX)"
	catifexec mount -t "$FFSTYPE" -r /dev/"$mtdblock" "$mpt"
	if [ $? -eq 0 ]; then
	    catifexec ls -laNR --time-style=long-iso "$mpt"
	    catifexec umount "$mpt"
	else
	    echo 'INFO: no "failsafe-res" filesystem or corrupted'
	fi
	catifexec rmdir "$mpt"
    else
	echo 'INFO: "failsafe-res" mtd partition not found'
    fi

    section_start 'Filesystem "failsafe-data" contents'
    mtdblock="$(sed -e '/"failsafe-data"$/{s,\([0-9]\+\):.*,block\1,;p};d' /proc/mtd)"
    if [ -n "$mtdblock" ]; then
	mpt="$(mktemp -d /tmp/mnt.XXXXXXXX)"
	catifexec mount -t "$FFSTYPE" -r /dev/"$mtdblock" "$mpt"
	if [ $? -eq 0 ]; then
	    catifexec ls -laNR --time-style=long-iso "$mpt"
	    catifexec umount "$mpt"
	else
	    echo 'INFO: no "failsafe-data" filesystem or corrupted'
	fi
	catifexec rmdir "$mpt"
    else
	echo 'INFO: "failsafe-data" mtd partition not found'
    fi
else
    section_start "Skipping further flash memory tests (not in extended mode)"
fi

section_start 'EEPROM data'
for d in /sys/bus/i2c/drivers/Bonsai-EEPROM/*; do
    if [ -f "$d"/subname ]; then
	s="$(cat "$d"/subname 2>/dev/null)"
	echo "EEPROM $s dumped to eeprom-$s from $d"
	catiffile "$d"/data > $ROOT/eeprom-"$s"
    fi
done

# Restore stdout
restore_stdout

# === FS data ===

# From here stdout redirected to file
reassign_stdout $ROOT/fs-data

# NOTE: USB attached disks appear as SCSI; some USB keys have the
# filesystem on the raw device without a partition table
for dev in /dev/mmcblk[0-9]p[0-9] /dev/hd[a-d][0-9] /dev/sd[a-z]*; do
    [ -e "$dev" ] || continue # no such path
    section_start "Filesystem info for $dev"
    if [ -b "$dev" ]; then
	type="$(blkid -c /dev/null -s TYPE -o value "$dev")"
	case "$type" in
	    "")
		echo "no filesystem"
		;;
	    ext*) # one of the ext2/3/4 filesystem, we can get info about it
		tune2fs -l "$dev"
		;;
	    *)
		echo "unknown filesystem of type '$type'"
		;;
	esac
    else
	echo "ERROR: device $dev not found"
    fi
done

# Try to have as much data as possible committed to disk
# before doing any fsck below to get the best possible info
# if some fs is mounted
if [ -n "$DO_EXTENDED" ]; then
    sync
fi

for dev in /dev/mmcblk[0-9]p[0-9] /dev/hd[a-d][0-9]; do
    [ -e "$dev" ] || continue # no such path
    section_start "Filesystem check for $dev"
    if [ -n "$DO_EXTENDED" ]; then
	if [ -b "$dev" ] ; then
	    fsck -n -f "$dev"
	else
	    echo "ERROR: device $dev not found"
	fi
    else
	echo -e "Skipping filesystem check (not in extended mode)"
    fi
done

# Restore stdout
restore_stdout

# === RPM data ===

# From here stdout redirected to file
reassign_stdout $ROOT/rpm-list

# Produce list of installed RPMs
echo -e "name\tepoch\tversion\trelease\tarch\tinstall date\tvendor"
catifexec rpm -qa --qf \
    '%{N}\t%{E}\t%{V}\t%{R}\t%{ARCH}\t%{INSTALLTIME:date}\t%{VENDOR}\n'

# Restore stdout
restore_stdout

# === RPM verify ===

# From here stdout redirected to file
reassign_stdout $ROOT/rpm-verify

# Verify all RPMs
if [ -n "$DO_EXTENDED" ]; then
    catifexec rpm -Va
else
    echo "Skipping RPM verify (not in extended mode)"
fi

# Restore stdout
restore_stdout

# === Kernel data ===

# From here stdout redirected to file
reassign_stdout $ROOT/kernel-data

for f in cmdline version cpuinfo bootcount \
    meminfo slabinfo buddyinfo iomem vmstat \
    filesystems partitions mounts \
    modules devices misc mtd fb \
    cmem \
    loadavg uptime interrupts stat yaffs \
    scsi/scsi \
    sysvipc/shm sysvipc/msg sysvipc/sem \
    diskstats locks \
    bus/input/devices bus/input/handlers \
    bus/usb/devices; do

  section_start "$f"
  catiffile /proc/$f

done

# Restore stdout
restore_stdout

# === Kernel message ring ===

catifexec dmesg 2>/dev/null > $ROOT/kernel-messages

# === Kernel log dumps ===

# From here stdout redirected to file
reassign_stdout $ROOT/kernel-log-dumps

mtd=$(sed -e '/"klog-dumps"$/{s,:.*,,;p};d' /proc/mtd)
if [ -n "$mtd" ]; then
    mtddev=/dev/"${mtd}"ro
    if [ -c "$mtddev" ]; then
	if [ "$MTDTYPE" = "nand" ]; then
	    # discard stderr (has nanddump info)
	    nanddump -i -b -o "$mtddev" 2>/dev/null
	elif [ "$MTDTYPE" = "nor" ]; then
	    cat "$mtddev" 2>&1
	fi
    else
	echo "ERROR: path '$mtddev' not found or not a device"
    fi
else
    echo "No kernel log dumps partition in flash"
fi

# Restore stdout
restore_stdout

# === sysfs ===

# From here stdout redirected to file
reassign_stdout $ROOT/sysfs

for f in /sys/spxlicense/*{,/*}; do
    if [ -f "$f" -a -r "$f" ]; then
	section_start "$f"
	catiffile "$f"
    fi
done

for f in /sys/bus/mmc/devices/*/*; do
    if [ -f "$f" -a -r "$f" ]; then
	section_start "$f"
	catiffile "$f"
    fi
done

for f in /sys/class/graphics/fb0/device/*{,/*}; do
    if [ -f "$f" -a -r "$f" ]; then
	section_start "$f"
	catiffile "$f"
    fi
done

for f in /sys/class/x-display/disp0/device/*{,/*}; do
    if [ -f "$f" -a -r "$f" ]; then
	section_start "$f"
	catiffile "$f"
    fi
done

for f in /sys/class/hwmon/*/device/*; do
    if [ -f "$f" -a -r "$f" ]; then
	section_start "$f"
	catiffile "$f"
    fi
done

for f in /sys/class/net/eth0/*; do
    if [ -f "$f" -a -r "$f" ]; then
	section_start "$f"
	catiffile "$f"
    fi
done

for f in /sys/block/*/*{,/*}; do
    if [ -f "$f" -a -r "$f" ]; then
	section_start "$f"
	catiffile "$f"
    fi
done

for f in /sys/bus/platform/devices/bonsairtc.0/voltage_low; do
    if [ -f "$f" -a -r "$f" ]; then
	section_start "$f"
	catiffile "$f"
    fi
done

# Restore stdout
restore_stdout

# === USB data ===

# From here stdout redirected to file
reassign_stdout $ROOT/usb-data

section_start "USB tree"
catifexec lsusb -t

section_start "USB devices"
catifexec lsusb -v

# Restore stdout
restore_stdout

# === input data ===

# From here stdout redirected to file
reassign_stdout $ROOT/input-data

for f in /dev/input/event*; do
    if [ -e "$f" ]; then
	section_start "$f"
	catifexec evtest -i "$f"
    fi
done

# Restore stdout
restore_stdout

# === hid data ===

# From here stdout redirected to file
reassign_stdout $ROOT/hid-data

for f in /dev/usb/hiddev*; do
    if [ -e "$f" ]; then
	section_start "$f"
	catifexec hidtest -i "$f"
    fi
done

# Restore stdout
restore_stdout

# === raperca data ===

reassign_stdout $ROOT/raperca-content

section_start "/srv/raperca listing"
catifexec ls --time-style=full-iso -laNU1 /srv/raperca

section_start "/srv/raperca/content listing"
catifexec ls --time-style=full-iso -laNU1 ${DO_EXTENDED:+-R} /srv/raperca/content

section_start "/srv/.shadow-raperca/content listing"
catifexec ls --time-style=full-iso -laNU1 ${DO_EXTENDED:+-R} /srv/.shadow-raperca/content

section_start "/srv/raperca/cache listing"
catifexec ls --time-style=full-iso -laNU1 ${DO_EXTENDED:+-R} /srv/raperca/cache

section_start "/srv/raperca/capture listing"
catifexec ls --time-style=full-iso -laNU1 ${DO_EXTENDED:+-R} /srv/raperca/capture

section_start "/srv/raperca/webstorage listing"
catifexec ls --time-style=full-iso -laNU1 ${DO_EXTENDED:+-R} /srv/raperca/webstorage

# Restore stdout
restore_stdout

# === media data ===

reassign_stdout $ROOT/media-content

section_start "/media/usb1 listing"
catifexec ls --time-style=full-iso -laNU1 /media/usb1

# Restore stdout
restore_stdout

#
# TAR to stdout
#

# archive includes all files gathered above
# plus the ones given in the "here document" below

(tar -c -f - --exclude=spxpasswd.xml --files-from=- --ignore-failed-read -C / ${ROOT#/} \
    2>/dev/null <<-EOD
	etc/apache
	etc/chatscripts
	etc/cron.d
	etc/cron.daily
	etc/cron.hourly
	etc/cron.monthly
	etc/cron.weekly
	etc/crontab
	etc/default
	etc/dev.d
	etc/dhcpc
	etc/fonts
	etc/fstab
	etc/group
	etc/host.conf
	etc/hostname
	etc/hosts
	etc/hosts.allow
	etc/hosts.deny
	etc/hotplug
	etc/hotplug.d
	etc/init.d
	etc/inittab
	etc/ld.so.conf
	etc/limits
	etc/localtime
	etc/logrotate.conf
	etc/logrotate.d
	etc/modprobe.conf
	etc/modprobe.d
	etc/modules
	etc/network
	etc/nscd.conf
	etc/nsswitch.conf
	etc/ntp.conf
	etc/passwd
	etc/php.d
	etc/php.ini
	etc/ppp/options
	etc/ppp/peers
	etc/raperca
	etc/rc.d
	etc/resolv.conf
	etc/resolv.conf.sv
	etc/snmp
	etc/spinetix
	etc/spinetix-release
	etc/ssl/cert.pem
	etc/ssl/certs
	etc/sysctl.conf
	etc/syslog.conf
	etc/udev/udev.conf
	etc/udev/rules.d
	etc/updater.conf
	etc/vid.modes
	etc/xinetd.conf
	etc/xinetd.d
	var/cache/raperca/spxbackup.xml
	var/lib/dhcpc
	var/lib/hwwatchdog
	var/log
	var/run/modem
	srv/raperca/log
	srv/raperca/comportfsm
EOD
) | gzip -3
