#!/bin/bash shellfu import jat shellfu import pretty # # Helpers for dumping files to log # # This module provides several functions to enable showing # contents of (supposedly small) files or command outputs directly # inside JAT log. # # This can be very helpful, especially if you know you are dealing # with relatively small files or to provide generic diagnostic # data. # # For example: # # some_command >out 2>err # jat__eval "grep foo out" # jat__eval "grep bar out" 1 # jat__eval "test -s err" 1 # jat_dump__file out err # # Here, if your asserts fail, it will be very useful to see the content # right in the log. # # Another example (from real life): # # rlPhaseStartTest infodump # ps -C cimserver -o euser,ruser,suser,fuser,f,comm,label \ # | jat_dump__pipe PS_SECINFO # ps -C cimserver -f \ # | jat_dump__pipe PS_F # ps -C cimserver -Lf \ # | jat_dump__pipe PS_THREADS # ps -ef \ # | jat_dump__pipe PS_EF # jat_dump__file /etc/passwd /etc/group # rlPhaseEnd # # Here, in one phase -- without getting in the way of other code # we can get reasonable profile of the cimserver process and # basic system setup. # # # Pipe via hexdump before printing? # # `always` to enforce all files through hexdump, `never` to disable # hexdump, `auto` to have the filetype detected. # JAT_DUMP__HEXMODE=${JAT_DUMP__HEXMODE:-auto} # # Command to use for hex dumping # JAT_DUMP__HEXCMD=${JAT_DUMP__HEXCMD:-hexdump -C} # # Limit in lines before jat__submit() is used instead of printing # JAT_DUMP__LIMIT_L=${JAT_DUMP__LIMIT_L:-100} # # Limit in bytes before jat__submit() is used instead of printing # JAT_DUMP__LIMIT_B=${JAT_DUMP__LIMIT_B:-10000} # # Function to use for logging # JAT_DUMP__DMPFUN=${JAT_DUMP__DMPFUN:-jat__log_info} jat_dump__file() { # # Dump contents of given file(s) using jat__log_info # # Usage: # # jat_dump__file [options] FILE... # # Description: # # Print contents of given file(s) to the main TESTOUT.log # using jat_log* functions and adding BEGIN/END delimiters. # If a file is too big, use jat__submit() instead. If a file # is binary, pipe it through `hexdump -C`. # # Options: # # * -h, --hex # * -H, --no-hex # enforce or ban hexdump for all files # * -b BYTES, --max-bytes BYTES # * -l LINES, --max-lines LINES # set limit to lines or bytes; if file is bigger, # use jat__submit() instead # * -I, --info # * -E, --error # * -D, --debug # use different level of jat_log* (Info by default) # * -s, --skip-empty # do not do anything if file is empty # * -S, --skip-missing # do not do anything if file is missing # local hexmode=$JAT_DUMP__HEXMODE local limit_b=$JAT_DUMP__LIMIT_B local limit_l=$JAT_DUMP__LIMIT_L local dmpfun="$JAT_DUMP__DMPFUN" local skip_empty=false local skip_missing=false while true; do case "$1" in -h|--hex) hexmode=always; shift 1 ;; -H|--no-hex) hexmode=never; shift 1 ;; -b|--max-bytes) limit_b=$2; shift 2 ;; -l|--max-lines) limit_l=$2; shift 2 ;; -I|--info) dmpfun=jat__log_info; shift 1 ;; -E|--error) dmpfun=jat__log_error; shift 1 ;; -D|--debug) dmpfun=debug; shift 1 ;; -s|--skip-empty) skip_empty=true; shift 1 ;; -S|--skip-missing) skip_missing=true; shift 1 ;; *) break ;; esac done local fpath # path to original file local hexdump # path to hexdumped file, if needed for fpath in "$@"; do # skipping logic ! test -e "$fpath" && $skip_missing && continue test -e "$fpath" || { jat__log_error "no such file: $fpath"; continue; } test -f "$fpath" || { jat__log_error "not a file: $fpath"; continue; } ! test -s "$fpath" && $skip_empty && continue if __jat_dump__use_hex "$hexmode" "$fpath"; then hexdump=$(__jat_dump__mkhexdump < "$fpath") __jat_dump__logobj HEXDUMP "$hexdump" "$fpath" "$fpath" rm "$hexdump" else __jat_dump__logobj FILE "$fpath" "$fpath" fi done } jat_dump__mkphase() { # # Make a dump phase incl. JAT formalities # # Usage: # jat_dump__mkphase [-n NAME] FILE... # # Create a separate diag phase called NAME ('dump' by # default) and dump all listed files there. # local name="dump" # phase name, should you not like dump while true; do case "$1" in -n|--name) name=$2; shift 2 ;; *) break ;; esac done jat__pstartd "$name" jat_dump__file "$@" jat__pend } jat_dump__pipe() { # # Dump contents passed to stdin using jat__log_info # # Usage: # # some_command | jat_dump__pipe [options] [NAME] # # Description: # # Cache contents of stream given on STDIN and print them to # the main TESTOUT.log using jat__log* functions and adding # BEGIN/END delimiters. If the content is too big, use # jat__submit() instead. If the content is binary, pipe it # through `hexdump -C`. # # NAME will appear along with pipe content delimiters, and # if a limit is reached, will be used as name for file to # store using jat__submit(). NAME will be generated if # omitted. # # Options: # # * -h, --hex # * -H, --no-hex # enforce or ban hexdump for all files # * -b BYTES, --max-bytes BYTES # * -l LINES, --max-lines LINES # set limit to lines or bytes; if file is bigger, # use jat__submit() instead # * -I, --info # * -E, --error # * -D, --debug # use different level of jat__log* (Info by default) # local hexmode=$JAT_DUMP__HEXMODE local limit_b=$JAT_DUMP__LIMIT_B local limit_l=$JAT_DUMP__LIMIT_L local dmpfun="$JAT_DUMP__DMPFUN" local cache while true; do case "$1" in -h|--hex) hexmode=always; shift 1 ;; -H|--no-hex) hexmode=never; shift 1 ;; -b|--max-bytes) limit_b=$2; shift 2 ;; -l|--max-lines) limit_l=$2; shift 2 ;; -I|--info) dmpfun=jat__log_info; shift 1 ;; -E|--error) dmpfun=jat__log_error; shift 1 ;; -D|--debug) dmpfun=debug; shift 1 ;; *) break ;; esac done local name="$1" # cache the stream cache=$(mktemp -t jat_dump-cache.XXXXXXXXXX) cat > "$cache" # make up name if not given (re-use XXXXXXXXXX from $cache) test -n "$name" || name="ANONYMOUS.$$.${cache##*.}" if __jat_dump__use_hex "$hexmode" "$cache"; then hexdump=$(__jat_dump__mkhexdump < "$cache") __jat_dump__logobj HEXDUMP "$hexdump" "$name" "$cache" rm "$hexdump" else __jat_dump__logobj PIPE "$cache" "$name" fi rm "$cache" } ## ## starts behind this strip ## ## museum of INTERNAL ## ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ## ## '''''''' ## no admission; you can steal but not touch anything ## __jat_dump__count() { # # Count sizes, compare to limits and print problem report if any # #inherits: limit_l limit_b local file="$1" local sizes size_b size_l local over_b over_l many sizes="$(wc -lc <"$file")" # [newline] [bytes] sizes="$(eval "echo $sizes")" # squash + trim whites size_b="${sizes##* }" size_l="${sizes%% *}" test "$size_b" -gt "$limit_b"; over_b=$? test "$size_l" -gt "$limit_l"; over_l=$? case "$over_b:$over_l" in 1:1) many="" ;; 1:0) many="lines ($size_l)" ;; 0:1) many="bytes ($size_b)" ;; 0:0) many="lines ($size_l) and bytes ($size_b)" ;; *) jat__log_error "panic: ${FUNCNAME[1]}" ;; esac echo "$many" test -z "$many" } __jat_dump__is_binary() { # # Check if file is binary # local path="$1" test -s "$path" || return 1 grep -qI . "$path" && return 1 return 0 } __jat_dump__logobj() { # # Dump the object type $1 from $2, or save it under name $3 # # Type can be FILE, HEXDUMP or PIPE. If object is too big to dump # to log, it will be submitted using jat__submit(). in that case # $3 may be specified as alternative name. # # In case of HEXDUMP, path to original file must be specified # as $4, since in case file is too big, we want to submitt *both* # the original and the hexdump (suffixed with .hex). # # FIXME: no way of dumping HEXDUMP without supplying alternate name # # In case of PIPE that needs to be submitted, suffiix ".pipe" is added # to the filename. # # FIXME: Account for PIPE that gets HEXDUMPed (now be treated as file) # local otype=$1 # PIPE|HEXDUMP|FILE local opath=$2 # printable data local oname=${3:-$opath} # chosen name local oorig=$4 # original file in case of HEXDUMP local bloat="" # human description of limit excess; empty # .. means OK to dump to log; else use jat__submit() local llines=() # logged lines oname="$(tr / - <<<"$oname")" bloat="$(__jat_dump__count "$opath")" debug "${FUNCNAME[1]}:otype='$otype'" debug "${FUNCNAME[1]}:opath='$opath'" debug "${FUNCNAME[1]}:oname='$oname'" debug "${FUNCNAME[1]}:oorig='$oorig'" debug "${FUNCNAME[1]}:bloat='$bloat'" # start outputting if test -z "$bloat"; then test -s "$opath" || { case $dmpfun in debug) debug "=====EMPTY $otype $oname=====" ;; *) jat__log_info "=====EMPTY $otype $oname=====" ;; esac return } { echo "=====BEGIN $otype $oname=====" cat "$opath" test -n "$(tail -c 1 "$opath")" \ && echo "=====NO_NEWLINE_AT_EOF=====" echo "=====END $otype $oname=====" } \ | { readarray -t llines $dmpfun "${llines[@]}" } else case $otype in FILE) jat__log_info "not dumping, file has too many $bloat: $opath" jat__submit "$opath" "$oname" ;; PIPE) jat__log_info "not dumping, pipe has too many $bloat: $oname" jat__submit "$opath" "$oname.pipe" ;; HEXDUMP) jat__log_info "not dumping, hexdump is too long for: $oorig" jat__submit "$opath" "$oname.hex" jat__submit "$oorig" "$oname" ;; esac fi } __jat_dump__mkhexdump() { # # Create hexdump (as tempfile) from stdin and echo path to it # local hdtmp hdtmp=$(mktemp -t jat_dump-hex.XXXXXXXXXX) $JAT_DUMP__HEXCMD > "$hdtmp" echo "$hdtmp" } __jat_dump__use_hex() { # # True if we need to use hexdump # local hexmode="$1" local fpath="$2" case "$hexmode" in always) return 0 ;; never) return 1 ;; auto) __jat_dump__is_binary "$fpath"; return $? ;; *) jat__log_error "bad value of JAT_DUMP__HEXMODE: $hexmode" ;; esac } #shellfu module-version=__MKIT_PROJ_VERSION__