|  | #! /bin/bash | 
|  |  | 
|  | help=$" | 
|  | dreport creates an archive(xz compressed) consisting of the following: | 
|  | * Configuration information | 
|  | * Debug information | 
|  | * A summary report | 
|  | The type parameter controls the content of the data. The generated | 
|  | archive is stored in the user specified location. | 
|  |  | 
|  | usage: dreport [OPTION] | 
|  |  | 
|  | Options: | 
|  | -n, —-name <name>     Name to be used for the archive. | 
|  | Default name format obmcdump_<id>_<epochtime> | 
|  | -d, —-dir <directory> Archive directory to copy the compressed report. | 
|  | Default output directory is /tmp | 
|  | -i, —-id <id>         Dump identifier to associate with the archive. | 
|  | Identifiers include numeric characters. | 
|  | Default dump identifier is 0 | 
|  | -t, —-type <type>     Data collection type. Valid types are | 
|  | "user", "core". | 
|  | Default type is "user" initiated. | 
|  | -f, —-file <file>     Optional file to be included in the archive. | 
|  | Absolute path of the file must be passed as | 
|  | parameter. This is useful to include application | 
|  | core in the dump. | 
|  | -s, --size <size>     Maximum allowed size(in KB) of the archive. | 
|  | Report will be truncated in case size exceeds | 
|  | this limit. Default size is 500KB. | 
|  | -v, —-verbose         Increase logging verbosity. | 
|  | -V, --version         Output version information. | 
|  | -q, —-quiet           Only log fatal errors to stderr | 
|  | -h, —-help            Display this help and exit. | 
|  | " | 
|  |  | 
|  | #CONSTANTS | 
|  | declare -r TRUE=1 | 
|  | declare -r FALSE=0 | 
|  | declare -r UNLIMITED="unlimited" | 
|  | declare -r SUMMARY_DUMP="summary" | 
|  | declare -r TYPE_USER="user" | 
|  | declare -r TYPE_CORE="core" | 
|  | declare -r SUMMARY_LOG="summary.log" | 
|  | declare -r DREPORT_LOG="dreport.log" | 
|  | declare -r TMP_DIR="/tmp" | 
|  | declare -r EPOCHTIME=$(date +"%s") | 
|  | declare -r TIME_STAMP="date -u" | 
|  |  | 
|  | #Error Codes | 
|  | declare -r SUCCESS="0" | 
|  | declare -r INTERNAL_FAILURE="1" | 
|  | declare -r RESOURCE_UNAVAILABLE="2" | 
|  |  | 
|  | #VARIABLES | 
|  | declare -x name="" | 
|  | declare -x dump_dir="/tmp" | 
|  | declare -x dump_id="00000000" | 
|  | declare -x dump_type=$TYPE_USER | 
|  | declare -x verbose=$FALSE | 
|  | declare -x quiet=$FALSE | 
|  | declare -x dump_size="unlimited" | 
|  | declare -x name_dir="" | 
|  | declare -x optional_file="" | 
|  | declare -x dreport_log="" | 
|  | declare -x summary_log="" | 
|  | declare -x cur_dump_size=0 | 
|  | declare -a command_list=("") | 
|  | declare -x core_pid="0" | 
|  |  | 
|  | # @brief Initialize user type command array | 
|  | function runlevel_user() | 
|  | { | 
|  | command_list=( | 
|  | get_fw_level | 
|  | get_uname | 
|  | get_uptime | 
|  | get_disk_usage | 
|  | bmc_state | 
|  | host_state | 
|  | chassis_state | 
|  | get_host_info | 
|  | get_obmc_console | 
|  | get_cpuinfo | 
|  | get_meminfo | 
|  | get_top | 
|  | get_esel | 
|  | get_journal | 
|  | get_failed_services | 
|  | ) | 
|  | } | 
|  |  | 
|  | # @brief Initialize core type command array | 
|  | function runlevel_core() | 
|  | { | 
|  | command_list=( | 
|  | move_optional_file | 
|  | get_proc_journal | 
|  | get_fw_level | 
|  | get_uname | 
|  | get_uptime | 
|  | get_disk_usage | 
|  | bmc_state | 
|  | host_state | 
|  | chassis_state | 
|  | get_host_info | 
|  | get_failed_services | 
|  | get_obmc_console | 
|  | get_cpuinfo | 
|  | get_meminfo | 
|  | get_top | 
|  | ) | 
|  | } | 
|  |  | 
|  | function get_fw_level() | 
|  | { | 
|  | desc="Firmware Release" | 
|  | command="cat /etc/os-release" | 
|  | copy_loc="firmware_release.log" | 
|  | run_command "$command" "$copy_loc" "$desc" | 
|  | } | 
|  |  | 
|  | function get_uname() | 
|  | { | 
|  | desc="uname" | 
|  | command="uname -a" | 
|  | copy_loc="uname.log" | 
|  | run_command "$command" "$copy_loc" "$desc" | 
|  | } | 
|  |  | 
|  | function get_uptime() | 
|  | { | 
|  | desc="uptime" | 
|  | command="uptime" | 
|  | copy_loc="uptime.log" | 
|  | run_command "$command" "$copy_loc" "$desc" | 
|  | } | 
|  |  | 
|  | function get_disk_usage() | 
|  | { | 
|  | desc="Disk Usage" | 
|  | command="df -hT" | 
|  | copy_loc="disk_usage.log" | 
|  | run_command "$command" "$copy_loc" "$desc" | 
|  | } | 
|  |  | 
|  | function get_journal() | 
|  | { | 
|  | desc="Journal log" | 
|  | command="journalctl -o json-pretty -r" | 
|  | copy_loc="journal.log" | 
|  | run_command "$command" "$copy_loc" "$desc" | 
|  | } | 
|  |  | 
|  | function get_proc_journal() | 
|  | { | 
|  | desc="Process Journal log" | 
|  | command="journalctl -o verbose _PID=$core_pid" | 
|  | copy_loc="proc_journal.log" | 
|  | run_command "$command" "$copy_loc" "$desc" | 
|  | } | 
|  |  | 
|  | function get_host_info() | 
|  | { | 
|  | desc="Host information" | 
|  | command="hostnamectl status" | 
|  | copy_loc="hostnamectl.log" | 
|  | run_command "$command" "$copy_loc" "$desc" | 
|  | } | 
|  |  | 
|  | function get_failed_services() | 
|  | { | 
|  | desc="Failed Services" | 
|  | command="systemctl --failed" | 
|  | copy_loc="failed_services.log" | 
|  | run_command "$command" "$copy_loc" "$desc" | 
|  | } | 
|  |  | 
|  | function get_obmc_console() | 
|  | { | 
|  | desc="OBMC Console" | 
|  | command="cat /var/log/obmc-console.log" | 
|  | copy_loc="obmc_console.log" | 
|  | run_command "$command" "$copy_loc" "$desc" | 
|  | } | 
|  |  | 
|  | function get_cpuinfo() | 
|  | { | 
|  | desc="cpuinfo" | 
|  | command="cat /proc/cpuinfo" | 
|  | copy_loc="cpuinfo.log" | 
|  | run_command "$command" "$copy_loc" "$desc" | 
|  | } | 
|  |  | 
|  | function get_meminfo() | 
|  | { | 
|  | desc="meminfo" | 
|  | command="cat /proc/meminfo" | 
|  | copy_loc="meminfo.log" | 
|  | run_command "$command" "$copy_loc" "$desc" | 
|  | } | 
|  |  | 
|  | function get_top() | 
|  | { | 
|  | desc="top" | 
|  | command="top -n 1 -b" | 
|  | copy_loc="top.log" | 
|  | run_command "$command" "$copy_loc" "$desc" | 
|  | } | 
|  |  | 
|  | function bmc_state() | 
|  | { | 
|  | desc="BMC State" | 
|  | command="busctl get-property \ | 
|  | xyz.openbmc_project.State.BMC \ | 
|  | /xyz/openbmc_project/state/bmc0 \ | 
|  | xyz.openbmc_project.State.BMC \ | 
|  | CurrentBMCState" | 
|  | copy_loc="BMCState.log" | 
|  | run_command "$command" "$copy_loc" "$desc" | 
|  | } | 
|  |  | 
|  | function host_state() | 
|  | { | 
|  | desc="Host State" | 
|  | command="busctl get-property \ | 
|  | xyz.openbmc_project.State.Host \ | 
|  | /xyz/openbmc_project/state/host0 \ | 
|  | xyz.openbmc_project.State.Host \ | 
|  | CurrentHostState" | 
|  | copy_loc="HostState.log" | 
|  | run_command "$command" "$copy_loc" "$desc" | 
|  | } | 
|  |  | 
|  | function chassis_state() | 
|  | { | 
|  | desc="Chassis State" | 
|  | command="busctl get-property \ | 
|  | xyz.openbmc_project.State.Chassis \ | 
|  | /xyz/openbmc_project/state/chassis0 \ | 
|  | xyz.openbmc_project.State.Chassis \ | 
|  | CurrentPowerState" | 
|  | copy_loc="HostState.log" | 
|  | run_command "$command" "$copy_loc" "$desc" | 
|  | } | 
|  |  | 
|  | function get_esel() | 
|  | { | 
|  | desc="eSEL" | 
|  |  | 
|  | entries=$(busctl --list --no-pager tree \ | 
|  | xyz.openbmc_project.Logging | grep \ | 
|  | '/xyz/openbmc_project/logging/entry/') | 
|  |  | 
|  | #check for eSEL entries. | 
|  | if [ -z "$entries" ]; then | 
|  | log_info "No $desc entries" | 
|  | return 0 | 
|  | fi | 
|  |  | 
|  | command="busctl --list --no-pager tree \ | 
|  | xyz.openbmc_project.Logging | grep \ | 
|  | '/xyz/openbmc_project/logging/entry/' \ | 
|  | | xargs -I {} busctl --verbose --no-pager \ | 
|  | call xyz.openbmc_project.Logging {} \ | 
|  | org.freedesktop.DBus.Properties GetAll s \ | 
|  | xyz.openbmc_project.Logging.Entry" | 
|  | copy_loc="eSEL.log" | 
|  | run_command "$command" "$copy_loc" "$desc" | 
|  | } | 
|  |  | 
|  | function move_optional_file() | 
|  | { | 
|  | desc="Move Optional file" | 
|  |  | 
|  | mv $optional_file $name_dir | 
|  | if [ $? -ne 0 ]; then | 
|  | log_error "Failed to move file $optional_file" | 
|  | return 1 | 
|  | fi | 
|  | if check_size "$name_dir/$(basename "$optional_file")"; then | 
|  | log_info "Moving file $file_name" | 
|  | else | 
|  | log_warning "Skipping $file_name move" | 
|  | fi | 
|  | } | 
|  |  | 
|  | # @brief Run the requested command and save the output | 
|  | #        into temporary location for successful size check | 
|  |  | 
|  | function run_command() | 
|  | { | 
|  | command="$1" | 
|  | copy_loc="$2" | 
|  | desc="$3" | 
|  |  | 
|  | eval $command >> "$name_dir/$copy_loc" | 
|  | if [ $? -ne 0 ]; then | 
|  | log_error "Failed to collect $desc" | 
|  | return 1 | 
|  | fi | 
|  |  | 
|  | if check_size "$name_dir/$copy_loc"; then | 
|  | log_info "Collected $desc" | 
|  | else | 
|  | log_warning "Skipping $desc" | 
|  | fi | 
|  | } | 
|  |  | 
|  | # @brief Initiate data collection based on the type. | 
|  | # @return 0 on success, error code otherwise | 
|  | function collect_data() | 
|  | { | 
|  | case $dump_type in | 
|  | $TYPE_USER) | 
|  | runlevel_user | 
|  | capture_data "${command_list[@]}" | 
|  | ;; | 
|  | $TYPE_CORE) | 
|  | set_pid | 
|  | runlevel_core | 
|  | capture_data "${command_list[@]}" | 
|  | ;; | 
|  | $SUMMARY_DUMP) | 
|  | #No data collection is required. | 
|  | ;; | 
|  | *) # unknown option | 
|  | log_error "Skipping: Unknown dump type: $dump_type" | 
|  | ;; | 
|  | esac | 
|  | } | 
|  |  | 
|  | # @brief get pid from the file | 
|  | #        dreport "core" type user provides core file as optional file parameter. | 
|  | #        As per coredump source code systemd-coredump uses below format | 
|  | #        https://github.com/systemd/systemd/blob/master/src/coredump/coredump.c | 
|  | #        /var/lib/systemd/coredump/core.%s.%s." SD_ID128_FORMAT_STR “. | 
|  | #        <process ID>.%s000000" | 
|  | function set_pid() | 
|  | { | 
|  | #Escape bash characters in file name | 
|  | file=$(printf %q "$optional_file") | 
|  |  | 
|  | #matching systemd-coredump core file format. | 
|  | core_pid=$(echo $file | awk -F . '{ print $5}') | 
|  | } | 
|  |  | 
|  | # @brief Capture debug data based on the input command array. | 
|  | #        and stores in to global temporary name specific location. | 
|  | # @param $1 Source array | 
|  | # @return 0 on success, error code otherwise | 
|  | function capture_data() | 
|  | { | 
|  | source=("$@") | 
|  |  | 
|  | for ((i=0;i<${#source[@]};i+=1)); do | 
|  | ${source[i]} | 
|  | done | 
|  |  | 
|  | return 0 | 
|  | } | 
|  |  | 
|  | # @brief Calculate file or directory compressed size based on input | 
|  | #        and check whether the size in the the allowed size limit. | 
|  | #        Remove the file or directory from the name_dir | 
|  | #        if the check fails. | 
|  | # @param $1 Source file or directory | 
|  | # @return 0 on success, error code if size exceeds the limit. | 
|  | # Limitation: compress and tar will have few bytes size difference | 
|  | function check_size() | 
|  | { | 
|  | source=$1 | 
|  |  | 
|  | #No size check required incase dump_size is set to unlimited | 
|  | if [ $dump_size = $UNLIMITED ]; then | 
|  | return 0 | 
|  | fi | 
|  |  | 
|  | #get the file or directory size | 
|  | if [[ -d $source ]] && [[ -n $source ]]; then | 
|  | tar -cf "$source.tar" -C \ | 
|  | $(dirname "$source") $(basename "$source") | 
|  | size=$(stat -c%s "$source.tar") | 
|  | rm "$source.tar" | 
|  | else | 
|  | size=$(stat -c%s "$source") | 
|  | fi | 
|  |  | 
|  | if [ $((size + cur_dump_size)) -gt $dump_size ]; then | 
|  | #Exceed the allowed limit, | 
|  | #tar and compress the files and check the size | 
|  | tar -Jcf "$name_dir.tar.xz" -C \ | 
|  | $(dirname "$name_dir") $(basename "$name_dir") | 
|  | size=$(stat -c%s "$name_dir.tar.xz") | 
|  | if [ $size -gt $dump_size ]; then | 
|  | #Remove the the specific data from the name_dir and contniue | 
|  | rm "$source" "$name_dir.tar.xz" | 
|  | return $RESOURCE_UNAVAILABLE | 
|  | else | 
|  | rm "$name_dir.tar.xz" | 
|  | fi | 
|  | fi | 
|  |  | 
|  | #Remove the compressed file from the name directory | 
|  | cur_dump_size=$((size + cur_dump_size)) | 
|  | return $SUCCESS | 
|  | } | 
|  |  | 
|  | # @brief Initial version of the summary log | 
|  | init_summary() | 
|  | { | 
|  | log_summary "Name:          $name.tar.xz" | 
|  | log_summary "Epochtime:     $EPOCHTIME" | 
|  | log_summary "ID:            $dump_id" | 
|  | log_summary "Type:          $dump_type" | 
|  | log_summary "Optional file: $optional_file" | 
|  | } | 
|  |  | 
|  | # @brief Check the validity of user inputs and initialize global | 
|  | #        variables. Create directory for temporary data collection | 
|  | # @return 0 on success, error code otherwise | 
|  |  | 
|  | function initialize() | 
|  | { | 
|  | #Dump file name | 
|  | if [ -z $name ]; then | 
|  | name=$"obmcdump_"$dump_id"_$EPOCHTIME" | 
|  | fi | 
|  |  | 
|  | #Create temporary data directory. | 
|  | mkdir -p "$TMP_DIR/$name" | 
|  | if [ $? -ne 0 ]; then | 
|  | echo "Error: Failed to create the temporary directory." | 
|  | return $RESOURCE_UNAVAILABLE; | 
|  | fi | 
|  |  | 
|  | #name directory | 
|  | name_dir="$TMP_DIR/$name" | 
|  |  | 
|  | #dreport log file | 
|  | dreport_log="$name_dir/$DREPORT_LOG" | 
|  |  | 
|  | #summary log file | 
|  | summary_log="$name_dir/$SUMMARY_LOG" | 
|  |  | 
|  | #Type | 
|  | if [[ !($dump_type = $TYPE_USER || $dump_type = $TYPE_CORE) ]]; then | 
|  | log_error "Invalid -type, Only summary log is available" | 
|  | dump_type=$SUMMARY_DUMP | 
|  | fi | 
|  |  | 
|  | #Size | 
|  | #Check the input is integer. | 
|  | if [ "$dump_size" -eq "$dump_size" ] 2>/dev/null; then | 
|  | #Converts in to bytes. | 
|  | dump_size="$((dump_size * 1024))" | 
|  | else | 
|  | dump_size=$UNLIMITED | 
|  | fi | 
|  |  | 
|  | return $SUCCESS | 
|  | } | 
|  |  | 
|  | # @brief Packaging the dump and transferring to dump location. | 
|  | function package() | 
|  | { | 
|  | mkdir -p "$dump_dir" | 
|  | if [ $? -ne 0 ]; then | 
|  | log_error "Could not create the destination directory $dump_dir" | 
|  | dest_dir=$TMP_DIR | 
|  | fi | 
|  |  | 
|  | #tar and compress the files. | 
|  | tar -Jcf "$name_dir.tar.xz" -C \ | 
|  | $(dirname "$name_dir") $(basename "$name_dir") | 
|  |  | 
|  | if [ $? -ne 0 ]; then | 
|  | echo $($TIME_STAMP) "Could not create the compressed tar file" | 
|  | rm -r "$name_dir" | 
|  | return $INTERNAL_FAILURE | 
|  | fi | 
|  |  | 
|  | #remove the temporary name specific directory | 
|  | rm -r "$name_dir" | 
|  |  | 
|  | echo $($TIME_STAMP) "Report is available in $dump_dir" | 
|  |  | 
|  | if [ "$TMP_DIR" == "$dump_dir" ]; then | 
|  | return $SUCCESS | 
|  | fi | 
|  |  | 
|  | #copy the compressed tar file into the destination | 
|  | cp "$name_dir.tar.xz" "$dump_dir" | 
|  | if [ $? -ne 0 ]; then | 
|  | echo "Failed to copy the $name_dir.tar.xz to $dump_dir" | 
|  | rm "$name_dir.tar.xz" | 
|  | return $INTERNAL_FAILURE | 
|  | fi | 
|  |  | 
|  | #Remove the temporary copy of the file | 
|  | rm "$name_dir.tar.xz" | 
|  | } | 
|  |  | 
|  | # @brief log the error message | 
|  | # @param error message | 
|  | function log_error() | 
|  | { | 
|  | echo $($TIME_STAMP) "ERROR: $@" >> $dreport_log | 
|  | if ((quiet != TRUE)); then | 
|  | echo $($TIME_STAMP) "ERROR: $@" >&2 | 
|  | fi | 
|  | } | 
|  |  | 
|  | # @brief log warning message | 
|  | # @param warning message | 
|  | function log_warning() | 
|  | { | 
|  | if ((verbose == TRUE)); then | 
|  | echo $($TIME_STAMP) "WARNING: $@" >> $dreport_log | 
|  | if ((quiet != TRUE)); then | 
|  | echo $($TIME_STAMP) "WARNING: $@" >&2 | 
|  | fi | 
|  | fi | 
|  | } | 
|  |  | 
|  | # @brief log info message | 
|  | # @param info message | 
|  | function log_info() | 
|  | { | 
|  | if ((verbose == TRUE)); then | 
|  | echo $($TIME_STAMP) "INFO: $@" >> $dreport_log | 
|  | if ((quiet != TRUE)); then | 
|  | echo $($TIME_STAMP) "INFO: $@" >&1 | 
|  | fi | 
|  | fi | 
|  | } | 
|  |  | 
|  | # @brief log summary message | 
|  | # @param message | 
|  | function log_summary() | 
|  | { | 
|  | echo $($TIME_STAMP) "$@" >> $summary_log | 
|  | if ((quiet != TRUE)); then | 
|  | echo $($TIME_STAMP) "$@" >&1 | 
|  | fi | 
|  | } | 
|  |  | 
|  | # @brief Main function | 
|  | function main() | 
|  | { | 
|  | #initialize the global variables and | 
|  | #create temporary storage locations | 
|  | initialize | 
|  | result=$? | 
|  | if [[ ${result} -ne $SUCCESS ]]; then | 
|  | echo $($TIME_STAMP) "Error: Failed to initialize, Exiting" | 
|  | exit; | 
|  | fi | 
|  |  | 
|  | #Initilize the summary log | 
|  | init_summary | 
|  |  | 
|  | #collect data based on the type. | 
|  | collect_data | 
|  |  | 
|  | package  #package the dump | 
|  | result=$? | 
|  | if [[ ${result} -ne $SUCCESS ]]; then | 
|  | echo $($TIME_STAMP) "Error: Failed to package, Exiting" | 
|  | else | 
|  | echo $($TIME_STAMP) "Sucessfully completed" | 
|  | exit; | 
|  | fi | 
|  | } | 
|  |  | 
|  | TEMP=`getopt -o n:d:i:t:s:f:vVqh \ | 
|  | --long name:,dir:,dumpid:,type:,size:,file:,verbose,version,quiet,help \ | 
|  | -- "$@"` | 
|  |  | 
|  | if [ $? -ne 0 ] | 
|  | then | 
|  | echo "Error: Invalid options" | 
|  | exit 1 | 
|  | fi | 
|  |  | 
|  | eval set -- "$TEMP" | 
|  |  | 
|  | while [[ $# -gt 1 ]]; do | 
|  | key="$1" | 
|  | case $key in | 
|  | -n|--name) | 
|  | name=$2 | 
|  | shift 2;; | 
|  | -d|--dir) | 
|  | dump_dir=$2 | 
|  | shift 2;; | 
|  | -i|--dumpid) | 
|  | dump_id=$2 | 
|  | shift 2;; | 
|  | -t|--type) | 
|  | dump_type=$2 | 
|  | shift 2;; | 
|  | -s|--size) | 
|  | dump_size=$2 | 
|  | shift 2;; | 
|  | -f|--file) | 
|  | optional_file=$2 | 
|  | shift 2;; | 
|  | -v|—-verbose) | 
|  | verbose=$TRUE | 
|  | shift;; | 
|  | -V|--version) | 
|  | shift;; | 
|  | -q|—-quiet) | 
|  | quiet=$TRUE | 
|  | shift;; | 
|  | -h|--help) | 
|  | echo "$help" | 
|  | exit;; | 
|  | *) # unknown option | 
|  | log_error "Unknown argument: $1" | 
|  | log_info "$help" | 
|  | exit 1;; | 
|  | esac | 
|  | done | 
|  |  | 
|  | main #main program | 
|  | exit $? |