Skip to content

Instantly share code, notes, and snippets.

@jigpu
Last active March 15, 2021 18:15
Show Gist options
  • Save jigpu/b7b47e4bede584cec8562f666fd84af4 to your computer and use it in GitHub Desktop.
Save jigpu/b7b47e4bede584cec8562f666fd84af4 to your computer and use it in GitHub Desktop.
Simultaneously capture XI2, evdev, and HID data from a tablet
#!/usr/bin/env bash
# Usage: capture.sh [limit [count]]
# Simultaneously capture HID, evdev, and Xinput events from a device.
#
# limit Maximum output file size in bytes (default: 0 [unlimited])
# count Number of output files to rotate through (default: 1)
#
# This script must be run as root in order to read all events.
set -e
trap cleanup INT QUIT TERM
function cleanup {
echo
echo "Archiving capture data..."
sleep 0.1
jobs -p | xargs kill 2>/dev/null || true
wait
tar cvzf record_$NOW.tar.gz record_$NOW.*.log*
rm record_$NOW.*.log*
echo
echo "Files have been archived to $(pwd)/record_$NOW.tar.gz"
echo "Please upload this file for analysis."
}
function kernelver_int {
awk -F'[-.]' '{print $1*1000000+$2*1000+$3}' <<<$1
}
function hid_available {
test $(kernelver_int $(uname -r)) -ge $(kernelver_int "3.17") || grep -o 7.[56789] /etc/redhat-release >/dev/null 2>/dev/null
return $?
}
function logger {
DEST=$1
LOGSIZE=$2
LOGCOUNT=$3
NBYTES=0
T=$(date +%s.%N)
while IFS='' read -r TEXT; do
if [[ "$DEST" == *".xi2.log" && "$TEXT" == "EVENT "* ]]; then
NOW=$(date +%s.%N)
awk -vA="$T" -vB="$NOW" 'BEGIN {print B " [" (B-A)*1000 " ms]"; exit}' >> "$DEST"
T=$NOW
fi
echo "$TEXT" >> "$DEST"
if [[ $LOGSIZE -gt 0 ]]; then
# Calling stat to get the size every time we write a line is very slow...
# Lets do our own accounting instead.
NBYTES=$(($NBYTES + ${#TEXT} + 2))
if [[ $NBYTES -gt $LOGSIZE ]]; then
echo "Rotating $DEST..."
for I in $(seq $(($LOGCOUNT-1)) -1 0); do
mv -f "$DEST.$I" "$DEST.$(($I+1))" 2> /dev/null || true
done
mv "$DEST" "$DEST.0" || true
rm -f "$DEST.$LOGCOUNT" 2> /dev/null || true
NBYTES=0
fi
fi
done
}
if [[ "$EUID" -ne 0 ]]; then
echo "This command must be run as root to capture hardware and kernel logs."
exit 1
fi
LOGLIMIT=0
LOGCOUNT=1
if [[ $# -ge 1 ]]; then
LOGLIMIT=$1
fi
if [[ $# -ge 2 ]]; then
LOGCOUNT=$2
fi
REC_XI2=$(command -v xinput 2> /dev/null || true)
REC_KRN=$(command -v evemu-record 2> /dev/null || command -v evtest 2>/dev/null || true)
REC_HID=$(command -v hid-recorder 2> /dev/null || true)
REC_USB=""
REC_TTY=$(command -v isdv4-serial-debugger 2>/dev/null || true)
if ! hid_available ; then
# Can't capture HID data prior to Linux 3.17...
REC_USB="grep"
if ! modprobe usbmon; then
echo "Unable to load usbmon kernel module."
exit 1
fi
fi
ERRMSG=""
if [[ -z "$REC_XI2" ]]; then
ERRMSG="${ERRMSG}\n * xinput [Try: \`apt-get install xinput\` or \`yum install xorg-x11-server-utils\`]"
fi
if [[ -z "$REC_KRN" ]]; then
ERRMSG="${ERRMSG}\n * evemu-record [Try: \`apt-get install evemu-tools\` or \`yum install evemu\` (may require EPEL)]"
fi
if hid_available && [[ -z "$REC_HID" ]]; then
ERRMSG="${ERRMSG}\n * hid-recorder [Try: \`yum install hid-replay\` (may require EPEL) or https://bentiss.github.io/hid-replay-docs/ ]"
fi
if [[ -z "$REC_TTY" ]]; then
ERRMSG="${ERRMSG}\n * isdv4-serial-debugger [Try: \`apt-get install xserver-xorg-input-wacom\` \`yum install xorg-x11-wacom-devel\` ]"
fi
if [[ -n "$ERRMSG" ]] ; then
echo "The following utilities are missing and must be installed to capture logs:"
echo "$ERRMSG"
exit 1
fi
DEV_X11=""
DEV_EVDEV=""
DEV_HID=""
DEV_USB=""
DEV_TTY=""
xinput list
while [[ "x$DEV_X11" == "x" ]]; do
echo -ne "\nID number of the device to record: "
read DEV_X11
if ! xinput list $DEV_X11 > /dev/null 2>&1; then
echo "Unknown device ID."
DEV_X11=""
fi
done
DEV_EVDEV=$(xinput list-props "$DEV_X11" | awk 'BEGIN {OFS=FS="\t"} /^\tDevice Node/ {$1=$2="";print substr($0,4,length()-4)}')
if [[ -c "$DEV_EVDEV" ]]; then
echo "Found evdev device node: $DEV_EVDEV"
else
echo "Unknown device node: $DEV_EVDEV"
exit 1
fi
SYSFS_PATH=$(readlink -e "/sys/$(udevadm info -q path "$DEV_EVDEV")" 2>/dev/null || true)
if [[ -n "$SYSFS_PATH" ]]; then
echo "Found evdev sysfs at $SYSFS_PATH"
else
echo "Unable to locate sysfs path from $(udevadm info -q path "$DEV_EVDEV")"
exit 1
fi
HIDRAW_PATH=$(readlink -e "$SYSFS_PATH/device/device/hidraw" 2>/dev/null || true)
USBBUS_PATH=$(readlink -e "$SYSFS_PATH/../../../../../busnum" 2>/dev/null || true)
USBDEV_PATH=$(readlink -e "$SYSFS_PATH/../../../../../devnum" 2>/dev/null || true)
TTYDEV_PATH=$(ls "$SYSFS_PATH/../../../../serio*" 2>/dev/null || true)
if [[ -n "$HIDRAW_PATH" ]]; then
echo "Found hidraw sysfs at $HIDRAW_PATH"
DEV_HID="/dev/$(ls $HIDRAW_PATH)"
if [[ -c "$DEV_HID" ]]; then
echo "Found hidraw device node: $DEV_HID"
else
echo "Unknown hidraw device node: $DEV_HID"
exit 1
fi
elif [[ -n "$USBBUS_PATH" && -n "$USBDEV_PATH" ]]; then
echo "Found usb bus sysfs at $USBBUS_PATH"
echo "Found usb dev sysfs at $USBDEV_PATH"
BUS=$(cat "$USBBUS_PATH")
DEV=$(cat "$USBDEV_PATH")
DEV_USB=$(printf "%d:%03d" $BUS $DEV)
echo "Found USB bus/device: $DEV_USB"
if ! test -f /sys/kernel/debug/usb/usbmon/0u; then
echo "Unable to locate usbmon character device"
exit 1
fi
elif [[ -n "$TTYDEV_PATH" ]]; then
echo "Found tty sysfs at $TTYDEV_PATH"
DEV_TTY="/dev/"$(basename $(readlink -f "$SYSFS_PATH/../../../.."))
if [[ -c "$DEV_TTY" ]]; then
echo "Found TTY device node: $DEV_TTY"
else
echo "Unknown TTY device node: $DEV_TTY"
exit 1
fi
else
echo "Unable to find a hardware device from $SYSFS_PATH"
exit 1
fi
cd /tmp
NOW=$(date +%s)
echo
echo "Capturing events into temporary files..."
if [[ $LOGLIMIT -gt 0 ]]; then
echo "(Limiting to $LOGLIMIT bytes across $LOGCOUNT files)"
fi
$REC_XI2 test-xi2 --root "$DEV_X11" | logger record_$NOW.xi2.log $LOGLIMIT $LOGCOUNT &
echo " XI2: $(pwd)/record_$NOW.xi2.log"
$REC_KRN "$DEV_EVDEV" | logger record_$NOW.evemu.log $LOGLIMIT $LOGCOUNT &
echo " evdev: $(pwd)/record_$NOW.evemu.log"
if [[ -n "$DEV_HID" ]] ; then
$REC_HID "$DEV_HID" | logger record_$NOW.hid.log $LOGLIMIT $LOGCOUNT &
echo " HW: $(pwd)/record_$NOW.hid.log"
elif [[ -n "$DEV_USB" ]] ; then
$REC_USB ":$DEV_USB:" /sys/kernel/debug/usb/usbmon/0u | logger record_$NOW.usb.log $LOGLIMIT $LOGCOUNT &
echo " HW: $(pwd)/record_$NOW.usb.log"
elif [[ -n "$DEV_TTY" ]] ; then
$REC_TTY "$DEV_TTY" | logger record_$NOW.tty.log $LOGLIMIT $LOGCOUNT &
echo " HW: $(pwd)/record_$NOW.tty.log"
fi
echo
echo "Press CTRL+C to stop recording."
wait
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment