mirror of
https://github.com/ibm-s390-linux/s390-tools.git
synced 2026-08-05 02:14:52 +00:00
When we have identified the current event subject to represent the re-IPL target, we record its WWID for future identification in a stateful ID-file (per default: /run/udev/chreiplzfcpmp-ipl-volume-id). In addition to the WWID, we also record the current re-IPL triplet (<Dev-Bus-ID>:<WWPN>:<LUN>), so that when that changes - e.g. due to an operator manually changing the re-IPL target -, we know that the recorded WWID is stale. This record may be used in cases when the current re-IPL target is completely gone from the system, so we can't used it as comparison object for when events arrive for paths that go to the same volume, but don't have the same I_T_L nexus. They however have the same WWID. We may use these (new) paths as replacement for the one that is completely gone. The new helper `chreipl-fcp-mpath-record-volume-identifier` uses the kernel scsi-device attribute `wwid` as source for the WWID (verbatim). As with reading the re-IPL firmware information, when writing to the ID-file, a lock is taken via `flock`, to prevent overlapping writes/reads to the file. 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>
299 lines
9.1 KiB
Bash
299 lines
9.1 KiB
Bash
#!/bin/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
|
|
|
|
# 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:
|
|
# 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
|
|
}
|