mirror of
https://github.com/ibm-s390-linux/s390-tools.git
synced 2026-08-05 02:14:52 +00:00
`chreipl-fcp-mpath-common.sh.in` is never executed, only used as argument for `source` in the udev helper scripts, so the shebang is unnecessary, and might be confusing. Also, tools like `rpmlint` from the rpm software management will complain about this; e.g.: s390-tools-chreipl-fcp-mpath.s390x: W: non-executable-script /usr/lib/chreipl-fcp-mpath/chreipl-fcp-mpath-common.sh 644 /bin/bash Reviewed-by: Steffen Maier <maier@linux.ibm.com> Signed-off-by: Benjamin Block <bblock@linux.ibm.com> Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
447 lines
14 KiB
Bash
447 lines
14 KiB
Bash
# SPDX-License-Identifier: MIT
|
|
#
|
|
# chreipl-fcp-mpath: use multipath information to change FCP IPL target
|
|
# (C) Copyright IBM Corp. 2021
|
|
#
|
|
# Uses the following system-utilities (and shell-builtins):
|
|
# GNU coreutils:
|
|
# - mktemp
|
|
# - readlink
|
|
# - sync
|
|
# util-linux:
|
|
# - flock
|
|
# - logger
|
|
|
|
# Makes use of udev event environment variables:
|
|
# SEQNUM
|
|
|
|
# (1) expand failed globs to an empty string
|
|
# (2) extended pattern matching to strip leading/trailing whitespaces
|
|
shopt -s nullglob extglob
|
|
# (1) don't overwrite existing files using redirects (e.g.: `>`)
|
|
set -o noclobber
|
|
|
|
# make sure any state files created are only writeable by the owning user
|
|
umask 027
|
|
|
|
# create log if DEBUG is enabled (with Make: D=1)
|
|
#
|
|
# Each script importing this library and expecting a debug log to be created
|
|
# must declare a *trace tag* in a variable `debug_trace_tag`. This is used as
|
|
# identifier in the log file name. The format is:
|
|
#
|
|
# [[:digit:]][[:digit:]][[:alpha:]][[:alpha:]][[:alpha:]][[:alpha:]]
|
|
# \ /\ /
|
|
# --------\ /-------- -------------------\ /-------------------
|
|
# \/ \/
|
|
# relative position of some unique abbreviation for the script
|
|
# execution in the name, excluding any common prefix
|
|
# udev rules
|
|
if '@DEBUG@' && [ -v debug_trace_tag ] && tlg="$(
|
|
mktemp -p '@debugoutdir@' \
|
|
"chreiplzfcpmp-${debug_trace_tag}-${SEQNUM:-0}.XXXXXXXXXX" \
|
|
2>/dev/null)"
|
|
then
|
|
readonly tlg
|
|
exec >|"${tlg}" 2>&1
|
|
set -x
|
|
set
|
|
else
|
|
unset tlg
|
|
fi
|
|
|
|
declare -gr ID_FILE='@chreiplzfcpmp-id-file@'
|
|
declare -gr FW_LOCK_FILE='@chreiplzfcpmp-fwlock-file@'
|
|
|
|
declare -gA TRAP_EXIT_FN=()
|
|
declare -gf trap_exit 1>/dev/null
|
|
function trap_exit() {
|
|
local fn
|
|
|
|
for fn in "${TRAP_EXIT_FN[@]}"; do
|
|
"${fn}"
|
|
done
|
|
|
|
trap - EXIT
|
|
}
|
|
trap trap_exit EXIT
|
|
|
|
# Output variables:
|
|
# id_file_unlock_exclusive_create() - call to unlock when finished with
|
|
# critical section
|
|
#
|
|
# XXX: `id_file_lock_*` can't be taken recursively
|
|
function id_file_lock_exclusive_create() {
|
|
declare -g ID_FILE_LOCK=""
|
|
|
|
# prevent concurrent file creation
|
|
#
|
|
# First, open the file defined in ${ID_FILE} for writing; this will
|
|
# succeed and create the file only if it doesn't exist already. If the
|
|
# file already exist, the first open attempt will fail and we fall
|
|
# back to opening it only for reading; this will always succeed if the
|
|
# file already exists (the reason why the first attempty failed). In
|
|
# both cases store the corresponding file descriptor in
|
|
# ${ID_FILE_LOCK}.
|
|
#
|
|
# XXX: This should be race free.
|
|
# open() with O_EXCL... is atomic (we set `noclobber` as shell
|
|
# option); at least as long as we talk about a local FS.
|
|
if ! { exec {ID_FILE_LOCK}>"${ID_FILE}"; } 2>/dev/null; then
|
|
{ exec {ID_FILE_LOCK}<"${ID_FILE}"; } 2>/dev/null \
|
|
|| return 1
|
|
fi
|
|
|
|
declare -gf id_file_unlock_exclusive_create 1>/dev/null
|
|
function id_file_unlock_exclusive_create() {
|
|
if [ -v ID_FILE_LOCK ]; then
|
|
sync "${ID_FILE}" 2>/dev/null
|
|
# release file and implicitly the lock, if taken
|
|
exec {ID_FILE_LOCK}>&-
|
|
unset ID_FILE_LOCK
|
|
fi
|
|
|
|
unset "TRAP_EXIT_FN[id_file_unlock_exclusive_create]"
|
|
}
|
|
TRAP_EXIT_FN+=(
|
|
[id_file_unlock_exclusive_create]=id_file_unlock_exclusive_create
|
|
)
|
|
|
|
flock --exclusive --timeout 5 "${ID_FILE_LOCK}" || return 2
|
|
|
|
return 0
|
|
}
|
|
|
|
# Output variables:
|
|
# id_file_unlock_exclusive_no_create() - call to unlock when finished with
|
|
# critical section
|
|
#
|
|
# XXX: `id_file_lock_*` can't be taken recursively
|
|
function id_file_lock_exclusive_no_create() {
|
|
declare -g ID_FILE_LOCK=""
|
|
|
|
# Open the file defined in ${ID_FILE} for reading, and store the
|
|
# corresponding file descriptor in ${ID_FILE_LOCK}.
|
|
#
|
|
# XXX: return code is used in `chreipl-fcp-mpath-try-change-ipl-path`
|
|
{ exec {ID_FILE_LOCK}<"${ID_FILE}"; } 2>/dev/null || return 1
|
|
|
|
declare -gf id_file_unlock_exclusive_no_create 1>/dev/null
|
|
function id_file_unlock_exclusive_no_create() {
|
|
if [ -v ID_FILE_LOCK ]; then
|
|
sync "${ID_FILE}" 2>/dev/null
|
|
# release file and implicitly the lock, if taken
|
|
exec {ID_FILE_LOCK}<&-
|
|
unset ID_FILE_LOCK
|
|
fi
|
|
|
|
unset "TRAP_EXIT_FN[id_file_unlock_exclusive_no_create]"
|
|
}
|
|
TRAP_EXIT_FN+=(
|
|
[id_file_unlock_exclusive_no_create]=id_file_unlock_exclusive_no_create
|
|
)
|
|
|
|
flock --exclusive --timeout 5 "${ID_FILE_LOCK}" || return 2
|
|
|
|
return 0
|
|
}
|
|
|
|
# Output variables:
|
|
# id_file_unlock_shared_no_create() - call to unlock when finished with
|
|
# critical section
|
|
#
|
|
# XXX: `id_file_lock_*` can't be taken recursively
|
|
function id_file_lock_shared_no_create() {
|
|
declare -g ID_FILE_LOCK=""
|
|
|
|
# Open the file defined in ${ID_FILE} for reading, and store the
|
|
# corresponding file descriptor in ${ID_FILE_LOCK}.
|
|
{ exec {ID_FILE_LOCK}<"${ID_FILE}"; } 2>/dev/null || return 1
|
|
|
|
declare -gf id_file_unlock_shared_no_create 1>/dev/null
|
|
function id_file_unlock_shared_no_create() {
|
|
if [ -v ID_FILE_LOCK ]; then
|
|
# release file and implicitly the lock, if taken
|
|
exec {ID_FILE_LOCK}<&-
|
|
unset ID_FILE_LOCK
|
|
fi
|
|
|
|
unset "TRAP_EXIT_FN[id_file_unlock_shared_no_create]"
|
|
}
|
|
TRAP_EXIT_FN+=(
|
|
[id_file_unlock_shared_no_create]=id_file_unlock_shared_no_create
|
|
)
|
|
|
|
flock --shared --timeout 5 "${ID_FILE_LOCK}" || return 2
|
|
|
|
return 0
|
|
}
|
|
|
|
# Output variables:
|
|
# firmware_unlock_exclusive() - call to unlock when finished with critical section
|
|
#
|
|
# XXX: `firmware_lock_*` can't be taken recursively
|
|
function firmware_lock_exclusive() {
|
|
declare -g FIRMWARE_LOCK=""
|
|
|
|
# Open the file defined in ${FW_LOCK_FILE} for reading, and store the
|
|
# corresponding file descriptor in ${FIRMWARE_LOCK} (it doesn't matter
|
|
# whether this is a normal file or directory). This file descriptor
|
|
# will only be used for locking - not for actual I/O.
|
|
{ exec {FIRMWARE_LOCK}<"${FW_LOCK_FILE}"; } 2>/dev/null || return 1
|
|
|
|
declare -gf firmware_unlock_exclusive 1>/dev/null
|
|
function firmware_unlock_exclusive() {
|
|
if [ -v FIRMWARE_LOCK ]; then
|
|
# release file and implicitly the lock, if taken
|
|
exec {FIRMWARE_LOCK}<&-
|
|
unset FIRMWARE_LOCK
|
|
fi
|
|
|
|
unset "TRAP_EXIT_FN[firmware_unlock_exclusive]"
|
|
}
|
|
TRAP_EXIT_FN+=([firmware_unlock_exclusive]=firmware_unlock_exclusive)
|
|
|
|
flock --exclusive --timeout 5 "${FIRMWARE_LOCK}" || return 2
|
|
|
|
return 0
|
|
}
|
|
|
|
# Output variables:
|
|
# firmware_unlock_shared() - call to unlock when finished with critical section
|
|
#
|
|
# XXX: `firmware_lock_*` can't be taken recursively
|
|
function firmware_lock_shared() {
|
|
declare -g FIRMWARE_LOCK=""
|
|
|
|
# Open the file defined in ${FW_LOCK_FILE} for reading, and store the
|
|
# corresponding file descriptor in ${FIRMWARE_LOCK} (it doesn't matter
|
|
# whether this is a normal file or directory). This file descriptor
|
|
# will only be used for locking - not for actual I/O.
|
|
{ exec {FIRMWARE_LOCK}<"${FW_LOCK_FILE}"; } 2>/dev/null || return 1
|
|
|
|
declare -gf firmware_unlock_shared 1>/dev/null
|
|
function firmware_unlock_shared() {
|
|
if [ -v FIRMWARE_LOCK ]; then
|
|
# release file and implicitly the lock, if taken
|
|
exec {FIRMWARE_LOCK}<&-
|
|
unset FIRMWARE_LOCK
|
|
fi
|
|
|
|
unset "TRAP_EXIT_FN[firmware_unlock_shared]"
|
|
}
|
|
TRAP_EXIT_FN+=([firmware_unlock_shared]=firmware_unlock_shared)
|
|
|
|
flock --shared --timeout 5 "${FIRMWARE_LOCK}" || return 2
|
|
|
|
return 0
|
|
}
|
|
|
|
# Output variables:
|
|
# IPL_TYPE
|
|
# IPL_BUSID
|
|
# IPL_WWPN
|
|
# IPL_LUN
|
|
function firmware_get_ipl_information() {
|
|
declare -g IPL_TYPE="" IPL_BUSID="" IPL_WWPN="" IPL_LUN=""
|
|
|
|
# Take lock so we don't see any intermediate state from other helpers
|
|
# running in parallel
|
|
firmware_lock_shared || return 5
|
|
|
|
{ read -r IPL_TYPE _ < /sys/firmware/reipl/reipl_type; } 2>/dev/null \
|
|
|| return 1
|
|
{ read -r IPL_BUSID _ < /sys/firmware/reipl/fcp/device; } 2>/dev/null \
|
|
|| return 2
|
|
{ read -r IPL_WWPN _ < /sys/firmware/reipl/fcp/wwpn; } 2>/dev/null \
|
|
|| return 3
|
|
{ read -r IPL_LUN _ < /sys/firmware/reipl/fcp/lun; } 2>/dev/null \
|
|
|| return 4
|
|
|
|
firmware_unlock_shared
|
|
|
|
# show read values in debug log if enabled
|
|
if '@DEBUG@'; then
|
|
declare -p IPL_TYPE IPL_BUSID IPL_WWPN IPL_LUN 1>&2
|
|
fi
|
|
|
|
return 0
|
|
}
|
|
|
|
# Input:
|
|
# 1: absolute canonical path to the scsi device in sysfs, e.g.:
|
|
# /sys/devices/css0/0.0.0014/0.0.1700/host1/rport-1:0-0/target1:0:0/1:0:0:1075789848
|
|
# Output variables:
|
|
# SDEV_LUN
|
|
function sdev_get_lun() {
|
|
local sdev="${1}" sdev_lun_str
|
|
# bash uses `intmax_t` as width for integer variables, and glibc
|
|
# defines this either as `long int` on 64 bit systems, or
|
|
# `long long int` on other.
|
|
local -i sdev_lun=0 fcp_lun=0
|
|
|
|
sdev_lun_str="${sdev##*:}"
|
|
# e.g.: 1075789848
|
|
[[ "${sdev_lun_str}" == +([[:digit:]]) ]] || return 1
|
|
# "cast" to integer
|
|
sdev_lun="${sdev_lun_str}"
|
|
|
|
# convert the Linux integer LUN format to the hexadecimal 64 bit T10
|
|
# LUN representation format used by many s390x interfaces
|
|
(( fcp_lun = (((sdev_lun >> 0) & 0xffff) << 48)
|
|
| (((sdev_lun >> 16) & 0xffff) << 32)
|
|
| (((sdev_lun >> 32) & 0xffff) << 16)
|
|
| (((sdev_lun >> 48) & 0xffff) << 0) ))
|
|
# the '0x' prefix is part of the length
|
|
printf -v SDEV_LUN "%#018llx" "${fcp_lun}"
|
|
|
|
# show read values in debug log if enabled
|
|
if '@DEBUG@'; then
|
|
declare -p SDEV_LUN 1>&2
|
|
fi
|
|
|
|
return 0
|
|
}
|
|
|
|
# Input:
|
|
# 1: path to the scsi device in sysfs, e.g.:
|
|
# /sys/devices/css0/0.0.0014/0.0.1700/host1/rport-1:0-0/target1:0:0/1:0:0:1075789848
|
|
# , or a symlink pointing to the scsi device, e.g.:
|
|
# /sys/class/block/sds/device
|
|
# Output variables:
|
|
# SDEV_BUSID
|
|
# SDEV_WWPN
|
|
# SDEV_LUN
|
|
function sdev_get_fcp_addressing() {
|
|
local sdev="${1}" fcp_lun rport rport_wwpn zfcp_dev
|
|
|
|
declare -g SDEV_BUSID="" SDEV_WWPN="" SDEV_LUN=""
|
|
|
|
sdev="$(readlink -se "${sdev}")" || return 1
|
|
|
|
# get the LUN for this SDEV
|
|
#
|
|
# sets ${SDEV_LUN}
|
|
sdev_get_lun "${sdev}" || return 2
|
|
|
|
# get the WWPN of the remote port this SDEV is attached to
|
|
printf -v rport "%s" "${sdev}"/../../fc_remote_ports/rport-*:*-*
|
|
# e.g.: /sys/devices/css0/0.0.0016/0.0.1740/host0/rport-0:0-1/fc_remote_ports/rport-0:0-1
|
|
[ "${rport}" != "" ] || return 3
|
|
|
|
# XXX: This works even if the rport is currently in a bad
|
|
# state, so e.g. when it has just gone down because of a
|
|
# cable pull.
|
|
{ read -r rport_wwpn _ < "${rport}"/port_name; } 2>/dev/null \
|
|
|| return 4
|
|
|
|
# The Linux kernel doesn't guarantee the same format as in
|
|
# /sys/firmware/..., so make sure it is the one we expect.
|
|
[[ "${rport_wwpn}" =~ ^0x[[:xdigit:]]{1,16}$ ]] || return 5
|
|
# the '0x' prefix is part of the length
|
|
printf -v rport_wwpn "%#018llx" "${rport_wwpn}"
|
|
|
|
# get the Device Bus-ID of the device via which this SDEV is attached
|
|
zfcp_dev="$(readlink -se "${sdev}"/../../../..)" || return 6
|
|
# e.g.: /sys/devices/css0/0.0.0016/0.0.1740
|
|
zfcp_dev="${zfcp_dev##*/}"
|
|
|
|
# shellcheck disable=2034
|
|
SDEV_BUSID="${zfcp_dev}"
|
|
# shellcheck disable=2034
|
|
SDEV_WWPN="${rport_wwpn}"
|
|
return 0
|
|
}
|
|
|
|
# Input:
|
|
# 1: path to the scsi device in sysfs
|
|
# Output variables:
|
|
# SDEV_WWID
|
|
function sdev_get_wwid() {
|
|
local sdev="${1}"
|
|
local -a wwid
|
|
|
|
declare -g SDEV_WWID=""
|
|
|
|
# read the volume identifier without stripping any content
|
|
#
|
|
# XXX: we can read the WWID file, even if the SDEV is currently not
|
|
# operational (e.g.: due to the path has gone away), as long as
|
|
# the VPD PG 83 is still cached in the kernel; and the page gets
|
|
# only released on SDEV device release.
|
|
{ readarray -d "" -t wwid < "${sdev}"/wwid; } 2>/dev/null \
|
|
|| return 1
|
|
if '@DEBUG@'; then declare -p wwid 1>&2; fi
|
|
|
|
# test whether we read something
|
|
#
|
|
# This strips all leading spaces from the beginning of the read WWID
|
|
# (until the first non-space or NUL character), and checks whether the
|
|
# result is empty.
|
|
# Hence, we return early if the WWID consists of only whitespace.
|
|
#
|
|
# XXX: there could be unexpected characters in the returned ID.
|
|
# `scsi_id` from the udev helpers sanitizes the strings it reads
|
|
# from the devices, so they can be used in environment variables
|
|
# without much danger.
|
|
# But we don't export anything here, so it should be fine.
|
|
[ "${wwid[0]/#*([[:space:]])}" != "" ] || return 2
|
|
|
|
# shellcheck disable=2034
|
|
SDEV_WWID="${wwid[0]}"
|
|
return 0
|
|
}
|
|
|
|
# Input:
|
|
# 1: path to the scsi device in sysfs
|
|
# Return Value:
|
|
# == 0: SDEV referenced by `1` in good state
|
|
# != 0: otherwise
|
|
function sdev_test_path_state() {
|
|
local sdev="${1}" state zfcp_failed zfcp_in_recovery rport port_state
|
|
|
|
sdev="$(readlink -se "${sdev}")" || return 1
|
|
|
|
{ read -r state _ < "${sdev}"/state; } 2>/dev/null || return 2
|
|
{ read -r zfcp_failed _ < "${sdev}"/zfcp_failed; } 2>/dev/null \
|
|
|| return 3
|
|
{ read -r zfcp_in_recovery _ < "${sdev}"/zfcp_in_recovery; } 2>/dev/null \
|
|
|| return 4
|
|
|
|
printf -v rport "%s" "${sdev}"/../../fc_remote_ports/rport-*:*-*
|
|
# e.g.: /sys/devices/css0/0.0.0016/0.0.1740/host0/rport-0:0-1/fc_remote_ports/rport-0:0-1
|
|
[ "${rport}" != "" ] || return 5
|
|
{ read -r port_state _ < "${rport}"/port_state; } 2>/dev/null \
|
|
|| return 6
|
|
|
|
if '@DEBUG@'; then
|
|
declare -p state zfcp_failed zfcp_in_recovery port_state 1>&2
|
|
fi
|
|
|
|
[ "${state}" = "running" ] || return 7
|
|
[ "${zfcp_failed}" = "0" ] || return 8
|
|
[ "${zfcp_in_recovery}" = "0" ] || return 9
|
|
{ [ "${port_state}" = "Online" ] \
|
|
|| [ "${port_state}" = "Marginal" ]; } || return 10
|
|
|
|
return 0
|
|
}
|
|
|
|
# Input:
|
|
# *: all input parameters are used as quoted message
|
|
function log_note() {
|
|
logger -p 'daemon.notice' -t 'chreipl-fcp-mpath' "${*}" &>/dev/null
|
|
}
|
|
|
|
# Input:
|
|
# *: all input parameters are used as quoted message
|
|
function log_crit() {
|
|
logger -p 'daemon.crit' -t 'chreipl-fcp-mpath' "${*}" &>/dev/null
|
|
}
|
|
|
|
# Input:
|
|
# *: all input parameters are used as quoted message
|
|
function log_alert() {
|
|
logger -p 'daemon.alert' -t 'chreipl-fcp-mpath' "${*}" &>/dev/null
|
|
}
|