mirror of
https://github.com/ibm-s390-linux/s390-tools.git
synced 2026-08-05 02:14:52 +00:00
Signed-off-by: André Wild <awild@linux.ibm.com> Reviewed-by: Hendrik Brueckner <brueckner@linux.ibm.com> Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
552 lines
13 KiB
Bash
Executable File
552 lines
13 KiB
Bash
Executable File
#!/bin/bash
|
|
#
|
|
# cpictl - Configure the Control-Program-Information (CPI) settings
|
|
#
|
|
# This is an internal helper script that is used by the "cpi.service"
|
|
# systemd unit and the "90-cpi.rules" udev rule.
|
|
#
|
|
# The bash shell is really needed. Other shells have different ideas of how
|
|
# bitwise operators work.
|
|
#
|
|
# Copyright 2017 IBM Corp.
|
|
#
|
|
# s390-tools is free software; you can redistribute it and/or modify
|
|
# it under the terms of the MIT license. See LICENSE for details.
|
|
#
|
|
|
|
readonly CPI_LOCK="/var/lock/cpictl.lock"
|
|
|
|
readonly PRG="${0##*/}"
|
|
|
|
readonly SYSTEM_LEVEL_PATH="/sys/firmware/cpi/system_level"
|
|
readonly SYSTEM_TYPE_PATH="/sys/firmware/cpi/system_type"
|
|
readonly SYSTEM_NAME_PATH="/sys/firmware/cpi/system_name"
|
|
readonly SYSPLEX_NAME_PATH="/sys/firmware/cpi/sysplex_name"
|
|
readonly CPI_SET="/sys/firmware/cpi/set"
|
|
|
|
# Location of os-release file - can be specified externally for testing purpose
|
|
readonly OS_RELEASE=${CPI_OS_RELEASE:-"/etc/os-release"}
|
|
|
|
declare LEVEL
|
|
declare TYPE
|
|
declare NAME
|
|
declare SYSPLEX
|
|
|
|
declare PV_GUEST
|
|
declare -i CPI_PERMIT="$CPI_PERMIT_ON_PVGUEST"
|
|
|
|
declare -i DRYRUN=0
|
|
|
|
# Exit codes
|
|
readonly EXIT_SUCCESS=0
|
|
readonly EXIT_FAILURE=1
|
|
readonly EXIT_ARG_TOO_LONG=3
|
|
readonly EXIT_INVALID_CHARS=4
|
|
readonly EXIT_INVALID_ARGS=5
|
|
readonly EXIT_NOTCONFIGURED=6
|
|
|
|
# Distro-IDs as supported by SE/HMC firmware
|
|
readonly DISTRO_GENERIC=0
|
|
readonly DISTRO_RHEL=1
|
|
readonly DISTRO_SLES=2
|
|
readonly DISTRO_UBUNTU=3
|
|
readonly DISTRO_FEDORA=4
|
|
readonly DISTRO_OPENSUSE=5
|
|
readonly DISTRO_DEBIAN=6
|
|
readonly DISTRO_RHCOS=7
|
|
|
|
print_help_and_exit()
|
|
{
|
|
cat <<EndHelp
|
|
Usage: $PRG [OPTIONS]
|
|
|
|
Configure the Control-Program-Information (CPI) settings.
|
|
|
|
-b, --set-bit BIT Set and commit the bit BIT in the flags
|
|
-e, --environment Set and commit the system type, level, name and
|
|
sysplex name with values taken from environment
|
|
variables
|
|
-h, --help Print this help, then exit
|
|
-L, --level LEVEL Set and commit OS level to LEVEL. Format: 0x<level> or
|
|
[[[flags:]distro_id:distro_version:]kernel_version]
|
|
-N, --name SYSTEM Set and commit the system name to SYSTEM
|
|
-S, --sysplex SYSPLEX Set and commit the sysplex name to SYSPLEX
|
|
-T, --type TYPE Set and commit OS type to TYPE
|
|
-v, --version Print version information, then exit
|
|
--permit-cpi Permit to send Control-Program Identification data of
|
|
protected virtualization guest to the host (must be
|
|
specified before any commit option). See also the
|
|
important note.
|
|
--commit Ignore all other options and commit any uncommitted
|
|
values
|
|
--dry-run Do not actually set or commit anything, but show what
|
|
would be done
|
|
--show Ignore all other options and show current (possibly
|
|
uncommitted) values
|
|
|
|
Environment variables used for the --defaults option:
|
|
CPI_SYSTEM_TYPE, CPI_SYSTEM_LEVEL, CPI_SYSTEM_NAME, CPI_SYSPLEX_NAME,
|
|
CPI_PERMIT_ON_PVGUEST (See also the important note.)
|
|
|
|
Important: Set CPI_PERMIT_ON_PVGUEST=1 or use --permit-cpi option only if you
|
|
trust the host system. Enabling these options allows the host to receive
|
|
potentially sensitive Control-Program Identification (CPI) data from the
|
|
protected virtualization guest, including:
|
|
- system_type
|
|
- system_level
|
|
- sysplex_name
|
|
- system_name
|
|
|
|
Available bits for the --set-bit option:
|
|
kvm: Indicate that system is a KVM host
|
|
|
|
The maximum length for system type, system name, and sysplex name is 8
|
|
characters. The allowed characters are: [A-Z0-9 @#$]
|
|
|
|
This tool should be called as root.
|
|
EndHelp
|
|
exit $EXIT_SUCCESS
|
|
}
|
|
|
|
print_version_and_exit()
|
|
{
|
|
cat <<EndVersion
|
|
$PRG: Configure the CPI settings version %S390_TOOLS_VERSION%
|
|
Copyright IBM Corp. 2017
|
|
EndVersion
|
|
exit $EXIT_SUCCESS
|
|
}
|
|
|
|
cpi_show()
|
|
{
|
|
cat <<-EndShow
|
|
System type: $(cat "$SYSTEM_TYPE_PATH")
|
|
System level: $(cat "$SYSTEM_LEVEL_PATH")
|
|
System name: $(cat "$SYSTEM_NAME_PATH")
|
|
Sysplex name: $(cat "$SYSPLEX_NAME_PATH")
|
|
EndShow
|
|
}
|
|
|
|
print_parse_error_and_exit()
|
|
{
|
|
echo "Try '$PRG --help' for more information." >&2
|
|
exit $EXIT_FAILURE
|
|
}
|
|
|
|
fail_with()
|
|
{
|
|
echo "$1" >&2
|
|
echo "Try '$PRG --help' for more information." >&2
|
|
exit ${2:-$EXIT_FAILURE}
|
|
}
|
|
|
|
cpi_commit()
|
|
{
|
|
# Commit Control-Program Identification changes on protected
|
|
# virtualization guests only if it is permitted by the guest. This
|
|
# prevents leakage of potentially sensitive information to untrusted
|
|
# hosts.
|
|
if [[ -f "/sys/firmware/uv/prot_virt_guest" ]]; then
|
|
read -r PV_GUEST < "/sys/firmware/uv/prot_virt_guest"
|
|
if [[ "$PV_GUEST" -eq 1 ]]; then
|
|
if [[ -z "$CPI_PERMIT" ]] || [[ "$CPI_PERMIT" -ne 1 ]]; then
|
|
echo "Sending CPI data from secure execution Linux guests is disabled. Use --permit-cpi to enable CPI data." >&2
|
|
exit "$EXIT_NOTCONFIGURED"
|
|
fi
|
|
fi
|
|
fi
|
|
echo 1 > "$CPI_SET" 2> /dev/null
|
|
}
|
|
|
|
do_length_check()
|
|
{
|
|
[ ${#1} -gt 8 ] &&
|
|
fail_with "$PRG: Specified $2 too long. The maximum length is 8 characters." $EXIT_ARG_TOO_LONG
|
|
}
|
|
|
|
do_character_check()
|
|
{
|
|
echo "$1" | grep -q -E '^[a-zA-Z0-9@#$ ]*$' ||
|
|
fail_with "$PRG: Invalid characters in $2. Valid characters are: A-Z0-9 @#$" $EXIT_INVALID_CHARS
|
|
}
|
|
|
|
cpi_set_bit()
|
|
{
|
|
LEVEL=$(printf '0x%x' $((LEVEL | (1 << (63 - $1)) )) )
|
|
}
|
|
|
|
#
|
|
# split_version - Split generic version string into array of sub-versions
|
|
#
|
|
# @version: Version string
|
|
# @delim: Characters that delimit sub-versions in version string
|
|
# @num: Number of sub-versions
|
|
#
|
|
# Print @num sub-versions of @version where each sub-version is delimited by
|
|
# any of the characters in @delim. Print 0 in place of non-decimal or missing
|
|
# sub-versions.
|
|
#
|
|
# Examples:
|
|
# version=10 delim=. num=3 => 10 0 0
|
|
# version=4.8 delim=. num=2 => 4 8
|
|
#
|
|
split_version()
|
|
{
|
|
local version="$1" delim="$2" num="$3"
|
|
local list i subver
|
|
|
|
IFS="$delim" read -r -a list <<< "$version"
|
|
|
|
for (( i=0; i<num; i++ )) ; do
|
|
subver="${list[i]:-0}"
|
|
# Handle non-number sub-versions
|
|
[[ "$subver" =~ ^[0-9]+$ ]] || subver=0
|
|
# Force decimal interpretation in case of leading zeroes
|
|
subver=$(( 10#$subver ))
|
|
printf "%s " "$subver"
|
|
done
|
|
}
|
|
|
|
#
|
|
# split_kver - Split Linux kernel version string into array of sub-versions
|
|
#
|
|
# @version: Linux kernel version string
|
|
# @num: Number of sub-versions
|
|
#
|
|
# Print @num sub-versions of the specified kernel @version. Print 0 in place
|
|
# of non-decimal or missing sub-versions.
|
|
#
|
|
# Examples:
|
|
# version=2.4-13 num=4 => 2 4 0 13
|
|
# version=3.0.93_3.0.101-0.8.2_0.8.1 num=6 => 3 0 93 3 0 101
|
|
# version=4.12.14-lp150.11.4 num=5 => 4 12 14 0 11
|
|
#
|
|
split_kver()
|
|
{
|
|
local version="$1" num="$2"
|
|
local main extra
|
|
|
|
# Separate extra version to handle short main version (e.g. 2.4-13)
|
|
IFS="-_" read -r main extra <<< "$version"
|
|
|
|
split_version "$main" "." $(( num > 3 ? 3 : num ))
|
|
[[ "$num" -gt 3 ]] && split_version "$extra" ".-_" $(( num - 3 ))
|
|
}
|
|
|
|
#
|
|
# bytes_to_word - Convert byte array to hexadecimal word
|
|
#
|
|
# @bytes: List of numbers representing byte values
|
|
#
|
|
# Print a big-endian hexadecimal representation of the word that results from
|
|
# combining the specified byte values.
|
|
#
|
|
bytes_to_word()
|
|
{
|
|
printf "0x"
|
|
printf "%02x" "$@"
|
|
}
|
|
|
|
#
|
|
# get_system_level - Print system level word for specified distribution version
|
|
#
|
|
# @distro: Distro ID (ID value from /etc/os-release)
|
|
# @ver_str: Distro version string (VERSION_ID from /etc/os-release)
|
|
# @kver_str: Kernel version string (output of 'uname -r')
|
|
# @flags: Optional statistics flags
|
|
#
|
|
# Print a 64 bit hexadecimal system level in a format as understood by firmware.
|
|
#
|
|
# The format is 0xabccddeeeeffgghh, where
|
|
# - a=statistics flags
|
|
# - b=distro id
|
|
# - c=distro major version
|
|
# - d=distro minor version(s)
|
|
# - e=kernel sublevel 2
|
|
# - f=kernel version
|
|
# - g=kernel patchlevel
|
|
# - h=kernel sublevel 1
|
|
#
|
|
get_system_level()
|
|
{
|
|
local distro="$1" ver_str="$2" kver_str="$3" flags="${4:-0}"
|
|
local distro_id d_major d_minor d_minor2
|
|
local k_ver k_patchlvl k_sublvl k_sublvl2 bytes=()
|
|
|
|
# Extract list of sub-version numbers from version strings
|
|
read -r d_major d_minor d_minor2 <<< "$(split_version "$ver_str" "._-" 3)"
|
|
read -r k_ver k_patchlvl k_sublvl k_sublvl2 <<< "$(split_kver "$kver_str" 4)"
|
|
|
|
# Handle excessive sublevel numbers consistently
|
|
if [[ "$k_sublvl" -gt 255 ]] ; then
|
|
k_sublvl=0
|
|
fi
|
|
if [[ "$k_sublvl2" -gt 65535 ]] ; then
|
|
k_sublvl2=0
|
|
fi
|
|
|
|
# Apply distro-specific logic
|
|
case "$distro" in
|
|
"rhel")
|
|
distro_id=$DISTRO_RHEL
|
|
;;
|
|
|
|
"sles")
|
|
distro_id=$DISTRO_SLES
|
|
;;
|
|
|
|
"ubuntu")
|
|
distro_id=$DISTRO_UBUNTU
|
|
|
|
# Encode minor and update version numbers in minor field
|
|
(( d_minor=((d_minor & 0xf) * 0x10) + (d_minor2 & 0xf) ))
|
|
;;
|
|
|
|
"fedora")
|
|
distro_id=$DISTRO_FEDORA
|
|
;;
|
|
|
|
"opensuse-leap")
|
|
distro_id=$DISTRO_OPENSUSE
|
|
;;
|
|
|
|
"debian")
|
|
distro_id=$DISTRO_DEBIAN
|
|
;;
|
|
|
|
"rhcos")
|
|
distro_id=$DISTRO_RHCOS
|
|
;;
|
|
|
|
*)
|
|
distro_id=$DISTRO_GENERIC
|
|
|
|
# Reset unsupported fields
|
|
d_major=0
|
|
d_minor=0
|
|
k_sublvl2=0
|
|
;;
|
|
esac
|
|
|
|
# Assemble byte data
|
|
(( bytes[0] = (flags & 0xf) * 0x10 + distro_id ))
|
|
(( bytes[1] = d_major ))
|
|
(( bytes[2] = d_minor ))
|
|
(( bytes[3] = (k_sublvl2 / 256) & 0xff ))
|
|
(( bytes[4] = k_sublvl2 & 0xff ))
|
|
(( bytes[5] = k_ver ))
|
|
(( bytes[6] = k_patchlvl ))
|
|
(( bytes[7] = k_sublvl ))
|
|
|
|
# Print as single hex word
|
|
bytes_to_word "${bytes[@]}"
|
|
}
|
|
|
|
get_distro()
|
|
{
|
|
local line ID="linux" VERSION_ID="0" VERSION="" update
|
|
|
|
[[ ! -e "$OS_RELEASE" ]] && return
|
|
|
|
# Only import required variables
|
|
while read -r line ; do
|
|
if [[ "$line" =~ ^ID= ]] || [[ "$line" =~ ^VERSION_ID= ]] ||
|
|
[[ "$line" =~ ^VERSION= ]] ; then
|
|
eval "$line"
|
|
fi
|
|
done <"$OS_RELEASE"
|
|
|
|
if [[ "$ID" == "ubuntu" ]] ; then
|
|
# Extract update version number only found in VERSION, e.g.
|
|
# VERSION_ID="18.04" VERSION="18.04.5 LTS"
|
|
update="${VERSION/*$VERSION_ID/}"
|
|
update="${update%% *}"
|
|
VERSION_ID="$VERSION_ID$update"
|
|
fi
|
|
|
|
echo "$ID:$VERSION_ID"
|
|
}
|
|
|
|
cpi_set_oslevel()
|
|
{
|
|
local level="${1:-}"
|
|
local flags list distro_id distro_ver kver id ver
|
|
|
|
if [[ "$level" =~ ^0x ]] && ! [[ "$level" =~ : ]] ; then
|
|
# Format: level=0x<hex>
|
|
printf -v LEVEL "0x%016x" "$level" 2>/dev/null ||
|
|
fail_with "$PRG: Invalid hexadecimal number in $level" \
|
|
$EXIT_INVALID_CHARS
|
|
return
|
|
fi
|
|
|
|
# Format: level=[[[flags:]distro_id:distro_ver:]kver]
|
|
IFS=":" read -r -a list <<< "$level:"
|
|
kver="${list[*]: -1: 1}"
|
|
distro_ver="${list[*]: -2: 1}"
|
|
distro_id="${list[*]: -3: 1}"
|
|
flags="${list[*]: -4: 1}"
|
|
|
|
if [[ -z "$kver" ]] ; then
|
|
# Use version of currently running kernel
|
|
kver="$(uname -r)"
|
|
fi
|
|
|
|
if [[ -z "$distro_ver" ]] || [[ -z "$distro_id" ]] ; then
|
|
# Use distro ID and version from os-release file
|
|
IFS=":" read -r id ver <<< "$(get_distro)"
|
|
distro_id=${distro_id:-$id}
|
|
distro_ver=${distro_ver:-$ver}
|
|
fi
|
|
|
|
if [[ -z "$flags" ]] ; then
|
|
# Keep statistics flags from current system level
|
|
flags=$(( (LEVEL >> 60) & 0xf ))
|
|
fi
|
|
|
|
LEVEL=$(get_system_level "$distro_id" "$distro_ver" "$kver" "$flags")
|
|
}
|
|
|
|
cpi_set_type()
|
|
{
|
|
TYPE="$1"
|
|
do_length_check "$TYPE" "system type"
|
|
do_character_check "$TYPE" "system type"
|
|
}
|
|
|
|
cpi_set_sysplex()
|
|
{
|
|
SYSPLEX="$1"
|
|
do_length_check "$SYSPLEX" "sysplex name"
|
|
do_character_check "$SYSPLEX" "sysplex name"
|
|
}
|
|
|
|
cpi_set_name()
|
|
{
|
|
NAME="$1"
|
|
do_length_check "$NAME" "system name"
|
|
do_character_check "$NAME" "system name"
|
|
}
|
|
|
|
# cpictl starts here
|
|
|
|
if [ $# -le 0 ]; then
|
|
echo "$PRG: No parameters specified"
|
|
print_parse_error_and_exit
|
|
fi
|
|
|
|
opts=$(getopt -o b:ehL:N:S:T:v -l set-bit:,environment,help,level:,name:,sysplex:,type:,commit,dry-run,permit-cpi,show,version -n "$PRG" -- "$@")
|
|
if [ $? -ne 0 ]; then
|
|
print_parse_error_and_exit
|
|
fi
|
|
|
|
# This guarantees that only one instance will be running, and will serialize
|
|
# the execution of multiple instances
|
|
[ -e "$CPI_LOCK" -a ! -w "$CPI_LOCK" ] &&
|
|
fail_with "$PRG: Cannot access lock file: $CPI_LOCK"
|
|
[ ! -w "${CPI_LOCK%/*}" ] &&
|
|
fail_with "$PRG: Cannot access lock file: $CPI_LOCK"
|
|
|
|
exec 9<> "$CPI_LOCK"
|
|
flock -x 9
|
|
|
|
# Get current values from sys/firmware
|
|
read LEVEL < "$SYSTEM_LEVEL_PATH"
|
|
read TYPE < "$SYSTEM_TYPE_PATH"
|
|
read NAME < "$SYSTEM_NAME_PATH"
|
|
read SYSPLEX < "$SYSPLEX_NAME_PATH"
|
|
|
|
# Parse command line options: Use eval to remove getopt quotes
|
|
eval set -- $opts
|
|
while [ -n $1 ]; do
|
|
case "$1" in
|
|
--help|-h)
|
|
print_help_and_exit
|
|
;;
|
|
--version|-v)
|
|
print_version_and_exit
|
|
;;
|
|
-b|--set-bit)
|
|
case "$2" in
|
|
kvm)
|
|
cpi_set_bit 0
|
|
;;
|
|
*)
|
|
fail_with "$PRG: Unknown bit \"$2\" for the $1 option"
|
|
;;
|
|
esac
|
|
shift 2
|
|
;;
|
|
-L|--level)
|
|
cpi_set_oslevel "$2"
|
|
shift 2
|
|
;;
|
|
-e|--environment)
|
|
cpi_set_type "$CPI_SYSTEM_TYPE"
|
|
cpi_set_name "$CPI_SYSTEM_NAME"
|
|
cpi_set_oslevel "$CPI_SYSTEM_LEVEL"
|
|
cpi_set_sysplex "$CPI_SYSPLEX_NAME"
|
|
shift
|
|
;;
|
|
-T|--type)
|
|
cpi_set_type "$2"
|
|
shift 2
|
|
;;
|
|
-S|--sysplex)
|
|
cpi_set_sysplex "$2"
|
|
shift 2
|
|
;;
|
|
-N|--name)
|
|
cpi_set_name "$2"
|
|
shift 2
|
|
;;
|
|
--show)
|
|
cpi_show
|
|
exit $EXIT_SUCCESS
|
|
;;
|
|
--permit-cpi)
|
|
CPI_PERMIT=1
|
|
shift
|
|
;;
|
|
--commit)
|
|
cpi_commit
|
|
exit $EXIT_SUCCESS
|
|
;;
|
|
--dry-run)
|
|
DRYRUN=1
|
|
shift
|
|
;;
|
|
--)
|
|
shift
|
|
break
|
|
;;
|
|
*)
|
|
break;
|
|
;;
|
|
esac
|
|
done
|
|
|
|
# Unparsed options are not supported
|
|
if [ $# -ne 0 ]; then
|
|
fail_with "$PRG: Invalid command-line option: $*" $EXIT_INVALID_ARGS
|
|
fi
|
|
|
|
# Print settings for --dry-run or commit them to sysfs otherwise
|
|
|
|
if [ $DRYRUN -eq 1 ]; then
|
|
cat <<-EndDryrun
|
|
System type: $TYPE
|
|
System level: $LEVEL
|
|
System name: $NAME
|
|
Sysplex name: $SYSPLEX
|
|
EndDryrun
|
|
else
|
|
echo "$LEVEL" > "$SYSTEM_LEVEL_PATH"
|
|
echo "$TYPE" > "$SYSTEM_TYPE_PATH"
|
|
echo "$NAME" > "$SYSTEM_NAME_PATH"
|
|
echo "$SYSPLEX" > "$SYSPLEX_NAME_PATH"
|
|
cpi_commit
|
|
fi
|
|
|
|
exit $EXIT_SUCCESS
|