summaryrefslogtreecommitdiff
path: root/mkdumprd
blob: 35f5eedd09091b9919be678c20a9eb98abdb6e84 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
#!/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\""

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 _dev=$1 _source _target _fstype _options _mntopts _pdev

    _source=$(findmnt -k -f -n -r -o SOURCE $_dev)
    _target=$(get_mntpoint_from_target $_dev)
    # mount under /sysroot if dump to root disk or mount under
    #/kdumproot/$_target in other cases in 2nd kernel. systemd
    #will be in charge to umount it.

    if [ "$_target" = "/" ];then
        _target="/sysroot"
    else
        _target="/kdumproot/$_target"
    fi

    _fstype=$(findmnt -k -f -n -r -o FSTYPE $_dev)
    [[ -e /etc/fstab ]] && _options=$(findmnt --fstab -f -n -r -o OPTIONS $_dev)
    if [ -z "$_options" ]; then
        _options=$(findmnt -k -f -n -r -o OPTIONS $_dev)
        if [[ $_fstype == "nfs"* ]]; then
            _options=$(echo $_options | sed 's/,addr=[^,]*//')
            _options=$(echo $_options | sed 's/,proto=[^,]*//')
            _options=$(echo $_options | sed 's/,clientaddr=[^,]*//')
        fi
    fi
    # mount fs target as rw in 2nd kernel
    _options=$(echo $_options | sed 's/\(^\|,\)ro\($\|,\)/\1rw\2/g')
    # filter out 'noauto' here, it will be force appended later, avoid duplication
    _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')
    # only mount the dump target when needed.
    _options="$_options,noauto"

    _mntopts="$_target $_fstype $_options"
    #for non-nfs _dev converting to use udev persistent name
    if [ -b "$_source" ]; then
        _pdev="$(get_persistent_dev $_source)"
        if [ -z "$_pdev" ]; then
            return 1
        fi

    else
        _pdev=$_dev
    fi

    echo "$_pdev $_mntopts"
}

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 $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
}

# $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 "$1")

    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)
        if ! findmnt $config_val >/dev/null; then
            perror_exit "Dump target $config_val is probably not mounted."
        fi

        # User configured target, use $SAVE_PATH as the dump path within the target
        check_save_path_user_configured "$config_val" "$SAVE_PATH"
        check_size fs "$config_val"
        add_mount "$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=$(persistent_policy="by-id" 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