blob: 0b5f77b65240532728d16d59be93d78137d1edbc [file] [log] [blame]
#! /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"
# Added additional check to make sure that user provided in
# systemd-coredump format.
function set_pid()
{
#from coredump.c file.
CORE_BASE_STRING="/var/lib/systemd/coredump/core"
#Escape bash characters in file name
file=$(printf %q "$optional_file")
string=$(echo $file | awk -F . '{ print $1}')
if [ "$CORE_BASE_STRING" = "$string" ]; then
#matching systemd-coredump core file format.
core_pid=$(echo $file | awk -F . '{ print $5}')
fi
}
# @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 $?