61d4fa72b5
Since commit ca01cbd
, mkdumprd can generate the initramfs even if dump
target is not mounted. But it will refuse to work if the dump target is
not marked as noauto, and not mounted.
If a target is not marked as noauto in fstab, it should be mounted
automatically on boot. If it's not mounted, most likely something is
wrong and user should fix that. So mkdumprd will just show an error
message and quit to avoid further failure.
This commit make the error message more clear and less confusing.
Signed-off-by: Kairui Song <kasong@redhat.com>
Acked-by: Dave Young <dyoung@redhat.com>
486 lines
13 KiB
Bash
486 lines
13 KiB
Bash
#!/bin/bash --norc
|
|
# New mkdumprd
|
|
#
|
|
# Copyright 2011 Red Hat, Inc.
|
|
#
|
|
# Written by Cong Wang <amwang@redhat.com>
|
|
#
|
|
|
|
[[ $dracutbasedir ]] || dracutbasedir=/usr/lib/dracut
|
|
. $dracutbasedir/dracut-functions.sh
|
|
. /lib/kdump/kdump-lib.sh
|
|
export IN_KDUMP=1
|
|
|
|
conf_file="/etc/kdump.conf"
|
|
SSH_KEY_LOCATION="/root/.ssh/kdump_id_rsa"
|
|
SAVE_PATH=$(get_save_path)
|
|
OVERRIDE_RESETTABLE=0
|
|
|
|
extra_modules=""
|
|
dracut_args="--quiet --hostonly --hostonly-cmdline --hostonly-i18n --hostonly-mode strict -o \"plymouth dash resume ifcfg earlykdump\""
|
|
|
|
readonly MKDUMPRD_TMPDIR="$(mktemp -d -t mkdumprd.XXXXXX)"
|
|
[ -d "$MKDUMPRD_TMPDIR" ] || perror_exit "dracut: mktemp -p -d -t dracut.XXXXXX failed."
|
|
readonly MKDUMPRD_TMPMNT="$MKDUMPRD_TMPDIR/target"
|
|
|
|
trap '
|
|
ret=$?;
|
|
is_mounted $MKDUMPRD_TMPMNT && umount -f $MKDUMPRD_TMPMNT;
|
|
[[ -d $MKDUMPRD_TMPDIR ]] && rm --one-file-system -rf -- "$MKDUMPRD_TMPDIR";
|
|
exit $ret;
|
|
' EXIT
|
|
|
|
# clean up after ourselves no matter how we die.
|
|
trap 'exit 1;' SIGINT
|
|
|
|
is_wdt_addition_needed() {
|
|
local active
|
|
|
|
is_wdt_mod_omitted
|
|
[[ $? -eq 0 ]] && return 1
|
|
[[ -d /sys/class/watchdog/ ]] || return 1
|
|
for dir in /sys/class/watchdog/*; do
|
|
[[ -f "$dir/state" ]] || continue
|
|
active=$(< "$dir/state")
|
|
[[ "$active" = "active" ]] && return 0
|
|
done
|
|
return 1
|
|
}
|
|
|
|
add_dracut_arg() {
|
|
dracut_args="$dracut_args $@"
|
|
}
|
|
|
|
add_dracut_module() {
|
|
add_dracut_arg "--add" "\"$1\""
|
|
}
|
|
|
|
add_dracut_mount() {
|
|
add_dracut_arg "--mount" "\"$1\""
|
|
}
|
|
|
|
add_dracut_sshkey() {
|
|
add_dracut_arg "--sshkey" "\"$1\""
|
|
}
|
|
|
|
# caller should ensure $1 is valid and mounted in 1st kernel
|
|
to_mount() {
|
|
local _target=$1 _fstype=$2 _options=$3 _new_mntpoint _pdev
|
|
|
|
_new_mntpoint=$(get_kdump_mntpoint_from_target $_target)
|
|
_fstype="${_fstype:-$(get_fs_type_from_target $_target)}"
|
|
_options="${_options:-$(get_mntopt_from_target $_target)}"
|
|
_options="${_options:-defaults}"
|
|
|
|
if [[ "$_fstype" == "nfs"* ]]; then
|
|
_pdev=$_target
|
|
_options=$(echo $_options | sed 's/,addr=[^,]*//')
|
|
_options=$(echo $_options | sed 's/,proto=[^,]*//')
|
|
_options=$(echo $_options | sed 's/,clientaddr=[^,]*//')
|
|
else
|
|
# for non-nfs _target converting to use udev persistent name
|
|
_pdev="$(kdump_get_persistent_dev $_target)"
|
|
if [ -z "$_pdev" ]; then
|
|
return 1
|
|
fi
|
|
fi
|
|
|
|
# mount fs target as rw in 2nd kernel
|
|
_options=$(echo $_options | sed 's/\(^\|,\)ro\($\|,\)/\1rw\2/g')
|
|
# with 'noauto' in fstab nfs and non-root disk mount will fail in 2nd
|
|
# kernel, filter it out here.
|
|
_options=$(echo $_options | sed 's/\(^\|,\)noauto\($\|,\)/\1/g')
|
|
# drop nofail or nobootwait
|
|
_options=$(echo $_options | sed 's/\(^\|,\)nofail\($\|,\)/\1/g')
|
|
_options=$(echo $_options | sed 's/\(^\|,\)nobootwait\($\|,\)/\1/g')
|
|
# use both nofail and x-systemd.before to ensure systemd will try best to
|
|
# mount it before kdump starts, this is an attempt to improve robustness
|
|
_options="$_options,nofail,x-systemd.before=initrd-fs.target"
|
|
|
|
echo "$_pdev $_new_mntpoint $_fstype $_options"
|
|
}
|
|
|
|
#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 $1:$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 $1:$SAVE_PATH"
|
|
fi
|
|
|
|
#check whether user has write permission on $1:$SAVE_PATH
|
|
_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 $1:$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=$(get_mntpoint_from_target $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
|
|
}
|
|
|
|
check_save_path_fs()
|
|
{
|
|
local _path=$1
|
|
|
|
if [ ! -d $_path ]; then
|
|
perror_exit "Dump path $_path does not exist."
|
|
fi
|
|
}
|
|
|
|
check_user_configured_target()
|
|
{
|
|
local _target=$1 _cfg_fs_type=$2 _mounted
|
|
local _mnt=$(get_mntpoint_from_target $_target)
|
|
local _opt=$(get_mntopt_from_target $_target)
|
|
local _fstype=$(get_fs_type_from_target $_target)
|
|
|
|
if [ -n "$_fstype" ]; then
|
|
# In case of nfs4, nfs should be used instead, nfs* options is deprecated in kdump.conf
|
|
[[ $_fstype = "nfs"* ]] && _fstype=nfs
|
|
|
|
if [ -n "$_cfg_fs_type" ] && [ "$_fstype" != "$_cfg_fs_type" ]; then
|
|
perror_exit "\"$_target\" have a wrong type config \"$_cfg_fs_type\", expected \"$_fstype\""
|
|
fi
|
|
else
|
|
_fstype="$_cfg_fs_type"
|
|
_fstype="$_cfg_fs_type"
|
|
fi
|
|
|
|
# For noauto mount, mount it inplace with default value.
|
|
# Else use the temporary target directory
|
|
if [ -n "$_mnt" ]; then
|
|
if ! is_mounted "$_mnt"; then
|
|
if [[ $_opt = *",noauto"* ]]; then
|
|
mount $_mnt
|
|
[ $? -ne 0 ] && perror_exit "Failed to mount $_target on $_mnt for kdump preflight check."
|
|
_mounted=$_mnt
|
|
else
|
|
perror_exit "Dump target \"$_target\" is neither mounted nor configured as \"noauto\""
|
|
fi
|
|
fi
|
|
else
|
|
_mnt=$MKDUMPRD_TMPMNT
|
|
mkdir -p $_mnt
|
|
mount $_target $_mnt -t $_fstype -o defaults
|
|
[ $? -ne 0 ] && perror_exit "Failed to mount $_target for kdump preflight check."
|
|
_mounted=$_mnt
|
|
fi
|
|
|
|
# For user configured target, use $SAVE_PATH as the dump path within the target
|
|
if [ ! -d "$_mnt/$SAVE_PATH" ]; then
|
|
perror_exit "Dump path \"$SAVE_PATH\" does not exist in dump target \"$_target\""
|
|
fi
|
|
|
|
check_size fs "$_target"
|
|
|
|
# Unmount it early, if function is interrupted and didn't reach here, the shell trap will clear it up anyway
|
|
if [ -n "$_mounted" ]; then
|
|
umount -f -- $_mounted
|
|
fi
|
|
}
|
|
|
|
# $1: core_collector config value
|
|
verify_core_collector() {
|
|
local _cmd="${1%% *}"
|
|
local _params="${1#* }"
|
|
|
|
if [ "$_cmd" != "makedumpfile" ]; then
|
|
if is_raw_dump_target; then
|
|
echo "Warning: specifying a non-makedumpfile core collector, you will have to recover the vmcore manually."
|
|
fi
|
|
return
|
|
fi
|
|
|
|
if is_ssh_dump_target || is_raw_dump_target; then
|
|
if ! strstr "$_params" "-F"; then
|
|
perror_exit "The specified dump target needs makedumpfile \"-F\" option."
|
|
fi
|
|
_params="$_params vmcore"
|
|
else
|
|
_params="$_params vmcore dumpfile"
|
|
fi
|
|
|
|
if ! $_cmd --check-params $_params; then
|
|
perror_exit "makedumpfile parameter check failed."
|
|
fi
|
|
}
|
|
|
|
add_mount() {
|
|
local _mnt=$(to_mount $@)
|
|
|
|
if [ $? -ne 0 ]; then
|
|
exit 1
|
|
fi
|
|
|
|
add_dracut_mount "$_mnt"
|
|
}
|
|
|
|
#handle the case user does not specify the dump target explicitly
|
|
handle_default_dump_target()
|
|
{
|
|
local _target
|
|
local _mntpoint
|
|
|
|
is_user_configured_dump_target && return
|
|
|
|
check_save_path_fs $SAVE_PATH
|
|
|
|
_save_path=$(get_bind_mount_source $SAVE_PATH)
|
|
_target=$(get_target_from_path $_save_path)
|
|
_mntpoint=$(get_mntpoint_from_target $_target)
|
|
|
|
SAVE_PATH=${_save_path##"$_mntpoint"}
|
|
add_mount "$_target"
|
|
check_size fs $_target
|
|
}
|
|
|
|
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
|
|
|
|
for dev in $(get_kdump_targets); do
|
|
[ -b "$dev" ] || continue
|
|
majmin=$(get_maj_min $dev)
|
|
check_block_and_slaves $1 $majmin && return 1
|
|
done
|
|
|
|
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 "Error: Can not save vmcore because 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
|
|
|
|
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
|
|
|
|
if is_wdt_addition_needed; then
|
|
add_dracut_arg "-a" "watchdog"
|
|
fi
|
|
|
|
while read config_opt config_val;
|
|
do
|
|
# remove inline comments after the end of a directive.
|
|
case "$config_opt" in
|
|
extra_modules)
|
|
extra_modules="$extra_modules $config_val"
|
|
;;
|
|
ext[234]|xfs|btrfs|minix|nfs)
|
|
check_user_configured_target "$config_val" "$config_opt"
|
|
add_mount "$config_val" "$config_opt"
|
|
;;
|
|
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=$(persistent_policy="by-id" kdump_get_persistent_dev $config_val)
|
|
if [ -z "$_praw" ]; then
|
|
exit 1
|
|
fi
|
|
add_dracut_arg "--device" "$_praw"
|
|
check_size raw $config_val
|
|
;;
|
|
ssh)
|
|
if strstr "$config_val" "@";
|
|
then
|
|
mkdir_save_path_ssh $config_val
|
|
check_size 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
|
|
;;
|
|
*)
|
|
;;
|
|
esac
|
|
done <<< "$(read_strip_comments $conf_file)"
|
|
|
|
handle_default_dump_target
|
|
|
|
if [ -n "$extra_modules" ]
|
|
then
|
|
add_dracut_arg "--add-drivers" \"$extra_modules\"
|
|
fi
|
|
|
|
if ! is_fadump_capable; then
|
|
# The 2nd rootfs mount stays behind the normal dump target mount,
|
|
# so it doesn't affect the logic of check_dump_fs_modified().
|
|
is_dump_to_rootfs && add_mount "$(to_dev_name $(get_root_fs_device))"
|
|
|
|
add_dracut_arg "--no-hostonly-default-device"
|
|
fi
|
|
|
|
echo "$dracut_args $@" | xargs dracut
|
|
|
|
_rc=$?
|
|
sync
|
|
exit $_rc
|