mirror of
https://github.com/ibm-s390-linux/s390-tools.git
synced 2026-08-05 02:14:52 +00:00
Extend cpictl to report Linux distribution type and version information, and another sublevel of Linux kernel version data via the system level field of the SCLP CPI firmware interface. This data is intended to be decoded by the IBM Z HMC web-UI to provide users a more detailed view of Linux software levels installed in LPARs. For this purpose, the format of the 64-bit system level word is extended in a compatible way, that is, the meaning of existing fields remains unchanged while additional data is added to previously unused fields. The new format is defined as 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 This format will be automatically reported for supported distributions based on data from /etc/os-release. As before, users can override the data by specifying a custom system level word in /etc/sysconfig/cpi. To support manual specification of new data fields, the format of cpictl's -L command line option and the CPI_SYSTEM_LEVEL environment variable are extended as follows: - 0x<level> Specifies the system level as 64 bit hex value - [[[flags:]distro_id:distro_version:]kernel_version] Specifies the system level as list of optional fields. Any field that is omitted is initialized with automatically determined values. Signed-off-by: Peter Oberparleiter <oberpar@linux.ibm.com> Reviewed-by: Jan Höppner <hoeppner@linux.ibm.com> Acked-by: Viktor Mihajlovski <mihajlov@linux.ibm.com> Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
503 lines
12 KiB
Bash
Executable File
503 lines
12 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 -i DRYRUN=0
|
|
|
|
# Exit codes
|
|
readonly EXIT_SUCCESS=0
|
|
readonly EXIT_FAILURE=1
|
|
readonly EXIT_ARG_TOO_LONG=3
|
|
readonly EXIT_INVALID_CHARS=4
|
|
|
|
# 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
|
|
--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
|
|
|
|
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()
|
|
{
|
|
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)"
|
|
|
|
# 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,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
|
|
;;
|
|
--commit)
|
|
cpi_commit
|
|
exit $EXIT_SUCCESS
|
|
;;
|
|
--dry-run)
|
|
DRYRUN=1
|
|
shift
|
|
;;
|
|
--)
|
|
shift
|
|
break
|
|
;;
|
|
*)
|
|
break;
|
|
;;
|
|
esac
|
|
done
|
|
|
|
# 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
|