f8d7090b59
When user does not specify dump target explicitly, it's better to dump to the "path" specified. That means after dump user enter into 1st kernel, can find vmcore in the "path". If that path is in root fs, vmcore is stored in root fs. If separate disk is mounted on any tier of "path", we just dump vmcore into the left path on the left separate disk. E.g in kdump.conf path /mnt/nfs in mount info, /dev/vdb on /mnt type ext4 (rw,relatime,seclabel,data=ordered) Then vmcore will be saved in /nfs of /dev/vdb. In this patch, pass mount info to dracut in this case if separate disk is mounted on any tier of "path". Meanwhile introduce a function in kdump-lib.sh to check if any target is specified. v4->v5: Per Vivek's comment, add a helper function is_fs_dump_target. Then is_user_configured_dump_target is rewrite with these helper functions. v5->v7: No v6 for this patch. Just use newly introduced function is_fs_type_nfs in handle_default_dump_target. Signed-off-by: Baoquan He <bhe@redhat.com> Acked-by: Vivek Goyal <vgoyal@redhat.com>
565 lines
14 KiB
Bash
565 lines
14 KiB
Bash
#!/bin/bash --norc
|
|
# New mkdumprd
|
|
#
|
|
# Copyright 2011 Red Hat, Inc.
|
|
#
|
|
# Written by Cong Wang <amwang@redhat.com>
|
|
#
|
|
|
|
. /lib/kdump/kdump-lib.sh
|
|
export IN_KDUMP=1
|
|
|
|
conf_file="/etc/kdump.conf"
|
|
SSH_KEY_LOCATION="/root/.ssh/kdump_id_rsa"
|
|
SAVE_PATH=$(grep ^path $conf_file| cut -d' ' -f2)
|
|
[ -z "$SAVE_PATH" ] && SAVE_PATH=$DEFAULT_PATH
|
|
extra_modules=""
|
|
dracut_args=("--hostonly" "-o" "plymouth dash resume")
|
|
OVERRIDE_RESETTABLE=0
|
|
|
|
perror_exit() {
|
|
echo $@ >&2
|
|
exit 1
|
|
}
|
|
|
|
perror() {
|
|
echo $@ >&2
|
|
}
|
|
|
|
get_persistent_dev() {
|
|
local i _tmp _dev
|
|
|
|
_dev=$(udevadm info --query=name --name="$1" 2>/dev/null)
|
|
[ -z "$_dev" ] && {
|
|
perror_exit "Kernel dev name of $1 is not found."
|
|
}
|
|
|
|
for i in /dev/mapper/* /dev/disk/by-uuid/* /dev/disk/by-id/*; do
|
|
_tmp=$(udevadm info --query=name --name="$i" 2>/dev/null)
|
|
if [ "$_tmp" = "$_dev" ]; then
|
|
echo $i
|
|
return
|
|
fi
|
|
done
|
|
|
|
perror "WARNING: Persistent device name of $1 not found. Using $1 as dump target name"
|
|
echo $1
|
|
}
|
|
|
|
add_dracut_arg() {
|
|
local arg qarg is_quoted=0
|
|
while [ $# -gt 0 ];
|
|
do
|
|
arg="${1//\'/\"}"
|
|
#Handle quoted substring properly for passing it to dracut_args array.
|
|
if [ $is_quoted -eq 0 ]; then
|
|
if [[ "$arg" == "\"" ]] || [[ $arg != ${arg#\"} ]]; then
|
|
is_quoted=1
|
|
arg=${arg#\"}
|
|
fi
|
|
fi
|
|
if [ $is_quoted -eq 1 ]; then
|
|
qarg="$qarg $arg"
|
|
if [[ "$arg" == "\"" ]] || [[ $arg != ${arg%\"} ]]; then
|
|
is_quoted=0
|
|
arg=${qarg%\"}
|
|
qarg=""
|
|
else
|
|
shift
|
|
continue
|
|
fi
|
|
fi
|
|
dracut_args+=("$arg")
|
|
shift
|
|
done
|
|
}
|
|
|
|
add_dracut_module() {
|
|
add_dracut_arg "--add" "$1"
|
|
}
|
|
|
|
add_dracut_mount() {
|
|
add_dracut_arg "--mount" "$1"
|
|
}
|
|
|
|
add_dracut_sshkey() {
|
|
add_dracut_arg "--sshkey" "$1"
|
|
}
|
|
|
|
# Generic substring function. If $2 is in $1, return 0.
|
|
strstr() { [[ $1 =~ $2 ]]; }
|
|
|
|
target_is_root() {
|
|
local _t
|
|
_t=$(findmnt -k -n -r -o TARGET $1|sort|head -1)
|
|
[ "$_t" = "/" ]
|
|
}
|
|
|
|
# caller should ensure $1 is valid and mounted in 1st kernel
|
|
to_mount() {
|
|
local _dev=$1 _s _t _o _mntopts _pdev
|
|
|
|
_s=$(findmnt -k -f -n -r -o SOURCE $_dev)
|
|
_t=$(findmnt -k -f -n -r -o TARGET,FSTYPE $_dev)
|
|
_o=$(findmnt -k -f -n -r -o OPTIONS $_dev)
|
|
_o=${_o/#ro/rw} #mount fs target as rw in 2nd kernel
|
|
_o="${_o},nofail" #with nofail set, systemd won't block for mount failure
|
|
_mntopts="$_t $_o"
|
|
#for non-nfs _dev converting to use udev persistent name
|
|
if [ -b "$_s" ]; then
|
|
_pdev="$(get_persistent_dev $_s)"
|
|
if [ $? -ne 0 ]; then
|
|
return 1
|
|
fi
|
|
|
|
else
|
|
_pdev=$_dev
|
|
fi
|
|
|
|
echo "$_pdev $_mntopts"
|
|
}
|
|
|
|
to_mount_point() {
|
|
echo $(findmnt -k -f -n -r -o TARGET $1)
|
|
}
|
|
|
|
is_readonly_mount() {
|
|
local _mnt
|
|
_mnt=$(findmnt -k -f -n -r -o OPTIONS $1)
|
|
|
|
#fs/proc_namespace.c: show_mountinfo():
|
|
#seq_puts(m, mnt->mnt_flags & MNT_READONLY ? " ro" : " rw");
|
|
[[ "$_mnt" =~ ^ro ]]
|
|
}
|
|
|
|
#Function: get_ssh_size
|
|
#$1=dump target
|
|
#called from while loop and shouldn't read from stdin, so we're using "ssh -n"
|
|
get_ssh_size() {
|
|
local _opt _out _size
|
|
_opt="-i $SSH_KEY_LOCATION -o BatchMode=yes -o StrictHostKeyChecking=yes"
|
|
_out=$(ssh -q -n $_opt $1 "df -P $SAVE_PATH")
|
|
[ $? -ne 0 ] && {
|
|
perror_exit "checking remote ssh server available size failed."
|
|
}
|
|
|
|
#ssh output removed the line break, so print field NF-2
|
|
_size=$(echo -n $_out| awk '{avail=NF-2; print $avail}')
|
|
echo -n $_size
|
|
}
|
|
|
|
#mkdir if save path does not exist on ssh dump target
|
|
#$1=ssh dump target
|
|
#caller should ensure write permission on $DUMP_TARGET:$SAVE_PATH
|
|
#called from while loop and shouldn't read from stdin, so we're using "ssh -n"
|
|
mkdir_save_path_ssh()
|
|
{
|
|
local _opt _dir
|
|
_opt="-i $SSH_KEY_LOCATION -o BatchMode=yes -o StrictHostKeyChecking=yes"
|
|
ssh -qn $_opt $1 mkdir -p $SAVE_PATH 2>&1 > /dev/null
|
|
_ret=$?
|
|
if [ $_ret -ne 0 ]; then
|
|
perror_exit "mkdir failed on $DUMP_TARGET:$SAVE_PATH"
|
|
fi
|
|
|
|
#check whether user has write permission on $SAVE_PATH/$DUMP_TARGET
|
|
_dir=$(ssh -qn $_opt $1 mktemp -dqp $SAVE_PATH 2>/dev/null)
|
|
_ret=$?
|
|
if [ $_ret -ne 0 ]; then
|
|
perror_exit "Could not create temporary directory on $DUMP_TARGET:$SAVE_PATH. Make sure user has write permission on destination"
|
|
fi
|
|
ssh -qn $_opt $1 rmdir $_dir
|
|
|
|
return 0
|
|
}
|
|
|
|
#Function: get_fs_size
|
|
#$1=dump target
|
|
get_fs_size() {
|
|
local _mnt=$(to_mount_point $1)
|
|
echo -n $(df -P "${_mnt}/$SAVE_PATH"|tail -1|awk '{print $4}')
|
|
}
|
|
|
|
#Function: get_raw_size
|
|
#$1=dump target
|
|
get_raw_size() {
|
|
echo -n $(fdisk -s "$1")
|
|
}
|
|
|
|
#Function: check_size
|
|
#$1: dump type string ('raw', 'fs', 'ssh')
|
|
#$2: dump target
|
|
check_size() {
|
|
local avail memtotal
|
|
|
|
memtotal=$(awk '/MemTotal/{print $2}' /proc/meminfo)
|
|
case "$1" in
|
|
raw)
|
|
avail=$(get_raw_size "$2")
|
|
;;
|
|
ssh)
|
|
avail=$(get_ssh_size "$2")
|
|
;;
|
|
fs)
|
|
avail=$(get_fs_size "$2")
|
|
;;
|
|
*)
|
|
return
|
|
esac
|
|
|
|
if [ $? -ne 0 ]; then
|
|
perror_exit "Check dump target size failed"
|
|
fi
|
|
|
|
if [ $avail -lt $memtotal ]; then
|
|
echo "Warning: There might not be enough space to save a vmcore."
|
|
echo " The size of $2 should be greater than $memtotal kilo bytes."
|
|
fi
|
|
}
|
|
|
|
# $1: core_collector config value
|
|
verify_core_collector() {
|
|
if grep -q "^raw" $conf_file && [ "${1%% *}" != "makedumpfile" ]; then
|
|
echo "Warning: specifying a non-makedumpfile core collector, you will have to recover the vmcore manually."
|
|
fi
|
|
if is_ssh_dump_target || is_raw_dump_target; then
|
|
if [ "${1%% *}" = "makedumpfile" ]; then
|
|
! strstr "$1" "-F" && {
|
|
perror_exit "The specified dump target needs makedumpfile \"-F\" option."
|
|
}
|
|
fi
|
|
fi
|
|
}
|
|
|
|
add_mount() {
|
|
if ! target_is_root "$1"; then
|
|
local _mnt=$(to_mount "$1")
|
|
if [ $? -ne 0 ]; then
|
|
exit 1
|
|
fi
|
|
add_dracut_mount "$_mnt"
|
|
fi
|
|
}
|
|
|
|
# get_maj_min <device>
|
|
# Prints the major and minor of a device node.
|
|
# Example:
|
|
# $ get_maj_min /dev/sda2
|
|
# 8:2
|
|
get_maj_min() {
|
|
local _dev
|
|
_dev=$(stat -L -c '$((0x%t)):$((0x%T))' "$1" 2>/dev/null)
|
|
_dev=$(eval "echo $_dev")
|
|
echo $_dev
|
|
}
|
|
|
|
# ugly workaround for the lvm design
|
|
# There is no volume group device,
|
|
# so, there are no slave devices for volume groups.
|
|
# Logical volumes only have the slave devices they really live on,
|
|
# but you cannot create the logical volume without the volume group.
|
|
# And the volume group might be bigger than the devices the LV needs.
|
|
check_vol_slaves() {
|
|
local _lv _vg _pv
|
|
for i in /dev/mapper/*; do
|
|
_lv=$(get_maj_min $i)
|
|
if [[ $_lv = $2 ]]; then
|
|
_vg=$(lvm lvs --noheadings -o vg_name $i 2>/dev/null)
|
|
# strip space
|
|
_vg=$(echo $_vg)
|
|
if [[ $_vg ]]; then
|
|
for _pv in $(lvm vgs --noheadings -o pv_name "$_vg" 2>/dev/null)
|
|
do
|
|
check_block_and_slaves $1 $(get_maj_min $_pv) && return 0
|
|
done
|
|
fi
|
|
fi
|
|
done
|
|
return 1
|
|
}
|
|
|
|
# Walk all the slave relationships for a given block device.
|
|
# Stop when our helper function returns success
|
|
# $1 = function to call on every found block device
|
|
# $2 = block device in major:minor format
|
|
check_block_and_slaves() {
|
|
local _x
|
|
[[ -b /dev/block/$2 ]] || return 1 # Not a block device? So sorry.
|
|
"$1" $2 && return
|
|
check_vol_slaves "$@" && return 0
|
|
if [[ -f /sys/dev/block/$2/../dev ]]; then
|
|
check_block_and_slaves $1 $(cat "/sys/dev/block/$2/../dev") && return 0
|
|
fi
|
|
[[ -d /sys/dev/block/$2/slaves ]] || return 1
|
|
for _x in /sys/dev/block/$2/slaves/*/dev; do
|
|
[[ -f $_x ]] || continue
|
|
check_block_and_slaves $1 $(cat "$_x") && return 0
|
|
done
|
|
return 1
|
|
}
|
|
|
|
to_dev_name() {
|
|
local dev="${1//\"/}"
|
|
|
|
case "$dev" in
|
|
UUID=*)
|
|
dev=`blkid -U "${dev#UUID=}"`
|
|
;;
|
|
LABEL=*)
|
|
dev=`blkid -L "${dev#LABEL=}"`
|
|
;;
|
|
esac
|
|
echo $dev
|
|
}
|
|
|
|
get_block_dump_target()
|
|
{
|
|
local _target
|
|
|
|
|
|
_target=$(get_user_configured_dump_disk)
|
|
[ -n "$_target" ] && echo $(to_dev_name $_target) && return
|
|
|
|
#get rootfs device name
|
|
_target=$(get_root_fs_device)
|
|
[ -b "$_target" ] && echo $(to_dev_name $_target)
|
|
}
|
|
|
|
#handle the case user does not specify the dump target explicitly
|
|
handle_default_dump_target()
|
|
{
|
|
local _target
|
|
local _mntpoint
|
|
local _fstype
|
|
|
|
is_user_configured_dump_target && return
|
|
|
|
check_save_path_fs $SAVE_PATH
|
|
|
|
_mntpoint=$(get_mntpoint_from_path $SAVE_PATH)
|
|
_target=$(get_target_from_path $SAVE_PATH)
|
|
if [ "$_mntpoint" != "/" ]; then
|
|
SAVE_PATH=${SAVE_PATH##"$_mntpoint"}
|
|
_fstype=$(get_fs_type_from_target $_target)
|
|
|
|
if $(is_fs_type_nfs $_fstype); then
|
|
add_dracut_module "nfs"
|
|
fi
|
|
|
|
add_mount "$_target"
|
|
check_size fs $_target
|
|
fi
|
|
}
|
|
|
|
get_default_action_target()
|
|
{
|
|
local _target
|
|
local _action=$(grep "^default" /etc/kdump.conf 2>/dev/null | awk '{print $2}')
|
|
if [ -n "$_action" ] && [ "$_action" = "dump_to_rootfs" ]; then
|
|
#get rootfs device name
|
|
_target=$(findmnt -k -f -n -o SOURCE /)
|
|
[ -b "$_target" ] && echo $(to_dev_name $_target)
|
|
fi
|
|
return
|
|
}
|
|
|
|
get_override_resettable()
|
|
{
|
|
local override_resettable
|
|
|
|
override_resettable=$(grep "^override_resettable" $conf_file)
|
|
if [ -n "$override_resettable" ]; then
|
|
OVERRIDE_RESETTABLE=$(echo $override_resettable | cut -d' ' -f2)
|
|
if [ "$OVERRIDE_RESETTABLE" != "0" ] && [ "$OVERRIDE_RESETTABLE" != "1" ];then
|
|
perror_exit "override_resettable value $OVERRIDE_RESETTABLE is invalid"
|
|
fi
|
|
fi
|
|
}
|
|
|
|
|
|
# $1: function name
|
|
for_each_block_target()
|
|
{
|
|
local dev majmin
|
|
|
|
#check dump target
|
|
dev=$(get_block_dump_target)
|
|
|
|
if [ -n "$dev" ]; then
|
|
majmin=$(get_maj_min $dev)
|
|
check_block_and_slaves $1 $majmin && return 1
|
|
fi
|
|
|
|
#check rootfs when default action dump_to_rootfs is set
|
|
dev=$(get_default_action_target)
|
|
if [ -n "$dev" ]; then
|
|
majmin=$(get_maj_min $dev)
|
|
check_block_and_slaves $1 $majmin && return 2
|
|
fi
|
|
|
|
return 0
|
|
}
|
|
|
|
|
|
|
|
#judge if a specific device with $1 is unresettable
|
|
#return false if unresettable.
|
|
is_unresettable()
|
|
{
|
|
local path="/sys/$(udevadm info --query=all --path=/sys/dev/block/$1 | awk '/^P:/ {print $2}' | sed -e 's/\(cciss[0-9]\+\/\).*/\1/g' -e 's/\/block\/.*$//')/resettable"
|
|
local resettable=1
|
|
|
|
if [ -f "$path" ]
|
|
then
|
|
resettable="$(cat $path)"
|
|
[ $resettable -eq 0 -a "$OVERRIDE_RESETTABLE" -eq 0 ] && {
|
|
local device=$(udevadm info --query=all --path=/sys/dev/block/$1 | awk -F= '/DEVNAME/{print $2}')
|
|
echo "Device $device is unresettable"
|
|
return 0
|
|
}
|
|
fi
|
|
|
|
return 1
|
|
}
|
|
|
|
#check if machine is resettable.
|
|
#return true if resettable
|
|
check_resettable()
|
|
{
|
|
local _ret _target
|
|
|
|
get_override_resettable
|
|
|
|
for_each_block_target is_unresettable
|
|
_ret=$?
|
|
|
|
[ $_ret -eq 0 ] && return
|
|
|
|
if [ $_ret -eq 1 ]; then
|
|
_target=$(get_block_dump_target)
|
|
perror "Can not save vmcore to target device $_target . This device can not be initialized in kdump kernel as it is not resettable"
|
|
elif [ $_ret -eq 2 ]; then
|
|
_target=$(get_default_action_target)
|
|
perror "Rootfs device $_target is not resettable, can not be used as the default target, please specify a default action"
|
|
fi
|
|
|
|
return 1
|
|
}
|
|
|
|
# $1: maj:min
|
|
is_crypt()
|
|
{
|
|
local majmin=$1 dev line ID_FS_TYPE=""
|
|
|
|
line=$(udevadm info --query=property --path=/sys/dev/block/$majmin \
|
|
| grep "^ID_FS_TYPE")
|
|
eval "$line"
|
|
[[ "$ID_FS_TYPE" = "crypto_LUKS" ]] && {
|
|
dev=$(udevadm info --query=all --path=/sys/dev/block/$majmin | awk -F= '/DEVNAME/{print $2}')
|
|
echo "Device $dev is encrypted."
|
|
return 0
|
|
}
|
|
return 1
|
|
}
|
|
|
|
check_crypt()
|
|
{
|
|
local _ret _target
|
|
|
|
for_each_block_target is_crypt
|
|
_ret=$?
|
|
|
|
[ $_ret -eq 0 ] && return
|
|
|
|
return 1
|
|
}
|
|
|
|
if ! check_resettable; then
|
|
exit 1
|
|
fi
|
|
|
|
if ! check_crypt; then
|
|
echo "Warning: Encrypted device is in dump path. User will prompted for password during second kernel boot."
|
|
fi
|
|
|
|
# firstly get right SSH_KEY_LOCATION
|
|
keyfile=$(awk '/^sshkey/ {print $2}' $conf_file)
|
|
if [ -f "$keyfile" ]; then
|
|
# canonicalize the path
|
|
SSH_KEY_LOCATION=$(/usr/bin/readlink -m $keyfile)
|
|
fi
|
|
|
|
if [ "$(uname -m)" = "s390x" ]; then
|
|
add_dracut_module "znet"
|
|
fi
|
|
|
|
while read config_opt config_val;
|
|
do
|
|
# remove inline comments after the end of a directive.
|
|
config_val=$(strip_comments $config_val)
|
|
case "$config_opt" in
|
|
extra_modules)
|
|
extra_modules="$extra_modules $config_val"
|
|
;;
|
|
ext[234]|xfs|btrfs|minix|nfs)
|
|
if ! findmnt $config_val >/dev/null; then
|
|
perror_exit "Dump target $config_val is probably not mounted."
|
|
fi
|
|
|
|
if [ "$config_opt" = "nfs" ]; then
|
|
add_dracut_module "nfs"
|
|
fi
|
|
add_mount "$config_val"
|
|
check_save_path_fs $(make_absolute_save_path $config_val)
|
|
check_size fs $config_val
|
|
;;
|
|
raw)
|
|
#checking raw disk writable
|
|
dd if=$config_val count=1 of=/dev/null > /dev/null 2>&1 || {
|
|
perror_exit "Bad raw disk $config_val"
|
|
}
|
|
_praw=$(get_persistent_dev $config_val)
|
|
if [ $? -ne 0 ]; then
|
|
exit 1
|
|
fi
|
|
add_dracut_arg "--device" "$_praw"
|
|
check_size raw $config_val
|
|
;;
|
|
ssh)
|
|
if strstr "$config_val" "@";
|
|
then
|
|
check_size ssh $config_val
|
|
mkdir_save_path_ssh $config_val
|
|
add_dracut_module "ssh-client"
|
|
add_dracut_sshkey "$SSH_KEY_LOCATION"
|
|
else
|
|
perror_exit "Bad ssh dump target $config_val"
|
|
fi
|
|
;;
|
|
core_collector)
|
|
verify_core_collector "$config_val"
|
|
;;
|
|
dracut_args)
|
|
add_dracut_arg $config_val
|
|
;;
|
|
*)
|
|
if [ -n $(echo $config_opt | grep "^#.*$") ]
|
|
then
|
|
continue
|
|
fi
|
|
;;
|
|
esac
|
|
done < $conf_file
|
|
|
|
handle_default_dump_target
|
|
|
|
if [ -n "$extra_modules" ]
|
|
then
|
|
add_dracut_arg "--add-drivers" "$extra_modules"
|
|
fi
|
|
|
|
dracut "${dracut_args[@]}" "$@"
|
|
_rc=$?
|
|
sync
|
|
exit $_rc
|