#!/bin/bash
#
# sclpdbf - Tool to display sclp kernel traces
#
# Copyright IBM Corp. 2021
#
# Usage: sclpdbf [TRACEFILE] [OPTIONS]
#
# Display the contents of the s390dbf kernel trace area for the sclp component
# in a human readable format.
#
# TRACEFILE may specify the name of a file containing the trace data in
# hex_ascii view format. If no TRACEFILE is specified, trace data of the
# currently running kernel is read from its default location at:
#
#   /sys/kernel/debug/s390dbf/sclp/hex_ascii      (default source)
#   /sys/kernel/debug/s390dbf/sclp_err/hex_ascii  (if -e is specified)
#
# OPTIONS
#   -h, --help            Print this help, then exit
#       --version         Print version information, then exit
#   -v, --verbose         Display verbose trace output
#   -e, --errlog          Display sclp error log instead of normal log
#   -r, --raw             Display log in raw format
#   -s, --symtab SYMFILE  Use symbol table in SYMFILE (default /proc/kallsyms)
#   -S, --no-symtab       Do not resolve kernel addresses to symbol names
#   -f, --flush           Flush current trace buffers
#   -l, --level LEVEL     Set tracing level to LEVEL (-1=off, 0 to 6)
#   -p, --pages PAGES     Set trace area size to PAGES
#   -P, --no-pager        Do not pipe trace output into pager

TOOLPATH="$(readlink -f "${BASH_SOURCE[0]}")"
TOOLNAME="${0##*/}"

DEBUGFS="/sys/kernel/debug"

# Enable color usage and pager when writing to terminal
if [[ -t 1 ]] ; then
	BOLD="\033[1m"
	RED="\033[7;31m"
	GREEN="\033[1;32m"
	BLUE="\033[1;34m"
	YELLOW="\033[33m"
	RESET="\033[0m"

	PAGER="less -r"
else
	PAGER="cat"
fi

# List of known trace entry IDs
declare -A IDS=(
	["SRV1"]="handle_SRV1"
	["SRV2"]="handle_SRV2"
	["INT"]="handle_INT"
	["UNEX"]="handle_UNEX"
	["RQAD"]="handle_RQAD"
	["RQOK"]="handle_RQOK"
	["RQAB"]="handle_RQAB"
	["RQTM"]="handle_RQTM"
	["EVNT"]="handle_EVNT"
	["STCG"]="handle_STCG"
	["REG"]="handle_REG"
	["UREG"]="handle_UREG"
	["TMO"]="handle_TMO"
	["SYN1"]="handle_SYN1"
	["SYN2"]="handle_SYN2"
)

# Symbolic names for SCLP request status
declare -a REQSTATUS=(
	"FILLED"
	"QUEUED"
	"RUNNING"
	"DONE"
	"${RED}FAILED${RESET}"
	"${RED}QUEUED_TIMEOUT${RESET}"
)

# Symbolic names for SCLP command words
declare -A CMDWS=(
	[0x00010001]="READ_CPU_INFO"
	[0x00020001]="READ_SCP_INFO"
	[0x00040001]="READ_STORAGE_INFO"
	[0x00120001]="READ_SCP_INFO_FORCED"
	[0x00770005]="READ_EVENT_DATA"
	[0x00760005]="WRITE_EVENT_DATA"
	[0x00780005]="WRITE_EVENT_MASK"
)

# Symbolic names for RC values
declare -a RC=(
	[0]="OK"
	[5]="${RED}EIO${RESET}"
	[16]="${RED}EBUSY${RESET}"
	[22]="${RED}EINVAL${RESET}"
)

# Colors to be used for hex dump display
FIELDCOLS=(
	[0]=""
	[1]="$BLUE"
	[2]="$GREEN"
)

# Color index for hex bytes in SCCB hex dump display
SCCBFIELDS="1 1 2 2 2 2 1 1"

# Color index for hex bytes in event buffer hex dump display
EVNTFIELDS="1 1 2 1"

# ASCII -> printable ASCII mapping table
ASCII='................................ !"#$%&'\''()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~.................................................................................................................................'

# EBCDIC 500 -> printable ASCII mapping table
EBCDIC='................................................................ .........[.<(+!&.........]$*);^-/.........,%_>?........p`:#@'\''=".abcdefghi.......jklmnopqr.......~stuvwxyz.................|....{ABCDEFGHI......}JKLMNOPQR......\.STUVWXYZ......0123456789......'


function print_help() {
	local line started=0

	while read -r line ; do
		[[ $started -eq 0 && $line =~ Usage: ]] && started=1
		[[ $started -eq 1 && $line =~ ^$ ]] && break
		[[ $started -eq 1 ]] && echo "${line:2}"
	done < "$TOOLPATH"
}

function print_version() {
	local line

	echo "${TOOLNAME}: version %S390_TOOLS_VERSION%"
	while read -r line ; do
		[[ ! $line =~ Copyright ]] && continue
		echo "${line:2}"
		break
	done < "$TOOLPATH"
}

#
# die MSG
#
# Terminate with the specified error message and a non-zero exit code.
#
function die() {
	local msg="$*"

	echo "Error: $msg" >&2
	exit 1
}

#
# indent NUM
#
# Indent all data read from stdin by NUM spaces.
#
function indent() {
	local num=$1 spaces

	printf -v spaces "%*s" "$num" ""
	sed -e "s/^/$spaces/g"
}

#
# repeat STR NUM
#
# Print STR for NUM times.
#
function repeat() {
	local str="$1" num="$2"

	eval "printf '$str%.0s' {1..$num}"
}

# Trace file may be wrapped (4 5 6 1 2 3) - ensure ascending timestamp order
function sort_file() {
	local filename="$1"
	local before=() after=() area t data t_start line

	{
		read -r area t_start data
		after+=("$area $t_start $data")

		while read -r area t data ; do
			# Look for wrap in time sequence
			if [[ "$t" < "$t_start" ]] ; then
				before+=("$area $t $data")
				break
			fi
			after+=("$area $t $data")
		done

		while read -r area t data ; do
			before+=("$area $t $data")
		done
	} <"$filename"

	if [[ ${#before[@]} -gt 0 ]] ; then
		printf "%s\n" "${before[@]}"
	fi

	if [[ -n "$t_start" ]] ; then
		printf "%s\n" "${after[@]}"
	fi
}

function check_tracefile() {
	local file="$1" area ts level ex cpu caller data

	if [[ ! -e "$file" ]] ; then
		die "Could not find trace data file $file"
	fi

	if [[ ! -r "$file" ]] ; then
		die "Missing access permissions for $file"
	fi

	read -r area ts level ex cpu caller data <"$file"

	if [[ -z "$area" ]] ; then
		echo "No traces found in $file"
		exit 0
	fi

	if [[ -z "$ts" ]] || [[ -z "$level" ]] || [[ -z "$ex" ]] ||
	   [[ -z "$cpu" ]] || [[ -z "$caller" ]] || [[ -z "$data" ]] ; then
		die "Unrecognized trace data format in $file"
	fi
}

#
# print_ascii HEX
#
# Convert ASCII bytes in hexadecimal notation to printable ASCII.
#
function print_ascii() {
	local hex="$1" asc

	printf -v asc '${ASCII:0x%s:1}' $hex
	eval echo -n "$asc"
}

#
# print_ebcdic HEX
#
# Convert EBCDIC bytes in hexadecimal notation to printable ASCII.
#
function print_ebcdic() {
	local hex="$1" ebc

	printf -v ebc '${EBCDIC:0x%s:1}' $hex
	eval echo -n "$ebc"
}

#
# hex_to_num HEX START LEN CONV VAR
#
# Convert hex bytes in HEX starting at position START with length LEN to
# an integer. The result will be stored in VAR as hexadecimal number if CONV
# is 0, decimal if CONV is 1.
#
function hex_to_num() {
	local _hex="$1" _start="$2" _len="$3" _conv="$4" _var="$5" _value _fmt

	# Remove spaces
	_hex="${_hex// /}"

	# Extract relevant hex digits as decimal number
	(( _start*=2 ))
	(( _len*=2 ))
	_value="0x${_hex:$_start:$_len}"
	(( _value=_value ))

	# Store as requested output format
	if [[ "$_conv" -eq 1 ]] ; then
		_fmt="%d"
	else
		_fmt="0x%x"
	fi

	printf -v "$_var" "$_fmt" "$_value"
}

#
# is_id TAG
#
# Return 0 if TAG is a known trace tag, non-zero otherwise.
#
function is_id() {
	local tag="$1"

	[[ -z "$tag" ]] && return 1
	[[ "$tag" =~ [^A-Z0-9] ]] && return 1
	[[ -n "${IDS[$tag]}" ]]
}

#
# reqstatus NUM
#
# Convert the specified SCLP request status to a named representation.
#
function reqstatus() {
	local num="$1" _var="$2" txt

	[[ -n "$_var" ]] && _var="-v $_var"

	txt="${REQSTATUS[$num]}"

	eval "printf $_var '${txt:-Unknown} ($num)'"
}

#
# cmdwname CMDW
#
# Convert the specified SCLP command word to a symbolic name.
#
function cmdwname() {
	local _cmdw="$1" _var="$2" c

	[[ -n "$_var" ]] && _var="-v $_var"
	printf -v c "0x%08x" $(( _cmdw ))

	if [[ -n "${CMDWS[$c]}" ]] ; then
		if [[ $OPT_VERBOSE -eq 1 ]] ; then
			_cmdw="${CMDWS[$c]} ($_cmdw)"
		else
			_cmdw="${CMDWS[$c]}"
		fi
	fi

	eval "printf $_var \"$_cmdw\""
}

#
# rcname RC
#
# Convert the specified return code to a symbolic name.
#
function rcname() {
	local _rc="$1" _var="$2"

	if [[ -n "${RC[$_rc]}" ]] ; then
		if [[ $OPT_VERBOSE -eq 1 ]] ; then
			_rc="${RC[$_rc]} ($_rc)"
		else
			_rc="${RC[$_rc]}"
		fi
	fi

	printf -v "$_var" "$_rc"
}

#
# respcol RESP
#
# Add color to the specified SCCB response code
#
function respcol() {
	local _resp="$1" _var="$2" _c

	_c=$(( _resp&0xff ))
	if [[ $_c -ne 0x10 ]] && [[ $_c -ne 0x20 ]] && [[ $_c -ne 0 ]] ; then
		_resp="$RED$_resp$RESET"
	fi

	printf -v "$_var" "$_resp"
}

#
# lencol LEN
#
# Add color to the specified SCCB length
#
function lencol() {
	local _len="$1" _var="$2"

	if [[ $_len -lt 8 ]] ; then
		_len="$RED$_len$RESET"
	fi

	printf -v "$_var" "$_len"
}

#
# sccbcol SCCB
#
# Add color to the specified SCCB address
#
function sccbcol() {
	local _sccb="$1" _var="$2"

	if [[ $(( _sccb&0x7 )) -ne 0 ]] ; then
		_sccb="$RED$_sccb$RESET"
	fi

	printf -v "$_var" "$_sccb"
}

#
# bitlist NUM
#
# Print list of bit numbers set in the 64 bit word specified by NUM.
#
function bitlist() {
	local num="$1" _var="$2" list="" i c="" w=64

	i=1
	while [[ $i -lt $w ]] ; do
		if [[ $(( num&1<<(w-i) )) -ne 0 ]] ; then
			list="$list$c$i"
			c=','
		fi
		(( i++ ))
	done

	[[ -z "$list" ]] && list="-"

	eval "$_var='$list'"
}


function read_symtab() {
	local filename="$1" symtab=() i

	echo "Reading symbol table from $OPT_SYMTAB"
	readarray symtab <"$filename"

	for i in "${symtab[@]}" ; do
		set -- $i
		(( i=0x$1 ))
		SYMS[$i]="$3"
	done

	echo "Found ${#SYMS[@]} symbols"
}

#
# get_sym ADDR
#
# Convert the specified kernel address to a symbolic name.
#
function get_sym() {
	local _addr="$1" _var="$2" from to i

	[[ -n "$_var" ]] && _var="-v $_var"

	_addr=0x${_addr#0x}

	if [[ $OPT_NOSYM -eq 1 ]] ; then
		eval printf $_var "0x%x" $_addr
		return
	fi

	from=$_addr
	(( to=from-0x10000 ))
	[[ $to -lt 1 ]] && to=1

	i=$from
	while [[ $i -ge $to ]] ; do
		if [[ -z "${SYMS[$i]}" ]] ; then
			(( i-- ))
			continue
		fi

		if [[ $_addr -eq $i ]] ; then
			eval printf $_var "${SYMS[$i]}"
		else
			eval printf $_var "%s+0x%x" "${SYMS[$i]}" $(( _addr-$i ))
		fi
		return
	done

	eval printf $_var "0x%x" $_addr
}

# Print hex data in alternating colors to help visual grouping
function hex_color() {
	local hex="$1" delim="$2" fields=($3) i byte currcol lastcol

	lastcol=0
	i=0
	for byte in $hex ; do
		currcol="${fields[$i]}"
		[[ -z "$currcol" ]] && currcol=0

		if [[ "$currcol" -ne "$lastcol" ]] ; then
			echo -en "$RESET${FIELDCOLS[$currcol]}"
		fi
		echo -n "$byte$delim"

		lastcol="$currcol"
		(( i++ ))
	done
	echo -ne "$RESET"
}

function handle_buffer() {
	local name="$1" fields="$2" hex=("${@:3}") offset=0 v size

	[[ ${#hex[@]} -eq 0 ]] && return

	if [[ $OPT_VERBOSE -eq 0 ]] ; then
		for v in "${hex[@]}" ; do
			hex_color "$v" "" "$fields"
			echo -n "  "
			print_ascii "$v"
			echo -n "  "
			print_ebcdic "$v"
			echo
			fields=""
		done
		return
	fi

	[[ -n "$name" ]] && printf "\n%s:\n" "$name"
	hex_to_num "${hex[0]}" 0 2 1 size

	printf "$BOLD%-4s  %-48s %-16s  %-16s$RESET\n" "OFF" "HEX" "ASCII" "EBCDIC"

	for v in "${hex[@]}" ; do
		printf "%04x: " "$offset"
		hex_color "$v" " " "$fields"
		echo -n " "
		print_ascii "$v"
		echo -n "  "
		print_ebcdic "$v"
		echo
		fields=""

		(( offset+=16 ))
	done

	if [[ -n "$size" ]] && [[ $offset -lt $size ]] ; then
		(( size-=offset ))
		echo "Truncated $size bytes"
	fi
}

function short() {
	set -- $*
	echo -en "$BOLD$1$RESET"
	printf " %s" "${@:2}"
}

function long() {
	echo -e "$BOLD$*$RESET"
}

function handle_SRV1() {
	local hex1="$1" hex2=("${@:2}") cmdw sccb len response

	hex_to_num "$hex1" 4 4 0 cmdw
	cmdwname "$cmdw" cmdw
	hex_to_num "$hex1" 8 8 0 sccb
	sccbcol "$sccb" sccb

	if [[ $OPT_VERBOSE -eq 0 ]] ; then
		short "SERVC cmd=$cmdw sccb=$sccb"
		echo
		handle_buffer "SCCB contents" "$SCCBFIELDS" "${hex2[@]}" |
			indent 4
		return
	fi

	hex_to_num "${hex2[0]}" 0 2 1 len
	lencol "$len" len
	hex_to_num "${hex2[0]}" 6 2 0 response
	respcol "$response" response

	long "Service call about to be issued"
	echo "  SCLP command ....: $cmdw"
	echo -e "  SCCB address ....: $sccb"
	echo -e "  SCCB length .....: $len"
	echo -e "  SCCB response ...: $response"

	handle_buffer "SCCB contents" "$SCCBFIELDS" "${hex2[@]}" | indent 2
}

function handle_SRV2() {
	local hex="$1" rc seq

	hex_to_num "$hex" 4 4 1 rc
	rcname "$rc" rc

	if [[ $OPT_VERBOSE -eq 0 ]] ; then
		short "SERVC rc=$rc"
		echo
		return
	fi

	hex_to_num "$hex" 8 8 1 seq

	long "Service call completed"
	echo -e "  Result code ...........: $rc"
	echo "  SRVC sequence number ..: $seq"
}

function handle_INT() {
	local hex1="$1" hex2=("${@:2}") parm32 pending sccb cmdw len response

	hex_to_num "$hex1" 4 4 1 parm32
	(( pending=parm32 & 0x3 ))
	(( sccb=parm32 & 0xfffffff8 ))
	printf -v sccb "0x%x" "$sccb"
	sccbcol "$sccb" sccb
	hex_to_num "$hex1" 12 4 0 cmdw
	cmdwname "$cmdw" cmdw
	hex_to_num "${hex2[0]}" 6 2 0 response
	respcol "$response" response

	if [[ $OPT_VERBOSE -eq 0 ]] ; then
		short "INT pend=$pending sccb=$sccb resp=$response"
		echo
		handle_buffer "SCCB contents" "$SCCBFIELDS" "${hex2[@]}" |
			indent 4
		return
	fi

	hex_to_num "${hex2[0]}" 0 2 1 len
	lencol "$len" len

	long "Interrupt received"
	echo "  Event pending indicator ..: $pending"
	echo "  Active command ...........: $cmdw"
	echo -e "  SCCB address .............: $sccb"
	echo -e "  SCCB length ..............: $len"
	echo -e "  SCCB response ............: $response"

	handle_buffer "SCCB contents" "$SCCBFIELDS" "${hex2[@]}" | indent 2
}

function handle_UNEX() {
	local hex="$1" sccb

	hex_to_num "$hex" 4 4 0 sccb
	sccbcol "$sccb" sccb

	if [[ $OPT_VERBOSE -eq 0 ]] ; then
		short "${RESET}${RED}UNEXPECTED${RESET} sccb=$sccb"
		echo
		return
	fi

	long "${RESET}${RED}Unexpected SCCB completion${RESET}"
	echo "  SCCB address ...: $sccb"
}

function handle_req() {
	local hex="$1"
	local sccb status response timeout start_count

	hex_to_num "$hex" 4 4 0 sccb
	sccbcol "$sccb" sccb

	if [[ $OPT_VERBOSE -eq 0 ]] ; then
		echo -n "sccb=$sccb"
		return
	fi

	hex_to_num "$hex" 8 2 1 status
	reqstatus "$status" status
	hex_to_num "$hex" 10 2 0 response
	respcol "$response" response
	hex_to_num "$hex" 12 2 1 timeout
	hex_to_num "$hex" 14 2 1 start_count

	echo -e "Request status ........: $status"
	echo "Request timeout .......: $timeout"
	echo "Request start count ...: $start_count"
	echo -e "SCCB address ..........: $sccb"
	echo -e "SCCB response .........: $response"
}

function handle_RQAD() {
	local hex="$1" sccb caller

	hex_to_num "$hex" 4 4 0 sccb
	sccbcol "$sccb" sccb
	hex_to_num "$hex" 8 8 0 caller
	get_sym "$caller" caller

	if [[ $OPT_VERBOSE -eq 0 ]] ; then
		short "RQADD caller=$caller"
		echo
		return
	fi

	long "Request is added"
	echo -e "  SCCB address...........: $sccb"
	echo "  Caller ................: $caller"
}

function handle_RQOK() {
	if [[ $OPT_VERBOSE -eq 0 ]] ; then
		short "RQOK "
		handle_req "$1"
		echo
		return
	fi

	long "Request completed successfully"

	handle_req "$1" | indent 2
}

function handle_RQAB() {
	if [[ $OPT_VERBOSE -eq 0 ]] ; then
		short "${RESET}${RED}RQABORT${RESET} "
		handle_req "$1"
		echo
		return
	fi

	long "${RESET}${RED}Request was aborted${RESET}"

	handle_req "$1" | indent 2
}

function handle_RQTM() {
	if [[ $OPT_VERBOSE -eq 0 ]] ; then
		short "${RESET}${RED}RQTIMEOUT${RESET} "
		handle_req "$1"
		echo
		return
	fi

	long "${RESET}${RED}Request timed out${RESET}"

	handle_req "$1" | indent 2
}

function handle_EVNT() {
	local hex="$1" hex2=("${@:2}") cb len type

	hex_to_num "$hex" 8 8 0 cb
	get_sym "$cb" cb

	if [[ $OPT_VERBOSE -eq 0 ]] ; then
		short "EVENT callback=$cb"
		echo
		handle_buffer "Event buffer contents" "$EVNTFIELDS" \
			"${hex2[@]}" | indent 4
		return
	fi

	hex_to_num "${hex2[0]}" 0 2 1 len
	hex_to_num "${hex2[0]}" 2 1 1 type

	long "Dispatching event buffer"
	echo "  Receiving callback ......: $cb"
	echo "  Event buffer length .....: $len"
	echo "  Event type ..............: $type"

	handle_buffer "Event buffer contents" "$EVNTFIELDS" "${hex2[@]}" |
		indent 2
}

function handle_STCG() {
	local hex="$1" cb

	hex_to_num "$hex" 8 8 0 cb
	get_sym $cb cb

	if [[ $OPT_VERBOSE -eq 0 ]] ; then
		short "STATECHANGE callback=$cb"
		echo
		return
	fi

	long "Dispatching state change"
	echo "  Receiving callback ...: $cb"
}

function handle_register() {
	local hex="$1" receive send

	hex_to_num "$hex" 0 8 0 receive
	bitlist "$receive" receive
	hex_to_num "$hex" 8 8 0 send
	bitlist "$send" send

	echo "Receive mask ...: $receive"
	echo "Send mask ......: $send"
}

function handle_REG() {
	local hex1="$1" hex2="$2" retip

	hex_to_num "$hex1" 8 8 0 retip
	get_sym "$retip" retip

	if [[ $OPT_VERBOSE -eq 0 ]] ; then
		short "LISTENERADD caller=$retip"
		echo
		return
	fi

	long "Register event listener"
	echo "  Caller .........: $retip"

	handle_register "$hex2" | indent 2
}

function handle_UREG() {
	local hex1="$1" hex2="$2" retip

	hex_to_num "$hex1" 8 8 0 retip
	get_sym "$retip" retip

	if [[ $OPT_VERBOSE -eq 0 ]] ; then
		short "LISTENERDEL caller=$retip"
		echo
		return
	fi

	long "Unregister event listener"
	echo "  Caller .........: $retip"

	handle_register "$hex2" | indent 2
}

function handle_TMO() {
	local hex="$1" force

	hex_to_num "$hex" 4 4 1 force

	if [[ $OPT_VERBOSE -eq 0 ]] ; then
		short "${RESET}${RED}TIMEOUT${RESET} force=$force"
		echo
		return
	fi

	long "${RESET}${RED}Timeout occurred${RESET}"
	echo "  Force restart ..: $force"
}

function handle_SYN1() {
	local hex="$1" state seq c cr

	hex_to_num "$hex" 4 4 1 state
	hex_to_num "$hex" 8 8 1 seq

	if [[ $state -eq 0 ]] ; then
		# Sync wait despite running state=idle
		c="${RESET}${RED}"
		cr="${RESET}"
	fi

	if [[ $OPT_VERBOSE -eq 0 ]] ; then
		short "${c}SYNCWAIT_START${cr}"
		echo
		return
	fi

	long "Synchronous wait start"
	echo -e "  SCLP running state ...... ...: ${c}$state${cr}"
	echo "  Sync wait sequence number ...: $seq"
}

function handle_SYN2() {
	local hex="$1" state c cr

	hex_to_num "$hex" 4 4 1 state
	hex_to_num "$hex" 8 8 1 seq

	if [[ $state -eq 1 ]] ; then
		# Sync wait exit despite running state=running
		c="${RESET}${RED}"
		cr="${RESET}"
	fi

	if [[ $OPT_VERBOSE -eq 0 ]] ; then
		short "${c}SYNCWAIT_END${cr}"
		echo
		return
	fi

	long "Synchronous wait end"
	echo -e "  SCLP running state ...... ...: ${c}$state${cr}"
	echo "  Sync wait sequence number ...: $seq"
}

#
# handle_unknown LINES
#
# Handle unrecognized trace data.
#
function handle_unknown() {
	local hex=("$@")

	if [[ $OPT_VERBOSE -eq 0 ]] ; then
		short "INCOMPLETE_ENTRY"
		echo
		handle_buffer "" "" "${hex[@]}" | indent 4
	else
		long "Incomplete trace entry"
		handle_buffer "Raw data" "" "${hex[@]}" | indent 2
	fi
}

#
# print_ts
#
# Convert timestamp to date format, assuming local timezone.
#
function print_ts() {
	local ts="$1" s ns

	s=${ts%:*}
	ns=${ts#*:}
	echo -n "$(date '+%Y-%m-%d %H:%M:%S' -d @"$s").$ns"
}

#
# print_ts_diff NOW LAST
#
# Print delta between timestamps NOW and LAST.
#
function print_ts_diff() {
	local now="$1" last="$2" _var="$3"
	local now_s now_us last_s last_us dist_s dist_us

	[[ -z "$last" ]] && return
	[[ -n "$_var" ]] && _var="-v $_var"

	now_s=${now%:*}
	now_us=${now#*:}
	last_s=${last%:*}
	last_us=${last#*:}

	now_s=$(( 10#$now_s ))
	now_us=$(( 10#$now_us ))
	last_s=$(( 10#$last_s ))
	last_us=$(( 10#$last_us ))

	(( dist_us=now_us-last_us ))
	(( dist_s=now_s-last_s ))
	if [[ $dist_us -lt 0 ]] ; then
		(( dist_us+=1000000 ))
		(( dist_s--))
	fi

	if [[ $dist_s -gt 3599 ]] ; then
		eval printf $_var "+%dh%dm" "$((dist_s/3600 ))" "$(( (dist_s%3600)/60 ))"
	elif [[ $dist_s -gt 59 ]] ; then
		eval printf $_var "+%dm%ds" "$((dist_s/60 ))" "$(( dist_s%60 ))"
	elif [[ $dist_s -gt 0 ]] ; then
		eval printf $_var "+%d.%ds" $dist_s "$(( dist_us/100000 ))"
	elif [[ $dist_us -gt 1000 ]] ; then
		eval printf $_var "+%dms" "$(( dist_us/1000 ))"
	else
		eval printf $_var "+%dus" $dist_us
	fi
}

function header() {
	local ts="$1" cpu="$2" caller="$3" ts2 tsdiff=""

	cpu=$(( 10#$cpu ))

	if [[ $OPT_VERBOSE -eq 0 ]] ; then
		echo -ne "$YELLOW"
		ts2=${ts//:/.}
		print_ts_diff "$ts" "$LAST_TS" tsdiff
		printf "%03d %-18s (%7s)" "$cpu" "$ts2" "$tsdiff"
		echo -ne ": $RESET"
	else
		echo -ne "${YELLOW}CPU $cpu at "
		print_ts "$ts"
		if [[ -n "$LAST_TS" ]] ; then
			printf " ("
			print_ts_diff "$ts" "$LAST_TS"
			printf ")"
		fi
		get_sym "$caller" caller
		echo -e " at $caller$RESET"
		echo -ne "$YELLOW"
		repeat "=" 78
		echo -e "$RESET"
	fi
	LAST_TS="$ts"
}

function handle_lines() {
	local id="$1" fn

	shift
	[[ $# -eq 0 ]] && return

	if [[ -z "$id" ]] ; then
		fn="handle_unknown"
	else
		fn="${IDS[$id]}"
	fi

	"$fn" "$@"

	[[ $OPT_VERBOSE -eq 1 ]] && echo
}

# Read trace file in s390dbf hex_ascii format and convert to readable format
function process() {
	local area ts level ex cpu caller data hex ascii id
	local active_id lines=() once=0

	while read -r area ts level ex cpu caller data ; do
		hex="${data%% |*}"
		ascii="${data#*| }"

		id=${ascii:0:4}
		id=${id%%.*}

		if [[ "$once" != 1 ]] ; then
			echo -n "  Start time ...: "
			print_ts "$ts"
			printf "\n\n"
			if [[ $OPT_VERBOSE -eq 0 ]] ; then
				echo -en "$BOLD"
				printf "%3s %18s (%7s): %s" \
					"CPU" "TIME" "DELTA" "TRACE ENTRY"
				echo -e "$RESET"
			fi
			once=1
		fi

		if ! is_id "$id" ; then
			if [[ -z "$active_id" ]] &&
			   [[ "${#lines[@]}" -eq 0 ]]; then
				header "$ts" "$cpu" "$caller"
			fi

			# Add to data for active ID
			lines+=("$hex")

			continue
		fi

		# Handle previous data
		handle_lines "$active_id" "${lines[@]}"

		active_id="$id"
		header "$ts" "$cpu" "$caller"
		lines=("$hex")
	done

	# Handle final data
	handle_lines "$active_id" "${lines[@]}"
}


#
# Main
#

declare -a SYMS
declare TRACEFILE TRACEBASE TRACELEVEL TRACEPAGES LAST_TS SHORTOPTS LONGOPTS
declare OPT_VERBOSE OPT_SYMTAB OPT_NOSYM OPT_RAW OPTS

TRACEBASE="$DEBUGFS/s390dbf/sclp"
TRACENAME="default log"
SHORTOPTS="vhs:fl:SePp:r"
LONGOPTS="help,version,verbose,symtab:,no-symtab,raw,flush,level:,pages:,no-pager,errlog"

# Parse parameters
OPTS=$(getopt -o "$SHORTOPTS" -l "$LONGOPTS" -n "Usage error" -- "$@") || exit 1

eval set -- $OPTS

OPT_VERBOSE=0
OPT_SYMTAB="/proc/kallsyms"
OPT_NOSYM=0
OPT_RAW=0
OPT_NOTRACE=0

while [[ "$1" != "--" ]] ; do
	case "$1" in
	-h|--help)
		print_help
		exit 0
		;;
	--version)
		print_version
		exit 0
		;;
	-v|--verbose)
		OPT_VERBOSE=1
		;;
	-s|--symtab)
		[[ ! -e "$2" ]] && die "Cannot access symbol table file $2"
		OPT_SYMTAB="$2"
		shift
		;;
	-S|--no-symtab)
		OPT_NOSYM=1
		;;
	-r|--raw)
		OPT_RAW=1
		;;
	-f|--flush)
		echo "Flushing trace buffers for $TRACENAME"
		if ! echo - > "$TRACEBASE/flush" ; then
			die "Cannot flush trace buffer"
		fi
		OPT_NOTRACE=1
		;;
	-l|--level)
		shift
		echo "Setting trace level for $TRACENAME to $1"
		if ! echo "$1" > "$TRACEBASE/level" ; then
			die "Cannot set trace level"
		fi
		OPT_NOTRACE=1
		;;
	-p|--pages)
		shift
		echo "Setting trace size for $TRACENAME to $1 pages ($(($1*4))kb)"
		if ! echo "$1" > "$TRACEBASE/pages" ; then
			die "Cannot set trace size"
		fi
		OPT_NOTRACE=1
		;;
	-P|--no-pager)
		PAGER="cat"
		;;
	-e|--errlog)
		TRACEBASE="$DEBUGFS/s390dbf/sclp_err"
		TRACENAME="error log"
		echo "Switching to $TRACENAME"
		;;
	esac
	shift
done
shift

if [[ $OPT_NOTRACE -eq 1 ]] ; then
	echo "Done"
	exit 0
fi

if [[ $# -gt 0 ]] ; then
	TRACEFILE="$(readlink -f "$1")"
	[[ ! -e "$TRACEFILE" ]] && die "Cannot access $TRACEFILE: File not found"
else
	TRACEFILE="$TRACEBASE/hex_ascii"
	[[ ! -d "$DEBUGFS" ]] && die "Cannot access $DEBUGFS: Directory not found"
	[[ ! -x "$DEBUGFS" ]] && die "Cannot read $DEBUGFS: Permission denied"
	[[ ! -d "$TRACEBASE" ]] && die "Kernel is missing required support"
	read TRACELEVEL <"$TRACEBASE/level"
	read TRACEPAGES <"$TRACEBASE/pages"
fi

check_tracefile "$TRACEFILE"

if [[ $OPT_RAW -eq 1 ]] ; then
	sort_file "$TRACEFILE" | $PAGER
	exit 0
fi

if [[ $OPT_NOSYM -eq 0 ]] ; then
	read_symtab "$OPT_SYMTAB"
fi

{
	echo "Trace data:"
	echo "  Source .......: $TRACEFILE"
	[[ -n "$TRACELEVEL" ]] && echo "  Level ........: $TRACELEVEL"
	[[ -n "$TRACEPAGES" ]] &&
		echo "  Size .........: $TRACEPAGES pages ($((4*TRACEPAGES))kb)"
	process  < <(sort_file "$TRACEFILE")
} | $PAGER

exit 0
