Compare commits

..

210 Commits

Author SHA1 Message Date
Joern Siglen
660bab6e68 New release s390-tools-2.38.0
Signed-off-by: Steffen Eiden <seiden@linux.ibm.com>
2025-06-25 11:43:15 +02:00
Steffen Eiden
3dd91bb84e rust: Update rust crates version to 0.12.0
Signed-off-by: Steffen Eiden <seiden@linux.ibm.com>
2025-06-25 10:39:22 +02:00
Steffen Eiden
8fadcd9fe5 pvsecret: Fix some edge cases for plaintext keys
Fix two edge cases for generating and interpreting plaintext secrets:
1. The maximum payload size was two bytes to long. The space for the
   length header was forgotten to take into account.
2. One of the checks for if the plaintext secret has a size was too
   strict.

Fixes: fd024387d7 ("rust/pv: Retrievable secrets support")
Reviewed-by: Jan Höppner <hoeppner@linux.ibm.com>
Signed-off-by: Steffen Eiden <seiden@linux.ibm.com>
2025-06-24 15:07:48 +02:00
Shalini Chellathurai Saroja
e8237e6ae0 cpictl: provide error for invalid options
cpictl tool supports options which start with - or -- alone. Getopt
command verifies options which start with - or -- alone. When an
option that does not start with - or -- is provided, the tool commits
the changes and does not report error as shown below.

$ ./cpictl abc
Inside cpi_commit function

Report error and do not commit for invalid options.

Signed-off-by: Shalini Chellathurai Saroja <shalini@linux.ibm.com>
Reviewed-by: Peter Oberparleiter <oberpar@linux.ibm.com>
Signed-off-by: Steffen Eiden <seiden@linux.ibm.com>
2025-06-24 15:07:48 +02:00
Jan Polensky
c746c5c267 cpumf/man/lshwc.8: Fix typo in example description
Corrected a grammatical error for proper subject-verb agreement.

Signed-off-by: Jan Polensky <japo@linux.ibm.com>
Reviewed-by: Jan Höppner <hoeppner@linux.ibm.com>
Signed-off-by: Steffen Eiden <seiden@linux.ibm.com>
2025-06-24 15:07:48 +02:00
Jan Polensky
b69b750ef5 cpumf/lshwc: Add support for structured output formats
Introduce a new command-line option --format FORMAT to control the output
format of lshwc. Supported formats include CSV, JSON, JSON-SEQ, and key-value
PAIRS. The output is now generated using the shared util_fmt infrastructure,
enabling consistent and machine-readable output.

This change also includes:
- Label generation for counters
- Integration of util_fmt for structured formatting
- Updated man page with documentation for the new option
- Updated man page with JSON output structure documentation
- Add option (-q) to force quoting all elements.

Reference: https://www.ietf.org/rfc/rfc4180.txt

Signed-off-by: Jan Polensky <japo@linux.ibm.com>
Reviewed-by: Thomas Richter <tmricht@linux.ibm.com>
Signed-off-by: Steffen Eiden <seiden@linux.ibm.com>
2025-06-24 15:07:48 +02:00
Thomas Richter
120ad30f1e cpumf/lscpumf: Add support for IBM z17 counter sets
Add support for IBM z17 machine types 9175 and 9176 CPU Measurement
facility basic, problem, crypto and extended counter sets.

Signed-off-by: Thomas Richter <tmricht@linux.ibm.com>
Acked-by: Sumanth Korikkar <sumanthk@linux.ibm.com>
Signed-off-by: Steffen Eiden <seiden@linux.ibm.com>
2025-06-24 15:07:48 +02:00
Steffen Eiden
bfc9854eb5 check_hostkeydoc: Move check_hostkeydoc to scripts & deprecation warning
Move the check_hostkeydoc script to scripts. This eliminates the last file
in the genprotimg directory. Additionally, add a deprecation warning to
that script. Every pv tool can verify the chain itself using the pv
library.

Acked-by: Jan Höppner <hoeppner@linux.ibm.com>
Signed-off-by: Steffen Eiden <seiden@linux.ibm.com>
2025-06-24 15:07:48 +02:00
Steffen Eiden
619c466275 check_hostkeydoc: Fix indentation
Replace spaces with tabs using `unexpand check_hostkeydoc`.

Acked-by: Jan Höppner <hoeppner@linux.ibm.com>
Signed-off-by: Steffen Eiden <seiden@linux.ibm.com>
2025-06-24 15:07:48 +02:00
Marc Hartmayer
017377c093 check_hostkeydoc: Add workaround for old OpenSSL versions
Add a workaround for OpenSSL version that do not include the upstream
fix [1]. The missing fix results in an OpenSSL error when it tries to
download the CRL provided by DigiCert. The workaround is to identify
this situation and then fallback to download the CRL using 'curl'
instead.

[1] cdbe47bf3c

Signed-off-by: Marc Hartmayer <mhartmay@linux.ibm.com>
Signed-off-by: Steffen Eiden <seiden@linux.ibm.com>
2025-06-24 15:07:48 +02:00
Jan Polensky
7e38c7106b libutil: Avoid quoting CSV header cells unless FMT_QUOTEALL is set
This patch modifies the default behavior of CSV header generation:
header cells are no longer enclosed in double quotes unless the
FMT_QUOTEALL flag is explicitly set. According to RFC 4180, quoting is
only required when a cell contains control characters, commas, or double
quotes.

The goal of this change is to produce cleaner and more readable CSV
output by default, and to avoid unnecessary quoting in header rows. It
also simplifies algorithms that rely on FMT_UTIL and improves
compatibility with downstream tools expecting unquoted headers.

Tools that expect unquoted headers include:
 * SQL tools: Headers optional, usually unquoted
 * R (read.csv): Uses header=TRUE, no quotes needed
 * Pandas: Assumes headers, quoting not required
 * Excel: Detects headers; quotes only for special chars

Signed-off-by: Jan Polensky <japo@linux.ibm.com>
Reviewed-by: Peter Oberparleiter <oberpar@linux.ibm.com>
Signed-off-by: Steffen Eiden <seiden@linux.ibm.com>
2025-06-24 15:07:48 +02:00
Mete Durlu
8dc06d14d7 udev: Introduce a rule to set newly hotplugged CPUs online
On s390 architecture a newly hotplugged CPU, should only stay offline if
it is on deconfigured state, otherwise it should automatically become
online. Introduce a new udev rule to enforce this behavior. This rule
should always be installed.

Acked-by: Jan Höppner <hoeppner@linux.ibm.com>
Acked-by: Heiko Carstens <hca@linux.ibm.com>
Signed-off-by: Mete Durlu <meted@linux.ibm.com>
Signed-off-by: Steffen Eiden <seiden@linux.ibm.com>
2025-06-24 15:07:48 +02:00
Jörn Siglen
f07504d92e dbginfo.sh: Add Ubuntu snap tool
Adding snap as the new Ubuntu software repository tool.

Reviewed-by: Mario Held <mario.held@de.ibm.com>
Signed-off-by: Jörn Siglen <SIGLEN@de.ibm.com>
Signed-off-by: Steffen Eiden <seiden@linux.ibm.com>
2025-06-24 15:07:48 +02:00
Peter Oberparleiter
02554fa6d8 chpstat: Add missing CMG 5 data fields
DPU-related data fields dpu_num_cores, dpu_channel_exec_time_cpc, and
dpu_exec_time_cpc are missing from machine-readable output for CMG 5
channel-paths.

Fix this by adding them to the corresponding output routines.

Reviewed-by: Vineeth Vijayan <vneethv@linux.ibm.com>
Signed-off-by: Peter Oberparleiter <oberpar@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2025-06-11 13:00:55 +02:00
Peter Oberparleiter
3eb09e8842 chpstat: Fix DPU utilization calculation
chpstat reports incorrect DPU utilization values for CMG 4 and 5
channel-paths. Calculated values are too high due to counters being
interpreted as units of seconds while they are reported in units of
timer ticks. Also, a zero partition channel-path utilization value
(DPU PART) is incorrectly displayed as unavailable (-).

Fix this by using the correct timer unit in DPU utilization
calculations and making sure that zero DPU PART values are reported
correctly.

Reviewed-by: Vineeth Vijayan <vneethv@linux.ibm.com>
Signed-off-by: Peter Oberparleiter <oberpar@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2025-06-11 13:00:55 +02:00
Steffen Eiden
3eb9711021 pvsecret: Fix man entry for aes-xts keys
Fixes: a8a3e7d49c ("rust/pvsecret: Update manuals and README")
Signed-off-by: Steffen Eiden <seiden@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2025-06-11 13:00:55 +02:00
Peter Jin
2349236bb4 zdev: Add support for virtio devices
Add support for managing virtual I/O ccw (virtio-ccw) devices in
lszdev/chzdev.

New zdev device type virtio. Each virtio device type (blk, net,
gpu, vsock, etc.) is represented by a unique subtype of the virtio
base type.

If the virtio device type is recognized, then it will show up as
virtio-blk or virtio-net, or whatever the virtio device type is.
Otherwise, it will just show up as virtio-ccw.

Fixes: https://github.com/ibm-s390-linux/s390-tools/issues/29
Reviewed-by: Peter Oberparleiter <oberpar@linux.ibm.com>
Signed-off-by: Peter Jin <pjin@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2025-06-11 13:00:55 +02:00
Gautam Gala
2d86117167 rust/pvimg: SE header tampering tool for use with tests
Adding a new tool (tamper_pvimg) for helping to test if the expected
RC and RRC values are received for several different reasons when
DIAG 308 subcode does not complete successfully. The reasons can include
issues related to host key hash, SE header, ALD/PDL/TLD mistmatch, etc.

This tool can read the SE image and manipulate one of the SE header
fields so that the SE image will fail to enter secure mode during the
stage3a booting process. The stage3a bootloader should receive UVC
command code id, RC and RRC values corresponding to the SE header
field that was manipulated. These values will be checked for
consistency in the tests for correctness of DIAG 308 subcode 10 RCs
and RRCs.

[hoeppner@linux.ibm.com: shortened commit subject]
Signed-off-by: Gautam Gala <ggala@linux.ibm.com>
Signed-off-by: Steffen Eiden <sediden@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2025-06-11 12:58:03 +02:00
Gautam Gala
a0265d1069 rust/pvimg: Convert variables to public for use with SE header tampering tool
Converting several variables to public and exporting some structures
for use in SE header tampering tool.

Signed-off-by: Gautam Gala <ggala@linux.ibm.com>
Signed-off-by: Steffen Eiden <seiden@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2025-06-11 12:38:45 +02:00
Gautam Gala
084792bdc6 pvimg/boot: Print error messages from stage3a bootloader
When DIAG 308 subcode 10 is specified, and the configuration fails to enter
the secure mode, then the stage3a bootloader reads the DIAG response code,
UV RC, RRC and command id from bit positions 48-63, 32-47, 16-31, and
0-15 of the general register R1 + 1.

The bootloader now prints the error codes to help pinpoint the error cause.
For some cases that are reported to occur often the bootloader also prints
the cause when possible or prints hints to help understand the cause.

QEMU patch "DIAG 308: extend subcode 10 to return UVC cmd id, RC and RRC
values upon failure to enter secure mode" (commit-id: )
is needed with this patch.

Reviewed-by: Steffen Eiden <seiden@linux.ibm.com>
Signed-off-by: Gautam Gala <ggala@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2025-06-11 12:38:45 +02:00
Ingo Franzki
5ece6a3373 etc/modules-load.d/s390-pkey.conf: Add pkey sub-modules and paes_s390
Ensure that the pkey sub-modules are also loaded, as well as the paes
cipher.

Signed-off-by: Ingo Franzki <ifranzki@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2025-06-11 12:38:45 +02:00
Ingo Franzki
14c977768e zkey/dracut: Add a dracut config file for zkey
Provide a dracut config file that ensures that the required drivers and
executables, as well as the zkey repository is included into the initramfs.

Signed-off-by: Ingo Franzki <ifranzki@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2025-06-11 12:38:45 +02:00
Ingo Franzki
5bedcff65c zkey/initramfs: Update initramfs hook to correct drivers and include zkey plugins
The zkey tool might be configured to use plugins. Include the plugins also
into the initramfs, otherwise zkey might not be usable at early boot time,
when it is configure to use a plugin.

Update the list of driver to include, pkey_sysfs does not exist, its built
into the pkey base driver.

Signed-off-by: Ingo Franzki <ifranzki@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2025-06-11 12:38:45 +02:00
Jörn Siglen
479820528c dbginfo.sh: Reworking the container section
Adding podman commands to dbginfo.sh, which is a successor for docker.
We reduce standard details for docker (it can be collected on demand).
Inlcuding /etc/containers for registry and container engine configuration.

Suggested-by: Hendrik Brueckner <brueckner@linux.ibm.com>
Reviewed-by: Mario Held <mario.held@de.ibm.com>
Signed-off-by: Jörn Siglen <SIGLEN@de.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2025-05-21 15:50:31 +02:00
Ingo Franzki
486882a4df libkmipclient: Fix findings from static code checker
The Coverity static code checker found a few bugs in the libkmipclient
code, fix those.

Signed-off-by: Ingo Franzki <ifranzki@linux.ibm.com>
Reviewed-by: Joerg Schmidbauer <jschmidb@de.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2025-05-21 15:50:31 +02:00
Marc Hartmayer
ce997c6d01 rust/Makefile: Create a symbolic link to the manpage 'pvimg-create' for genprotimg
Create a symbolic link to the 'pvimg-create' manpage of for 'genprotimg'
during the manpage installation. This helps the users to migrate to
'pvimg create'.

Suggested-by: Jan Höppner <hoeppner@linux.ibm.com>
Reviewed-by: Jan Höppner <hoeppner@linux.ibm.com>
Signed-off-by: Marc Hartmayer <mhartmay@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2025-05-21 15:50:31 +02:00
Marc Hartmayer
b9d1c86613 CHANGELOG.md: Describe the 'genprotimg' changes in v2.36.0
This helps the users and distro-partners to understand what has changed.

Acked-by: Jan Höppner <hoeppner@linux.ibm.com>
Signed-off-by: Marc Hartmayer <mhartmay@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2025-05-21 15:50:31 +02:00
Marc Hartmayer
f9337f0576 genprotimg: manpage: Describe the 'genprotimg' changes in v2.36.0
Describe the breaking changes to genprotimg in the notes section. This
helps the users to adapt to the new behavior.

Fixes: f4cf4ae6eb (rust: Add a new tool called 'pvimg')
Acked-by: Jan Höppner <hoeppner@linux.ibm.com>
Signed-off-by: Marc Hartmayer <mhartmay@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2025-05-21 15:50:31 +02:00
Thomas Richter
a06302e7f0 cpumf/lspai: Add output format option
Add formatted output triggered with command line option --format XXX.
Sort included header files.

Signed-off-by: Thomas Richter <tmricht@linux.ibm.com>
Reviewed-by: Sumanth Korikkar <sumanthk@linux.ibm.com>
Reviewed-by: Maria Eisenhaendler <maria1@de.ibm.com>
Reviewed-by: Jan Höppner <hoeppner@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2025-05-21 15:50:31 +02:00
Jan Höppner
7bc0fb809d zpcictl: Fix command line parsing for invalid options
Currently zpcictl silently accepts incorrect command line options while
it should be displaying an error message for invalid ones. There is a
check for the case when no arguments are supplied, but invalid arguments
or only specifying a device without an action does not display an error
nor give a failure exit code.

Fix this by changing parse_cmdline() to return a boolean indicating if
any arguments were supplied while exiting with an error and message when
invalid options are detected.

Reviewed-by: Niklas Schnelle <schnelle@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2025-05-21 15:50:31 +02:00
Thomas Richter
bc511f84fe cpumf/lshwc: Update man page for lshwc command line option time
Improve man page entry for lshwc command line option -t and --time.

Signed-off-by: Thomas Richter <tmricht@linux.ibm.com>
Reviewed-by: Maria Eisenhaendler <maria1@de.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2025-05-21 15:50:30 +02:00
Ingo Franzki
3a3cea0061 zkey: Fix EP11 secure key reencipher function
Re-enciphering an EP11 secure key may fail with an error message like:
  "zkey-cryptsetup: Failed to re-encipher the secure volume key for device
  '<device>'"
or
  "zkey: Failed to re-encipher '<key>' from CURRENT to NEW master key"
or similar.

The verbose messages show the following debug message:
  "Command XCP_ADM_REENCRYPT failed. rc = 0x20"

This is due to uninitialized variables, which might cause the EP11 admin
request to contain garbage data, causing it to fail with CKR_DATA_INVALID
(0x20).

Fixes: 0be7efc956 ("zkey: Add support for re-enciphering EP11 secure keys")
Fixes: 4e2ebe0370 ("libseckey: Fix re-enciphering of EP11 secure key")
Signed-off-by: Ingo Franzki <ifranzki@linux.ibm.com>
Reviewed-by: Joerg Schmidbauer <jschmidb@de.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2025-05-21 15:50:18 +02:00
Marc Hartmayer
616d2f52f9 rust/Makefile: Install Bash and Zsh completions by default
The Bash and Zsh completion files are useful, therefore install them by
default.

Reviewed-by: Jan Höppner <hoeppner@linux.ibm.com>
Signed-off-by: Marc Hartmayer <mhartmay@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2025-05-21 15:50:18 +02:00
Peter Oberparleiter
6dd671f24e zdev: Make initramfs root configuration more robust
zdev's initramfs hook does not add directives for enabling the root
device to the initrd if the root device is only configured in the
active configuration. As a result, a reboot using the new initrd fails
because the root device cannot be found.

This situation can occur for example when the zdev initramfs hook is
called from within an installer, where devices are only enabled in the
active configuration.

Address this situation by considering both the active and persistent
configuration of the root device during initramfs configuration. In case
the device is configured in both configurations, the persistent
configuration takes precedence.

Reviewed-by: Vineeth Vijayan <vneethv@linux.ibm.com>
Signed-off-by: Peter Oberparleiter <oberpar@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2025-05-06 13:40:36 +02:00
Jakob Naucke
443652dcd2 rust/pv*: Add support for CCK update
The ultravisor supports a new secret type in `add-secret` to update
the customer communication key (CCK). Support this new secret
type (0x16).

[seiden@linux.ibm.com: Constify CCK Header struct usage]
Acked-by: Marc Hartmayer <mhartmay@linux.ibm.com>
Reviewed-by: Steffen Eiden <seiden@linux.ibm.com>
Signed-off-by: Jakob Naucke <naucke@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2025-05-05 17:20:37 +02:00
Jakob Naucke
b11bb64732 rust/pvimg: Add secret flag to allow CCK update
The CCK will be able to be updated by an ultravisor call. For security
reasons, this feature must be enabled at Secure Execution header
creation time.

Because this makes it possible to dump without having set a CCK
earlier, the requirement to specify a CCK when creating an image with
guest dump support is extended to also allow for CCK update instead.

Change a CLI test for this that was a duplicate anyway.

[seiden@linux.ibm.com: Allow --cck & --enable-cck-update simultaneously]
Reviewed-by: Marc Hartmayer <mhartmay@linux.ibm.com>
Reviewed-by: Steffen Eiden <seiden@linux.ibm.com>
Signed-off-by: Jakob Naucke <naucke@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2025-05-05 17:20:37 +02:00
Steffen Eiden
2cd7c7a875 pv: Refactor Null secret
With the recent upgrade to zerocopy 0.8 it is now possible to create
those structs at compile time. This removes the use of a magic array
constant.

Signed-off-by: Steffen Eiden <seiden@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2025-05-05 17:20:37 +02:00
Steffen Eiden
8929d21948 rust: Upgrade zerocopy dependency to 0.8.X
This enables some const constructors, Dataful Enums,
Dynamically Sized Types and much more.

v0.8 introduces breaking changes including, but not limited to:
  - Rename AsBytes to IntoBytes
  - Fine-grain (derive) Traits that need to be implemented on top.
  - Rename FromZeroes to FromZeros
for which this patch takes care of as well.

Also a direct FromZeros derive is no longer necessary. As it is touched
anyways, remove it where appropriate.

See: https://github.com/google/zerocopy/discussions/1680

Signed-off-by: Steffen Eiden <seiden@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2025-05-05 17:20:37 +02:00
Steffen Eiden
f7bba3a687 pvsecret: Deny adding secrets with preexisting IDs
Denies the addition of secrets with an ID that is already stored in the
secret store. This can be overruled by using the force option.

This is considered a breaking change as adding duplicated IDs was
possible without the '--force' option before.

Reviewed-by: Finn Callies <fcallies@linux.ibm.com>
Tested-by: Finn Callies <fcallies@linux.ibm.com>
Signed-off-by: Steffen Eiden <seiden@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2025-05-05 17:20:37 +02:00
Mikhail Zaslonko
5d2c93bc7a zdump/dfi: Get online cpu count based on lowcore_ptr array
In case of missing dfi_cpu_info in the dump, obtain the number of online
cpus of the dumped system based on the number of present entries in the
lowcore_ptr array (if vmcoreinfo is available).

Reviewed-by: Alexander Egorenkov <egorenar@linux.ibm.com>
Signed-off-by: Mikhail Zaslonko <zaslonko@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2025-05-05 17:20:37 +02:00
Mikhail Zaslonko
f1055a62e3 zdump: Remove unused structures and prototypes from dfi.h
Reviewed-by: Alexander Egorenkov <egorenar@linux.ibm.com>
Signed-off-by: Mikhail Zaslonko <zaslonko@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2025-05-05 17:20:37 +02:00
Eduard Shishkin
4c4ab4fd70 zipl_helper.device-mapper: Support mirrors over NVMe devices
\# lsblk
...
nvme0n1     259:0    0   7.3T  0 disk
\_nvme0n1p1 259:4    0     2G  0 part
  \_md0       9:0    0     2G  0 raid1 /mnt
nvme1n1     259:2    0 931.5G  0 disk
\_nvme1n1p1 259:1    0     2G  0 part
  \_md0       9:0    0     2G  0 raid1 /mnt

\# ./zipl_helper.md 9:0

Expected results:

targetbase=259:0
targettype=SCSI
targetblocksize=512
targetoffset=8192
targetbase=259:2
targettype=SCSI
targetblocksize=512
targetoffset=8192

Actual results:

targetbase=259:0
targettype=SCSI
targetblocksize=512
targetoffset=8192
targetbase=259:0   <--- Incorrect!
targettype=SCSI
targetblocksize=512
targetoffset=8192

Make zipl helpers be aware of NVMe devices;
Fix the procedure of partition resolution by including the NVMe case.

Reviewed-by: Stefan Haberland <sth@linux.ibm.com>
Signed-off-by: Eduard Shishkin <edward6@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2025-05-05 17:20:37 +02:00
Eduard Shishkin
34a5e47508 zipl_helper.device-mapper: Fix imprecise is_device_mapper() predicate
Fix is_device_mapper() predicate to not base on checking a hardcoded
major number (253), which not always correct, since on some systems
dm-devices have different majors".

Reviewed-by: Stefan Haberland <sth@linux.ibm.com>
Signed-off-by: Eduard Shishkin <edward6@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2025-05-05 17:20:35 +02:00
Steffen Eiden
6d7cdb9e10 pvsecret: Improve documentation for duplicated secret IDs
Reviewed-by: Finn Callies <fcallies@linux.ibm.com>
Tested-by: Finn Callies <fcallies@linux.ibm.com>
Signed-off-by: Steffen Eiden <seiden@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2025-05-05 17:15:01 +02:00
Steffen Eiden
1fefdecbe7 pvsecret: Support retrieving a secret by index
Allow users to retrieve a secret by its index. This allows users to
extract a specific secret in case of multiple ids.

Reviewed-by: Finn Callies <fcallies@linux.ibm.com>
Tested-by: Finn Callies <fcallies@linux.ibm.com>
Signed-off-by: Steffen Eiden <seiden@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2025-05-05 17:15:01 +02:00
Steffen Eiden
e62cdf9a6e pvsecret: Warn during retrieve if a secret-id is stored multiple times
Warn a user that there are multiple secrets in the secret store with the
same secret id, but retrieve one of them anyways.

This helps users to notice issues before they happen, as retrieve may
not retrieve the expected secret due to duplicated IDs.

Reviewed-by: Finn Callies <fcallies@linux.ibm.com>
Tested-by: Finn Callies <fcallies@linux.ibm.com>
Signed-off-by: Steffen Eiden <seiden@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2025-05-05 17:15:01 +02:00
Steffen Eiden
fa00d1eac1 pvsecret: Warn during add if a secret-id is already in the store
Warn a user that the secret-id to be added is already in the secret
store, but add it anyways.

This helps users to notice issues before they happen, as retrieve may
not retrieve the expected secret due to duplicated IDs.

Reviewed-by: Finn Callies <fcallies@linux.ibm.com>
Tested-by: Finn Callies <fcallies@linux.ibm.com>
Signed-off-by: Steffen Eiden <seiden@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2025-05-05 17:15:01 +02:00
Jan Höppner
0eac97542d libutil/util_file: Handle over-read in util_file_read_fd()
In certain situations util_file_read_fd_buf() might return a larger
buffer than printable characters were read (e.g. a file was padded with
zeros). This can lead to util_file_read_fd() returning NULL with a freed
buffer even though a certain amount of printable characters were read.

This behaviour causes a regression introduced with commit 9efd1df31d
("ipl_tools: Refactor read helper using util_file_read_text_file()") in
ipl_tools were the scp_data sysfs attribute is padded with 0 to fit an 8
byte alignment required by the architecture.

Fix this by comparing the size read with the actual string length and
use the smaller value for further processing.

Fixes: 9efd1df31d ("ipl_tools: Refactor read helper using util_file_read_text_file()")
Tested-by: Jan Polensky <japo@linux.ibm.com>
Reviewed-by: Jan Polensky <japo@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2025-05-05 17:15:01 +02:00
Thomas Richter
b3db52fd16 libutil: Add machine type definition for machines 9175 and 9176
Add machine type definition for machines 9175 and 9176.

Signed-off-by: Thomas Richter <tmricht@linux.ibm.com>
Reviewed-by: Sumanth Korikkar <sumanthk@linux.ibm.com>
Suggested-by: Christian Borntraeger <borntraeger@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2025-05-05 17:15:01 +02:00
Steffen Eiden
a9eb80b1a2 pvattest: Fix successful 'check' evaluation
Invert the polarization of the successful state. The checks are
considered to be successful if there are no issues. Therefore, test for
an empty issue list and not for a non-empty.

Fixes: 697dcc0f6b ("rust/pvattest: Add check command")
Reported-by: Reinhard Buendgen <buendgen@de.ibm.com>
Reviewed-by: Jan Höppner <hoeppner@linux.ibm.com>
Reviewed-by: Marc Hartmayer <mhartmay@linux.ibm.com>
Signed-off-by: Steffen Eiden <seiden@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2025-05-05 17:15:01 +02:00
Marc Hartmayer
e93668ae5b rust/Makefile: Fix file permissions for shell completion files
While at it, preserve the timestamps as well because it's recommended.

See https://docs.fedoraproject.org/en-US/packaging-guidelines/ShellCompletions/.

Fixes: b3055154db ("rust/Makefile: Add 'install-(bash|zsh)-completion' targets")
Reviewed-by: Steffen Eiden <seiden@linux.ibm.com>
Signed-off-by: Marc Hartmayer <mhartmay@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2025-04-16 12:36:56 +02:00
Eduard Shishkin
c68c3cb8b2 ipl_tools/chreipl: Use S390TOOLS_LIBDIR variable
Use S390TOOLS_LIBDIR environment variable to locate the helper
script.

Reviewed-by: Stefan Haberland <sth@linux.ibm.com>
Signed-off-by: Eduard Shishkin <edward6@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2025-04-16 12:36:50 +02:00
Eduard Shishkin
f63c548c2a ipl_tools/chreipl: Support md partitions
Fix the helper name construction procedure to support also the case
when the node is over a regular md-partition:

\# lsblk
dasda                             94:0    0 20.6G  0 disk
`-dasda1                          94:1    0 20.6G  0 part
  `-md0                            9:0    0 20.6G  0 raid1
    |-md0p1                      259:0    0    1M  0 md    /mnt
    `-md0p2                      259:1    0 20.6G  0 md
dasdb                             94:4    0 20.6G  0 disk
`-dasdb1                          94:5    0 20.6G  0 part
  `-md0                            9:0    0 20.6G  0 raid1
    |-md0p1                      259:0    0    1M  0 md    /mnt
    `-md0p2                      259:1    0 20.6G  0 md

\# chreipl /mnt

Actual result:

chreipl: Unsupported device node "/mnt" specified

Expected result:

Re-IPL type: ccw
Device:      0.0.6569
Loadparm:    ""
Bootparms:   ""
clear:       0

Reviewed-by: Stefan Haberland <sth@linux.ibm.com>
Signed-off-by: Eduard Shishkin <edward6@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2025-04-16 12:36:40 +02:00
Eduard Shishkin
1ab2fbb482 zipl_helper.device-mapper: Support dm- over md-devices
Add support for complex dm- over md-devices.
It includes an important particular use case in distro installation
process, when a linear dm-target is built on the top of software
RAID1:

dasda                      94:0    0  20.6G  0 disk
\_dasda1                   94:1    0    20G  0 part
  \_md127                   9:127  0    20G  0 raid1
    \_rhel_a46lp05-root00 253:5    0    20G  0 lvm   /
dasdb                      94:4    0  20.6G  0 disk
\_dasdb1                   94:5    0    20G  0 part
  \_md127                   9:127  0    20G  0 raid1
    \_rhel_a46lp05-root00 253:5    0    20G  0 lvm   /

\ # zipl_helper.device-mapper /

Expected result:

targetbase=94:0
targettype=CDL
targetgeometry=30051,15,12
targetblocksize=4096
targetoffset=4632
targetbase=94:4
targettype=CDL
targetgeometry=30051,15,12
targetblocksize=4096
targetoffset=4632

Actual result (incorrect):

targetbase=9:127
targettype=SCSI
targetblocksize=4096
targetoffset=256

Reviewed-by: Stefan Haberland <sth@linux.ibm.com>
Signed-off-by: Eduard Shishkin <edward6@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2025-04-16 12:35:52 +02:00
Thomas Richter
fed79474e4 cpumf: Convert S390_CPUMF_XXX to util_path_sysfs()
Use util_path_sysfs() to form a fully qualified sysfs file name
for files S390_CPUMF_CF, S390_CPUMF_CFDIAG, S390_CPUMF_SF,
S390_SYSFS_PAI_CRYPTO and S390_SYSFS_PAI_EXT.
No functional change.

Signed-off-by: Thomas Richter <tmricht@linux.ibm.com>
Reviewed-by: Sumanth Korikkar <sumanthk@linux.ibm.com>
Reviewed-by: Jan Höppner <hoeppner@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2025-04-16 12:12:45 +02:00
Thomas Richter
8c1ffd8989 cpumf: Convert S390_CPUMSF_BUFFERSZ to util_path_sysfs()
Use util_path_sysfs() to form a fully qualified sysfs file name
for file S390_CPUMSF_BUFFERSZ.
No functional change.

Signed-off-by: Thomas Richter <tmricht@linux.ibm.com>
Reviewed-by: Sumanth Korikkar <sumanthk@linux.ibm.com>
Reviewed-by: Jan Höppner <hoeppner@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2025-04-16 12:12:45 +02:00
Ingo Franzki
33fc534a81 zkey: Adjust CCA host library version detection for newer CCA versions
Newer CCA versions might report the version string with CSUACFV or CSUACFQ
with keyword STATCCA using a different indicator character after the
version information. Ignore the indication character and the remaining
data entirely. Only the version information as such is of interest.

Signed-off-by: Ingo Franzki <ifranzki@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2025-04-16 12:12:45 +02:00
Thomas Richter
7e832ead9e libcpumf: Convert S390_CPUS_ONLINE to util_path_sysfs()
Use util_path_sysfs() to form a fully qualified sysfs file name
for file S390_CPUS_ONLINE.
No functional change.

Signed-off-by: Thomas Richter <tmricht@linux.ibm.com>
Reviewed-by: Sumanth Korikkar <sumanthk@linux.ibm.com>
Reviewed-by: Jan Höppner <hoeppner@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2025-04-16 12:12:44 +02:00
Thomas Richter
7f0f765bb4 libcpumf: Remove define S390_CPUS_POSSIBLE
This define S390_CPUS_POSSIBLE is not referenced anymore.
Remove it.
No functional change.

Signed-off-by: Thomas Richter <tmricht@linux.ibm.com>
Reviewed-by: Sumanth Korikkar <sumanthk@linux.ibm.com>
Reviewed-by: Jan Höppner <hoeppner@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2025-04-16 12:12:44 +02:00
Jan Polensky
69c7f45bb7 ipl_tools/cmd_lsreipl.c: Refactor to reduce code duplication
Eliminated redundant code to improve clarity and maintainability.
Replaced with existing utility functions from the library.

Signed-off-by: Jan Polensky <japo@linux.ibm.com>
Reviewed-by: Jan Höppner <hoeppner@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2025-04-16 12:12:44 +02:00
Jan Polensky
d843e08828 ipl_tools/ipl_tools.h: Remove redundant extern for functions
Function declarations in header files are implicitly extern, making
the explicit  use of extern unnecessary. Removing it simplifies the
code without changing semantics and behavior.

Signed-off-by: Jan Polensky <japo@linux.ibm.com>
Reviewed-by: Jan Höppner <hoeppner@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2025-04-16 12:12:44 +02:00
Joern Siglen
055c455571 dbginfo.sh: Update for network commands
New Distros are replacing iptables by nftables.
Collecting data of optical interfaces.

Suggested-by: Hendrik Brueckner<brueckner@linux.ibm.com>
Suggested-by: Niklas Schnelle <schnelle@linux.ibm.com>
Reviewed-by: Mario Held <mario.held@de.ibm.com>
Reviewed-by: Niklas Schnelle <schnelle@linux.ibm.com>
Signed-off-by: Hendrik Brueckner <brueckner@linux.ibm.com>
Signed-off-by: Joern Siglen <siglen@de.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2025-04-07 17:42:19 +02:00
Jakob Naucke
ba387af804 rust/pvimg: Macroize flag parsing
Macroize flag parsing to save some repetition and lines.

Reviewed-by: Marc Hartmayer <mhartmay@linux.ibm.com>
Signed-off-by: Jakob Naucke <naucke@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2025-04-07 17:42:18 +02:00
Jakob Naucke
8b9960abe9 rust/pvimg: Fix spelling of Enforcement
Fix spelling of CckExtensionSecretEnforcement and adhere to Rust
capitalisation conventions.

Reviewed-by: Steffen Eiden <seiden@linux.ibm.com>
Reviewed-by: Marc Hartmayer <mhartmay@linux.ibm.com>
Signed-off-by: Jakob Naucke <naucke@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2025-04-07 17:42:18 +02:00
Jakob Naucke
efdadfcdb9 rust/pv*: Unit test flag parsing
Add a unit test for flag parsing.

In order to ease this test, add derive Eq to ControlFlags and
Msb0Flags64. For the same test, add derive Default to
CreateBootImageArgs and the structs used in it. The latter can be
limited to only derive when testing to avoid confusion with any
user-level default.

Acked-by: Steffen Eiden <seiden@linux.ibm.com>
Reviewed-by: Marc Hartmayer <mhartmay@linux.ibm.com>
Signed-off-by: Jakob Naucke <naucke@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2025-04-07 17:42:18 +02:00
Jakob Naucke
ec10654c35 rust/pvimg: Make PCKMO plaintext control flags a constant
The PCKMO plaintext control flags are used in several places as is
and are also to be used in an upcoming unit test. Make them a constant.

Reviewed-by: Steffen Eiden <seiden@linux.ibm.com>
Reviewed-by: Marc Hartmayer <mhartmay@linux.ibm.com>
Reviewed-by: Hendrik Brueckner <brueckner@linux.ibm.com>
Signed-off-by: Jakob Naucke <naucke@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2025-04-07 17:42:18 +02:00
Jakob Naucke
76bddf9715 rust/pvapconfig: Add --unbind option
When the --unbind option is enabled, run with an empty config instead,
effectively unbinding all APQNs.

Reviewed-by: Harald Freudenberger <freude@linux.ibm.com>
Acked-by: Steffen Eiden <seiden@linux.ibm.com>
Signed-off-by: Jakob Naucke <naucke@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2025-04-07 17:42:18 +02:00
Thomas Richter
b4cc30ac10 cpumf/lshwc: Add command line flag for run time
Add a command line flag to specify run time of lshwc command in
terms of days, hours, minutes and seconds. The command line flag
is named -t and has a number as argument followed by one of the
letters 's' for seconds (default), 'm' for minutes, 'h' for hours
and 'd' for days. Default is seconds when no trailing letter is
detected.

Signed-off-by: Thomas Richter <tmricht@linux.ibm.com>
Reviewed-by: Jan Höppner <hoeppner@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2025-04-07 17:42:18 +02:00
Mikhail Zaslonko
9f9dddddd4 zdump: Drop build_arch for s390 DASD dumps
The build_arch field in s390 DASD dump header has originally been used to
indicate whether the dump tool has been built on s390 or s390x system.
Since no other architectures but s390x are supported for Linux on z, do
not process build_arch attribute. Bail out if any build architecture other
than ARCH_64 has been detected in s390_ext or s390mv_ext DASD dump header.
Remove build architecture line from 'zgetdump -i' output:
  Build arch.........: s390x (64 bit)
The man file for zgetdump is updated accordingly.

Signed-off-by: Mikhail Zaslonko <zaslonko@linux.ibm.com>
Reviewed-by: Alexander Egorenkov <egorenar@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2025-04-07 17:42:18 +02:00
Mikhail Zaslonko
f821a3c174 zdump: Drop support of 32-bit dump architecture
- Initialize dump and dump-tool architecture to DFI_ARCH_64 at the start
  of dfi_init() and dt_init() respectively.
- Bail out if any dump architecture other than ARCH_64 has been detected
  in s390_ext or s390mv_ext DASD dump header.
- Remove redundant dfi_arch_set() and dt_arch_set() functions.
- Get rid of l.arch local variables in dfi* and dt* source files and
  drop dfi_arch() function.
- Drop the usage of DFI_ARCH_32 and compeletely remove DFI_ARCH_UNKNOWN.
- Drop special register and lowcore processing functions used
  for DFI_ARCH_32.
- Drop df_s390_from_dfi_arch() and df_s390_to_dfi_arch() funcitons.
- Update the man file for zgetdump.

Signed-off-by: Mikhail Zaslonko <zaslonko@linux.ibm.com>
Reviewed-by: Alexander Egorenkov <egorenar@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2025-04-07 17:42:18 +02:00
Mikhail Zaslonko
ee4cb78dc2 zdump: Drop non-extended multi-volume DASD dump support
Since DASD standalone dumper does not support non-extended s390
multi-volume dumps for years, drop zgetdump support of non-extended s390_mv
DASD dumps and dump-tools:
- Merge dfi_s390mv_ext and dt_s390mv_ext with related dfi_s390mv and
  dt_s390mv counterparts.
- Update DFI and DT vectors of supported dump formats and dump-tools
  removing dfi_s390mv and dt_s390mv entries and keeping dt_s390mv_ext and
  dfi_s390mv_ext ones for s390_mv extended dump and dump-tool accordingly.
- Remove dfi_s390mv.c and dt_s390mv.c sources and update the Makefile.
- Make dfi_s390mv_ext_init() and dfi_s390mv_info() static.
- Remove magic number relevant to non-extended s390_mv DASD dumper.
- Completely drop DASD dumper version 5 case in df_s390_dumper_read().

Note: Since non-extended s390 multi-volume dumps can reside on DASD
partitions only and cannot be produced via DFO interface, we can drop this
dump format entirely.

Signed-off-by: Mikhail Zaslonko <zaslonko@linux.ibm.com>
Reviewed-by: Alexander Egorenkov <egorenar@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2025-04-07 17:42:18 +02:00
Mikhail Zaslonko
6f82c5792a zdump: Drop support of non-extended single volume DASD dumpers
Since DASD standalone dumper does not support non-extended s390 dump
format for years, drop zgetdump support of non-extended s390 single volume
DASD dump-tool:
- Merge dt_390sv_ext.c and dt_s390sv.c counterparts.
- Update DT vector of supported dump tools removing dt_s390sv entry and
  keeping dt_s390sv_ext entry for s390 single volume extended dump tool.
- Remove dt_s390sv.c source and update the Makefile accordingly.
- Remove magic constants relevant to non-extended s390 single volume DASD
  dumper.
- Drop s390 single volume DASD dumper version 5 in df_s390_dumper_read().

Note: We still need support of non-extended s390 dump format since such
output dump files can be produced by 'zgetdump -f s390' via dfo_s390. Thus,
both dfi_s390 and dfi_s390_ext DFI vector entries remain in order to
process s390 dumps files as well as s390_ext dumps on the DASD partition.

Signed-off-by: Mikhail Zaslonko <zaslonko@linux.ibm.com>
Reviewed-by: Alexander Egorenkov <egorenar@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2025-04-07 17:42:18 +02:00
Mikhail Zaslonko
2c5d4073a7 zdump: Drop support of obsolete dumps and dumpers
Drop support of obsolete dump and dump-tool versions (single-volume DASD,
FBA and Tape) in order to simplify zgetdump logic:
- CCW dumpers written in assembler instructions as well as stage2 dumpers
  of size less than 0x3000 (STAGE2_DUMPER_SIZE_V1 or STAGE2_DUMPER_SIZE_V2)
  haven't been used for years. Remove its traces completely as a cleanup.
  Keep the last version (version 5) of non-extended DASD dumper as well as
  newer extended DASD dumpers.
- Rename STAGE2_DUMPER_SIZE_V3 and STAGE2_DUMPER_SIZE_ZLIB constants.
- Drop support of non-extended s390 dumps of version < 5. Dump files
  of s390 format version 5 can be still produced by zgetdump (dfo_s390).
- Drop excessive dump version checking in df_s390_cpu_info_add() and
  df_s390_hdr_add() considering that obsolete s390 dumps of version lower
  than 5 no longer supported.
- Use cpu_cnt field in s390 dump header instead of the s390 dump version to
  indicate no cpu info available (DFI_CPU_CONTENT_NONE) for dfo_s390.
- Make df_s390_dumper_read() return error code upon unknown dumper
  version/magic detection.

Signed-off-by: Mikhail Zaslonko <zaslonko@linux.ibm.com>
Reviewed-by: Alexander Egorenkov <egorenar@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2025-04-07 17:42:18 +02:00
Jan Polensky
9efd1df31d ipl_tools: Refactor read helper using util_file_read_text_file()
Replace read_str() helper in ipl_tools/system.c with
util_file_read_text_file() to enhance maintainability and reduce
code duplication.

Additionally, allocate the buffer dynamically instead of using
fixed-size buffer to prevent potential overflows and data loss.

Signed-off-by: Jan Polensky <japo@linux.ibm.com>
Reviewed-by: Jan Höppner <hoeppner@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2025-04-07 17:42:18 +02:00
Jan Polensky
6f53d7d3cd ipl_tools: Remove unused header
Optimize dependency management by reducing header inclusions. This
reduces hidden dependencies, improves modularity, and enhances
compilation performance.

Signed-off-by: Jan Polensky <japo@linux.ibm.com>
Reviewed-by: Jan Höppner <hoeppner@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2025-04-07 17:42:18 +02:00
Jan Polensky
92486dfcaf ipl_tools/proc.{c,h}: Restrict header scope
Optimize dependency management by limiting header inclusions to
implementation files where possible. This reduces hidden dependencies,
improves modularity, and enhances compilation performance.

Signed-off-by: Jan Polensky <japo@linux.ibm.com>
Reviewed-by: Jan Höppner <hoeppner@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2025-04-07 17:42:18 +02:00
Jan Polensky
29007541e8 ipl_tools/system.c: Cleanup unused function strlow()
It is no longer referenced in the code.

Signed-off-by: Jan Polensky <japo@linux.ibm.com>
Reviewed-by: Jan Höppner <hoeppner@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2025-04-07 17:42:18 +02:00
Thomas Richter
2278f112ba cpumf/lshwc: Add support for delta counter value display
Add a flag to display the counter value in form of a delta
value. This format shows the increase of that counter value
in comparison to the previous readout.
The first line is the base for the delta calculation and always
shows 'Total'. The remaining lines show 'Delta' in the third
column.

 # ./lshwc -d -i 3 -l 5 -x -s :p
 Date,Time,CPU,P32,P33
 2024-11-27,15:45:55,Total,d7b,172
 2024-11-27,15:45:58,Delta,2be403,2d58bb
 2024-11-27,15:46:01,Delta,43e3b,22c41
 2024-11-27,15:46:04,Delta,58e3a,35319
 2024-11-27,15:46:07,Delta,5080e,2b81c
 #

Signed-off-by: Thomas Richter <tmricht@linux.ibm.com>
Reviewed-by: Jan Höppner <hoeppner@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2025-04-07 17:42:18 +02:00
Eduard Shishkin
c39722aff2 zipl/src: Implement '--dry-run' option for ngdumps
Use the directory specified by the shell environment variable TMPDIR
for temporary objects creation and ngdump job simulation. If TMPDIR is
not set, then use "/tmp" for the mentioned purposes;

While running ngdump job in 'dry-run' mode:
. Don't format/mount the target dump device. Instead, create the
  bootmap file and the meta-file at the temporary mount point without
  mounting anything to it. Thus, the mentioned files to be acrually
  created in the "proxy" file system owning the temporary mount point;
. Retrieve base disk info from the read-only dump device and
  complete that info with the block size of the proxy file system;

Separate the steps on retrieving/setting file system block size
into a dedicated procedure;

Use definitions instead of hardcoded file names;

Fix a bug in an error path (accessing freed memory);

Make misc_open_simulate() and misc_open_exclusive() static;

Update man pages with the requirements on the system environment
(resources) for ngdump job being executed in dry-run mode;

Provide hints for user (in stderr) in case when ngdump job in dry-run
mode failed due to inappropriate system environment.

Tested-by: Mikhail Zaslonko <zaslonko@linux.ibm.com>
Reviewed-by: Mikhail Zaslonko <zaslonko@linux.ibm.com>
Signed-off-by: Eduard Shishkin <edward6@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2025-04-07 17:42:18 +02:00
Eduard Shishkin
1e8edb6524 zipl/src: Implement --dry-run option for multi-volume DASD dumps
Tested-by: Mikhail Zaslonko <zaslonko@linux.ibm.com>
Reviewed-by: Mikhail Zaslonko <zaslonko@linux.ibm.com>
Signed-off-by: Eduard Shishkin <edward6@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2025-04-07 17:42:18 +02:00
Eduard Shishkin
bd5f03d1c4 zipl/src: implement --dry-run option for device-based DASD dumps
Tested-by: Mikhail Zaslonko <zaslonko@linux.ibm.com>
Reviewed-by: Mikhail Zaslonko <zaslonko@linux.ibm.com>
Signed-off-by: Eduard Shishkin <edward6@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2025-04-07 17:42:18 +02:00
Eduard Shishkin
f8ccd56bf1 zipl/src: implement --dry-run option for device-based SCSI dumps
Tested-by: Mikhail Zaslonko <zaslonko@linux.ibm.com>
Reviewed-by: Mikhail Zaslonko <zaslonko@linux.ibm.com>
Signed-off-by: Eduard Shishkin <edward6@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2025-04-07 17:42:18 +02:00
Eduard Shishkin
72218c5bc3 zipl/src: Introduce misc_open_simulate() and misc_open_device()
Introduce misc_open_simulate() to mark individual files as "opened
for write simulation".
Introduce misc_open_device() to open a file either in "usual", or
in "simulation" mode, depending on the passed argument

Tested-by: Mikhail Zaslonko <zaslonko@linux.ibm.com>
Reviewed-by: Mikhail Zaslonko <zaslonko@linux.ibm.com>
Signed-off-by: Eduard Shishkin <edward6@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2025-04-07 17:42:18 +02:00
Eduard Shishkin
b94566048b zipl/src: Introduce 'write simulation' mode for individual files
Introduce a write simulation operation, which doesn't write actual
data and just updates the current position in the file.
This allows to emulate block lists for files located on raw devices
(not formatted with a file system). This is used by the next patches
in the series to implement '--dry-run' zipl option for dumps of all
types (not only ngdumps).

Introduce a 'misc file descriptor', which allows to mark individual
files as "opened in a simulation mode".

Whenever bulding a bootmap file, use either real write, or write
simulation depending on the mode set in the 'misc file descriptor'.

Tested-by: Mikhail Zaslonko <zaslonko@linux.ibm.com>
Reviewed-by: Mikhail Zaslonko <zaslonko@linux.ibm.com>
Signed-off-by: Eduard Shishkin <edward6@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2025-04-07 17:42:18 +02:00
Eduard Shishkin
85a232a711 zipl/src: Add missed removal of temporary ramdisk
Remove temporary ramdisk created by zipl_helper.prepare-ngdump in
dump job session

Tested-by: Mikhail Zaslonko <zaslonko@linux.ibm.com>
Reviewed-by: Mikhail Zaslonko <zaslonko@linux.ibm.com>
Signed-off-by: Eduard Shishkin <edward6@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2025-04-07 17:42:18 +02:00
Thomas Richter
931c61f141 libcpumf/libcpumf_cpuset: Use util_file_read_line()
Use function util_file_read_line() to read the file contents
of sysfs file named /sys/devices/system/cpu/online.
It has only one line.

Signed-off-by: Thomas Richter <tmricht@linux.ibm.com>
Reviewed-by: Sumanth Korikkar <sumanthk@linux.ibm.com>
Reviewed-by: Jan Höppner <hoeppner@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2025-04-07 17:42:18 +02:00
Mete Durlu
253ca8a30e cpuplugd: Allow cpu hotplugging on systems without polarization
Cpuplugd checks for system polarization before adjusting cpu hotplug
states. Currently, if the sysfs attribute for polarization is not
present cpuplugd does not make any adjustments. Add a new polarization
type PLR_NONE to reflect systems with no polarization state and allow
cpuplugd to adjust cpu hotplugging.

Signed-off-by: Mete Durlu <meted@linux.ibm.com>
Reviewed-by: Gerald Schaefer <gerald.schaefer@linux.ibm.com>
Reviewed-by: Jan Höppner <hoeppner@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2025-04-07 17:42:18 +02:00
Thomas Richter
829fc3b294 cpumf/lshwc: Man page updates for new supported flags
Update and add description for new flags -x -X -s -H for command
lshwc.

Signed-off-by: Thomas Richter <tmricht@linux.ibm.com>
Reviewed-by: Maria Eisenhaendler <maria1@de.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2025-04-07 17:42:18 +02:00
Thomas Richter
3c5fb0f885 cpumf/lshwc: Free buffer allocated with malloc
Free all buffers allocated with malloc before program exit.
Handle memory leak in libcpumf_cpuset().

Signed-off-by: Thomas Richter <tmricht@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2025-04-07 17:42:18 +02:00
Thomas Richter
9df45d4256 cpumf/lshwc: Allocate memory for per-cpu counter
Allocate the memory required to save the per CPU CPUMF counter value
at the beginning of program execution when the involved number of
CPUs is known. This a bit faster then checking for memory allocation
each time a counter value is retrieved.

Signed-off-by: Thomas Richter <tmricht@linux.ibm.com>
Reviewed-by: Hendrik Brueckner <brueckner@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2025-04-07 17:42:18 +02:00
Thomas Richter
283ae4dd2f cpumf/lshwc: Add flag to hide undefined counters of each counter set
Add command line option -H to hide counter value numbers from counters
which are not defined in a counter set. They are usually all zero and
are of no interest.

Signed-off-by: Thomas Richter <tmricht@linux.ibm.com>
Reviewed-by: Hendrik Brueckner <brueckner@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2025-04-07 17:42:18 +02:00
Thomas Richter
718907d9ba cpumf/lswhc: Add short names to lshwc output
Add option -s or --short to display the header using a short
counter name. With this option the counter symbol names, which can be
very long as in IDCW_ON_DRAWER_DRAWER_HIT are replaced by a shorter
name. That name consists of an abbrevation for the counter set
this counter belongs to and the counter number in that set.

The abbrevations are:
B --> Basic counter set
P --> Problem state counter set
C --> Crypto counter set
E --> Extended counter set
M --> MT_Diagnostic counter set
U --> Undefined counter.

Display E165 for counter name IDCW_ON_DRAWER_DRAWER_HIT
which is counter number 165 from the extended counter set.

Signed-off-by: Thomas Richter <tmricht@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2025-04-07 17:42:18 +02:00
Thomas Richter
26cf3ec769 cpumf: Move defines from source to header files
Move defines from a source file lscpumf.c to the header
file in the include directory to be reused in a follow on
patch. Also reshuffle the list of defines in proper
sequence of increasing numbers.

Signed-off-by: Thomas Richter <tmricht@linux.ibm.com>
Reviewed-by: Hendrik Brueckner <brueckner@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2025-04-07 17:42:18 +02:00
Thomas Richter
7150da3e4f cpumf/lshwc: Add flags to display counter values in hex
Add flags to display all counter values in hexadecimal
format with and without leading 0x indicator.

Signed-off-by: Thomas Richter <tmricht@linux.ibm.com>
Reviewed-by: Hendrik Brueckner <brueckner@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2025-04-07 17:42:18 +02:00
Joern Siglen
f946ce27f7 dbginfo.sh: Add missing config data and logs
Get a summary of chrony ntp info.
Add config directories for audit and selinux.
Get dmesg log from crash directories.

Reviewed-by: Michael Storzer <MSTORZER@de.ibm.com>
Signed-off-by: Joern Siglen <siglen@de.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2025-04-07 17:42:18 +02:00
Jakob Naucke
4942504a9a rust/pv*: Split pvapconfig::ap to pv_core::apdevice
Move appropriate parts into new pv_core::ap module.

Signed-off-by: Jakob Naucke <naucke@linux.ibm.com>
Reviewed-by: Steffen Eiden <seiden@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2025-03-24 16:31:45 +01:00
Jakob Naucke
e1245ed4e5 rust/pv*: Use pv_core::Error in pvapconfig
For the parts of pvapconfig::ap to be moved to pv_core, use
pv_core::Result<a>, i.e. Result<a, pv_core::Error>, instead of
Result<a, String>.

Signed-off-by: Jakob Naucke <naucke@linux.ibm.com>
Reviewed-by: Steffen Eiden <seiden@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2025-03-24 16:31:45 +01:00
Jakob Naucke
272eb5ad8c rust/pvapconfig: Implement TryFrom for Apqn
Move assembling APQN struct out of gather_apqns and print non-fatal
warnings afterwards. Allows for moving Apqn to pv_core including this
TryFrom.

Signed-off-by: Jakob Naucke <naucke@linux.ibm.com>
Reviewed-by: Steffen Eiden <seiden@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2025-03-24 16:31:45 +01:00
Jakob Naucke
543d41cb59 rust/pvapconfig: No stderr in ApqnInfo
ApqnInfo is to be moved to pv_core. Move warnings about missing
master/wrapping keys to gather_apqns to avoid stderr output in library
code.

Signed-off-by: Jakob Naucke <naucke@linux.ibm.com>
Reviewed-by: Steffen Eiden <seiden@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2025-03-24 16:31:45 +01:00
Jakob Naucke
8619f14c0a rust/pvapconfig: Add doc-strings to ap
Add doc-strings and Debug derives.

Signed-off-by: Jakob Naucke <naucke@linux.ibm.com>
Reviewed-by: Steffen Eiden <seiden@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2025-03-24 16:31:45 +01:00
Jakob Naucke
56aa3e2d26 rust/pv_core: Use file_error!
Macro already exists, but wasn't used in some places where it can
reduce lines.

Signed-off-by: Jakob Naucke <naucke@linux.ibm.com>
Reviewed-by: Steffen Eiden <seiden@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2025-03-24 16:31:45 +01:00
Jakob Naucke
a9fbfcc7ae rust/pv_core: Fix error type of misc::read_file
Was Write, should be Read.

Fixes: 9b51b8b882 ("rust/pv: Refactor pv crate")
Signed-off-by: Jakob Naucke <naucke@linux.ibm.com>
Reviewed-by: Marc Hartmayer <mhartmay@linux.ibm.com>
Reviewed-by: Steffen Eiden <seiden@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2025-03-24 16:31:45 +01:00
Jakob Naucke
a32adbf5d8 rust/pvapconfig: Remove sysfs_{read,write}_*
Remove sysfs_{read,write}_{i32,string} in favor of pv_core tools,
which support error contexts, making longer error handlings unnecessary.

Signed-off-by: Jakob Naucke <naucke@linux.ibm.com>
Acked-by: Steffen Eiden <seiden@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2025-03-24 16:31:45 +01:00
Jakob Naucke
f9050799bb rust/pv_core: Add misc::read_file_string
Like read_file, this is a function that reads from a file with error
handling conveniences, but that reads to a trimmed string instead of a
byte vector.

Signed-off-by: Jakob Naucke <naucke@linux.ibm.com>
Reviewed-by: Steffen Eiden <seiden@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2025-03-24 16:31:45 +01:00
Jakob Naucke
66a10d5e3e rust/pv*: Move u8_to_hexstring to pv_core
Move pvapconfig::helper::u8_to_hexstring to pv_core::utils::encode_hex.
Discard pvapconfig::helper::hexstring_to_u8 in favor of
pv_core::utils::decode_hex.

Signed-off-by: Jakob Naucke <naucke@linux.ibm.com>
Reviewed-by: Steffen Eiden <seiden@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2025-03-24 16:31:44 +01:00
Jakob Naucke
152f446d76 rust: Fix cargo clippy findings
Fix the new `cargo clippy` findings for updated 1.84 toolchain.

Signed-off-by: Jakob Naucke <naucke@linux.ibm.com>
Reviewed-by: Steffen Eiden <seiden@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2025-03-24 16:31:44 +01:00
Joern Siglen
646e080217 dbginfo.sh: Fix typos in console messages
Signed-off-by: Joern Siglen <siglen@de.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2025-03-19 16:35:05 +01:00
Jan Höppner
3fd8c48245 pvsecret: Fix incorrect font escape sequences in man page
There are a couple place with incorrectly used font escape sequences.

$ man --warnings -E UTF-8 -l -Tutf8 -Z rust/pvsecret/man/pvsecret.1

troff:<standard input>:107: warning: cannot select font 'F'
troff:<standard input>:107: warning: cannot select font 'E'

$ man --warnings -E UTF-8 -l -Tutf8 -Z rust/pvsecret/man/pvsecret-verify.1
troff:<standard input>:119: warning: cannot select font 'F'
troff:<standard input>:119: warning: cannot select font 'F'
troff:<standard input>:119: warning: cannot select font 'F'
troff:<standard input>:119: warning: cannot select font 'F'
troff:<standard input>:119: warning: cannot select font 'F'
troff:<standard input>:131: warning: cannot select font 'F'

Use the correct escape sequences and regenerate the man pages.

Github-ID: https://github.com/ibm-s390-linux/s390-tools/issues/170
Reviewed-by: Steffen Eiden <seiden@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2025-03-19 16:35:05 +01:00
Jan Höppner
c93b74b527 pvattest: Fix various man page issues
The pvattest man pages are generated mostly from the rust doc entries
within the tool. Some escape sequences and whatis-entries are incorrect
as found by different linter:

$ man --warnings -E UTF-8 -l -Tutf8 -Z ./rust/pvattest/man/pvattest-create.1
troff:<standard input>:23: warning: macro 'create'' not defined

$ lexgrog -w -m rust/pvattest/man/pvattest-verify.1
rust/pvattest/man/pvattest-verify.1: parse failed

Fix this by regenerating the man pages with correct escape sequences and
whatis-entries.

Github-ID: https://github.com/ibm-s390-linux/s390-tools/issues/170
Reviewed-by: Steffen Eiden <seiden@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2025-03-19 16:35:05 +01:00
Jan Höppner
4fe6025b18 chreipl-fcp-mpath: Regenerate man page with new pandoc version
Older versions of pandoc (<3.1.7) can sometimes produce incorrect man
page macros [1][2]. The chreipl-fcp-mpath man page had a few of those:

$ man --warnings -E UTF-8 -l -Tutf8 -Z chreipl-fcp-mpath/chreipl-fcp-mpath.7
troff:<standard input>:5: warning: cannot select font 'CB'
troff:<standard input>:160: warning: cannot select font 'C'
troff:<standard input>:174: warning: cannot select font 'C'
troff:<standard input>:183: warning: cannot select font 'C'

Regenerate the man page using pandoc 3.1.11 to fix those issues.

[1] https://github.com/jgm/pandoc/issues/9020
[2] https://github.com/bin-cli/bin-cli/issues/44
Github-ID: https://github.com/ibm-s390-linux/s390-tools/issues/170
Reviewed-by: Steffen Eiden <seiden@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2025-03-19 16:35:05 +01:00
Jan Höppner
73b6a684ec vmur: Remove undefined macro SP from man page
The vmur man page uses the .SP macro a lot. This is not a valid macro
and it is not defined anywhere.

$ man --warnings -E UTF-8 -l -Tutf8 -Z vmur/vmur.8
troff:<standard input>:92: warning: macro 'SP' not defined

Remove it entirely as the initial intention is unclear and the rendered
man page doesn't change.

Github-ID: https://github.com/ibm-s390-linux/s390-tools/issues/170
Reviewed-by: Steffen Eiden <seiden@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2025-03-19 16:35:05 +01:00
Jan Höppner
40d9df5cc8 opticsmon: Add missing macro definition in man page
The .CL macro was copied from another man page but it is not defined in
the opticsmon man page:

$ man --warnings -E UTF-8 -l -Tutf8 -Z opticsmon/opticsmon.8
troff:<standard input>:70: warning: macro 'CL' not defined

Add the missing definition to fix this.

Github-ID: https://github.com/ibm-s390-linux/s390-tools/issues/170
Reviewed-by: Steffen Eiden <seiden@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2025-03-19 16:35:05 +01:00
Jan Höppner
9ebb8da624 zdev: Fix spelling error in man page italic macro
The SITE-ID parameter in the --site section is supposed to be formatted
as italic. Instead of .I .ID is used leading to misinterpretation and
the mentioning of the parameter missing in the rendered man page.

Found via:
$ man --warnings -E UTF-8 -l -Tutf8 -Z zdev/man/lszdev.8
troff:<standard input>:455: warning: macro 'ID' not defined

Use .I correctly to fix the issue.

Github-ID: https://github.com/ibm-s390-linux/s390-tools/issues/170
Reviewed-by: Steffen Eiden <seiden@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2025-03-19 16:35:05 +01:00
Jan Höppner
589ce29d41 lsqeth: Fix incorrectly used font change sequence in man page
The lsqeth man page uses the font change sequence \f without a
parameter, which is not allow:

$ man --warnings -E UTF-8 -l -Tutf8 -Z zconf/qeth/lsqeth.8
troff:<standard input>:13: error: a space character is not allowed in an escape sequence parameter
troff:<standard input>:13: error: a space character is not allowed in an escape sequence parameter
troff:<standard input>:13: error: a space character is not allowed in an escape sequence parameter
troff:<standard input>:13: error: a space character is not allowed in an escape sequence parameter
troff:<standard input>:13: error: a newline character is not allowed in an escape sequence parameter

Fix it by using proper espace sequence to format the specific parts of
the text correctly. This also brings the synopsis a bit more in line
with other qeth tools.

Github-ID: https://github.com/ibm-s390-linux/s390-tools/issues/170
Reviewed-by: Steffen Eiden <seiden@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2025-03-19 16:35:05 +01:00
Jan Höppner
b343672eb8 cpacfinfo: Fix missing escape sequence in man page
For the --available option the description contains a line starting with
". If", which is intended to be a punctuation and start of a new
sentence but is misinterpreted by groff as a macro. As a result, the
rendered paragraph is incorrectly displayed.

$ man --warnings -E UTF-8 -l -Tutf8 -Z rust/cpacfinfo/man/cpacfinfo.1
troff:<standard input>:128: warning: macro 'If' not defined

Even though it's not consistent with the rest of the man page, use the
font change escape sequence and surround the specific bold formatted
parts to fix this issue and correctly display the sentence.

Github-ID: https://github.com/ibm-s390-linux/s390-tools/issues/170
Reviewed-by: Steffen Eiden <seiden@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2025-03-19 16:35:05 +01:00
Jan Höppner
df47036f01 zcryptstats: Remove beginning paragraph in man page
A single paragraph opening .TP macro at the end of the man page is
causing errors found with:
$ man --warnings -E UTF-8 -l -Tutf8 -Z zconf/zcrypt/zcryptstats.8
troff: error: automatically ending diversion 'an-div' on exit

It also causes the footer of the man page not rendering correctly.
Remove the macro. While at it, remove whitespace damage as well.

Github-ID: https://github.com/ibm-s390-linux/s390-tools/issues/170
Reviewed-by: Ingo Franzki <ifranzki@linux.ibm.com>
Reviewed-by: Steffen Eiden <seiden@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2025-03-19 16:35:04 +01:00
Jan Polensky
5febec5f44 zkey/kmip/zkey-kmip.1: Fix typos in man page
Signed-off-by: Jan Polensky <japo@linux.ibm.com>
Reviewed-by: Jan Höppner <hoeppner@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2025-03-19 16:35:04 +01:00
Jan Polensky
84f44dd20f rust/pvapconfig/man/pvapconfig.1: Fix typos in man page
Signed-off-by: Jan Polensky <japo@linux.ibm.com>
Reviewed-by: Jan Höppner <hoeppner@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2025-03-19 16:35:04 +01:00
Jan Polensky
d86dd0658f zpcictl/zpcictl.8: Fix typo in man page
Signed-off-by: Jan Polensky <japo@linux.ibm.com>
Reviewed-by: Jan Höppner <hoeppner@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2025-03-19 16:35:04 +01:00
Jan Polensky
00c36baee0 zconf/lsluns.8: Fix typo in man page
Signed-off-by: Jan Polensky <japo@linux.ibm.com>
Reviewed-by: Jan Höppner <hoeppner@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2025-03-19 16:35:04 +01:00
Jan Polensky
a40519fd16 scripts/dbginfo.sh.8: Fix typos in man page
Signed-off-by: Jan Polensky <japo@linux.ibm.com>
Reviewed-by: Jan Höppner <hoeppner@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2025-03-19 16:35:04 +01:00
Jan Polensky
b836b4fb83 cpumf/man/lscpumf.8: Fix typo in man page
Signed-off-by: Jan Polensky <japo@linux.ibm.com>
Reviewed-by: Jan Höppner <hoeppner@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2025-03-19 16:35:04 +01:00
Mete Durlu
1ab819cd81 zmemtopo: Add man page
Add man page with tool description and usage examples.

Signed-off-by: Mete Durlu <meted@linux.ibm.com>
Reviewed-by: Jan Höppner <hoeppner@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2025-03-19 16:35:04 +01:00
Mete Durlu
ee50f20c11 zmemtopo: Add parseable format
Allow users to fetch zmemtopo data in parse-able formats, such as
csv, json, json-seq and pairs.

Signed-off-by: Mete Durlu <meted@linux.ibm.com>
Reviewed-by: Jan Höppner <hoeppner@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2025-03-19 16:35:04 +01:00
Mete Durlu
0f17fe5183 s390-tools: Introduce new tool zmemtopo
s390 is able to provide memory topology information of the current CEC
via a new userspace-kernel interface. zmemtopo is a tool to convert the
raw data into human readable form.

Usage: zmemtopo [OPTIONS]

Display CEC memory topology of allocated memory increments.

OUTPUT FORMAT OPTIONS
 -l, --level NESTING_LEVEL  Set the topology display depth to NESTING_LEVEL
 -f, --full                 Display tree view with padded elements
 -r, --reverse              Reverse tree view hierarchy direction
 -t, --table                Use table view to display topology
 -s, --sort FIELD           Sort view by field
                            (nr, lpar, size)
 -i, --ascii                Use only ASCII characters

GENERAL OPTIONS
 -h, --help                 Print this help, then exit
 -v, --version              Print version information, then exit

Upon calling zmemtopo displays available topology level's structure
and memory increments defined on each topology location.

Ex:
$ zmemtopo
LPAR/LEVEL     SIZE
LPAR003          8G
└LEVEL4_0        8G
  ├LEVEL3_0      2G
  ├LEVEL3_1      2G
  ├LEVEL3_2      2G
  └LEVEL3_3      2G
LPAR005          8G
└LEVEL4_1        8G
  ├LEVEL3_0      2G
  ├LEVEL3_1      2G
  ├LEVEL3_2      2G
  └LEVEL3_3      2G
LPAR006          8G
└LEVEL4_3        8G
...

Signed-off-by: Mete Durlu <meted@linux.ibm.com>
Reviewed-by: Jan Höppner <hoeppner@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2025-03-19 16:35:04 +01:00
Jan Polensky
650e2da843 ipl_tools/cmd_lsreipl.c: Add secure boot state to output
The actual secure boot state ensures that a Linux instance has loaded
only trusted, signed software. During boot component signatures are
verified. If secure boot is enabled, unsigned or unverifiable components
prevent booting. Since Secure Boot on IBM Z is always triggered by the
hypervisor, its state is merely observable by the active system.

Secure boot: 1 - Linux booted secure (only trusted sources)
Secure boot: 0 - Booted from any source without verification

	$ dmesg -t | grep Secure-IPL
	setup: Linux is running with Secure-IPL enabled

	$ cat /sys/kernel/security/lockdown
	none [integrity] confidentiality

	$ lsreipl
	Re-IPL type: fcp
	WWPN:        0x500507630710572c
	LUN:         0x4022409600000000
	Device:      0.0.1908
	bootprog:    0
	br_lba:      0
	Loadparm:    ""
	Bootparms:   ""
	Secure boot: 1

References:
* https://www.ibm.com/docs/en/linux-on-systems?topic=using-verifying-secure-boot
* https://www.ibm.com/docs/en/linux-on-systems?topic=introduction-requirements
Signed-off-by: Jan Polensky <japo@linux.ibm.com>
Reviewed-by: Jan Höppner <hoeppner@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2025-03-19 16:34:52 +01:00
Jan Polensky
426311f440 ipl_tools/cmd_chreipl.c: Replace /sys mount point with util_path_sysfs
The default sysfs path is always set to '/sys' unless the SYSFS_ROOT
environment variable is defined.

To address security concerns, secure_getenv() is used within
util_path_sysfs() to protect against malicious values in SYSFS_ROOT.
Additionally, constructing the sysfs path dynamically in an allocated
buffer, rather than using a fixed-size buffer, helps prevent potential
buffer overflows.

These modifications also significantly improve testability by allowing
sysfs read and write operations to be redirected to an alternative file
path, which enables testing without affecting the active system state.

Signed-off-by: Jan Polensky <japo@linux.ibm.com>
Reviewed-by: Jan Höppner <hoeppner@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2025-03-19 16:34:52 +01:00
Jan Polensky
5333085c55 ipl_tools/ccw.c: Cleanup obsolete ccw_busid_get_sysfs_old()
The function is no longer required since the removal of the PHYSDEVPATH
entry in /sys/block/%s/uevent with commit 39aba963d937 ("driver core:
remove CONFIG_SYSFS_DEPRECATED_V2 but keep it for block devices") in
2010.

Signed-off-by: Jan Polensky <japo@linux.ibm.com>
Reviewed-by: Jan Höppner <hoeppner@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2025-03-19 16:34:52 +01:00
Jan Polensky
4c301d47dd ipl_tools/cmd_lsshut.c: Replace /sys mount point with util_path_sysfs
The default sysfs path is always set to '/sys' unless the SYSFS_ROOT
environment variable is defined.

To address security concerns, secure_getenv() is used within
util_path_sysfs() to protect against malicious values in SYSFS_ROOT.
Additionally, constructing the sysfs path dynamically in an allocated
buffer, rather than using a fixed-size buffer, helps prevent potential
buffer overflows.

These modifications also significantly improve testability by allowing
sysfs read operations to be redirected to an alternative file path,
which enables testing without affecting the active system state.

Signed-off-by: Jan Polensky <japo@linux.ibm.com>
Reviewed-by: Jan Höppner <hoeppner@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2025-03-19 16:34:52 +01:00
Jan Polensky
aea2ba8728 ipl_tools/cmd_lsreipl.c: Construct sysfs paths with util_path_sysfs()
The default sysfs path is always set to '/sys' unless the SYSFS_ROOT
environment variable is defined.

To address security concerns, secure_getenv() is used within
util_path_sysfs() to protect against malicious values in SYSFS_ROOT.
Additionally, constructing the sysfs path dynamically in an allocated
buffer, rather than using a fixed-size buffer, helps prevent potential
buffer overflows.

These modifications also significantly improve testability by allowing
sysfs read operations to be redirected to an alternative file path,
which enables testing without affecting the active system state.

Signed-off-by: Jan Polensky <japo@linux.ibm.com>
Reviewed-by: Jan Höppner <hoeppner@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2025-03-19 16:34:52 +01:00
Jan Polensky
594a554e56 ipl_tools/cmd_chreipl.c: Fix typo errrors->error
Fix typo in comment also helps to prevent pre-commit hook issues, w/o
functional changes or changes in behavior.

Signed-off-by: Jan Polensky <japo@linux.ibm.com>
Reviewed-by: Jan Höppner <hoeppner@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2025-03-19 16:34:52 +01:00
Jan Polensky
a0c3a74019 ipl_tools: Remove empty lines
Remove unnecessary empty lines to prevent pre-commit hook issues, w/o
functional changes or changes in behavior.

Signed-off-by: Jan Polensky <japo@linux.ibm.com>
Reviewed-by: Jan Höppner <hoeppner@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2025-03-19 16:34:52 +01:00
Ingo Franzki
efdd34a56c zkey-cryptsetup: Add new 'convert' command
The 'convert' command converts a LUKS2 volume that uses a clear volume key
and the 'aes' cipher to use a secure volume key and the 'paes' cipher.

Optionally, the volume can use the integrity option with LUKS2 using a
clear key integrity key.

Signed-off-by: Ingo Franzki <ifranzki@linux.ibm.com>
Reviewed-by: Jorg Schmidbauer <jschmidb@de.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2025-03-19 16:34:52 +01:00
Ingo Franzki
c80a0c8af0 zkey-cryptsetup: Add '--volume-key-file' as alias for '--master-key-file'
Starting with cryptsetup version 2.5.0 the luksFormat command accepts
option '--volume-key-file' to specify the volume key file. Option
'--master-key-file' is still accepted as an alias to '--volume-key-file'.

Follow this pattern with the zkey-cryptsetup tool and also accept both
options '--volume-key-file' and '--master-key-file'. The short option
form stays '-m' for compatibility reasons (cryptsetup does not have a
short option for that).

Signed-off-by: Ingo Franzki <ifranzki@linux.ibm.com>
Reviewed-by: Jorg Schmidbauer <jschmidb@de.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2025-03-19 16:34:52 +01:00
Ingo Franzki
d98a10f67f zkey-cryptsetup: Fix logging of libcryptsetup debug messages
Messages from libcryptsetup for logging level CRYPT_LOG_DEBUG
may or may not include an EOL, dependent on the cryptsetup version:
Between libcryptsetup version 2.1 and 2.2 debug messages do not
include an EOL character, but since 2.2 they do.

Append an EOL only if the message does not already end with EOL.

Signed-off-by: Ingo Franzki <ifranzki@linux.ibm.com>
Reviewed-by: Jorg Schmidbauer <jschmidb@de.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2025-03-19 16:34:52 +01:00
Steffen Eiden
fb6e7e70c6 rust/pvsecret: Update man page for retrieve secret
Clarify the Output format in the man files.

Signed-off-by: Steffen Eiden <seiden@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2025-03-19 16:34:52 +01:00
Ingo Franzki
e49569db60 rust/pvsecret: Update man page for clarifying the creation of HMAC secrets
The key supplied in the plain bytes file with 'pvsecret create retrievable'
with type 'hmac-sha' is they key 'K_0' as of FIPS-198-1, i.e. the key 'K'
after any necessary pre-processing. The pre-processing must be performed
by the user prior to creating the retrievable secret.

Describe in detail how that pre-processing must be performed.

Signed-off-by: Ingo Franzki <ifranzki@linux.ibm.com>
Signed-off-by: Steffen Eiden <seiden@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2025-03-19 16:34:52 +01:00
Marc Hartmayer
b3055154db rust/Makefile: Add 'install-(bash|zsh)-completion' targets
Add installation targets for bash and zsh autocompletions for the PV
related tools. Do not use them by default.

Acked-by: Steffen Eiden <seiden@linux.ibm.com>
Signed-off-by: Marc Hartmayer <mhartmay@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2025-03-19 16:34:52 +01:00
Marc Hartmayer
6718c0edc9 common.mak: Introduce 'DATADIR'
This makes it easier to reuse. Stick to the conventions used by Meson [1].

[1] https://mesonbuild.com/Builtin-options.html#directories

Reviewed-by: Steffen Eiden <seiden@linux.ibm.com>
Signed-off-by: Marc Hartmayer <mhartmay@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2025-03-19 16:34:52 +01:00
Joern Siglen
28d9405f73 dbginfo.sh: Updating info for disks and lvm
Adding some different views on disk and file system setup.
Getting more details on lvm setup.

Suggested-by: Mohamed AbouRehab <mohamed.abou.rehab@ibm.com>
Reviewed-by: Michael Storzer <MSTORZER@de.ibm.com>
Signed-off-by: Joern Siglen <siglen@de.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2025-03-19 16:34:52 +01:00
Sumanth Korikkar
05f0aeb61e zpwr: Add man page for zpwr tool
Add man page for zpwr tool and its usage.

Reviewed-by: Jan Höppner <hoeppner@linux.ibm.com>
Signed-off-by: Sumanth Korikkar <sumanthk@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2025-02-14 14:38:58 +01:00
Sumanth Korikkar
6004a7029c s390-tools: Add zpwr tool
zpwr displays power readings of a partition and central processing
complex (CPC) from power information block (pib). pib is retrieved by
issuing diag324 ioctl to /dev/diag device.

Reviewed-by: Jan Höppner <hoeppner@linux.ibm.com>
Signed-off-by: Sumanth Korikkar <sumanthk@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2025-02-14 14:38:58 +01:00
Jan Höppner
16add4a4ae scsi_logging_level: Delete SCSI logging script
The script scsi_logging_level was made available in sg3_utils 2007 [1]
copied from s390-tools. The last functional change in s390-tools was
made 2006.

The sg3_utils version saw at least some improvements over the years.
Providing otherwise identical tools can lead to unnecessary conflicts.
Delete the script from s390-tools and let sg3_utils provide it.

[1] 7502647d46
Fixes: https://github.com/ibm-s390-linux/s390-tools/issues/184
Acked-by: Nihar Panda <niharp@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2025-02-14 14:38:18 +01:00
Jan Höppner
ef752d1f45 Prepare for next release
Signed-off by: Jan Höppner <hoeppner@linux.ibm.com>
2025-02-07 15:47:07 +01:00
Jan Höppner
793c037ded New release s390-tools-2.37.0
Signed-off by: Jan Höppner <hoeppner@linux.ibm.com>
2025-02-07 15:47:07 +01:00
Jan Höppner
9f476217f5 rust: Upgrade dependencies
Upgrade all dependencies by running 'cargo upgrade' from cargo-edit.

Link: https://crates.io/crates/cargo-edit
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2025-02-07 14:25:11 +01:00
Jan Höppner
49111d613b rust/pvattest/tests: Add missing unittest asset host.pem.crt
The host.pem.crt file required by pvattest/src/cmd/check/host_key.rs for
unittesting is missing upstream. Add it back in.

Fixes: https://github.com/ibm-s390-linux/s390-tools/issues/182
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2025-02-07 14:25:11 +01:00
Steffen Eiden
26d2e2d786 rust/pv: Fix padding of retrievable private keys
Fix the padding of keys that are smaller than the architected slot for
the retrievable key. Previously the keys where appended with zeros.
However, processing software expects a left-padding.
Affected key types:
  * Ed448
  * SecP521

While at it, fix documentation in the retrievable key struct.

Fixes: fd024387d7 ("rust/pv: Retrievable secrets support")
Acked-by: Ingo Franzki <ifranzki@linux.ibm.com>
Signed-off-by: Steffen Eiden <seiden@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2025-02-06 13:18:32 +01:00
Mikhail Zaslonko
7bd159de1d zdump: Add support for eckd_mv dumper v2
Use STAGE2_DUMPER_SIZE_MV size for eckd_mv dumper v2
in df_s390_dumper_read() to verify the dump tool.

Signed-off-by: Mikhail Zaslonko <zaslonko@linux.ibm.com>
Reviewed-by: Alexander Egorenkov <egorenar@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2025-01-28 17:25:19 +01:00
Mikhail Zaslonko
605680d6fd zipl/boot: Increase section size for eckd_mv dumper
Update eckd2dump linker script to remove stage2 space constraints and
avoid linker section overlaps when using 'unfortunate' compiler
version & flags combination.
Since Multi-volume dumper size changes, we also update the dump-tool version
number for 'zgetdump' to distinguish.

Signed-off-by: Mikhail Zaslonko <zaslonko@linux.ibm.com>
Fixes: https://github.com/ibm-s390-linux/s390-tools/issues/171
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2025-01-28 17:25:19 +01:00
Finn Callies
f1a479a794 capcfstats: Update manpage to MSA 10 and MSA 11
The tooling already supports the PAI counters to the respective CPACF
functions introduced with MSA 10 and MSA 11 but the manpage did not
reflect that until now. The list of pai counters is moved to a new
section called APPENDIX to not block any important information with the
list.
Additionally a few double space after end of sentences are removed.

Acked-by: Steffen Eiden <seiden@linux.ibm.com>
Reviewed-by: Harald Freudenberger <freude@linux.ibm.com>
Signed-off-by: Finn Callies <fcallies@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2025-01-28 17:25:19 +01:00
Finn Callies
fa2d68e80c capcfstatsd: Update service unit to build on z/VM and KVM
cpacfstatsd can be run successfully on z/VM and KVM mashines, but the
service unit did not allow that. A user would have to start the
cpacfstats daemon by hand every time until now.

Acked-by: Steffen Eiden <seiden@linux.ibm.com>
Reviewed-by: Harald Freudenberger <freude@linux.ibm.com>
Signed-off-by: Finn Callies <fcallies@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2025-01-28 17:25:19 +01:00
Joern Siglen
e1730585e9 dbginfo.sh: Adding kdump info
Collect kdump.log to analyse kdump issues.

Suggested-by: Michael Storzer <MSTORZER@de.ibm.com>
Reviewed-by: Michael Storzer <MSTORZER@de.ibm.com>
Signed-off-by: Joern Siglen <siglen@de.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2025-01-28 17:25:19 +01:00
Joern Siglen
ef55a5d677 dbginfo.sh: Add details on CPU-measurement
Adding lscpumf command to get details on the setup for CPU-measurement.

Suggested-by: Eberhard Pasch <epasch@de.ibm.com>
Reviewed-by: Michael Storzer <MSTORZER@de.ibm.com>
Signed-off-by: Joern Siglen <siglen@de.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2025-01-28 17:25:19 +01:00
Joern Siglen
bc4b7475f7 Removing email references to linux390@de.ibm.com
The Email address linux390@de.ibm.com is about to be sunset.
Cleaning up the last references in man page/help function for:
- mon_tools
- dbginfo.sh

Reviewed-by: Michael Storzer <MSTORZER@de.ibm.com>
Signed-off-by: Joern Siglen <siglen@de.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2025-01-28 17:25:19 +01:00
Dan Horák
abcb93aacb zipl: Update inline assembly for GCC 15
Properly escape % (as %%) for extended assembly in stage3.c in
start_kernel().

Fixes: https://github.com/ibm-s390-linux/s390-tools/issues/179
Closes: https://github.com/ibm-s390-linux/s390-tools/pull/180
Signed-off-by: Dan Horák <dan@danny.cz>
Reviewed-by: Stefan Haberland <sth@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2025-01-28 17:25:19 +01:00
Eduard Shishkin
b09a9b3490 zipl_helper.device-mapper: Fix logical device resolution
Reported issue:

\# lsblk
sdc                                8:32   0   20G  0 disk
|-sdc1                             8:33   0   20G  0 part
`-mpatha                         253:1    0   20G  0 mpath
  `-mpatha1                      253:17   0   20G  0 part
sdd                                8:48   0   20G  0 disk
|-sdd1                             8:49   0   20G  0 part
`-mpatha                         253:1    0   20G  0 mpath
  `-mpatha1                      253:17   0   20G  0 part

\# zipl -d /dev/mapper/mpatha1
Success
\# zgetdump -d /dev/mapper/mpatha
zgetdump: No dump tool found on "/dev/mapper/mpatha"

The root cause is in incorrect logical device resolution:

\# zipl_helper.device-mapper 253:17

Expected result:

targetbase=253:1
targettype=SCSI
targetblocksize=512
targetoffset=2048

Actual result:

targetbase=8:48
targettype=SCSI
targetblocksize=512
targetoffset=2048

Do not resolve dm device which provides access to boot sectors.

Fixes: 3db20bccef ("zipl_helper.device-mapper: Add missed step in logical device resolution")
Signed-off-by: Eduard Shishkin <edward6@linux.ibm.com>
Reported-by: Mikhail Zaslonko <zaslonko@linux.ibm.com>
Tested-by: Mikhail Zaslonko <zaslonko@linux.ibm.com>
Acked-by: Mikhail Zaslonko <zaslonko@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2025-01-28 17:25:19 +01:00
Jakob Naucke
caaf2b2116 rust/pvimg: Fix flag parsing for allowing dump
Parsing of pvimg flags did not pick up allowing dumping correctly.

Fixes: f4cf4ae6eb (rust: Add a new tool called 'pvimg')
Reviewed-by: Marc Hartmayer <mhartmay@linux.ibm.com>
Signed-off-by: Jakob Naucke <naucke@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2025-01-28 17:25:19 +01:00
Joern Siglen
382ec368fc dbginfo.sh: Add new crypto command
s390tools has a new tool to view the CPACF data, now added
to our data collection

Suggested-by: Mario Held <mario.held@de.ibm.com>
Suggested-by: Ingo Franzki <ifranzki@linux.ibm.com>
Reviewed-by: Finn Callies <fcallies@linux.ibm.com>
Signed-off-by: Joern Siglen <siglen@de.ibm.com>
Signed-off-by: Steffen Eiden <seiden@linux.ibm.com>
2025-01-10 16:38:45 +01:00
Joern Siglen
9dd96144d4 dbginfo.sh: Rework network section
Define the network devices as global variable with closer selection
to avoid repeated calls with undefined interfaces.
Relable the OSA part of networks.

Reviewed-by: Mario Held <mario.held@de.ibm.com>
Signed-off-by: Joern Siglen <siglen@de.ibm.com>
Signed-off-by: Steffen Eiden <seiden@linux.ibm.com>
2025-01-10 16:38:45 +01:00
Joern Siglen
efd41ca435 dbginfo.sh: Update copyright 2nd year
change second year of copyright to 2025

Reviewed-by: Mario Held <mario.held@de.ibm.com>
Signed-off-by: Joern Siglen <siglen@de.ibm.com>
Signed-off-by: Steffen Eiden <seiden@linux.ibm.com>
2025-01-10 16:38:45 +01:00
Marc Hartmayer
7bc12d0202 rust/pvimg: Document the change from '--comm-key' to '--cck' in the help message
This fixes problems when users search for '--comm-key' in the help
message.

Fixes: 5b6d7a467d ("rust/pvimg: Add '--cck <FILE>' command line option and make '--comm-key' an alias")
Reviewed-by: Steffen Eiden <seiden@linux.ibm.com>
Reviewed-by: Nico Boehr <nrb@linux.ibm.com>
Signed-off-by: Marc Hartmayer <mhartmay@linux.ibm.com>
Signed-off-by: Steffen Eiden <seiden@linux.ibm.com>
2025-01-10 16:38:45 +01:00
Marc Hartmayer
b49cd13d16 cpumf/man: Fix whatis-entries in manpages
Fix whatis-entries in the lshwc, lspai, and pai manpages by inlining the
tool name. The following command line can be used to test a local
manpage:

$ lexgrog -w -m cpumf/man/lshwc.8
cpumf/man/lshwc.8: "lshwc - extract CPU Measurement Facilities counter sets"

Github-ID: https://github.com/ibm-s390-linux/s390-tools/issues/170
Reviewed-by: Steffen Eiden <seiden@linux.ibm.com>
Signed-off-by: Marc Hartmayer <mhartmay@linux.ibm.com>
Signed-off-by: Steffen Eiden <seiden@linux.ibm.com>
2025-01-10 16:38:45 +01:00
Marc Hartmayer
56e32dfcbb rust/pvcore/uvdevice: Fix typo
Reviewed-by: Steffen Eiden <seiden@linux.ibm.com>
Signed-off-by: Marc Hartmayer <mhartmay@linux.ibm.com>
Signed-off-by: Steffen Eiden <seiden@linux.ibm.com>
2025-01-10 16:38:45 +01:00
Ingo Franzki
833a8e7309 zkey: Support validation of key of type PVSECRET-AES
Keys of type PVSECRET-AES can also be verified via the pkey IOCTL
PKEY_VERIFYKEY2, but the card and domain fields must be zero, because such
a key does not use a crypto card. Also XTS keys of type PVSRCRET-AES are
not represented by 2 concatenated keys but by just one key of type
PVSECRET-AES. Thus, special handling is required for XTS keys.

Signed-off-by: Ingo Franzki <ifranzki@linux.ibm.com>
Reviewed-by: Jorg Schmidbauer <jschmidb@de.ibm.com>
Signed-off-by: Steffen Eiden <seiden@linux.ibm.com>
2025-01-10 16:38:45 +01:00
Ingo Franzki
a8eb2bd4e7 zkey: Reject re-enciphering of PVSECRET-AES keys
Keys of type PVSECRET-AES can not be reenciphered using 'zkey reencipher'
or 'zkey-cryptsetup reencipher'. Reject that with a proper error message.

Signed-off-by: Ingo Franzki <ifranzki@linux.ibm.com>
Reviewed-by: Jorg Schmidbauer <jschmidb@de.ibm.com>
Signed-off-by: Steffen Eiden <seiden@linux.ibm.com>
2025-01-10 16:38:45 +01:00
Ingo Franzki
5276d408fd zkey: Reject key generation and APQN association for PVSECRET-AES keys
Keys of type PVSECRET-AES can not be generated using 'zkey generate'.
Furthermore, APQNs can not be associated with keys of type PVSECRET-AES
via 'zkey change'. Reject that with a proper error message.

Signed-off-by: Ingo Franzki <ifranzki@linux.ibm.com>
Reviewed-by: Jorg Schmidbauer <jschmidb@de.ibm.com>
Signed-off-by: Steffen Eiden <seiden@linux.ibm.com>
2025-01-10 16:38:45 +01:00
Ingo Franzki
95bf7eb285 zkey: Add the 'pvsecrets import' command
The 'pvsecrets import' command imports a protected virtualization secret
into the zkey key repository. Like other key import or key generation
commands, additional information can be associated with the imported key,
such as a textual description, the volume to encrypt with together with
the volume type, the sector size, and a dummy passphrase. You can not
associate a set of APQNs, since a protected virtualization secret does
not need or use a crypto card.

This command only works when running in a secure execution guest.

Signed-off-by: Ingo Franzki <ifranzki@linux.ibm.com>
Reviewed-by: Jorg Schmidbauer <jschmidb@de.ibm.com>
Signed-off-by: Steffen Eiden <seiden@linux.ibm.com>
2025-01-10 16:38:45 +01:00
Ingo Franzki
fdf66dc148 zkey: Add PVSECRETS-AES key type
Add the definitions and utility functions for the PVSECRETS-AES key type.
A PVSECRETS-AES key token contains the secret id of a protected
virtualization secret. It does not contain the key material, just a
reference to the key in the ultravisor.

When such a key token is used to perform crypto operations later on, the
PAES kernel cipher will obtain the protected key belonging to this secret
id with the help of the pkey kernel module.

Signed-off-by: Ingo Franzki <ifranzki@linux.ibm.com>
Reviewed-by: Jorg Schmidbauer <jschmidb@de.ibm.com>
Signed-off-by: Steffen Eiden <seiden@linux.ibm.com>
2025-01-10 16:38:45 +01:00
Ingo Franzki
5ce79ea667 zkey: Add the 'pvsecrets list' command
The 'pvsecrets list' command lists the available protected virtualization
secrets. By default, only those pvsecret types are listed, that can be used
with zkey. If option '--all/-a' is specified, then all pvsecret types are
listed. Nevertheless, pvsecret types not supported by zkey can not be used
with zkey.

This command only works when running in a secure execution guest.

Signed-off-by: Ingo Franzki <ifranzki@linux.ibm.com>
Reviewed-by: Jorg Schmidbauer <jschmidb@de.ibm.com>
Signed-off-by: Steffen Eiden <seiden@linux.ibm.com>
2025-01-10 16:38:45 +01:00
Ingo Franzki
8c4b2872b8 zkey: Add support for retrieving a list of ultravisor secrets
Add functions to interface with the ultravisor device (/dev/uv) when
running in a secure execution guest to retrieve a list of available
secrets.

Signed-off-by: Ingo Franzki <ifranzki@linux.ibm.com>
Reviewed-by: Jorg Schmidbauer <jschmidb@de.ibm.com>
Signed-off-by: Steffen Eiden <seiden@linux.ibm.com>
2025-01-10 16:38:45 +01:00
Marc Hartmayer
7be364aa84 rust/pvimg/man: Fix whatis-entries in manpages
Before this change:
$ lexgrog -w -m man/pvimg-create.1
man/pvimg-create.1: parse failed

After this change:

$ lexgrog -w -m man/pvimg-create.1
man/pvimg-create.1: "pvimg-create - Create an IBM Secure Execution image"

In addition, pvimg is highlighted in the manpage description section.

Github-ID: https://github.com/ibm-s390-linux/s390-tools/issues/170
Signed-off-by: Marc Hartmayer <mhartmay@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2024-12-20 13:12:34 +01:00
Marc Hartmayer
43858063ca rust: Upgrade 'thiserror' to '2.0.6'
Since there have been breaking changes, let's fix them. Fortunately,
there was only one problem to fix.

Command line used:

  $ cargo upgrade --incompatible -p 'thiserror'

Acked-by: Jan Höppner <hoeppner@linux.ibm.com>
Signed-off-by: Marc Hartmayer <mhartmay@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2024-12-20 13:01:15 +01:00
Marc Hartmayer
739761f0d2 rust: Update Rust depenencies
Command line used (https://crates.io/crates/cargo-edit):

 $ cargo upgrade

Acked-by: Jan Höppner <hoeppner@linux.ibm.com>
Signed-off-by: Marc Hartmayer <mhartmay@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2024-12-20 13:01:15 +01:00
Steffen Eiden
a8a3e7d49c rust/pvsecret: Update manuals and README
Acked-by: Marc Hartmayer <marc@linux.ibm.com>
Reviewed-by: Christoph Schlameuss <schlameuss@linux.ibm.com>
Signed-off-by: Steffen Eiden <seiden@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2024-12-20 13:01:15 +01:00
Steffen Eiden
ff04f76257 rust/pv*: Allow the use of non-hashes secret IDs
Secret IDs identify a secret in the store. Tooling (pvsecret) calculates
them by hashing a user-defined string. With this patch it is now
possible to skip the hash step and directly use the input string as the
ID. Up to the first 31 bytes of the input ASCII-string are used. The last byte
is the NUL char. During list pvsecret tries to interpret the secret
as ASCII string and if possible displays the ASCII characters alongside
the hex number.

Also, use the Upper/Lower Hex formatters for the hexstring formatting of
SecretId. Display will, additionally show the ASCII representation if
applicable.

While at it, use Self wherever possible.

Acked-by: Marc Hartmayer <marc@linux.ibm.com>
Reviewed-by: Christoph Schlameuss <schlameuss@linux.ibm.com>
Signed-off-by: Steffen Eiden <seiden@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2024-12-20 13:01:15 +01:00
Steffen Eiden
93216d916c rust/pv*: Support longer secret lists
Make use of the enhanced list secrets UAPI for the uvdevice in the latest kernel
version. This allows fetching secret lists with more than 85 entries via
reserving more userspace memory in the IOCTL argument.

While at it, move the errno readout next to the ioctl-syscall.

Acked-by: Marc Hartmayer <marc@linux.ibm.com>
Reviewed-by: Christoph Schlameuss <schlameuss@linux.ibm.com>
Signed-off-by: Steffen Eiden <seiden@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2024-12-20 13:01:15 +01:00
Steffen Eiden
256289a30a rust/pv_core: Refactor secret list
Improve the secret list implementation. Use structs+{As,From}Bytes
instead of arbitrary seeks and reads/writes to parse the secret list.

Acked-by: Marc Hartmayer <marc@linux.ibm.com>
Reviewed-by: Christoph Schlameuss <schlameuss@linux.ibm.com>
Signed-off-by: Steffen Eiden <seiden@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2024-12-20 13:01:15 +01:00
Steffen Eiden
93da795520 rust/pvsecret: Add support for retrievable secrets
Support for creating and retrieving retrievable secrets.

Acked-by: Marc Hartmayer <marc@linux.ibm.com>
Reviewed-by: Christoph Schlameuss <schlameuss@linux.ibm.com>
Signed-off-by: Steffen Eiden <seiden@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2024-12-20 13:01:15 +01:00
Steffen Eiden
a14f9d4edc rust/pvsecret: Improve CLI
Improve the wording of the help/man text/

Acked-by: Marc Hartmayer <marc@linux.ibm.com>
Reviewed-by: Christoph Schlameuss <schlameuss@linux.ibm.com>
Signed-off-by: Steffen Eiden <seiden@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2024-12-20 13:01:15 +01:00
Steffen Eiden
fd024387d7 rust/pv: Retrievable secrets support
Support retrievable secret for Add-Secret requests.

Acked-by: Marc Hartmayer <marc@linux.ibm.com>
Reviewed-by: Christoph Schlameuss <schlameuss@linux.ibm.com>
Signed-off-by: Steffen Eiden <seiden@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2024-12-20 13:01:15 +01:00
Steffen Eiden
4af137f4fa rust/pv_core: Support for listing Retrievable Secrets
Add support for listing retrievable secrets in the List Secrets UVC.

Acked-by: Marc Hartmayer <marc@linux.ibm.com>
Reviewed-by: Christoph Schlameuss <schlameuss@linux.ibm.com>
Signed-off-by: Steffen Eiden <seiden@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2024-12-20 13:01:15 +01:00
Steffen Eiden
01cd81ecf5 rust/pv_core: Retrieve Secret UVC
Create the uvdevice-IOCTL functionality for the new Retrieve Secret UVC.

Reviewed-by: Christoph Schlameuss <schlameuss@linux.ibm.com>
Acked-by: Marc Hartmayer <marc@linux.ibm.com>
Signed-off-by: Steffen Eiden <seiden@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2024-12-20 13:01:15 +01:00
Steffen Eiden
69eb06f39e rust/pv_core: Update ffi.rs to linux/uvdevice.h v6.13
While at it, add a file global #[allow(dead_code)].
The file is a rustified copy of linux/arch/s390/include/uapi/asm/uvdevice.h
and there might be things that are not needed here but are defined in that header.

Acked-by: Marc Hartmayer <marc@linux.ibm.com>
Reviewed-by: Christoph Schlameuss <schlameuss@linux.ibm.com>
Signed-off-by: Steffen Eiden <seiden@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2024-12-20 13:01:15 +01:00
Steffen Eiden
d1636168b2 rust/pv: Support for writing data in PEM format
Use existing OpenSSL functionalities to create PEM files containing
arbitrary data.

Acked-by: Marc Hartmayer <marc@linux.ibm.com>
Acked-by: Christoph Schlameuss <schlameuss@linux.ibm.com>
Signed-off-by: Steffen Eiden <seiden@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2024-12-20 13:01:15 +01:00
Steffen Eiden
1e44ace41d rust/pvsecret: Refactor writing secret
Refactor the writing of secret-type dependent output files to ease
extensions.

Reviewed-by: Marc Hartmayer <marc@linux.ibm.com>
Reviewed-by: Christoph Schlameuss <schlameuss@linux.ibm.com>
Signed-off-by: Steffen Eiden <seiden@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2024-12-20 13:01:15 +01:00
Marc Hartmayer
a9b8caa411 genprotimg/sample/check_hostkeydoc: Fix documentation link
Move the script to 'genprotimg/samples/check_hostkeydoc' and create a
symlink at 'rust/pvimg/tools/check_hostkeydoc' in order to keep
compatibility with existing documentation. The problem with the original
fix was that the github.com website does not follow symbolic links.

Reviewed-by: Steffen Eiden <seiden@linux.ibm.com>
Signed-off-by: Marc Hartmayer <mhartmay@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2024-12-20 13:01:15 +01:00
Marc Hartmayer
6e48c5ebaa pvimg: info: Rename '--key' into '--hdr-key' and use '--key' as an alias
Rename '--key' into '--hdr-key' and use '--key' as an (non-visible)
alias for '--hdr-key' in order to keep the command line backwards
compatible. The chances of someone using '--key' are very low, as this
version has not yet been released by any OS distribution.

This change makes the command line options for the different subcommands
more consistent and therefore easier to use.

Suggested-by: Reinhard Bündgen <buendgen@de.ibm.com>
Acked-by: Hendrik Brueckner <brueckner@linux.ibm.com>
Reviewed-by: Steffen Eiden <seiden@linux.ibm.com>
Signed-off-by: Marc Hartmayer <mhartmay@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2024-12-20 13:01:15 +01:00
Marc Hartmayer
944581eaef rust/pvimg: Add upper estimates for the Secure Execution header
A Secure Execution header V1 can be at maximum two pages large, optional
items are not supported, and the size of the encrypted part cannot be
larger than the total size of the Secure Execution header add this as
Deku assertions and additional conditions to the code. In addition, add
a check for the number of key slots.

Fixes: f4cf4ae6eb ("rust: Add a new tool called 'pvimg'")
Reviewed-by: Steffen Eiden <seiden@linux.ibm.com>
Signed-off-by: Marc Hartmayer <mhartmay@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2024-12-20 13:01:15 +01:00
Marc Hartmayer
3f6572e901 rust/utils: mkdtemp: fix memory leak
Fix memory leak of @template_raw. The documentation of CString::into_raw
reads:

"Consumes the CString and transfers ownership of the string to a C
caller.
...
Failure to call CString::from_raw will lead to a memory leak." [1]

Let's fix the memory leak by always calling `CString::from_raw` and
therefore reclaim the ownership.

[1] https://doc.rust-lang.org/std/ffi/struct.CString.html#method.into_raw

Fixes: e56acf4f14 ("pv_core: add `TemporaryDirectory`")
Reviewed-by: Steffen Eiden <seiden@linux.ibm.com>
Signed-off-by: Marc Hartmayer <mhartmay@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2024-12-19 21:45:37 +01:00
Peter Oberparleiter
6520070a46 chpstat: Fix invalid utilization data on older kernels
On kernels without support for the new "measurement_chars_full" CHPID
sysfs attribute, chpstat displays invalid utilization numbers (nan/inf).
This is due to an invalid buffer address calculation when reading the
old "measurement_chars" attribute.

Fix this by using the correct buffer address calculation.

Fixes: 026ecbafea ("chpstat: Add support for full CMCB")
Signed-off-by: Peter Oberparleiter <oberpar@linux.ibm.com>
Reviewed-by: Vineeth Vijayan <vneethv@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2024-12-19 21:45:37 +01:00
Marc Hartmayer
3b8fdcc892 pvimg: Add '--hdr-key' command line option to 'pvimg create'
Add '--hdr-key <FILE>' as a command line option to the 'pvimg create'
command. This key can then be used later to decrypt the Secure Execution
header of a Secure Execution image, e.g. 'pvimg info --key <FILE>
--format json <SE_IMG>'. While updating the manpages, add missing hyphen
escapes in the manpages.

Reviewed-by: Steffen Eiden <seiden@linux.ibm.com>
Acked-by: Hendrik Brueckner <brueckner@linux.ibm.com>
Signed-off-by: Marc Hartmayer <mhartmay@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2024-12-18 17:08:51 +01:00
Eduard Shishkin
352e2fe5d3 zipl/src: Fix incorrect installation of zipl_helper.md
Dereferencing zipl.helper.md by 'make install' causes "duplicate
BUILD-IDs" conflict for distro partners.

Install zipl_helper.device-mapper as a regular file and all other
helpers - as symlinks to the zipl_helper.device-mapper

Fixes: 3296d85e35 ("zipl/src: add support of md-mirrors")
Fixes: https://github.com/ibm-s390-linux/s390-tools/issues/178
Signed-off-by: Eduard Shishkin <edward6@linux.ibm.com>
Acked-by: Jan Höppner <hoeppner@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2024-12-18 17:08:51 +01:00
Eduard Shishkin
37b7b68f1b zipl/src: Fix imprecise check that file locates on specified device
In case when target parameters are specified by user, the check
that a file locates on a specified device, compares a logical
device with a base disk, which is incorrect.

Fix the check to compare base disks (a specified one with the base
disk determined by disk_get_info() procedure called w/o any user
hints).

Fixes: c0f02d2f68 ("zipl/src: Fix problems when target parameters are specified by user")
Signed-off-by: Eduard Shishkin <edward6@linux.ibm.com>
Acked-by: Jan Höppner <hoeppner@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2024-12-18 17:08:51 +01:00
Eduard Shishkin
3db20bccef zipl_helper.device-mapper: Add missed step in logical device resolution
Steps to reproduce the problem:

\# lsblk

NAME                             MAJ:MIN RM  SIZE RO TYPE  MOUNTPOINT
loop0                              7:0    0    5G  0 loop
|-loop0p1                        253:15   0  128M  0 part
`-loop0p2                        253:16   0  4.9G  0 part  /mnt

\# ./zipl_helper.device-mapper 253:16

Expected result:

targetbase=7:0
targettype=SCSI
targetblocksize=4096
targetoffset=32784

Actual result:

targetbase=253:16
targettype=SCSI
targetblocksize=4096
targetoffset=32784

The problem is in a missed step which resolves the uppermost
logical dm-device to a physical device. Reproducible only for loop
devices.

Add missing step to correctly resolve to a physical device.

Fixes: 670bf3e870 ("zipl: refactor zipl_helper.device-mapper")
Signed-off-by: Eduard Shishkin <edward6@linux.ibm.com>
Acked-by: Jan Höppner <hoeppner@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2024-12-18 17:08:51 +01:00
Marc Hartmayer
560b276f7e rust/pvimg: Fix possible 'range start index out of range for slice' error
Fix possible 'range start index 16 out of range for slice of length 0'
error by adding a check of the slice data length.

Fixes: f4cf4ae6eb ("rust: Add a new tool called 'pvimg'")
Reviewed-by: Steffen Eiden <seiden@linux.ibm.com>
Signed-off-by: Marc Hartmayer <mhartmay@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2024-12-18 17:08:50 +01:00
Marc Hartmayer
a19c43f613 rust/Cargo.toml: Change to "thin local" LTO for faster release builds
"fat" LTO takes much longer to compile than with "thin local" LTO, so
use the Rust release build defaults, which is "thin local" LTO [1-2].

If someone wants to enable "fat" LTO, they can use one of the common
methods to adjust the Rust build process, for example:

  $ cargo build --config 'profile.release.lto=true' --release

or

  $ export CARGO_PROFILE_RELEASE_LTO=true
  $ cargo build --release

[1] https://doc.rust-lang.org/cargo/reference/profiles.html#release
[2] https://doc.rust-lang.org/cargo/reference/profiles.html#lto

Reviewed-by: Steffen Eiden <seiden@linux.ibm.com>
Acked-by: Jan Höppner <hoeppner@linux.ibm.com>
Signed-off-by: Marc Hartmayer <mhartmay@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2024-12-18 17:08:50 +01:00
Marc Hartmayer
5b6d7a467d rust/pvimg: Add '--cck <FILE>' command line option and make '--comm-key' an alias
Add '--cck <FILE>' as an command line option and make '--comm-key' an
alias of it. This makes the command line more similar to the other
Secure Execution related PV-tools (e.g. pvattest and pvsecret).

Suggested-by: Reinhard Bündgen <buendgen@de.ibm.com>
Reviewed-by: Steffen Eiden <seiden@linux.ibm.com>
Signed-off-by: Marc Hartmayer <mhartmay@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2024-12-18 17:08:50 +01:00
Marc Hartmayer
b1fdbac5f9 rust/pvimg/man: Document command line option aliases in the manpages
Document the visible command line option aliases in the manpages.

Reviewed-by: Steffen Eiden <seiden@linux.ibm.com>
Signed-off-by: Marc Hartmayer <mhartmay@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2024-12-18 17:08:50 +01:00
Marc Hartmayer
cf51ac7860 rust/pvimg: Add '--(enable|disable)-image-encryption' flags to 'pvimg create'
With runtime attestation it might be useful to have non-encrypted Secure
Execution images. This patch adds the support for this to the 'pvimg
create' and 'genprotimg' commands.

Reviewed-by: Steffen Eiden <seiden@linux.ibm.com>
Acked-by: Hendrik Brueckner <brueckner@linux.ibm.com>
Signed-off-by: Marc Hartmayer <mhartmay@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2024-12-13 16:26:35 +01:00
Niklas Schnelle
cf5560a100 libzpci: opticsmon: Refactor on_link_change() using new zpci_find_by_netdev()
Finding a PCI device given the name of a netdev seems generally useful
so pull this out into a new zpci_find_by_netdev() function in libzpci
and use this to simplify on_link_change() removing the need for
backwards goto.

Reviewed-by: Halil Pasic <pasic@linux.ibm.com>
Reviewed-by: Jan Höppner <hoeppner@linux.ibm.com>
Signed-off-by: Niklas Schnelle <schnelle@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2024-12-13 16:26:35 +01:00
Niklas Schnelle
dff965465c opticsmon: Fix runaway loop in on_link_change()
When on_link_change() gets called with a netdev that would be monitored
but hasn't entered zpci_list yet, reloads is 1 after the loops and
a reload occurs. Then the netdev is found in the list and reloads
becomes -1 which incorrectly triggers more reloads until underflow.
Fix this by returning once the device is found. Also just check for
reloads being larger than zero.

Fixes: c34adb9cab ("opticsmon: Introduce opticsmon tool")
Reviewed-by: Halil Pasic <pasic@linux.ibm.com>
Signed-off-by: Niklas Schnelle <schnelle@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2024-12-13 16:26:35 +01:00
Marc Hartmayer
79de37bbc8 genprotimg/samples/check_hostkeydoc: Create symlink to the new location
Create a symbolic link to the new location of the 'check_hostkeydoc'
script in order to keep compatibility with existing documentation.

Reported-by: Stephan Hartig <hartig@de.ibm.com>
Reviewed-by: Steffen Eiden <seiden@linux.ibm.com>
Signed-off-by: Marc Hartmayer <mhartmay@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2024-12-13 16:26:35 +01:00
Fedor Loshakov
e5c2fba0a8 ziomon/ziorep_utilization: introduce parameter for reports separation
The output of ziorep_utilization tool currently contains reports for both
the physical and the virtual adapter. As binding of physical and virtual
adapter reports together can be confusing for evaluation of results, introduce
--fcp-device tool parameter for reports separation. This parameter was
introduced to mark virtual adapter report as it represents utilization report
of FCP devices.

So, if --fcp-device was specified by the user, virtual adapter report is
printed. Otherwise physical adapter report is printed.

Parameter --fcp-device has no influence on CSV format report printing.

Refactor also print_reports() function for both reports for better
--fcp-device parameter handling.

Add clarification messages on how to use --fcp-device parameter.

Delete empty separator line between former two reports.

Add description of --fcp-device parameter to man pages.

Add examples for using of ziorep_utilization tool to man pages.

Signed-off-by: Fedor Loshakov <loshakov@linux.ibm.com>
Reviewed-by: Benjamin Block <bblock@linux.ibm.com>
Reviewed-by: Sakshi Singh <005c7w@linux.ibm.com>
Reviewed-by: M Nikhil <nikh1092@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2024-12-13 16:26:35 +01:00
Fedor Loshakov
04267470b1 ziomon/ziorep_utilization: swap Bus-ID and CHPID columns in virt report
As virtual adapter report in ziorep_utilization represents utilization
values of FCP devices, swap Bus-ID and CHPID columns in virtual
adapter report to avoid confusion during evaluation. Also use
print_delimiter() function instead of slash for Bus-ID and CHPID fields
separation.

Before patch virtual adapter report looks like:
...
CHP Bus-ID  |qdio util.%|queu|fail|-thp in MB/s-|I/O reqs-|
 ID            max   avg full  erc     rd    wrt   rd  wrt
2020-05-14 14:00:41
 60/0.0.1906   2.3   0.8    0    0   0.0  329.8    30 192K
 61/0.0.1946   0.8   0.7    0    0   0.0  341.8    30 192K
...

After patch application virtual adapter report looks like:
...
Bus-ID  |CHP|qdio util.%|queu|fail|-thp in MB/s-|I/O reqs-|
          ID   max   avg full  erc     rd    wrt   rd  wrt
2020-05-14 14:00:41
0.0.1906  60   0.0   0.8    0    0   0.0  331.2     0 1.3K
0.0.1946  61   0.0   0.7    0    0   0.0  344.0     0 1.3K
...

Note, that this patch does not change CSV-format report.

Signed-off-by: Fedor Loshakov <loshakov@linux.ibm.com>
Reviewed-by: Steffen Maier <maier@linux.ibm.com>
Reviewed-by: Benjamin Block <bblock@linux.ibm.com>
Reviewed-by: Sakshi Singh <005c7w@linux.ibm.com>
Reviewed-by: M Nikhil <nikh1092@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2024-12-13 16:26:35 +01:00
Fedor Loshakov
cf7060ea08 ziomon/ziorep_utilization: add pchid column to physical adapter report
In ziorep_utilization tool physical adapter report represents PCHID
scope, rather than CHPID. PCHID column is added as a very first column of
physical adapter report for non-CSV format report.

Old version of physical adapter report with CHPID column only
looks like:
...
CHP|adapter in %-|--bus in %---|--cpu in %---|
 ID min max   avg min max   avg min max   avg
2020-05-14 14:00:41
 60   0   0   0.0   1   1   1.0   0   0   0.0
 61   0   0   0.0   1   1   1.0   0   0   0.0
...

New version of physical adapter report with additional PCHID column
looks like:
...
PCH |CHP|adapter in %-|--bus in %---|--cpu in %---|
 ID   ID min max   avg min max   avg min max   avg
2020-05-14 14:00:41
01c0  60   0   0   0.0   1   1   1.0   0   0   0.0
01c1  61   0   0   0.0   1   1   1.0   0   0   0.0
...

For CSV-format report PCHID column is appended to the end of each record line.

For older kernel releases, where PCHID sysfs entry is not available, replace
PCHID column value with "n/a".

Signed-off-by: Fedor Loshakov <loshakov@linux.ibm.com>
Reviewed-by: Benjamin Block <bblock@linux.ibm.com>
Reviewed-by: Sakshi Singh <005c7w@linux.ibm.com>
Reviewed-by: M Nikhil <nikh1092@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2024-12-13 16:26:35 +01:00
Fedor Loshakov
f340a96fdf ziomon/ziorep_cfgreader: extract PCHID from .config file
Add an ability for ziorep_config tool to extract PCHID additional adapter
parameter from .config file. With this patch device_info structure will have
pchid field, which can be used by different printers classes. Also add
function for querying PCHID by using of devno for providing reports. Translate
"n/a" field from .config file record into invalid PCHID number.

Signed-off-by: Fedor Loshakov <loshakov@linux.ibm.com>
Reviewed-by: Sakshi Singh <005c7w@linux.ibm.com>
Reviewed-by: M Nikhil <nikh1092@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2024-12-13 16:26:35 +01:00
Fedor Loshakov
798bd752cf ziomon/ziorep_config: extract PCHID and add to internal report
The ziomon_fcpconf saves a lot of information about adapter into .cfg file.
With this patch ziorep_config tool will extract PCHID additional adapter
parameter from .cfg file and put it into .config file for future evaluation by
ziorep_* tools group.

This patch also adds notification for cases, when chid sysfs entry does not
exist for specified CHPID, for example for old kernels.

Before patch application typical line example from .config file looked like:
... 253:0 /dev/sde 8388672 8:64 Disk

After application of the patch typical line example from .config file
looks like:
... 253:0 /dev/sde 8388672 8:64 Disk 01c0

On older distros, which do not contain pchid sysfs entry, typical line
example from .config file looks like:
... 253:0 /dev/sde 8388672 8:64 Disk n/a

As PCHID value of FCP adapter can now be extracted from .config file,
add PCHID field to report of ziorep_config tool.

If there was no PCHID sysfs entry on the system, print following message into
ziorep_config command output:
...
PCHID:   there is no PCHID entry in data source
...

Signed-off-by: Fedor Loshakov <loshakov@linux.ibm.com>
Reviewed-by: Steffen Maier <maier@linux.ibm.com>
Reviewed-by: Benjamin Block <bblock@linux.ibm.com>
Reviewed-by: Sakshi Singh <005c7w@linux.ibm.com>
Reviewed-by: M Nikhil <nikh1092@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2024-12-13 16:26:35 +01:00
Fedor Loshakov
638f0aa4f9 ziomon/ziorep_traffic: disambiguate paths with device bus-ID
Add device bus-ID to ziorep_traffic output for better distinguishing
of paths in setups, which have paths in the same pathgroup with same
WWPN and LUN.

Output without device bus-ID column:
$ ziorep_traffic log
       WWPN                LUN       ...
                                        ...
2019-10-21 11:44:38
0x50050763071bc5e3:0x4006404f00000000   ...
0x50050763071bc5e3:0x4006404f00000000   ...

Output with device bus-ID column:
$ ziorep_traffic log
DEVBUSID        WWPN                LUN       ...
                                                 ...
2019-10-21 11:44:38
0.0.1980:0x50050763071bc5e3:0x4006404f00000000   ...
0.0.19c0:0x50050763071bc5e3:0x4006404f00000000   ...

After patch application device bus-ID can also be seen in detailed
(with -D option) output and in CSV output format (with -x option).

Also adjust output headings correspondingly.

Signed-off-by: Fedor Loshakov <loshakov@linux.ibm.com>
Reviewed-by: Steffen Maier <maier@linux.ibm.com>
Reviewed-by: Benjamin Block <bblock@linux.ibm.com>
Reviewed-by: Sakshi Singh <005c7w@linux.ibm.com>
Reviewed-by: M Nikhil <nikh1092@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2024-12-13 16:26:35 +01:00
Jan Höppner
d3b7581669 Prepare for next release
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2024-12-06 12:26:08 +01:00
265 changed files with 13636 additions and 4951 deletions

2
.gitignore vendored
View File

@@ -137,4 +137,6 @@ zkey/kmip/libkmipclient.dep
zkey/kmip/zkey-kmip.so
zkey/zkey
zkey/zkey-cryptsetup
zmemtopo/zmemtopo
zpcictl/zpcictl
zpwr/zpwr

View File

@@ -43,6 +43,7 @@ List of all individuals having contributed content to s390-tools
- Frank Munzert
- Frank Pavlic
- Fritz Elfert
- Gautam Gala
- Gerald Schaefer
- Gerhard Tonn
- Graham Inggs
@@ -59,9 +60,11 @@ List of all individuals having contributed content to s390-tools
- Horst Hummel
- Ingo Franzki
- Ingo Tuchscherer
- Jakob Naucke
- Jakub Čajka
- Jan Glauber
- Jan Höppner
- Jan Polensky
- Jan Willeke
- Jason J. Herne
- Javier Martinez Canillas
@@ -69,6 +72,7 @@ List of all individuals having contributed content to s390-tools
- Jens Remus
- Jochen Roehrig
- Joern Siglen
- Jörn Siglen
- Juergen Christ
- Julian Wiedmann
- Karsten Graul
@@ -96,6 +100,7 @@ List of all individuals having contributed content to s390-tools
- Nikita Dubrovskii
- Niklas Schnelle
- Nikolay Gueorguiev
- Peter Jin
- Peter Oberparleiter
- Peter Tiedemann
- Philipp Kern
@@ -111,6 +116,8 @@ List of all individuals having contributed content to s390-tools
- Sebastian Ott
- Sertonix
- Seshagiri N. Ippili
- Shalini Chellathurai Saroja
- Siglen
- Simon Sturm
- Stefan Bader
- Stefan Haberland
@@ -120,6 +127,7 @@ List of all individuals having contributed content to s390-tools
- Steffen Eiden
- Steffen Maier
- Steffen Thoss
- Sumanth Korikkar
- Susanne Wintenberger
- Sven Schnelle
- Sven Schuetz

View File

@@ -1,6 +1,100 @@
Release history for s390-tools (MIT version)
--------------------------------------------
* __v2.38.0 (2025-06-25)__
For Linux kernel version: 6.14 / 6.15
Add new tools:
- udev: New rule to set newly hotplugged CPUs online
- zmemtopo: Display memory topology information
- zpwr: Display power readings of a partition and CPC
Removed tools / features:
- check_hostkeydoc: Remove installation target
- scsi_logging_level: Delete SCSI logging script (available in sg3_utils)
- zdump: Drop build_arch for s390 DASD dumps
- zdump: Drop non-extended multi-volume DASD dump support
- zdump: Drop support of 32-bit dump architecture
- zdump: Drop support of non-extended single volume DASD dumpers
- zdump: Drop support of obsolete dumps and dumpers
Changes of existing tools / libraries:
- Various man-pages fixes
- check_hostkeydoc: Add deprecation warning
- check_hostkeydoc: Move to scripts directory
- cpuplugd: Allow cpu hotplugging on systems without polarization
- dbginfo.sh: Add Ubuntu snap tool
- dbginfo.sh: Add missing config data and logs
- dbginfo.sh: Reworking the container section
- dbginfo.sh: Update for network commands
- dbginfo.sh: Updating info for disks and lvm
- libutil: Add machine type definition for machines 9175 and 9176
- lscpumf: Add support for IBM z17 counter sets
- lshwc: Add command line flag for run time
- lshwc: Add flags to display counter values in hex
- lshwc: Add output '--format' option
- lshwc: Add support for delta counter value display
- lspai: Add output '--format' option
- lsreipl: Add secure boot state to output
- lswhc: Add short names to lshwc output
- pv_tools: Add Bash and Zsh completions
- pvapconfig: Add '--unbind' option
- pvimg/boot: Print error messages from stage3a bootloader
- pvimg: Add support for CCK update
- pvsecret: Add support for CCK update
- pvsecret: Allow retrieving secrets by index & warn for duplicated entries
- pvsecret: Deny adding secrets with duplicated secret IDs
- zdev: Add support for virtio devices
- zipl: Enhance mirror support
- zipl: Implement '--dry-run' option for all dump jobs
- zipl_helper.device-mapper: Support mirrors over NVMe devices
- zkey/dracut: Add a dracut config file for zkey
- zkey/initramfs: Update initramfs hook to correct drivers and include zkey plugins
- zkey: Add support for converting a clear-key LUKS2 volume to use a secure key
Bug Fixes:
- chpstat: Add missing CMG 5 data fields
- chpstat: Fix DPU utilization calculation
- libutil/util_file: Handle over-read in util_file_read_fd()
- pvattest: Fix successful 'check' evaluation
- pvsecret: Fix some edge cases for plaintext keys
- zipl_helper.device-mapper: Fix imprecise is_device_mapper() predicate
- zkey: Fix EP11 secure key reencipher function
- zpcictl: Fix command line parsing for invalid options
* __v2.37.0 (2025-02-07)__
For Linux kernel version: 6.13
Changes of existing tools:
- dbginfo.sh: Add details on CPU-measurement
- dbginfo.sh: Add new crypto command
- dbginfo.sh: Add overview commands and crypto update
- dbginfo.sh: Adding kdump info
- dbginfo.sh: Removing outdated email references
- dbginfo.sh: Rework network section
- dbginfo.sh: Update copyright 2nd year
- pvimg: Add '--(enable|disable)-image-encryption' flags to 'pvimg create'
- pvimg: Add '--cck <FILE>' command line option and make '--comm-key' an alias
- pvimg: Add '--hdr-key' command line option to 'pvimg create'
- pvimg: Rename '--key' into '--hdr-key' and use '--key' as an alias (for 'pvimg info')
- pvsecret: Add support for retrievable secrets
- ziorep_config: Add PCHID field to adapter report
- ziorep_traffic: Add DEVBUSID column to traffic report
- ziorep_utilization: Add --fcp-device parameter to print virtual adapter report
- ziorep_utilization: Add PCHID column to physical adapter report
- ziorep_utilization: Now prints only physical adapter report by default
- ziorep_utilization: Swap Bus-ID and CHPID columns in virtual adapter report
- zipl/boot: Increase section size for eckd_mv dumper
- zkey: Add support for listing and importing protected virtualization secrets
Bug Fixes:
- chpstat: Fix invalid utilization data on older kernels
- opticsmon: Fix runaway loop in on_link_change()
- zipl: Update inline assembly for GCC 15
- zipl_helper.device-mapper: Add missed step in logical device resolution
* __v2.36.0 (2024-12-06)__
For Linux kernel version: 6.12
@@ -17,6 +111,14 @@ Release history for s390-tools (MIT version)
- chpstat: Add support for full CMCB
- chpstat: Add support for new CMG types
- dbginfo.sh: add overview commands and crypto update
- genprotimg:
1. genprotimg is now a symbolic link to the new tool `pvimg create`
2. Breaking API changes in genprotimg:
1. An existing output file is no longer silently overwritten; to revert
to the old behavior use the `--overwrite` option.
2. The Linux kernel component is now checked to determine whether it is a
binary s390x kernel. To disable this behavior use the
`--no-component-check` option.
- hyptop: Support for structured output (json, json-seq, csv)
- lszfcp: Add missing fallback marker for non-good fc_host port_state
- lszfcp: Improve speed with many SCSI devices

View File

@@ -15,7 +15,8 @@ TOOL_DIRS = zipl zdump fdasd dasdfmt dasdview tunedasd \
vmcp man mon_tools dasdinfo vmur cpuplugd ipl_tools \
ziomon iucvterm hyptop cmsfs-fuse qethqoat zfcpdump zdsfs cpumf \
systemd hmcdrvfs cpacfstats zdev dump2tar zkey netboot etc zpcictl \
lsstp hsci hsavmcore chreipl-fcp-mpath ap_tools rust opticsmon
lsstp hsci hsavmcore chreipl-fcp-mpath ap_tools rust opticsmon \
zpwr zmemtopo
else
BASELIB_DIRS =

View File

@@ -128,9 +128,6 @@ Package contents
* sclpdbf:
Display debug data for the sclp kernel component.
* scsi_logging_level:
Create, get or set the logging level for the SCSI logging facility.
* zconf:
Set of scripts to configure and list status information of Linux on s390
devices.

View File

@@ -1,182 +1,170 @@
.\" Automatically generated by Pandoc 2.16.1
.\" Automatically generated by Pandoc 3.1.11.1
.\"
.TH "CHREIPL-FCP-MPATH" "7" "2022-02-24" "s390-tools 2.20.0-build-20220224" "Administrator Manual"
.hy
.TH "CHREIPL\-FCP\-MPATH" "7" "2025\-03\-12" "s390-tools 2.37.0-build-20250312" "Administrator Manual"
.SH NAME
.PP
chreipl-fcp-mpath - use multipath information for re-IPL path failover
on a running Linux instance
chreipl\-fcp\-mpath \- use multipath information for re\-IPL path
failover on a running Linux instance
.SH DESCRIPTION
.PP
The IPL process of Linux on Z or LinuxONE from an FCP-attached SCSI
The IPL process of Linux on Z or LinuxONE from an FCP\-attached SCSI
volume uses exactly one path to the volume.
If this path is unavailable, the IPL fails.
.PP
The \f[B]chreipl-fcp-mpath\f[R] toolset monitors \f[B]udev\f[R] events
about paths to the re-IPL volume.
If the currently configured re-IPL path becomes unavailable, the toolset
checks for operational paths to the same volume.
If available, it reconfigures the re-IPL settings to use an operational
The \f[B]chreipl\-fcp\-mpath\f[R] toolset monitors \f[B]udev\f[R] events
about paths to the re\-IPL volume.
If the currently configured re\-IPL path becomes unavailable, the
toolset checks for operational paths to the same volume.
If available, it reconfigures the re\-IPL settings to use an operational
path.
.PP
Thus, re-IPL from an FCP-attached SCSI volume can be successful despite
path failures on a running Linux instance if at least one path to the
re-IPL volume remains operational.
Thus, re\-IPL from an FCP\-attached SCSI volume can be successful
despite path failures on a running Linux instance if at least one path
to the re\-IPL volume remains operational.
.PP
\f[B]Chreipl-fcp-mpath\f[R] requires \f[B]udev\f[R],
\f[B]multipathd\f[R] and \f[B]dm-multipath\f[R].
\f[B]Chreipl\-fcp\-mpath\f[R] requires \f[B]udev\f[R],
\f[B]multipathd\f[R] and \f[B]dm\-multipath\f[R].
Once installed, the toolset runs automatically and autonomously.
No user intervention is possible or required.
.PP
Other than installing the toolset, there is no user interface for
\f[B]chreipl-fcp-mpath\f[R].
\f[B]chreipl\-fcp\-mpath\f[R].
.SS Requirements
.PP
The \f[B]chreipl-fcp-mpath\f[R] tool has the following requirements on
The \f[B]chreipl\-fcp\-mpath\f[R] tool has the following requirements on
the Linux instance that is being monitored:
.IP \[bu] 2
The Linux instance must have started successfully, during IPL.
.IP \[bu] 2
The running Linux instance must use \f[B]dm-multipath\f[R] and
\f[B]multipathd\f[R] for the configured re-IPL volume - a volume that
contains a zipl boot record and has one of its paths used in the re-IPL
The running Linux instance must use \f[B]dm\-multipath\f[R] and
\f[B]multipathd\f[R] for the configured re\-IPL volume \- a volume that
contains a zipl boot record and has one of its paths used in the re\-IPL
configuration.
.IP \[bu] 2
\f[B]udev\f[R] must run.
.IP \[bu] 2
The toolset must observe at least one event about the configured re-IPL
The toolset must observe at least one event about the configured re\-IPL
path.
Examples for such events are: the SCSI disk comes online, or a path of
the corresponding multipath device goes down or comes back online.
.RS 2
.IP \[bu] 2
The WWID of the re-IPL volume must not change while the Linux instance
The WWID of the re\-IPL volume must not change while the Linux instance
is running.
.RE
.IP \[bu] 2
When the configured re-IPL path becomes unavailable while the Linux
instance is running, at least one operational path to the re-IPL volume
When the configured re\-IPL path becomes unavailable while the Linux
instance is running, at least one operational path to the re\-IPL volume
must be available, or must become available.
If no such path is available when the Linux instance is rebooted, the
re-IPL path is not changed.
re\-IPL path is not changed.
.IP \[bu] 2
The tool assumes that any manually reconfigured re-IPL device is valid
The tool assumes that any manually reconfigured re\-IPL device is valid
and operational.
.RS 2
.PP
The tool treats a newly configured re-IPL device like the initially
configured re-IPL device.
In particular, if the newly configured re-IPL device fulfills the
requirements of the tool, re-IPL path failover takes place if the
configured re-IPL path becomes unavailable.
The tool treats a newly configured re\-IPL device like the initially
configured re\-IPL device.
In particular, if the newly configured re\-IPL device fulfills the
requirements of the tool, re\-IPL path failover takes place if the
configured re\-IPL path becomes unavailable.
.RE
.SS Caution with Manual Changes to the Configured re-IPL Target
.PP
\f[B]chreipl-fcp-mpath\f[R] is designed to accept operator-inititated
changes of the re-IPL device.
.SS Caution with Manual Changes to the Configured re\-IPL Target
\f[B]chreipl\-fcp\-mpath\f[R] is designed to accept operator\-inititated
changes of the re\-IPL device.
However, concurrent changes by the operator and tool driven changes can
result in the operator change being overwritten.
.PP
To avoid this problem, change the re-IPL device only during steady-state
operations, when no path events happen.
To avoid this problem, change the re\-IPL device only during
steady\-state operations, when no path events happen.
Alternatively, make sure that no events are processed while you change
the device.
See EXAMPLES for one way to suspend event processing.
.SH MESSAGES
.PP
During monitoring and event processing, \f[B]chreipl-fcp-mpath\f[R]
During monitoring and event processing, \f[B]chreipl\-fcp\-mpath\f[R]
writes messages to the syslog.
.PP
When the configured re-IPL path is changed to a different path to the
When the configured re\-IPL path is changed to a different path to the
same volume (priority \f[I]daemon.notice\f[R]):
.RS
.PP
Changed re-IPL path to: <device-bus-id>:<wwpn>:<lun>.
Changed re\-IPL path to: <device\-bus\-id>:<wwpn>:<lun>.
.RE
.PP
When a path event indicates that the last available path has become
non-operational (priority \f[I]daemon.alert\f[R]):
non\-operational (priority \f[I]daemon.alert\f[R]):
.RS
.PP
The re-IPL device cannot be changed because no operational path to the
re-IPL volume remains.
The next re-IPL might fail unless you re-attach or enable at least one
valid path to the re-IPL volume.
The re\-IPL device cannot be changed because no operational path to the
re\-IPL volume remains.
The next re\-IPL might fail unless you re\-attach or enable at least one
valid path to the re\-IPL volume.
.RE
.PP
When changing the configured re-IPL device failed because of an error
When changing the configured re\-IPL device failed because of an error
with the used Linux kernel interface (priority \f[I]daemon.crit\f[R]):
.RS
.PP
Changing the re-IPL device failed.
The current re-IPL settings might be inconsistent.
Check and correct the settings (see the README.md of chreipl-fcp-mpath)
to make sure that the current re-IPL device is valid.
Changing the re\-IPL device failed.
The current re\-IPL settings might be inconsistent.
Check and correct the settings (see the README.md of
chreipl\-fcp\-mpath) to make sure that the current re\-IPL device is
valid.
.RE
.PP
A failure to change the re-IPL device can indicate an inconsistent
A failure to change the re\-IPL device can indicate an inconsistent
setting that cannot be corrected automatically by
\f[B]chreipl-fcp-mpath\f[R].
As a result, the next re-IPL might fail or might not use the intended
re-IPL device.
\f[B]chreipl\-fcp\-mpath\f[R].
As a result, the next re\-IPL might fail or might not use the intended
re\-IPL device.
.PP
You can use the following tools to check and correct the current
settings:
.IP \[bu] 2
\f[B]lsreipl\f[R] to confirm that the intended re-IPL device is
\f[B]lsreipl\f[R] to confirm that the intended re\-IPL device is
configured;
.IP \[bu] 2
\f[B]chreipl\f[R] to change the re-IPL device;
\f[B]chreipl\f[R] to change the re\-IPL device;
.IP \[bu] 2
\f[B]lszfcp\f[R] to inspect the state of available paths to the re-IPL
\f[B]lszfcp\f[R] to inspect the state of available paths to the re\-IPL
device.
.SH EXAMPLES
.SS Manual Changes to the Configured re-IPL Device
.PP
.SS Manual Changes to the Configured re\-IPL Device
As outlined in DESCRIPTION, be cautious when manually changing the
configured re-IPL device.
configured re\-IPL device.
Assure that your reconfiguration actions do not collide with concurrent
automatic event processing by \f[B]chreipl-fcp-mpath\f[R].
automatic event processing by \f[B]chreipl\-fcp\-mpath\f[R].
You can avoid such collisions, by stopping event processing, making your
changes, and then re-enabling event processing.
changes, and then re\-enabling event processing.
You need \f[I]root\f[R] privileges for running the commands in the
following example:
.IP
.nf
\f[C]
.EX
\[ti] # udevadm settle
\[ti] # udevadm control --stop-exec-queue
\[ti] # udevadm control \-\-stop\-exec\-queue
\[ti] # chreipl ...
\[ti] # udevadm control --start-exec-queue
\f[R]
.fi
\[ti] # udevadm control \-\-start\-exec\-queue
.EE
.SS Listing messages with journalctl
.PP
If your Linux instance includes \f[B]journalctl\f[R], use the following
command to list all messages that are issued by
\f[B]chreipl-fcp-mpath\f[R]:
\f[B]chreipl\-fcp\-mpath\f[R]:
.IP
.nf
\f[C]
\[ti] # journalctl -t chreipl-fcp-mpath
\f[R]
.fi
.EX
\[ti] # journalctl \-t chreipl\-fcp\-mpath
.EE
.PP
To list only messages that were issued since the last IPL, use this
command:
.IP
.nf
\f[C]
\[ti] # journalctl -t chreipl-fcp-mpath -b
\f[R]
.fi
.EX
\[ti] # journalctl \-t chreipl\-fcp\-mpath \-b
.EE
.SH REPORTING BUGS
.PP
Use the \f[B]Issues\f[R] functionality on GitHub to report any bugs in
\f[B]chreipl-fcp-mpath\f[R]: s390-tools
Issues (https://github.com/ibm-s390-linux/s390-tools/issues).
\f[B]chreipl\-fcp\-mpath\f[R]: \c
.UR https://github.com/ibm-s390-linux/s390-tools/issues
s390\-tools Issues
.UE \c
\&.
.SH SEE ALSO
.PP
\f[B]chreipl\f[R](8), \f[B]dracut\f[R](8), \f[B]journalctl\f[R](1),
\f[B]lsreipl(8)\f[R], \f[B]lszfcp\f[R](8), \f[B]multipath\f[R](8),
\f[B]multipathd\f[R](8), \f[B]udev\f[R](7), \f[B]udevadm\f[R](8),

View File

@@ -32,7 +32,7 @@ endif
# The variable "DISTRELEASE" should be overwritten in rpm spec files with:
# "make DISTRELEASE=%{release}" and "make install DISTRELEASE=%{release}"
VERSION := 2
RELEASE := 36
RELEASE := 38
PATCHLEVEL := 0
DISTRELEASE := build-$(shell date +%Y%m%d)
S390_TOOLS_RELEASE := $(VERSION).$(RELEASE).$(PATCHLEVEL)-$(DISTRELEASE)
@@ -265,9 +265,12 @@ LIBDIR = $(INSTALLDIR)/lib
USRLIBDIR = $(INSTALLDIR)/usr/lib
USRLIB64DIR = $(INSTALLDIR)/usr/lib64
SYSCONFDIR = $(INSTALLDIR)/etc
MANDIR = $(INSTALLDIR)/usr/share/man
DATADIR = $(INSTALLDIR)/usr/share
MANDIR = $(DATADIR)/man
BASHCOMPLETIONDIR = $(DATADIR)/bash-completion/completions
ZSHCOMPLETIONDIR = $(DATADIR)/zsh/site-functions
VARDIR = $(INSTALLDIR)/var
TOOLS_DATADIR = $(INSTALLDIR)/usr/share/s390-tools
TOOLS_DATADIR = $(DATADIR)/s390-tools
TOOLS_LIBDIR = $(INSTALLDIR)/lib/s390-tools
ZFCPDUMP_DIR = $(TOOLS_LIBDIR)/zfcpdump
# Systemd support files are installed only if a directory is specified

View File

@@ -46,8 +46,8 @@ All counters are initially disabled and must be switched on to measure
CPACF activities of the system. There is a slight performance penalty with
CPACF counters enabled.
CPACF activity counters come in two flavors: CPU-MF and PAI. CPU-MF
counters are only available on LPARs and have to be authorized. If
CPACF activity counters come in two flavors: CPU-MF and PAI. CPU-MF
counters are only available on LPARs and have to be authorized. If
they are available, the counters
.B des,
.B aes,
@@ -55,14 +55,131 @@ they are available, the counters
.B rng,
and
.B ecc
are made available. These counters can individually be activated,
reset, printed, or deactivated. PAI counters are a lot more detailed.
are made available. These counters can individually be activated,
reset, printed, or deactivated. PAI counters are a lot more detailed.
The user interface only offers the counters
.B pai_user
and
.B pai_kernel
to count CPACF usage in user-space or kernel-space. When printing
these counters, detailed counters are shown. The detailed names are:
to count CPACF usage in user-space or kernel-space. When printing
these counters, detailed counters are shown.
A complete list of counters can be found at the end of this manpage.
Note that the counters starting with PCKMO and Reserved are only
available in the pai_kernel set. Also note that the counters are
designed to count successful operations. In the case of KMA this
means only complete GCM operations including final hashing are
counted.
Note: CPU-MF based CPACF performance counters are available on LPARs
only. PAI counters are available on all hypervisors. For security
reasons only members of the group \fIcpacfstats\fR are allowed to run
the cpacfstats client application.
Example usage scenario:
.P
1. Start the cpacfstatsd daemon with root privileges.
.P
2. Check for successful startup by using the ps and syslog commands.
.P
3. Enable the CPACF counters of interest. For example, enable all counters
by issuing cpacfstats -e.
.P
4. Run your applications.
.P
5. Display counter values by using the cpacfstats command. Reset the
cryptographic counters as required. To reset, use, for example, cpacfstats
-r.
.P
6. Disable all the CPACF measurements, for example, by using cpacfstats
-d.
.P
7. Shutdown the cpacfstatsd daemon by using killall cpacfstatsd.
.SH OPTIONS
.TP
\fB\-h\fR or \fB\-\-help\fR
Display help information for the command.
.TP
\fB\-v\fR or \fB\-\-version\fR
Display version and copyright information for the command.
.TP
\fB\-e\fR or \fB\-\-enable\fR [counter]
Enable one or all CPACF performance counters. The optional counter
argument can be one of: \fBdes\fR, \fBaes\fR, \fBsha\fR, \fBprng\fR,
\fBecc\fR, or \fBall\fR. If the counter argument is omitted, all
performance counters are enabled. Enabling a counter does not reset
it. New events are added to the current counter value.
.TP
\fB\-d\fR or \fB\-\-disable\fR [counter]
Disable one or all CPACF performance counters. The optional counter
argument can be one of: \fBdes\fR, \fBaes\fR, \fBsha\fR, \fBprng\fR,
\fBecc\fR, or \fBall\fR. If the counter argument is omitted, all
performance counters are disabled. Disabling a counter does not reset
it. The counter value is preserved when a counter is disabled, and
counting will resume using the preserved value when the counter is
re-enabled.
.TP
\fB\-r\fR or \fB\-\-reset\fR [counter]
Reset one or all CPACF performance counters. The optional counter
argument can be one of: \fBdes\fR, \fBaes\fR, \fBsha\fR, \fBprng\fR,
\fBecc\fR, or \fBall\fR. If the counter argument is omitted, all
performance counters are reset to 0.
.TP
\fB\-p\fR or \fB\-\-print\fR [\fB\-n\fR or \fB\-\-nonzero\fR] [counter]
Display the value of one or all CPACF performance counters. The
optional counter argument can be one of: \fBdes\fR, \fBaes\fR,
\fBsha\fR, \fBprng\fR, \fBecc\fR, \fBpai_user\fR, \fBpai_kernel\fR, or
\fBall\fR. If the counter argument is omitted or if there is no
argument, all performance counters are displayed. If the optional
\fB\-n\fR or \fB\-\-nonzero\fR argument is given, then only PAI
counters that have a non-zero value are printed.
.TP
\fB\-j\fR or \fB\-\-json\fR
Display all activated counters in JSON format. The JSON contains an
array of counter objects. Each object contains the property
.B counter
specifying either a CPU-MF counter of one of the detailed PAI counter.
Additional properties include
.B error
an error number if the counter could not be read,
.B value
the counter value if the counter could be read,
.B space
for PAI counters to specify
.B user
or
.B kernel
space counter set, and
.B counterid
for PAI counters to specify the PAI counter number as specified in the
Principles of Operation.
.TP
The default command is --print all.
.
.SH FILES
.nf
/run/cpacfstatsd_socket
.fi
.
.SH RETURN VALUE
.IP 0
Successful program execution.
.IP 1
An error occurred, reasons include: invalid argument, cpacfstatsd could not
be reached (check that the daemon is running), insufficient access rights,
version mismatch between client and daemon, or the application is out of
memory. The application prints a message with the details of the error and
the errno value.
.
.SH NOTES
ECC counters are only available since z15. cpacfstats will show the
counters as \fIunsupported\fR if the hardware does not support ECC
counters.
.
.SH APPENDIX
The detailed pai counter names are:
.RS
.IP \(bu
KM DES,
@@ -376,118 +493,39 @@ PCKMO ECC Ed448,
Reserved 1, and
.IP \(bu
Reserved 2.
.IP \(bu
KM AES-XTS (full) 128bit
.IP \(bu
KM AES-XTS (full) 256bit
.IP \(bu
KM AES-XTS (full) 128bit protected key
.IP \(bu
KM AES-XTS (full) 256bit protected key
.IP \(bu
KMAC HMAC SHA 224
.IP \(bu
KMAC HMAC SHA 256
.IP \(bu
KMAC HMAC SHA 384
.IP \(bu
KMAC HMAC SHA 512
.IP \(bu
KMAC HMAC SHA 224 protected key
.IP \(bu
KMAC HMAC SHA 256 protected key
.IP \(bu
KMAC HMAC SHA 384 protected key
.IP \(bu
KMAC HMAC SHA 512 protected key
.IP \(bu
PCKMO HMAC 512 protected key
.IP \(bu
PCKMO HMAC 1024 protected key
.IP \(bu
PCKMO AES-XTS 128bit double key protected key
.IP \(bu
PCKMO AES-XTS 256bit double key protected key
.RE
Note that the counters starting with PCKMO and Reserved are only
available in the pai_kernel set. Also note that the counters are
designed to count successful operations. In the case of KMA this
means only complete GCM operations including final hashing are
counted.
Note: CPU-MF based CPACF performance counters are available on LPARs
only. PAI counters are available on all hypervisors. For security
reasons only members of the group \fIcpacfstats\fR are allowed to run
the cpacfstats client application.
Example usage scenario:
.P
1. Start the cpacfstatsd daemon with root privileges.
.P
2. Check for successful startup by using the ps and syslog commands.
.P
3. Enable the CPACF counters of interest. For example, enable all counters
by issuing cpacfstats -e.
.P
4. Run your applications.
.P
5. Display counter values by using the cpacfstats command. Reset the
cryptographic counters as required. To reset, use, for example, cpacfstats
-r.
.P
6. Disable all the CPACF measurements, for example, by using cpacfstats
-d.
.P
7. Shutdown the cpacfstatsd daemon by using killall cpacfstatsd.
.SH OPTIONS
.TP
\fB\-h\fR or \fB\-\-help\fR
Display help information for the command.
.TP
\fB\-v\fR or \fB\-\-version\fR
Display version and copyright information for the command.
.TP
\fB\-e\fR or \fB\-\-enable\fR [counter]
Enable one or all CPACF performance counters. The optional counter
argument can be one of: \fBdes\fR, \fBaes\fR, \fBsha\fR, \fBprng\fR,
\fBecc\fR, or \fBall\fR. If the counter argument is omitted, all
performance counters are enabled. Enabling a counter does not reset
it. New events are added to the current counter value.
.TP
\fB\-d\fR or \fB\-\-disable\fR [counter]
Disable one or all CPACF performance counters. The optional counter
argument can be one of: \fBdes\fR, \fBaes\fR, \fBsha\fR, \fBprng\fR,
\fBecc\fR, or \fBall\fR. If the counter argument is omitted, all
performance counters are disabled. Disabling a counter does not reset
it. The counter value is preserved when a counter is disabled, and
counting will resume using the preserved value when the counter is
re-enabled.
.TP
\fB\-r\fR or \fB\-\-reset\fR [counter]
Reset one or all CPACF performance counters. The optional counter
argument can be one of: \fBdes\fR, \fBaes\fR, \fBsha\fR, \fBprng\fR,
\fBecc\fR, or \fBall\fR. If the counter argument is omitted, all
performance counters are reset to 0.
.TP
\fB\-p\fR or \fB\-\-print\fR [\fB\-n\fR or \fB\-\-nonzero\fR] [counter]
Display the value of one or all CPACF performance counters. The
optional counter argument can be one of: \fBdes\fR, \fBaes\fR,
\fBsha\fR, \fBprng\fR, \fBecc\fR, \fBpai_user\fR, \fBpai_kernel\fR, or
\fBall\fR. If the counter argument is omitted or if there is no
argument, all performance counters are displayed. If the optional
\fB\-n\fR or \fB\-\-nonzero\fR argument is given, then only PAI
counters that have a non-zero value are printed.
.TP
\fB\-j\fR or \fB\-\-json\fR
Display all activated counters in JSON format. The JSON contains an
array of counter objects. Each object contains the property
.B counter
specifying either a CPU-MF counter of one of the detailed PAI counter.
Additional properties include
.B error
an error number if the counter could not be read,
.B value
the counter value if the counter could be read,
.B space
for PAI counters to specify
.B user
or
.B kernel
space counter set, and
.B counterid
for PAI counters to specify the PAI counter number as specified in the
Principles of Operation.
.TP
The default command is --print all.
.
.SH FILES
.nf
/run/cpacfstatsd_socket
.fi
.
.SH RETURN VALUE
.IP 0
Successful program execution.
.IP 1
An error occurred, reasons include: invalid argument, cpacfstatsd could not
be reached (check that the daemon is running), insufficient access rights,
version mismatch between client and daemon, or the application is out of
memory. The application prints a message with the details of the error and
the errno value.
.
.SH NOTES
ECC counters are only available since z15. cpacfstats will show the
counters as \fIunsupported\fR if the hardware does not support ECC
counters.
.
.SH SEE ALSO
.BR cpacfstatsd (8)

View File

@@ -16,11 +16,11 @@
#include <unistd.h>
#include <sys/stat.h>
#include "lib/util_opt.h"
#include "lib/util_prg.h"
#include "lib/util_base.h"
#include "lib/libcpumf.h"
#include "lib/util_base.h"
#include "lib/util_opt.h"
#include "lib/util_path.h"
#include "lib/util_prg.h"
static unsigned int verbose;
static unsigned long min_sdb, max_sdb;
@@ -88,21 +88,22 @@ static long parse_buffersize(char *string)
static int write_sfb(unsigned int min, unsigned int max)
{
int rc = EXIT_SUCCESS;
char text[64];
char text[64], *path;
size_t len;
FILE *fp;
fp = fopen(S390_CPUMSF_BUFFERSZ, "w");
path = util_path_sysfs(S390_CPUMSF_BUFFERSZ);
fp = fopen(path, "w");
if (!fp)
err(EXIT_FAILURE, S390_CPUMSF_BUFFERSZ);
err(EXIT_FAILURE, "%s", path);
snprintf(text, sizeof(text), "%u,%u", min, max);
len = strlen(text) + 1;
if (fwrite(text, 1, len, fp) != len) {
warn(S390_CPUMSF_BUFFERSZ);
warn("%s", path);
rc = EXIT_FAILURE;
}
if (fclose(fp)) {
warn(S390_CPUMSF_BUFFERSZ);
warn("%s", path);
rc = EXIT_FAILURE;
}
if (verbose && rc != EXIT_FAILURE)
@@ -110,6 +111,7 @@ static int write_sfb(unsigned int min, unsigned int max)
" Minimum:%7d sample-data-blocks\n"
" Maximum:%7d sample-data-blocks\n",
min, max);
free(path);
return rc;
}

View File

@@ -162,13 +162,6 @@ static struct samples { /* Sample definition for all machines */
* Second version number: >3 Range 448 to 495 inclusive (48 counters)
*/
#define CPUMF_CTRSET_NONE 0
#define CPUMF_CTRSET_BASIC 2
#define CPUMF_CTRSET_PROBLEM_STATE 4
#define CPUMF_CTRSET_CRYPTO 8
#define CPUMF_CTRSET_EXTENDED 1
#define CPUMF_CTRSET_MT_DIAG 32
struct counters {
int ctrnum;
int ctrset;
@@ -3136,6 +3129,641 @@ static struct counters cpumcf_z16_counters[] = {
},
};
static struct counters cpumcf_z17_counters[] = {
{
.ctrnum = 128,
.ctrset = CPUMF_CTRSET_EXTENDED,
.name = "L1D_RO_EXCL_WRITES",
.desc = "A directory write to the Level-1 Data cache where"
"\n\t\tthe line was originally in a Read-Only state in the"
"\n\t\tcache but has been updated to be in the Exclusive"
"\n\t\tstate that allows stores to the cache line.",
},
{
.ctrnum = 129,
.ctrset = CPUMF_CTRSET_EXTENDED,
.name = "DTLB2_WRITES",
.desc = "A translation has been written into The Translation"
"\n\t\tLookaside Buffer 2 (TLB2) and the request was made"
"\n\t\tby the Level-1 Data cache. This is a replacement"
"\n\t\tfor what was provided for the DTLB on z13 and prior"
"\n\t\tmachines.",
},
{
.ctrnum = 130,
.ctrset = CPUMF_CTRSET_EXTENDED,
.name = "DTLB2_MISSES",
.desc = "A TLB2 miss is in progress for a request made by"
"\n\t\tthe Level-1 Data cache. Incremented by one for"
"\n\t\tevery TLB2 miss in progress for the Level-1 Data"
"\n\t\tcache on this cycle. This is a replacement for what"
"\n\t\twas provided for the DTLB on z13 and prior"
"\n\t\tmachines.",
},
{
.ctrnum = 131,
.ctrset = CPUMF_CTRSET_EXTENDED,
.name = "CRSTE_1MB_WRITES",
.desc = "A translation entry was written into the Combined"
"\n\t\tRegion and Segment Table Entry array in the Level-2"
"\n\t\tTLB for a one-megabyte page.",
},
{
.ctrnum = 132,
.ctrset = CPUMF_CTRSET_EXTENDED,
.name = "DTLB2_GPAGE_WRITES",
.desc = "A translation entry for a two-gigabyte page was"
"\n\t\twritten into the Level-2 TLB.",
},
{
.ctrnum = 134,
.ctrset = CPUMF_CTRSET_EXTENDED,
.name = "ITLB2_WRITES",
.desc = "A translation entry has been written into the"
"\n\t\tTranslation Lookaside Buffer 2 (TLB2) and the"
"\n\t\trequest was made by the Level-1 Instruction cache."
"\n\t\tThis is a replacement for what was provided for the"
"\n\t\tITLB on z13 and prior machines.",
},
{
.ctrnum = 135,
.ctrset = CPUMF_CTRSET_EXTENDED,
.name = "ITLB2_MISSES",
.desc = "A TLB2 miss is in progress for a request made by"
"\n\t\tthe Level-1 Instruction cache. Incremented by one"
"\n\t\tfor every TLB2 miss in progress for the Level-1"
"\n\t\tInstruction cache in a cycle. This is a replacement"
"\n\t\tfor what was provided for the ITLB on z13 and prior"
"\n\t\tmachines.",
},
{
.ctrnum = 137,
.ctrset = CPUMF_CTRSET_EXTENDED,
.name = "TLB2_PTE_WRITES",
.desc = "A translation entry was written into the Page Table"
"\n\t\tEntry array in the Level-2 TLB.",
},
{
.ctrnum = 138,
.ctrset = CPUMF_CTRSET_EXTENDED,
.name = "TLB2_CRSTE_WRITES",
.desc = "Translation entries were written into the Combined"
"\n\t\tRegion and Segment Table Entry array and the Page"
"\n\t\tTable Entry array in the Level-2 TLB.",
},
{
.ctrnum = 139,
.ctrset = CPUMF_CTRSET_EXTENDED,
.name = "TLB2_ENGINES_BUSY",
.desc = "The number of Level-2 TLB translation engines busy"
"\n\t\tin a cycle.",
},
{
.ctrnum = 140,
.ctrset = CPUMF_CTRSET_EXTENDED,
.name = "TX_C_TEND",
.desc = "A TEND instruction has completed in a constrained"
"\n\t\ttransactional-execution mode.",
},
{
.ctrnum = 141,
.ctrset = CPUMF_CTRSET_EXTENDED,
.name = "TX_NC_TEND",
.desc = "A TEND instruction has completed in a non-"
"\n\t\tconstrained transactional-execution mode.",
},
{
.ctrnum = 143,
.ctrset = CPUMF_CTRSET_EXTENDED,
.name = "L1C_TLB2_MISSES",
.desc = "Increments by one for any cycle where a Level-1"
"\n\t\tcache or Level-2 TLB miss is in progress.",
},
{
.ctrnum = 145,
.ctrset = CPUMF_CTRSET_EXTENDED,
.name = "DCW_REQ",
.desc = "A directory write to the Level-1 Data cache"
"\n\t\tdirectory where the returned cache line was sourced"
"\n\t\tfrom the requestors Level-2 cache.",
},
{
.ctrnum = 146,
.ctrset = CPUMF_CTRSET_EXTENDED,
.name = "DCW_REQ_IV",
.desc = "A directory write to the Level-1 Data cache"
"\n\t\tdirectory where the returned cache line was sourced"
"\n\t\tfrom the requestors Level-2 cache with"
"\n\t\tintervention.",
},
{
.ctrnum = 147,
.ctrset = CPUMF_CTRSET_EXTENDED,
.name = "DCW_REQ_CHIP_HIT",
.desc = "A directory write to the Level-1 Data cache"
"\n\t\tdirectory where the returned cache line was sourced"
"\n\t\tfrom the requestors Level-2 cache after using"
"\n\t\tchip level horizontal persistence, Chip-HP hit.",
},
{
.ctrnum = 148,
.ctrset = CPUMF_CTRSET_EXTENDED,
.name = "DCW_REQ_DRAWER_HIT",
.desc = "A directory write to the Level-1 Data cache"
"\n\t\tdirectory where the returned cache line was sourced"
"\n\t\tfrom the requestors Level-2 cache after using"
"\n\t\tdrawer level horizontal persistence, Drawer-HP hit.",
},
{
.ctrnum = 149,
.ctrset = CPUMF_CTRSET_EXTENDED,
.name = "DCW_ON_CHIP",
.desc = "A directory write to the Level-1 Data cache"
"\n\t\tdirectory where the returned cache line was sourced"
"\n\t\tfrom an On-Chip Level-2 cache.",
},
{
.ctrnum = 150,
.ctrset = CPUMF_CTRSET_EXTENDED,
.name = "DCW_ON_CHIP_IV",
.desc = "A directory write to the Level-1 Data cache"
"\n\t\tdirectory where the returned cache line was sourced"
"\n\t\tfrom an On-Chip Level-2 cache with intervention.",
},
{
.ctrnum = 151,
.ctrset = CPUMF_CTRSET_EXTENDED,
.name = "DCW_ON_CHIP_CHIP_HIT",
.desc = "A directory write to the Level-1 Data cache"
"\n\t\tdirectory where the returned cache line was sourced"
"\n\t\tfrom an On-Chip Level-2 cache after using chip"
"\n\t\tlevel horizontal persistence, Chip-HP hit.",
},
{
.ctrnum = 152,
.ctrset = CPUMF_CTRSET_EXTENDED,
.name = "DCW_ON_CHIP_DRAWER_HIT",
.desc = "A directory write to the Level-1 Data cache"
"\n\t\tdirectory where the returned cache line was sourced"
"\n\t\tfrom an On-Chip Level-2 cache after using drawer"
"\n\t\tlevel horizontal persistence, Drawer-HP hit.",
},
{
.ctrnum = 153,
.ctrset = CPUMF_CTRSET_EXTENDED,
.name = "DCW_ON_MODULE",
.desc = "A directory write to the Level-1 Data cache"
"\n\t\tdirectory where the returned cache line was sourced"
"\n\t\tfrom an On-Module Level-2 cache.",
},
{
.ctrnum = 154,
.ctrset = CPUMF_CTRSET_EXTENDED,
.name = "DCW_ON_DRAWER",
.desc = "A directory write to the Level-1 Data cache"
"\n\t\tdirectory where the returned cache line was sourced"
"\n\t\tfrom an On-Drawer Level-2 cache.",
},
{
.ctrnum = 155,
.ctrset = CPUMF_CTRSET_EXTENDED,
.name = "DCW_OFF_DRAWER",
.desc = "A directory write to the Level-1 Data cache"
"\n\t\tdirectory where the returned cache line was sourced"
"\n\t\tfrom an Off-Drawer Level-2 cache.",
},
{
.ctrnum = 156,
.ctrset = CPUMF_CTRSET_EXTENDED,
.name = "DCW_ON_CHIP_MEMORY",
.desc = "A directory write to the Level-1 Data or Level-1"
"\n\t\tInstruction cache directory where the returned"
"\n\t\tcache line was sourced from On-Chip memory.",
},
{
.ctrnum = 157,
.ctrset = CPUMF_CTRSET_EXTENDED,
.name = "DCW_ON_MODULE_MEMORY",
.desc = "A directory write to the Level-1 Data or Level-1"
"\n\t\tInstruction cache directory where the returned"
"\n\t\tcache line was sourced from On-Module memory.",
},
{
.ctrnum = 158,
.ctrset = CPUMF_CTRSET_EXTENDED,
.name = "DCW_ON_DRAWER_MEMORY",
.desc = "A directory write to the Level-1 Data or Level-1"
"\n\t\tInstruction cache directory where the returned"
"\n\t\tcache line was sourced from On-Drawer memory.",
},
{
.ctrnum = 159,
.ctrset = CPUMF_CTRSET_EXTENDED,
.name = "DCW_OFF_DRAWER_MEMORY",
.desc = "A directory write to the Level-1 Data or Level-1"
"\n\t\tInstruction cache directory where the returned"
"\n\t\tcache line was sourced from Off-Drawer memory.",
},
{
.ctrnum = 160,
.ctrset = CPUMF_CTRSET_EXTENDED,
.name = "IDCW_ON_MODULE_IV",
.desc = "A directory write to the Level-1 Data or Level-1"
"\n\t\tInstruction cache directory where the returned"
"\n\t\tcache line was sourced from an On-Module Level-2"
"\n\t\tcache with intervention.",
},
{
.ctrnum = 161,
.ctrset = CPUMF_CTRSET_EXTENDED,
.name = "IDCW_ON_MODULE_CHIP_HIT",
.desc = "A directory write to the Level-1 Data or Level-1"
"\n\t\tInstruction cache directory where the returned"
"\n\t\tcache line was sourced from an On-Module Level-2"
"\n\t\tcache after using chip level horizontal"
"\n\t\tpersistence, Chip-HP hit.",
},
{
.ctrnum = 162,
.ctrset = CPUMF_CTRSET_EXTENDED,
.name = "IDCW_ON_MODULE_DRAWER_HIT",
.desc = "A directory write to the Level-1 Data or Level-1"
"\n\t\tInstruction cache directory where the returned"
"\n\t\tcache line was sourced from an On-Module Level-2"
"\n\t\tcache after using drawer level horizontal"
"\n\t\tpersistence, Drawer-HP hit.",
},
{
.ctrnum = 163,
.ctrset = CPUMF_CTRSET_EXTENDED,
.name = "IDCW_ON_DRAWER_IV",
.desc = "A directory write to the Level-1 Data or Level-1"
"\n\t\tInstruction cache directory where the returned"
"\n\t\tcache line was sourced from an On-Drawer Level-2"
"\n\t\tcache with intervention.",
},
{
.ctrnum = 164,
.ctrset = CPUMF_CTRSET_EXTENDED,
.name = "IDCW_ON_DRAWER_CHIP_HIT",
.desc = "A directory write to the Level-1 Data or Level-1"
"\n\t\tinstruction cache directory where the returned"
"\n\t\tcache line was sourced from an On-Drawer Level-2"
"\n\t\tcache after using chip level horizontal"
"\n\t\tpersistence, Chip-HP hit.",
},
{
.ctrnum = 165,
.ctrset = CPUMF_CTRSET_EXTENDED,
.name = "IDCW_ON_DRAWER_DRAWER_HIT",
.desc = "A directory write to the Level-1 Data or Level-1"
"\n\t\tinstruction cache directory where the returned"
"\n\t\tcache line was sourced from an On-Drawer Level-2"
"\n\t\tcache after using drawer level horizontal"
"\n\t\tpersistence, Drawer-HP hit.",
},
{
.ctrnum = 166,
.ctrset = CPUMF_CTRSET_EXTENDED,
.name = "IDCW_OFF_DRAWER_IV",
.desc = "A directory write to the Level-1 Data or Level-1"
"\n\t\tinstruction cache directory where the returned"
"\n\t\tcache line was sourced from an Off-Drawer Level-2"
"\n\t\tcache with intervention.",
},
{
.ctrnum = 167,
.ctrset = CPUMF_CTRSET_EXTENDED,
.name = "IDCW_OFF_DRAWER_CHIP_HIT",
.desc = "A directory write to the Level-1 Data or Level-1"
"\n\t\tinstruction cache directory where the returned"
"\n\t\tcache line was sourced from an Off-Drawer Level-2"
"\n\t\tcache after using chip level horizontal"
"\n\t\tpersistence, Chip-HP hit.",
},
{
.ctrnum = 168,
.ctrset = CPUMF_CTRSET_EXTENDED,
.name = "IDCW_OFF_DRAWER_DRAWER_HIT",
.desc = "A directory write to the Level-1 Data or Level-1"
"\n\t\tInstruction cache directory where the returned"
"\n\t\tcache line was sourced from an Off-Drawer Level-2"
"\n\t\tcache after using drawer level horizontal"
"\n\t\tpersistence, Drawer-HP hit.",
},
{
.ctrnum = 169,
.ctrset = CPUMF_CTRSET_EXTENDED,
.name = "ICW_REQ",
.desc = "A directory write to the Level-1 Instruction cache"
"\n\t\tdirectory where the returned cache line was sourced"
"\n\t\tthe requestors Level-2 cache.",
},
{
.ctrnum = 170,
.ctrset = CPUMF_CTRSET_EXTENDED,
.name = "ICW_REQ_IV",
.desc = "A directory write to the Level-1 Instruction cache"
"\n\t\tdirectory where the returned cache line was sourced"
"\n\t\tfrom the requestors Level-2 cache with"
"\n\t\tintervention.",
},
{
.ctrnum = 171,
.ctrset = CPUMF_CTRSET_EXTENDED,
.name = "ICW_REQ_CHIP_HIT",
.desc = "A directory write to the Level-1 Instruction cache"
"\n\t\tdirectory where the returned cache line was sourced"
"\n\t\tfrom the requestors Level-2 cache after using"
"\n\t\tchip level horizontal persistence, Chip-HP hit.",
},
{
.ctrnum = 172,
.ctrset = CPUMF_CTRSET_EXTENDED,
.name = "ICW_REQ_DRAWER_HIT",
.desc = "A directory write to the Level-1 Instruction cache"
"\n\t\tdirectory where the returned cache line was sourced"
"\n\t\tfrom the requestors Level-2 cache after using"
"\n\t\tdrawer level horizontal persistence, Drawer-HP hit.",
},
{
.ctrnum = 173,
.ctrset = CPUMF_CTRSET_EXTENDED,
.name = "ICW_ON_CHIP",
.desc = "A directory write to the Level-1 Instruction cache"
"\n\t\tdirectory where the returned cache line was sourced"
"\n\t\tfrom an On-Chip Level-2 cache.",
},
{
.ctrnum = 174,
.ctrset = CPUMF_CTRSET_EXTENDED,
.name = "ICW_ON_CHIP_IV",
.desc = "A directory write to the Level-1 Instruction cache"
"\n\t\tdirectory where the returned cache line was sourced"
"\n\t\tfrom an On-Chip Level-2 cache with intervention.",
},
{
.ctrnum = 175,
.ctrset = CPUMF_CTRSET_EXTENDED,
.name = "ICW_ON_CHIP_CHIP_HIT",
.desc = "A directory write to the Level-1 Instruction cache"
"\n\t\tdirectory where the returned cache line was sourced"
"\n\t\tfrom an On-Chip Level-2 cache after using chip"
"\n\t\tlevel horizontal persistence, Chip-HP hit.",
},
{
.ctrnum = 176,
.ctrset = CPUMF_CTRSET_EXTENDED,
.name = "ICW_ON_CHIP_DRAWER_HIT",
.desc = "A directory write to the Level-1 Instruction cache"
"\n\t\tdirectory where the returned cache line was sourced"
"\n\t\tfrom an On-Chip level 2 cache after using drawer"
"\n\t\tlevel horizontal persistence, Drawer-HP hit.",
},
{
.ctrnum = 177,
.ctrset = CPUMF_CTRSET_EXTENDED,
.name = "ICW_ON_MODULE",
.desc = "A directory write to the Level-1 Instruction cache"
"\n\t\tdirectory where the returned cache line was sourced"
"\n\t\tfrom an On-Module Level-2 cache.",
},
{
.ctrnum = 178,
.ctrset = CPUMF_CTRSET_EXTENDED,
.name = "ICW_ON_DRAWER",
.desc = "A directory write to the Level-1 Instruction cache"
"\n\t\tdirectory where the returned cache line was sourced"
"\n\t\tfrom an On-Drawer Level-2 cache.",
},
{
.ctrnum = 179,
.ctrset = CPUMF_CTRSET_EXTENDED,
.name = "ICW_OFF_DRAWER",
.desc = "A directory write to the Level-1 Instruction cache"
"\n\t\tdirectory where the returned cache line was sourced"
"\n\t\tfrom an Off-Drawer Level-2 cache.",
},
{
.ctrnum = 202,
.ctrset = CPUMF_CTRSET_EXTENDED,
.name = "CYCLES_SAMETHRD",
.desc = "The number of cycles the CPU is not in wait state"
"\n\t\tand the CPU is running by itself on the Core.",
},
{
.ctrnum = 203,
.ctrset = CPUMF_CTRSET_EXTENDED,
.name = "CYCLES_DIFFTHRD",
.desc = "The number of cycles the CPU is not in wait state"
"\n\t\tand the CPU is running with another thread on the"
"\n\t\tCore.",
},
{
.ctrnum = 204,
.ctrset = CPUMF_CTRSET_EXTENDED,
.name = "INST_SAMETHRD",
.desc = "The number of instructions executed on the CPU and"
"\n\t\tthe CPU is running by itself on the Core.",
},
{
.ctrnum = 205,
.ctrset = CPUMF_CTRSET_EXTENDED,
.name = "INST_DIFFTHRD",
.desc = "The number of instructions executed on the CPU and"
"\n\t\tthe CPU is running with another thread on the Core.",
},
{
.ctrnum = 206,
.ctrset = CPUMF_CTRSET_EXTENDED,
.name = "WRONG_BRANCH_PREDICTION",
.desc = "A count of the number of branches that were"
"\n\t\tpredicted incorrectly by the branch prediction"
"\n\t\tlogic in the Core. This includes incorrectly"
"\n\t\tpredicted branches that are executed in Firmware."
"\n\t\tExamples of instructions implemented in Firmware"
"\n\t\tare complicated instructions like MVCL (Move"
"\n\t\tCharacter Long) and PC (Program Call).",
},
{
.ctrnum = 225,
.ctrset = CPUMF_CTRSET_EXTENDED,
.name = "VX_BCD_EXECUTION_SLOTS",
.desc = "Count of floating point execution slots used for"
"\n\t\tfinished vector arithmetic Binary Coded Decimal"
"\n\t\tinstructions. Instructions: VAP, VSP, VMP, VMSP,"
"\n\t\tVDP, VSDP, VRP, VLIP, VSRP, VPSOP, VCP, VTP, VPKZ,"
"\n\t\tVUPKZ, VCVB, VCVBG, VCVD, VCVDG, VSCHP, VSCSHP,"
"\n\t\tVCSPH, VCLZDP, VPKZR, VSRPR, VUPKZH, VUPKZL, VTZ,"
"\n\t\tVUPH, VUPL, VCVBX, VCVDX.",
},
{
.ctrnum = 226,
.ctrset = CPUMF_CTRSET_EXTENDED,
.name = "DECIMAL_INSTRUCTIONS",
.desc = "Decimal instruction dispatched. Instructions: CVB,"
"\n\t\tCVD, AP, CP, DP, ED, EDMK, MP, SRP, SP, ZAP, TP.",
},
{
.ctrnum = 232,
.ctrset = CPUMF_CTRSET_EXTENDED,
.name = "LAST_HOST_TRANSLATIONS",
.desc = "Last Host Translation done.",
},
{
.ctrnum = 244,
.ctrset = CPUMF_CTRSET_EXTENDED,
.name = "TX_NC_TABORT",
.desc = "A transaction abort has occurred in a non-"
"\n\t\tconstrained transactional-execution mode.",
},
{
.ctrnum = 245,
.ctrset = CPUMF_CTRSET_EXTENDED,
.name = "TX_C_TABORT_NO_SPECIAL",
.desc = "A transaction abort has occurred in a constrained"
"\n\t\ttransactional-execution mode and the CPU is not"
"\n\t\tusing any special logic to allow the transaction to"
"\n\t\tcomplete.",
},
{
.ctrnum = 246,
.ctrset = CPUMF_CTRSET_EXTENDED,
.name = "TX_C_TABORT_SPECIAL",
.desc = "A transaction abort has occurred in a constrained"
"\n\t\ttransactional-execution mode and the CPU is using"
"\n\t\tspecial logic to allow the transaction to complete.",
},
{
.ctrnum = 248,
.ctrset = CPUMF_CTRSET_EXTENDED,
.name = "DFLT_ACCESS",
.desc = "Cycles CPU spent obtaining access to Deflate unit.",
},
{
.ctrnum = 253,
.ctrset = CPUMF_CTRSET_EXTENDED,
.name = "DFLT_CYCLES",
.desc = "Cycles CPU is using Deflate unit.",
},
{
.ctrnum = 256,
.ctrset = CPUMF_CTRSET_EXTENDED,
.name = "SORTL",
.desc = "Increments by one for every SORT LISTS (SORTL)"
"\n\t\tinstruction executed.",
},
{
.ctrnum = 265,
.ctrset = CPUMF_CTRSET_EXTENDED,
.name = "DFLT_CC",
.desc = "Increments by one for every DEFLATE CONVERSION CALL"
"\n\t\t(DFLTCC) instruction executed.",
},
{
.ctrnum = 266,
.ctrset = CPUMF_CTRSET_EXTENDED,
.name = "DFLT_CCFINISH",
.desc = "Increments by one for every DEFLATE CONVERSION CALL"
"\n\t\t(DFLTCC) instruction executed that ended in"
"\n\t\tCondition Codes 0, 1 or 2.",
},
{
.ctrnum = 267,
.ctrset = CPUMF_CTRSET_EXTENDED,
.name = "NNPA_INVOCATIONS",
.desc = "Increments by one for every NEURAL NETWORK"
"\n\t\tPROCESSING ASSIST (NNPA) instruction executed.",
},
{
.ctrnum = 268,
.ctrset = CPUMF_CTRSET_EXTENDED,
.name = "NNPA_COMPLETIONS",
.desc = "Increments by one for every NEURAL NETWORK"
"\n\t\tPROCESSING ASSIST (NNPA) instruction executed that"
"\n\t\tended in Condition Code 0.",
},
{
.ctrnum = 269,
.ctrset = CPUMF_CTRSET_EXTENDED,
.name = "NNPA_WAIT_LOCK",
.desc = "Cycles CPU spent obtaining access to IBM Z"
"\n\t\tIntegrated Accelerator for AI.",
},
{
.ctrnum = 270,
.ctrset = CPUMF_CTRSET_EXTENDED,
.name = "NNPA_HOLD_LOCK",
.desc = "Cycles CPU is using IBM Z Integrated Accelerator"
"\n\t\tfor AI.",
},
{
.ctrnum = 272,
.ctrset = CPUMF_CTRSET_EXTENDED,
.name = "NNPA_INST_ONCHIP",
.desc = "A NEURAL NETWORK PROCESSING ASSIST (NNPA)"
"\n\t\tinstruction has used the Local On-Chip IBM Z"
"\n\t\tIntegrated Accelerator for AI during its execution",
},
{
.ctrnum = 273,
.ctrset = CPUMF_CTRSET_EXTENDED,
.name = "NNPA_INST_OFFCHIP",
.desc = "A NEURAL NETWORK PROCESSING ASSIST (NNPA)"
"\n\t\tinstruction has used an Off-Chip IBM Z Integrated"
"\n\t\tAccelerator for AI during its execution.",
},
{
.ctrnum = 274,
.ctrset = CPUMF_CTRSET_EXTENDED,
.name = "NNPA_INST_DIFF",
.desc = "A NEURAL NETWORK PROCESSING ASSIST (NNPA)"
"\n\t\tinstruction has used a different IBM Z Integrated"
"\n\t\tAccelerator for AI since it was last executed.",
},
{
.ctrnum = 276,
.ctrset = CPUMF_CTRSET_EXTENDED,
.name = "NNPA_4K_PREFETCH",
.desc = "Number of 4K prefetches done for a remote IBM Z"
"\n\t\tIntegated Accelerator for AI.",
},
{
.ctrnum = 277,
.ctrset = CPUMF_CTRSET_EXTENDED,
.name = "NNPA_COMPL_LOCK",
.desc = "A PERFORM LOCKED OPERATION (PLO) has completed.",
},
{
.ctrnum = 278,
.ctrset = CPUMF_CTRSET_EXTENDED,
.name = "NNPA_RETRY_LOCK",
.desc = "A PERFORM LOCKED OPERATION (PLO) has been retried and"
"\n\t\tthe CPU did not use any special logic to allow the"
"\n\t\tPLO to complete.",
},
{
.ctrnum = 279,
.ctrset = CPUMF_CTRSET_EXTENDED,
.name = "NNPA_RETRY_LOCK_WITH_PLO",
.desc = "A PERFORM LOCKED OPERATION (PLO) has been retried and"
"\n\t\tthe CPU is using special logic to allow PLO to"
"\n\t\tcomplete.",
},
{
.ctrnum = 448,
.ctrset = CPUMF_CTRSET_MT_DIAG,
.name = "MT_DIAG_CYCLES_ONE_THR_ACTIVE",
.desc = "Cycle count with one thread active",
},
{
.ctrnum = 449,
.ctrset = CPUMF_CTRSET_MT_DIAG,
.name = "MT_DIAG_CYCLES_TWO_THR_ACTIVE",
.desc = "Cycle count with two threads active",
},
};
/* Return the type number of the CPU Measurement facility from the sysfs file.
* If the type number is equal to PERF_TYPE_RAW, then the prefix is 'r' to
* specify the raw counter number by the perf tool.
@@ -3476,6 +4104,12 @@ static struct counters *get_counter(int ctrset, size_t *len)
case UTIL_ARCH_MACHINE_TYPE_Z16_A02:
cp = cpumcf_z16_counters;
*len = ARRAY_SIZE(cpumcf_z16_counters);
break;
case UTIL_ARCH_MACHINE_TYPE_Z17:
case UTIL_ARCH_MACHINE_TYPE_Z17_2:
cp = cpumcf_z17_counters;
*len = ARRAY_SIZE(cpumcf_z17_counters);
break;
}
break;
}

View File

@@ -1,4 +1,4 @@
/* Copyright IBM Corp. 2021
/* Copyright IBM Corp. 2021, 2024
*
* s390-tools is free software; you can redistribute it and/or modify
* it under the terms of the MIT license. See LICENSE for details.
@@ -39,6 +39,7 @@
#include "lib/util_scandir.h"
#include "lib/util_libc.h"
#include "lib/util_file.h"
#include "lib/util_fmt.h"
#include "lib/libcpumf.h"
#include "lshwc.h"
@@ -51,18 +52,91 @@
static unsigned int read_interval = IOCTLSLEEP;
static int cfvn, csvn, authorization;
static unsigned long loop_count = 1;
static unsigned long loop_count = 1, timeout;
static unsigned char *ioctlbuffer;
static bool allcpu;
static char *ctrformat = "%ld";
static bool shortname;
static bool hideundef;
static bool delta, firstread;
static int output_format = FMT_CSV;
static bool quote_all;
static unsigned int max_possible_cpus; /* No of possible CPUs */
static struct ctrname { /* List of defined counters */
char *name; /* Counter name */
char *label; /* Output name */
bool hitcnt; /* Counter number read from ioctl() */
unsigned long total; /* Total counter value */
unsigned long *ccv; /* Per CPU counter value */
unsigned long *ccvprv; /* Per CPU counter value (previous read) */
} ctrname[MAXCTRS];
struct time_formats {
char epoch[32];
char date_time[32];
char date[16];
char time[16];
};
static void mk_labels(void)
{
char label[64];
size_t i;
for (i = 0; i < ARRAY_SIZE(ctrname); ++i) {
if (shortname) {
if (ctrname[i].name)
snprintf(label, sizeof(label), "%s", ctrname[i].name);
else
snprintf(label, sizeof(label), "U%ld", i);
} else {
if (output_format == FMT_CSV)
snprintf(label, sizeof(label), "%s(%ld)",
ctrname[i].name ?: "Counter", i);
else if (ctrname[i].name)
snprintf(label, sizeof(label), "%s", ctrname[i].name);
else
label[0] = 0;
}
if (output_format != FMT_CSV)
util_str_tolower(label);
ctrname[i].label = util_strdup(label);
}
}
static char *mk_name(int ctr, char *name)
{
char ctrset[8];
if (!shortname)
return util_strdup(name);
switch (libcpumf_ctrset(ctr, cfvn, csvn)) {
case CPUMF_CTRSET_BASIC:
ctrset[0] = 'B';
break;
case CPUMF_CTRSET_PROBLEM_STATE:
ctrset[0] = 'P';
break;
case CPUMF_CTRSET_CRYPTO:
ctrset[0] = 'C';
break;
case CPUMF_CTRSET_EXTENDED:
ctrset[0] = 'E';
break;
case CPUMF_CTRSET_MT_DIAG:
ctrset[0] = 'M';
break;
default:
ctrset[0] = 'U';
break;
}
sprintf(ctrset, "%c%d", ctrset[0], ctr);
return util_strdup(ctrset);
}
static bool read_counternames(void)
{
struct dirent **namelist = NULL;
@@ -79,7 +153,7 @@ static bool read_counternames(void)
for (i = 0; i < count && ctr >= 0; i++) {
util_asprintf(&ctrpath, "%s/%s", path, namelist[i]->d_name);
if (util_file_read_va(ctrpath, "event=%x", &ctr) == 1)
ctrname[ctr].name = util_strdup(namelist[i]->d_name);
ctrname[ctr].name = mk_name(ctr, namelist[i]->d_name);
else
warnx("Cannot parse %s", ctrpath);
free(ctrpath);
@@ -93,7 +167,9 @@ static void free_counternames(void)
{
for (size_t i = 0; i < ARRAY_SIZE(ctrname); ++i) {
free(ctrname[i].name);
free(ctrname[i].label);
free(ctrname[i].ccv);
free(ctrname[i].ccvprv);
}
}
@@ -298,77 +374,111 @@ static bool check_setpossible(void)
return true;
}
static void show_header(void)
static void safe_strtime(char *dest, size_t size, const char *fmt, const struct tm *tm)
{
static bool header;
bool comma = false;
if (header)
return; /* Printed already */
printf("Date,Time,CPU,"); /* Print counter name and number */
for (size_t i = 0; i < ARRAY_SIZE(ctrname); ++i) {
if (!ctrname[i].hitcnt)
continue;
if (comma)
putchar(',');
printf("%s(%ld)", ctrname[i].name ?: "Counter", i);
comma = true;
}
putchar('\n');
header = true;
if (!strftime(dest, size, fmt, tm))
dest[0] = 0;
}
static void line(char *header)
static void generate_timestamp(struct time_formats *date)
{
bool comma;
time_t now = time(NULL);
struct tm *now_tm = localtime(&now);
show_header();
if (allcpu) {
for (unsigned int h = 0; h < max_possible_cpus; ++h) {
char txt[16];
safe_strtime(date->date_time, sizeof(date->date_time), "%F %T%z", now_tm);
safe_strtime(date->date, sizeof(date->date), "%F", now_tm);
safe_strtime(date->time, sizeof(date->time), "%T", now_tm);
safe_strtime(date->epoch, sizeof(date->epoch), "%s", now_tm);
}
if (!check[h].cpu_hit)
continue;
comma = false;
snprintf(txt, sizeof(txt), "CPU%d,", h);
printf("%s%s", header, txt);
for (size_t i = 0; i < ARRAY_SIZE(ctrname); ++i) {
if (!ctrname[i].hitcnt)
continue;
if (comma)
putchar(',');
printf("%ld", ctrname[i].ccv[h]);
comma = true;
}
putchar('\n');
}
static void output_times(struct time_formats date)
{
if (output_format == FMT_CSV) {
util_fmt_pair(FMT_PERSIST, "Date", "%s", date.date);
util_fmt_pair(FMT_PERSIST, "Time", "%s", date.time);
} else {
util_fmt_pair(FMT_PERSIST | FMT_QUOTE, "date_time", "%s", date.date_time);
util_fmt_pair(FMT_PERSIST, "time_epoch", "%s", date.epoch);
}
}
/* Print total count of all CPUs */
printf("%sTotal,", header);
comma = false;
static void prepare_counter(size_t id, unsigned long value)
{
if (output_format == FMT_CSV) {
util_fmt_pair(FMT_PERSIST, ctrname[id].label, ctrformat, value);
} else {
util_fmt_obj_start(FMT_ROW, NULL);
if (strlen(ctrname[id].label))
util_fmt_pair(FMT_PERSIST | FMT_QUOTE, "name", ctrname[id].label);
util_fmt_pair(FMT_PERSIST, "id", ctrformat, id);
util_fmt_pair(FMT_PERSIST, "value", ctrformat, value);
util_fmt_obj_end();
}
}
static void output_per_cpu(struct time_formats date)
{
for (unsigned int h = 0; h < max_possible_cpus; ++h) {
if (!check[h].cpu_hit)
continue;
char txt[16];
snprintf(txt, sizeof(txt), "CPU%d", h);
util_fmt_obj_start(FMT_ROW, "cpu_%d", h);
output_times(date);
if (output_format == FMT_CSV) {
util_fmt_pair(FMT_PERSIST, "CPU", "CPU%d", h);
} else {
util_fmt_pair(FMT_PERSIST, "cpu", "%d", h);
util_fmt_obj_start(FMT_LIST, "counters");
}
for (size_t i = 0; i < ARRAY_SIZE(ctrname); ++i) {
if (!ctrname[i].hitcnt)
continue;
if (hideundef && !ctrname[i].name)
continue;
prepare_counter(i, ctrname[i].ccv[h]);
}
if (output_format != FMT_CSV)
util_fmt_obj_end();
util_fmt_obj_end();
}
}
static void output_total(struct time_formats date)
{
util_fmt_obj_start(FMT_ROW, "total");
output_times(date);
if (output_format == FMT_CSV) {
util_fmt_pair(FMT_PERSIST, "CPU", "%s", delta && !firstread ? "Delta" : "Total");
} else {
util_fmt_pair(FMT_PERSIST | FMT_QUOTE, "cpu", "%s",
delta && !firstread ? "delta" : "total");
util_fmt_obj_start(FMT_LIST, "counters");
}
for (size_t i = 0; i < ARRAY_SIZE(ctrname); ++i) {
if (!ctrname[i].hitcnt)
continue;
if (comma)
putchar(',');
printf("%ld", ctrname[i].total);
comma = true;
if (hideundef && !ctrname[i].name)
continue;
prepare_counter(i, ctrname[i].total);
ctrname[i].total = 0;
ctrname[i].hitcnt = false;
}
putchar('\n');
if (output_format != FMT_CSV)
util_fmt_obj_end();
util_fmt_obj_end();
}
static void show(void)
static void show_format(void)
{
time_t now = time(NULL);
struct tm *now_tm;
char now_text[32];
struct time_formats now;
now_tm = localtime(&now);
strftime(now_text, sizeof(now_text), "%F,%T,", now_tm);
line(now_text);
generate_timestamp(&now);
if (allcpu)
output_per_cpu(now);
output_total(now);
}
/* Return Counter set size numbers (in counters) */
@@ -442,11 +552,18 @@ static bool add_countervalue(size_t idx, unsigned int cpu, unsigned long value)
warnx("Invalid CPU number %d", cpu);
return false;
}
if (!ctrname[idx].ccv) /* Unknown counter */
ctrname[idx].ccv = calloc(max_possible_cpus,
sizeof(unsigned long));
if (ctrname[idx].ccv)
if (delta) {
if (firstread) {
ctrname[idx].ccvprv[cpu] = value;
ctrname[idx].ccv[cpu] = value;
} else {
ctrname[idx].ccv[cpu] = value - ctrname[idx].ccvprv[cpu];
ctrname[idx].ccvprv[cpu] = value;
value = ctrname[idx].ccv[cpu];
}
} else {
ctrname[idx].ccv[cpu] = value;
}
ctrname[idx].total += value;
ctrname[idx].hitcnt = true;
return true;
@@ -493,7 +610,8 @@ static int test_read(struct s390_hwctr_read *read)
}
}
}
show();
show_format();
firstread = false;
return 0;
}
@@ -556,6 +674,7 @@ static void do_sleep(void)
static int do_it(char *s)
{
struct s390_hwctr_start start;
unsigned int flags = FMT_WARN;
int ioctlfd;
int rc;
@@ -575,6 +694,24 @@ static int do_it(char *s)
return EXIT_FAILURE;
}
if (output_format == FMT_CSV)
flags |= FMT_NOMETA;
if (output_format == FMT_JSON || output_format == FMT_JSONSEQ)
flags |= FMT_HANDLEINT;
if (quote_all)
flags |= FMT_QUOTEALL;
mk_labels();
util_fmt_init(stdout, output_format, flags, 1);
util_fmt_obj_start(FMT_DEFAULT, "lshwc");
if (output_format == FMT_JSON || output_format == FMT_JSONSEQ) {
util_fmt_obj_start(FMT_ROW, "cpumcf info");
util_fmt_pair(FMT_PERSIST, "counter first", "%d", cfvn);
util_fmt_pair(FMT_PERSIST, "counter second", "%d", csvn);
util_fmt_pair(FMT_PERSIST, "authorization", "%d", authorization);
util_fmt_obj_end();
}
util_fmt_obj_start(FMT_LIST, "measurements");
for (unsigned long i = 0; !rc && i < loop_count; ++i) {
rc = do_read(ioctlfd);
if (rc) {
@@ -584,6 +721,9 @@ static int do_it(char *s)
if (read_interval && i + 1 < loop_count)
do_sleep();
}
util_fmt_obj_end();
util_fmt_obj_end();
util_fmt_exit();
rc = do_stop(ioctlfd);
close(ioctlfd);
return rc ? EXIT_FAILURE : EXIT_SUCCESS;
@@ -605,6 +745,40 @@ static struct util_opt opt_vec[] = {
.argument = "NUMBER",
.desc = "Specifies interval between read operations (seconds)"
},
{
.option = { "short", no_argument, NULL, 's' },
.desc = "Abbreviate counter name with counter set letter and number"
},
{
.option = { "hex0x", no_argument, NULL, 'X' },
.desc = "Counter values in hexadecimal format with leading 0x"
},
{
.option = { "hex", no_argument, NULL, 'x' },
.desc = "Counter values in hexadecimal format"
},
{
.option = { "hide", no_argument, NULL, 'H' },
.desc = "Do not display undefined counters of a counter set"
},
{
.option = { "delta", no_argument, NULL, 'd' },
.desc = "Display delta counter values"
},
{
.option = { "timeout", required_argument, NULL, 't' },
.argument = "NUMBER",
.desc = "run time in s (seconds) m (minutes) h (hours) and d (days)"
},
{
.option = { "quote-all", no_argument, NULL, 'q' },
.desc = "Apply quoting to all output elements"
},
{
.option = { "format", required_argument, NULL, 'f' },
.argument = "FORMAT",
.desc = "List counters in specified FORMAT (" FMT_TYPE_NAMES ")"
},
UTIL_OPT_HELP,
UTIL_OPT_VERSION,
UTIL_OPT_END
@@ -634,6 +808,8 @@ static void have_support(void)
int main(int argc, char **argv)
{
enum util_fmt_t fmt;
unsigned long no;
char *slash;
int ch;
@@ -665,12 +841,65 @@ int main(int argc, char **argv)
if (errno || *slash)
errx(EXIT_FAILURE, "Invalid argument for -%c", ch);
break;
case 'H':
hideundef = true;
break;
case 's':
shortname = true;
break;
case 'x':
ctrformat = "%lx";
break;
case 'X':
ctrformat = "%#lx";
break;
case 'a':
allcpu = true;
break;
case 'd':
delta = true;
firstread = true;
break;
case 't':
errno = 0;
no = strtoul(optarg, &slash, 0);
if (errno)
errx(EXIT_FAILURE, "Invalid argument for -%c", ch);
switch (*slash) {
case 's':
case '\0':
timeout += no;
break;
case 'm':
timeout += no * 60;
break;
case 'h':
timeout += no * 60 * 60;
break;
case 'd':
timeout += no * 60 * 60 * 24;
break;
default:
errx(EXIT_FAILURE, "Invalid argument for -%c", ch);
break;
}
break;
case 'q':
quote_all = true;
break;
case 'f':
if (!util_fmt_name_to_type(optarg, &fmt))
errx(EXIT_FAILURE, "Supported formats:" FMT_TYPE_NAMES);
output_format = fmt;
break;
}
}
if (timeout && timeout < read_interval)
read_interval = timeout;
/* If no timeout specified, simply add zero */
loop_count += timeout / read_interval;
have_support();
if (!libcpumf_cpumcf_info(&cfvn, &csvn, &authorization))
return EXIT_FAILURE;
@@ -681,6 +910,11 @@ int main(int argc, char **argv)
return EXIT_FAILURE;
}
for (unsigned int i = 0; i < ARRAY_SIZE(ctrname); ++i) {
ctrname[i].ccv = util_zalloc(max_possible_cpus * sizeof(unsigned long));
ctrname[i].ccvprv = util_zalloc(max_possible_cpus * sizeof(unsigned long));
}
if (optind >= argc) {
ch = do_it(NULL);
} else {
@@ -692,5 +926,6 @@ int main(int argc, char **argv)
}
free_counternames();
free(check);
free(ioctlbuffer);
return ch;
}

View File

@@ -19,18 +19,26 @@
#include <string.h>
#include <unistd.h>
#include "lib/util_opt.h"
#include "lib/util_prg.h"
#include "lib/util_base.h"
#include "lib/util_path.h"
#include "lib/util_scandir.h"
#include "lib/util_libc.h"
#include "lib/util_file.h"
#include "lib/util_fmt.h"
#include "lib/util_libc.h"
#include "lib/util_list.h"
#include "lib/util_opt.h"
#include "lib/util_path.h"
#include "lib/util_prg.h"
#include "lib/util_scandir.h"
#include "lib/libcpumf.h"
#define OPT_FORMAT 256 /* --format XXX option */
static struct util_opt opt_vec[] = {
UTIL_OPT_SECTION("OPTIONS"),
{
.option = { "format", required_argument, NULL, OPT_FORMAT },
.argument = "FORMAT",
.flags = UTIL_OPT_FLAG_NOSHORT,
.desc = "List counters in specified FORMAT (" FMT_TYPE_NAMES ")"
},
{
.option = { "numeric", no_argument, NULL, 'n' },
.desc = "Sort PAI counters by counter number"
@@ -58,6 +66,7 @@ static const struct util_prg prg = {
};
static bool numsort; /* If true sort counter numerically */
static int output_format = -1; /* Generate style if >= 0 */
#define PAI_PATH "/bus/event_source/devices/%s"
@@ -190,12 +199,44 @@ static void read_counternames(struct pai_node *node)
qsort(node->ctrlist, more, sizeof(*node->ctrlist), pai_ctrcmp);
}
static void show_format(enum util_fmt_t fmt)
{
struct pai_node *node;
util_fmt_init(stdout, fmt, FMT_HANDLEINT, 1);
util_fmt_obj_start(FMT_DEFAULT, NULL);
util_list_iterate(&pai_list, node) {
util_fmt_obj_start(FMT_DEFAULT, "pmu");
util_fmt_pair(FMT_PERSIST, "base", "%d", node->base);
util_fmt_pair(FMT_PERSIST, "type", "%d", node->pmu);
util_fmt_pair(FMT_QUOTE | FMT_PERSIST, "pmu-name", "%s", node->sysfs_name);
util_fmt_obj_start(FMT_LIST, "counters");
for (int i = 0; i < node->ctridx; ++i) {
util_fmt_obj_start(FMT_ROW, "counter");
util_fmt_pair(FMT_QUOTE, "name", "%s", node->ctrlist[i].name);
util_fmt_pair(FMT_DEFAULT, "config", "%d", node->ctrlist[i].nr);
util_fmt_pair(FMT_DEFAULT, "number", "%d",
node->ctrlist[i].nr - node->base);
util_fmt_obj_end();
}
util_fmt_obj_end(); /* Counters */
util_fmt_obj_end(); /* PMU */
}
util_fmt_obj_end();
util_fmt_exit();
}
static void show_painode(void)
{
struct pai_node *node;
int indent = 0;
int offset = 0;
if (output_format != -1) {
show_format(output_format);
return;
}
util_list_iterate(&pai_list, node) {
for (int i = 0; i < node->ctridx; ++i)
indent = MAX((size_t)indent, strlen(node->ctrlist[i].name));
@@ -243,7 +284,7 @@ static void make_painode(enum pai_types t)
node->base = pai_type_base(t);
/* Read PMU type number. */
path = util_path_sysfs(PAI_PATH, node->sysfs_name);
util_asprintf(&path, PAI_PATH, node->sysfs_name);
node->pmu = libcpumf_pmutype(path);
if (node->pmu < 0)
errx(EXIT_FAILURE, "Cannot open %s", path);
@@ -305,6 +346,7 @@ static void check_type_name(const char *type)
int main(int argc, char **argv)
{
enum util_fmt_t fmt;
int ch;
util_list_init(&pai_list, struct pai_node, node);
@@ -329,6 +371,11 @@ int main(int argc, char **argv)
case 't':
check_type_name(optarg);
break;
case OPT_FORMAT:
if (!util_fmt_name_to_type(optarg, &fmt))
errx(EXIT_FAILURE, "Supported formats:" FMT_TYPE_NAMES);
output_format = fmt;
break;
}
}

View File

@@ -50,7 +50,7 @@ counters for which the LPAR is not authorized. For counter measurements with
the perf program, the raw event identifier is displayed.
For Linux version 5.5 and later, the raw event identifier is
displayed as <type>:<number>, where type is an integer that the kernel
assignes to the CPU Measurement counter facility device driver.
assigns to the CPU Measurement counter facility device driver.
For earlier Linux versions the raw event identifier is displayed as r<number>.
.
.TP

View File

@@ -7,18 +7,22 @@
.\" ----------------------------------------------------------------------
.ds c \fBlshwc\fP
.
.TH \*c "8" "May 2022" "s390-tools" "CPU-MF management programs"
.TH \*c "8" "Mar 2025" "s390-tools" "CPU-MF management programs"
.
.SH NAME
\*c \- extract CPU Measurement Facilities counter sets
lshwc \- extract CPU Measurement Facilities counter sets
.
.SH SYNOPSIS
\*c
.RB [ \-a ]
.RB [ \-a ][ \-d ][ \-H ][ \-s ][ \-x ][ \-X ][ \-q ]
.RB [ \-l
.IR count ]
.RB [ \-i
.IR interval ]
.RB [ \-t
.IR time ]
.RB [ \-f
.IR format ]
\fR[\fIcpulist\fR][:\fIsets\fR]\fP
.br
\*c
@@ -51,6 +55,13 @@ Displays counter values from each CPU.
The default is a total summary line of all counters from all CPUs.
.
.TP
.BR \-d ", " \-\-delta
Displays counter values in form of deltas.
Each counter value shows the increment to the previous output line.
Without this flag the total value of each counter is displayed.
See Examples.
.
.TP
.BR \-i ", " \-\-interval \fI\ seconds\fP
Specifies a time interval, in seconds,
that the command waits between read operations.
@@ -61,6 +72,107 @@ The default is 60 seconds.
Performs the specified number of read operations.
.
.TP
.BR \-H ", " \-\-hide
Do not display values of undefined counters.
.
.TP
.BR \-s ", " \-\-short
Displays counter names as short names in the heading.
A short name consists of
a letter for the counter set followed by a number.
For example,
this flag displays counter number 0 in the basic counter
set as
.B B0
instead of
.BR Cycles .
Valid counter set letters are:
.RS
.IP B
Basic counter set
.IP P
Problem state counter set
.IP C
Crypto counter set
.IP E
Extended counter set
.IP M
MT-Diagnostic counter set
.IP U
Undefined counter.
.RE
.
.TP
.BR \-t ", " \-\-time
Specifies the runtime as an integer in days,
hours, minutes or seconds.
This option can be specified multiple times:
the runtime sums up.
Specify time as an integer followed by one of the letters
.RS
.IP d
Specifies the runtime in days.
.IP h
Specifies the runtime in hours.
.IP m
Specifies the runtime in minutes.
.IP s
Specifies the runtime in seconds.
This is the default when no letter is given.
.RE
.
.TP
.BR \-x ", " \-\-hex
Displays counter values as hexadecimal values.
.
.TP
.BR \-X ", " \-\-hex0x
Displays counter values as hexadecimal values with a leading 0x prefix.
.
.TP
.BR \-q
Apply quoting to every output element, regardless of content or format.
.
.TP
.BR \-f ", " \-\-format \fI\ format\fP
Retrieve output in one of the following formats:
JSON, CSV, JSON-SEQ or PAIRS.
If no format is specified, the output defaults to CSV.
.
.SS JSON Output Structure
The JSON output contains two top-level objects:
.B "meta"
and
.B "lshwc"
.TP
.B meta
General metadata such as API version, host, and timestamp.
.TP
.B lshwc
Measurement data and configuration.
.RS
.IP \fBcpumcf info\fP
CPU Measurement Counter Facility information.
.IP \fBmeasurements\fP
An array of objects, each representing a CPU or "total". 
Each object includes:
.RS
.IP \fBcpu\fP
CPU identifier, such as 0, 1 or "delta" or "total".
.IP \fBdate_time\fP
Timestamp in ISO 8601 format.
.IP \fBtime_epoch\fP
The number of seconds since the epoch: 1970-01-01 00:00:00 UTC.
.IP \fBcounters\fP
An array of counter objects, each containing \fBname\fP (if available), \fBid\fP,
and \fBvalue\fP.
.RE
.RE
.
.TP
\fR[\fIcpulist\fR][:\fIsets\fR]\fP
A comma-separated list of CPUs.
Each CPU can optionally be followed by characters that specify the counter set.
@@ -105,8 +217,8 @@ Date,Time,CPU,CPU_CYCLES(0),INSTRUCTIONS(1),L1I_DIR_WRITES(2),L1I_PENALTY_CYCLES
.ft
.fi
.sp 1
This example shows the counter values of the problem state counter set
per CPU. CPU 0 and CPU 1 is selected.
This example shows the counter values of the problem state counter set per CPU.
CPU 0 and CPU 1 are selected.
.nf
.ft CW
.sp 1
@@ -123,6 +235,121 @@ Date,Time,CPU,PROBLEM_STATE_CPU_CYCLES(32),PROBLEM_STATE_INSTRUCTIONS(33)
2021-04-01,11:56:47,Total,6432163447,2978400903
.ft
.fi
.sp 1
This example shows the counter values of the basic counter set
using delta output format.
.nf
.ft CW
.sp 1
# lshwc -d -l 10 -i 5 -s :b
Date,Time,CPU,B0,B1,B2,B3,B4,B5
2025-03-26,10:34:19,Total,208075,117287,1950,50548,1082,49609
2025-03-26,10:34:24,Delta,85800055,70353492,590286,13228290,364034,12945804
2025-03-26,10:34:29,Delta,70654751,60656797,483047,10838672,305703,10570868
2025-03-26,10:34:34,Delta,81043162,69476160,587141,13228161,376662,12868298
2025-03-26,10:34:39,Delta,73434017,62675417,524857,11787256,333966,11543649
2025-03-26,10:34:44,Delta,68367967,58452919,506712,11370740,310785,10589883
2025-03-26,10:34:49,Delta,70351947,57607764,507675,11433377,312433,10676243
2025-03-26,10:34:54,Delta,77154817,65371168,562153,12671030,349750,12311061
2025-03-26,10:34:59,Delta,88871882,75441201,655310,14875963,392530,13773130
2025-03-26,10:35:04,Delta,83763472,71730813,609260,13643680,366992,12672405
.ft
.fi
.sp 1
This example shows the counter values of the problem state counter set with
CPU 3 selected.
.nf
.ft CW
.sp 1
# lshwc -l2 -a 3:P -H --format json
{
"meta": {
"api_level": 1,
"version": "2.37.0-build-20250616",
"host": "b46lp08.lnxne.boe",
"time_epoch": 1750094646,
"time": "2025-06-16 19:24:06+0200"
},
"lshwc": {
"cpumcf info": {
"counter first": 3,
"counter second": 8,
"authorization": 47
},
"measurements": [
{
"date_time": "2025-06-16 19:24:06+0200",
"time_epoch": 1750094646,
"cpu": 3,
"counters": [
{
"name": "problem_state_cpu_cycles",
"id": 32,
"value": 0
},
{
"name": "problem_state_instructions",
"id": 33,
"value": 0
}
]
},
{
"date_time": "2025-06-16 19:24:06+0200",
"time_epoch": 1750094646,
"cpu": "total",
"counters": [
{
"name": "problem_state_cpu_cycles",
"id": 32,
"value": 0
},
{
"name": "problem_state_instructions",
"id": 33,
"value": 0
}
]
},
{
"date_time": "2025-06-16 19:25:06+0200",
"time_epoch": 1750094706,
"cpu": 3,
"counters": [
{
"name": "problem_state_cpu_cycles",
"id": 32,
"value": 0
},
{
"name": "problem_state_instructions",
"id": 33,
"value": 0
}
]
},
{
"date_time": "2025-06-16 19:25:06+0200",
"time_epoch": 1750094706,
"cpu": "total",
"counters": [
{
"name": "problem_state_cpu_cycles",
"id": 32,
"value": 0
},
{
"name": "problem_state_instructions",
"id": 33,
"value": 0
}
]
}
]
}
}
.ft
.fi
.SH "SEE ALSO"
.BR lscpumf (8)
.BR chcpumf (8)

View File

@@ -10,10 +10,12 @@
.TH \*c "8" "August 2023" "s390-tools" "CPU-MF management programs"
.
.SH NAME
\*c \- list Processor Activity Instrumentation (PAI) counters
lspai \- list Processor Activity Instrumentation (PAI) counters
.
.SH SYNOPSIS
\*c
.RB [ \-\-format
.IR FORMAT ]
.RB [ \-n ]
.RB [ \-t
.IR "\ TYPE" ]
@@ -55,6 +57,12 @@ Crypto refers to the Cryptografic Processing Assist facility counter set.
Shows the PAI counter sets sorted by counter number.
Default sort order is PAI counter name.
.
.TP
.BI \-\-format "\ FORMAT"
Retrieve output in one of the following formats:
JSON, csv, json-seq or pairs.
If no format is specified,
the output defaults to a human-readable format.
.SH "EXAMPLE"
The \*c invocation lists all PAI Neural Network Processing Assist Facility
(NNPA) counters in numeric order:

View File

@@ -10,7 +10,7 @@
.TH \*c "8" "May 2022" "s390-tools" "PAI Management Programs"
.
.SH NAME
\*c \- record and report Processor Activity Instrumentation (PAI) counters
pai \- record and report Processor Activity Instrumentation (PAI) counters
.
.SH SYNOPSIS
\*c

View File

@@ -314,8 +314,7 @@ void check_config()
cpuplugd_debug("Daemon started with vertical polarization.\n");
cpuplugd_debug("Cpu adjustments won't be made until system ");
cpuplugd_debug("is in horizontal polarization\n");
} else if (saved_polarization == PLR_HORIZONTAL &&
cpu == 1) {
} else if (cpu == 1) {
apply_cpu_config();
}
if (memory == 1) {

View File

@@ -87,6 +87,8 @@ int get_polarization(void)
char *path;
path = util_path_sysfs("devices/system/cpu/dispatching");
if (!util_path_exists(path))
return PLR_NONE;
if (util_file_read_i(&polarization, NUM_BASE, path) < 0) {
polarization = -1;
cpuplugd_debug("failed to read system polarization\n");

View File

@@ -40,6 +40,7 @@
#define CPUSTATS 10
#define PLR_HORIZONTAL 0
#define PLR_VERTICAL 1
#define PLR_NONE 2
/*
* Precedence of C operators

View File

@@ -1,2 +1,6 @@
# Load protected key support module on s390 early at boot
pkey
pkey_pckmo
pkey_ep11
pkey_cca
paes_s390

View File

@@ -0,0 +1,4 @@
#
# Rules to set online a newly hotplugged cpu on s390x
#
SUBSYSTEM=="cpu", ACTION=="add", CONST{arch}=="s390*", ATTR{configure}=="1", TEST=="online", ATTR{online}!="1", ATTR{online}="1"

View File

@@ -21,7 +21,7 @@
#define STAGE2_DESC _AC(0x78, UL)
#define STAGE2_ENTRY _AC(0x2018, UL)
#define ECKD2DUMP_MV_TAIL_ADDRESS _AC(0x5000, UL)
#define ECKD2DUMP_MV_TAIL_ADDRESS _AC(0x6000, UL)
#define STAGE2_HEAP_ADDRESS _AC(0x6000, UL)
#define ECKD2DUMP_HEAP_ADDRESS _AC(0xb000, UL)
#define STAGE2_HEAP_SIZE _AC(0x3000, UL)
@@ -31,11 +31,9 @@
#define ECKD2DUMP_STACK_SIZE _AC(0x2000, UL)
#define STAGE2_MAX_SIZE _AC(0x3000, UL)
#define STAGE2_DUMPER_SIZE_V1 _AC(0x1000, UL)
#define STAGE2_DUMPER_SIZE_V2 _AC(0x2000, UL)
#define STAGE2_DUMPER_SIZE_V3 _AC(0x3000, UL)
#define STAGE2_DUMPER_SIZE_MV _AC(0x3000, UL)
#define STAGE2_DUMPER_SIZE_ZLIB _AC(0x8000, UL)
#define STAGE2_DUMPER_SIZE_SV _AC(0x3000, UL)
#define STAGE2_DUMPER_SIZE_MV _AC(0x4000, UL)
#define STAGE2_DUMPER_SIZE_SV_ZLIB _AC(0x8000, UL)
#define STAGE3_ENTRY _AC(0xa000, UL)

View File

@@ -27,16 +27,9 @@
#define DF_S390_CPU_MAX 512
#define DF_S390_MAGIC_BLK_ECKD 3
#define DF_S390_DUMPER_MAGIC_SIZE 7
#define DF_S390_DUMPER_MAGIC32 "ZECKD31"
#define DF_S390_DUMPER_MAGIC64 "ZECKD64"
#define DF_S390_DUMPER_MAGIC_EXT "XECKD64"
#define DF_S390_DUMPER_MAGIC32_FBA "ZDFBA31"
#define DF_S390_DUMPER_MAGIC64_FBA "ZDFBA64"
#define DF_S390_DUMPER_MAGIC_FBA_EXT "XDFBA64"
#define DF_S390_DUMPER_MAGIC_MV "ZMULT64"
#define DF_S390_DUMPER_MAGIC_MV_EXT "XMULT64"
#define OLD_DUMPER_HEX_INSTR1 "\x0d\x10\x47\xf0" /* BASR + 1st halfword of BC */
#define OLD_DUMPER_HEX_INSTR2 "\x0d\xd0" /* BASR 13,0 */
/*
* Architecture of dumped system

View File

@@ -10,16 +10,35 @@
#include <sched.h>
#include <stdbool.h>
#define S390_CPUMF_CF "/sys/devices/cpum_cf/"
#define S390_CPUMF_CFDIAG "/sys/devices/cpum_cf_diag/"
#define S390_CPUMF_SF "/sys/devices/cpum_sf/"
#define S390_CPUS_POSSIBLE "/sys/devices/system/cpu/possible"
#define S390_CPUS_ONLINE "/sys/devices/system/cpu/online"
#define S390_CPUMSF_BUFFERSZ "/sys/module/kernel/parameters/cpum_sfb_size"
#define S390_SYSFS_PAI_CRYPTO "/sys/devices/pai_crypto/"
#define S390_SYSFS_PAI_EXT "/sys/devices/pai_ext/"
#define S390_CPUMF_CF "devices/cpum_cf/"
#define S390_CPUMF_CFDIAG "devices/cpum_cf_diag/"
#define S390_CPUMF_SF "devices/cpum_sf/"
#define S390_CPUS_ONLINE "devices/system/cpu/online"
#define S390_CPUMSF_BUFFERSZ "module/kernel/parameters/cpum_sfb_size"
#define S390_SYSFS_PAI_CRYPTO "devices/pai_crypto/"
#define S390_SYSFS_PAI_EXT "devices/pai_ext/"
#define S390_SYSFS_PAI_NNPA S390_SYSFS_PAI_EXT "events/NNPA_ALL"
#define CPUMF_CTRSET_NONE 0
#define CPUMF_CTRSET_EXTENDED 1
#define CPUMF_CTRSET_BASIC 2
#define CPUMF_CTRSET_PROBLEM_STATE 4
#define CPUMF_CTRSET_CRYPTO 8
#define CPUMF_CTRSET_MT_DIAG 32
/**
* Return counter set a counter belongs to.
*
* Return the counter set a given counter belongs to, given the
* CPU Measurement facility counter version first and second number.
*
* @param[in] ctr Counter number
* @param[in] cfvn CPUM Counter facility first version number
* @param[in] csvn CPUM Counter facility second version number
* @retval >=0 Counter set number to counter belongs to
*/
int libcpumf_ctrset(int ctr, int cfvn, int csvn);
/**
* Read out the PMU type from a given file.
*

View File

@@ -93,4 +93,7 @@ const char *zpci_pft_str(struct zpci_dev *zdev);
const char *zpci_operstate_str(operstate_t state);
operstate_t zpci_operstate_from_str(const char *oper_str);
struct zpci_dev *zpci_find_by_netdev(struct util_list *zpci_list, char *netdev_name,
struct zpci_netdev **netdev);
#endif /* LIB_ZPCI_PCI_LIST_H */

View File

@@ -28,6 +28,8 @@ enum util_arch_machine_type {
UTIL_ARCH_MACHINE_TYPE_Z15_T02 = 8562,
UTIL_ARCH_MACHINE_TYPE_Z16 = 3931,
UTIL_ARCH_MACHINE_TYPE_Z16_A02 = 3932,
UTIL_ARCH_MACHINE_TYPE_Z17 = 9175,
UTIL_ARCH_MACHINE_TYPE_Z17_2 = 9176,
};
int util_arch_machine_type(void);

View File

@@ -31,6 +31,7 @@ struct util_proc_dev_entry {
#define UTIL_PROC_DEV_ENTRY_SD "sd"
#define UTIL_PROC_DEV_ENTRY_BLKEXT "blkext"
#define UTIL_PROC_DEV_ENTRY_MD "md"
#define UTIL_PROC_DEV_ENTRY_DM "device-mapper"
int util_proc_part_get_entry(dev_t device, struct util_proc_part_entry *entry);
void util_proc_part_free_entry(struct util_proc_part_entry *entry);

View File

@@ -9,7 +9,6 @@
* it under the terms of the MIT license. See LICENSE for details.
*/
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
@@ -49,19 +48,19 @@ static int device_sysfs_path(const char *device, char *path, const size_t path_s
*/
int ccw_is_device(const char *busid)
{
char path[PATH_MAX];
static const char *const driver_paths[] = { "dasd-eckd", "virtio_ccw", "dasd-fba" };
char *path;
size_t i;
for (i = 0; i < ARRAY_SIZE(driver_paths); i++) {
path = util_path_sysfs("bus/ccw/drivers/%s/%s", driver_paths[i], busid);
if (access(path, R_OK) == 0) {
free(path);
return 1;
}
free(path);
}
snprintf(path, sizeof(path),
"/sys/bus/ccw/drivers/dasd-eckd/%s", busid);
if (access(path, R_OK) == 0)
return 1;
snprintf(path, sizeof(path),
"/sys/bus/ccw/drivers/virtio_ccw/%s", busid);
if (access(path, R_OK) == 0)
return 1;
snprintf(path, sizeof(path), "/sys/bus/ccw/drivers/dasd-fba/%s", busid);
if (access(path, R_OK) == 0)
return 1;
return 0;
}
@@ -72,6 +71,7 @@ int ccw_is_virtio_device(const char *device)
{
char path[PATH_MAX] = { '\0' };
unsigned virtio = 0;
char *path_pattern;
if (device_sysfs_path(device, path, sizeof(path)) != 0)
return -1;
@@ -80,76 +80,37 @@ int ccw_is_virtio_device(const char *device)
* The output has the following format:
* /sys/devices/css0/0.0.0000/0.0.0000/virtio0/block/vda
*/
if (sscanf(path, "/sys/devices/css0/%*[0-9a-f.]/%*[0-9a-f.]/virtio%u", &virtio) != 1)
path_pattern = util_path_sysfs("devices/css0/%%*[0-9a-f.]/%%*[0-9a-f.]/virtio%%u");
if (sscanf(path, path_pattern, &virtio) != 1) {
free(path_pattern);
return -1;
}
free(path_pattern);
return 0;
}
/*
* Return CCW Bus ID (old sysfs)
* Return CCW Bus ID
*/
static int ccw_busid_get_sysfs_old(const char *device, char *busid)
{
char path[PATH_MAX];
char buf[4096];
int rc = 0;
FILE *fh;
snprintf(path, sizeof(path), "/sys/block/%s/uevent", device);
fh = fopen(path, "r");
if (fh == NULL)
return -1;
/*
* The uevent file contains an entry like this:
* PHYSDEVPATH=/devices/css0/0.0.206a/0.0.7e78
*/
while (fscanf(fh, "%s", buf) >= 0) {
if (strstr(buf, "PHYSDEVPATH") != NULL) {
strcpy(busid, strrchr(buf, '/') + 1);
goto out_fclose;
}
}
rc = -1;
out_fclose:
fclose(fh);
return rc;
}
/*
* Return CCW Bus ID (new sysfs)
*/
static int ccw_busid_get_sysfs_new(const char *device, char *busid)
void ccw_busid_get(const char *device, char *busid)
{
char path[PATH_MAX] = { '\0' };
char *path_pattern;
if (device_sysfs_path(device, path, sizeof(path)) != 0)
return -1;
ERR_EXIT("Could not lookup device number for \"%s\"", device);
/*
* The output has the following format:
* /sys/devices/css0/0.0.0119/0.0.3f19/block/dasda
* /sys/devices/css0/0.0.0000/0.0.0000/virtio0/block/vda
*/
if (sscanf(path, "/sys/devices/css0/%*[0-9a-f.]/%[0-9a-f.]", busid) != 1)
return -1;
return 0;
}
path_pattern = util_path_sysfs("devices/css0/%%*[0-9a-f.]/%%[0-9a-f.]");
if (sscanf(path, path_pattern, busid) != 1) {
free(path_pattern);
ERR_EXIT("Could not lookup device number for \"%s\"", device);
}
free(path_pattern);
/*
* Return the device number for a device
* dasda can be found in /sys/block/dasda/uevent or in a
* symbolic link in the same directory. the first file only
* contains the relevant information if we run on a kernel with
* has the following kernel option enabled:
* CONFIG_SYSFS_DEPRECATED
*
* This does not work when booting from tape
*/
void ccw_busid_get(const char *device, char *busid)
{
if (ccw_busid_get_sysfs_old(device, busid) == 0)
return;
if (ccw_busid_get_sysfs_new(device, busid) == 0)
return;
ERR_EXIT("Could not lookup device number for \"%s\"", device);
return;
}

View File

@@ -12,12 +12,17 @@
#include <ctype.h>
#include <sys/sysmacros.h>
#include <sys/ioctl.h>
#include "lib/util_libc.h"
#include "lib/util_proc.h"
#include "lib/util_base.h"
#include "lib/zt_common.h"
#include "lib/util_path.h"
#include "ipl_tools.h"
#include "proc.h"
#include <linux/raid/md_u.h>
#define BOOTPARMS_NSS_MAX 56
#define BOOTPARMS_CCW_MAX 64
@@ -25,6 +30,8 @@
#define OPT_BRCHR 0x80
#define MD_MAJOR 9
enum target_type {
TT_CCW,
TT_FCP,
@@ -414,15 +421,38 @@ static int set_reipl_type(const char *dev_name)
return 0;
}
static int get_chreipl_helper_cmd(dev_t dev, char cmd[PATH_MAX])
static int is_md_device(const char *dev_name)
{
char abs_dev_name[PATH_MAX];
mdu_array_info_t array;
int is_md_device = 0;
int fd;
if (snprintf(abs_dev_name, PATH_MAX, "/dev/%s", dev_name) >= PATH_MAX)
return 0;
fd = open(abs_dev_name, O_RDONLY);
if (fd == -1)
return 0;
if (ioctl(fd, GET_ARRAY_INFO, &array) >= 0)
is_md_device = 1;
close(fd);
return is_md_device;
}
static int get_chreipl_helper_cmd(dev_t dev, char *dev_name, char cmd[PATH_MAX])
{
char *chreipl_helper;
struct proc_dev_entry pde;
char *chreipl_helper;
char *driver_name;
if (proc_dev_get_entry(dev, 1, &pde) != 0)
return -1;
driver_name = pde.name;
if (strcmp(driver_name, UTIL_PROC_DEV_ENTRY_BLKEXT) == 0 &&
is_md_device(dev_name))
driver_name = UTIL_PROC_DEV_ENTRY_MD;
util_asprintf(&chreipl_helper,
"%s/%s.%s", TOOLS_LIBDIR, "chreipl_helper", pde.name);
"%s.%s", util_libdir_path("chreipl_helper"), driver_name);
if (access(chreipl_helper, X_OK) != 0) {
proc_dev_free_entry(&pde);
free(chreipl_helper);
@@ -437,7 +467,7 @@ static int get_chreipl_helper_cmd(dev_t dev, char cmd[PATH_MAX])
/*
* Use chreipl_helper (E.g. for device mapper devices)
*/
static int set_reipl_type_helper(int maj, int min)
static int set_reipl_type_helper(int maj, int min, char *dev_name)
{
char helper_cmd[PATH_MAX], buf[4096];
struct proc_part_entry ppe;
@@ -445,7 +475,7 @@ static int set_reipl_type_helper(int maj, int min)
dev_t dev;
FILE *fh;
if (get_chreipl_helper_cmd(makedev(maj, min), helper_cmd) != 0)
if (get_chreipl_helper_cmd(makedev(maj, min), dev_name, helper_cmd) != 0)
return -1;
fh = popen(helper_cmd, "r");
if (fh == NULL)
@@ -499,7 +529,7 @@ static void parse_node_args(char *nargv[], int nargc)
ERR_EXIT("Invalid device node \"%s\" specified", path);
if (set_reipl_type(ppe.name) == 0)
goto out;
if (set_reipl_type_helper(major(dev), minor(dev)) == 0)
if (set_reipl_type_helper(major(dev), minor(dev), ppe.name) == 0)
goto out;
ERR_EXIT("Unsupported device node \"%s\" specified", path);
out:
@@ -719,11 +749,12 @@ static void parse_chreipl_options(int argc, char *argv[])
static void check_exists(const char *path, const char *attr)
{
char fpath[PATH_MAX];
char *fpath;
snprintf(fpath, sizeof(fpath), "/sys/firmware/%s", path);
fpath = util_path_sysfs("firmware/%s", path);
if (access(fpath, F_OK) != 0)
ERR_EXIT("System does not allow one to set %s", attr);
free(fpath);
}
static void write_str_optional(char *string, char *file, int exit_on_fail,
@@ -739,7 +770,7 @@ static void write_str_optional(char *string, char *file, int exit_on_fail,
* IMPLEMENTATION:
*
* "cio_ignore --is-ignored <busid>" returns 0 if the device is ignored,
* 1 for internal errrors, and 2 if the device is not ignored.
* 1 for internal errors, and 2 if the device is not ignored.
*
* We get the "cio_ignore" exit status by the return code of the system()
* function via WEXITSTATUS().
@@ -920,13 +951,14 @@ static void chreipl_nss(void)
static void chreipl_node(void)
{
char path[PATH_MAX];
char *path;
if (!l.dev_set)
ERR_EXIT("No device node specified");
snprintf(path, sizeof(path), "/sys/block/%s/device", l.dev);
path = util_path_sysfs("block/%s/device", l.dev);
if (chdir(path) != 0)
ERR_EXIT("Could not find device \"%s\"", l.dev);
free(path);
switch (l.reipl_type) {
case REIPL_CCW:

View File

@@ -9,6 +9,10 @@
* it under the terms of the MIT license. See LICENSE for details.
*/
#include "lib/util_path.h"
#include "lib/util_file.h"
#include "lib/util_libc.h"
#include "ipl_tools.h"
static struct {
@@ -42,24 +46,23 @@ static const char *get_ipl_banner(int show_ipl)
void print_nss(int show_ipl)
{
char *dir = show_ipl ? "ipl" : "reipl/nss";
char *path_bootparms = show_ipl ? "/sys/firmware/ipl/parm" :
"/sys/firmware/reipl/nss/parm";
char *path_bootparms = util_path_sysfs("firmware/%s/parm", dir);
printf("%-12s nss\n", get_ipl_banner(show_ipl));
print_fw_str("Name: %s\n", dir, "name");
if (access(path_bootparms, R_OK) == 0)
print_fw_str("Bootparms: \"%s\"\n", dir, "parm");
free(path_bootparms);
}
void print_fcp(int show_ipl, int dump)
{
char *dir = show_ipl ? "ipl" : "reipl/fcp";
char *path_bootparms = show_ipl ? "/sys/firmware/ipl/scp_data" :
"/sys/firmware/reipl/fcp/scp_data";
char *path_loadparm = show_ipl ? "/sys/firmware/ipl/loadparm" :
"/sys/firmware/reipl/fcp/loadparm";
char loadparm[9], loadparm_path[PATH_MAX];
char *path_reipl_clear = "/sys/firmware/reipl/fcp/clear";
char *path_bootparms = util_path_sysfs("firmware/%s/scp_data", dir);
char *path_loadparm = util_path_sysfs("firmware/%s/loadparm", dir);
char *path_reipl_clear = util_path_sysfs("firmware/reipl/fcp/clear");
char *path_secure_boot = util_path_sysfs("firmware/ipl/secure");
char *loadparm;
if (dump)
printf("%-12s fcp_dump\n", get_ipl_banner(show_ipl));
@@ -72,27 +75,31 @@ void print_fcp(int show_ipl, int dump)
print_fw_str("bootprog: %s\n", dir, "bootprog");
print_fw_str("br_lba: %s\n", dir, "br_lba");
if (access(path_loadparm, R_OK) == 0) {
sprintf(loadparm_path, "%s/%s", dir, "loadparm");
read_fw_str(loadparm, loadparm_path, sizeof(loadparm));
if (strcmp(loadparm, " ") == 0)
loadparm[0] = 0;
loadparm = util_file_read_text_file(path_loadparm, 1);
util_strstrip(loadparm);
printf("Loadparm: \"%s\"\n", loadparm);
free(loadparm);
}
if (access(path_bootparms, R_OK) == 0)
print_fw_str("Bootparms: \"%s\"\n", dir, "scp_data");
if (!show_ipl && access(path_reipl_clear, R_OK) == 0)
print_fw_str("clear: %s\n", dir, "clear");
if (access(path_secure_boot, R_OK) == 0)
print_fw_str("Secure boot: %s\n", "ipl", "secure");
free(path_bootparms);
free(path_loadparm);
free(path_reipl_clear);
free(path_secure_boot);
}
void print_nvme(int show_ipl, int dump)
{
char *dir = show_ipl ? "ipl" : "reipl/nvme";
char *path_bootparms = show_ipl ? "/sys/firmware/ipl/scp_data" :
"/sys/firmware/reipl/nvme/scp_data";
char *path_loadparm = show_ipl ? "/sys/firmware/ipl/loadparm" :
"/sys/firmware/reipl/nvme/loadparm";
char loadparm[9], loadparm_path[PATH_MAX];
char *path_reipl_clear = "/sys/firmware/reipl/nvme/clear";
char *path_bootparms = util_path_sysfs("firmware/%s/scp_data", dir);
char *path_loadparm = util_path_sysfs("firmware/%s/loadparm", dir);
char *path_reipl_clear = util_path_sysfs("firmware/reipl/nvme/clear");
char *path_secure_boot = util_path_sysfs("firmware/ipl/secure");
char *loadparm;
if (dump)
printf("%-12s nvme_dump\n", get_ipl_banner(show_ipl));
@@ -104,49 +111,54 @@ void print_nvme(int show_ipl, int dump)
print_fw_str("bootprog: %s\n", dir, "bootprog");
print_fw_str("br_lba: %s\n", dir, "br_lba");
if (access(path_loadparm, R_OK) == 0) {
sprintf(loadparm_path, "%s/%s", dir, "loadparm");
read_fw_str(loadparm, loadparm_path, sizeof(loadparm));
if (strcmp(loadparm, " ") == 0)
loadparm[0] = 0;
loadparm = util_file_read_text_file(path_loadparm, 1);
util_strstrip(loadparm);
printf("Loadparm: \"%s\"\n", loadparm);
free(loadparm);
}
if (access(path_bootparms, R_OK) == 0)
print_fw_str("Bootparms: \"%s\"\n", dir, "scp_data");
if (!show_ipl && access(path_reipl_clear, R_OK) == 0)
print_fw_str("clear: %s\n", dir, "clear");
if (access(path_secure_boot, R_OK) == 0)
print_fw_str("Secure boot: %s\n", "ipl", "secure");
free(path_bootparms);
free(path_loadparm);
free(path_reipl_clear);
free(path_secure_boot);
}
void print_ccw(int show_ipl)
{
char loadparm[9], loadparm_path[PATH_MAX];
char *dir = show_ipl ? "ipl" : "reipl/ccw";
char *path_loadparm = show_ipl ? "/sys/firmware/ipl/loadparm" :
"/sys/firmware/reipl/ccw/loadparm";
char *path_bootparms = show_ipl ? "/sys/firmware/ipl/parm" :
"/sys/firmware/reipl/ccw/parm";
char *path_reipl_clear = "/sys/firmware/reipl/ccw/clear";
char *path_loadparm = util_path_sysfs("firmware/%s/loadparm", dir);
char *path_bootparms = util_path_sysfs("firmware/%s/parm", dir);
char *path_reipl_clear = util_path_sysfs("firmware/reipl/ccw/clear");
char *loadparm;
printf("%-12s ccw\n", get_ipl_banner(show_ipl));
print_fw_str("Device: %s\n", dir, "device");
if (access(path_loadparm, R_OK) == 0) {
sprintf(loadparm_path, "%s/%s", dir, "loadparm");
read_fw_str(loadparm, loadparm_path, sizeof(loadparm));
if (strcmp(loadparm, " ") == 0)
loadparm[0] = 0;
loadparm = util_file_read_text_file(path_loadparm, 1);
util_strstrip(loadparm);
printf("Loadparm: \"%s\"\n", loadparm);
free(loadparm);
}
if (access(path_bootparms, R_OK) == 0)
print_fw_str("Bootparms: \"%s\"\n", dir, "parm");
if (!show_ipl && access(path_reipl_clear, R_OK) == 0)
print_fw_str("clear: %s\n", dir, "clear");
free(path_loadparm);
free(path_bootparms);
free(path_reipl_clear);
}
void print_eckd(int show_ipl, const char *name)
{
char *path_loadparm = show_ipl ? "/sys/firmware/ipl/loadparm" :
"/sys/firmware/reipl/eckd/loadparm";
char *dir = show_ipl ? "ipl" : "reipl/eckd";
char loadparm[9], loadparm_path[PATH_MAX];
char *path_loadparm = util_path_sysfs("firmware/%s/loadparm", dir);
char *path_secure_boot = util_path_sysfs("firmware/ipl/secure");
char *loadparm;
printf("%-12s %s\n", get_ipl_banner(show_ipl), name);
@@ -155,14 +167,17 @@ void print_eckd(int show_ipl, const char *name)
print_fw_str("br_chr: %s\n", dir, "br_chr");
print_fw_str("Bootparm: \"%s\"\n", dir, "scp_data");
if (access(path_loadparm, R_OK) == 0) {
sprintf(loadparm_path, "%s/%s", dir, "loadparm");
read_fw_str(loadparm, loadparm_path, sizeof(loadparm));
if (strcmp(loadparm, " ") == 0)
loadparm[0] = 0;
loadparm = util_file_read_text_file(path_loadparm, 1);
util_strstrip(loadparm);
printf("Loadparm: \"%s\"\n", loadparm);
free(loadparm);
}
if (!show_ipl)
print_fw_str("clear: %s\n", dir, "clear");
if (access(path_secure_boot, R_OK) == 0)
print_fw_str("Secure boot: %s\n", "ipl", "secure");
free(path_loadparm);
free(path_secure_boot);
}
static void parse_lsreipl_options(int argc, char *argv[])
@@ -196,16 +211,14 @@ static void parse_lsreipl_options(int argc, char *argv[])
void cmd_lsreipl(int argc, char *argv[])
{
char reipl_type_str[1024];
char *reipl_type_str;
parse_lsreipl_options(argc, argv);
if (l.ipl_set)
read_fw_str(reipl_type_str, "ipl/ipl_type",
sizeof(reipl_type_str));
reipl_type_str = read_fw_str("ipl/ipl_type");
else
read_fw_str(reipl_type_str, "reipl/reipl_type",
sizeof(reipl_type_str));
reipl_type_str = read_fw_str("reipl/reipl_type");
if (strcmp(reipl_type_str, "fcp") == 0)
print_fcp(l.ipl_set, 0);
@@ -225,5 +238,6 @@ void cmd_lsreipl(int argc, char *argv[])
else
printf("%s: %s (unknown)\n", get_ipl_banner(l.ipl_set),
reipl_type_str);
free(reipl_type_str);
exit(0);
}

View File

@@ -9,6 +9,8 @@
* it under the terms of the MIT license. See LICENSE for details.
*/
#include "lib/util_path.h"
#include "lib/util_file.h"
#include "ipl_tools.h"
static const char *const usage_lsshut =
@@ -60,12 +62,13 @@ static void parse_lsshut_options(int argc, char *argv[])
*/
static void read_vmcmd(char *str, const char *path)
{
char buf[128], tmp[512];
char *ptr_old, *ptr;
char tmp[512];
char *buf;
*str = 0;
buf = read_fw_str(path);
ptr_old = ptr = buf;
read_fw_str(buf, path, sizeof(buf));
while ((ptr = strchr(ptr_old, '\n'))) {
*ptr = 0;
sprintf(tmp, "\"%s\",", ptr_old);
@@ -74,23 +77,31 @@ static void read_vmcmd(char *str, const char *path)
}
sprintf(tmp, "\"%s\"", ptr_old);
strcat(str, tmp);
free(buf);
}
static void print_kdump(void)
{
struct stat sb;
char tmp[1024];
char *path;
char *tmp;
if (stat("/sys/kernel/kexec_crash_loaded", &sb) != 0)
path = util_path_sysfs("kernel/kexec_crash_loaded");
if (stat(path, &sb) != 0) {
free(path);
return;
read_str(tmp, "/sys/kernel/kexec_crash_loaded", sizeof(tmp));
}
tmp = util_file_read_text_file(path, 1);
if (strncmp(tmp, "1", 1) == 0)
printf("kdump,");
free(path);
free(tmp);
}
static void shutdown_trigger_print(struct shutdown_trigger *trigger)
{
char tmp[1024], cmd[1024], path[PATH_MAX];
char cmd[1024], path[PATH_MAX];
char *tmp;
sprintf(path, "shutdown_actions/%s", trigger->name_sysfs);
@@ -99,7 +110,7 @@ static void shutdown_trigger_print(struct shutdown_trigger *trigger)
if ((trigger == &shutdown_trigger_panic ||
trigger == &shutdown_trigger_restart))
print_kdump();
read_fw_str(tmp, path, sizeof(tmp));
tmp = read_fw_str(path);
if (strncmp(tmp, "vmcmd", strlen("vmcmd")) == 0) {
sprintf(path, "vmcmd/%s", trigger->name_sysfs);
read_vmcmd(cmd, path);
@@ -107,6 +118,7 @@ static void shutdown_trigger_print(struct shutdown_trigger *trigger)
} else {
printf("%s\n", tmp);
}
free(tmp);
}
void cmd_lsshut(int argc, char *argv[])

View File

@@ -10,6 +10,7 @@
*/
#include "lib/util_libc.h"
#include "lib/util_path.h"
#include "ipl_tools.h"
/*
@@ -18,11 +19,14 @@
*/
int fcp_is_device(const char *devno)
{
char path[PATH_MAX];
char *path;
snprintf(path, sizeof(path), "/sys/bus/ccw/drivers/zfcp/%s", devno);
if (chdir(path) != 0)
path = util_path_sysfs("bus/ccw/drivers/zfcp/%s", devno);
if (chdir(path) != 0) {
free(path);
return 0;
}
free(path);
return 1;
}
@@ -31,11 +35,12 @@ int fcp_is_device(const char *devno)
*/
void fcp_wwpn_get(const char *device, char *wwpn)
{
char path[PATH_MAX], buf[20];
char buf[20];
char *path;
FILE *fh;
int rc;
snprintf(path, sizeof(path), "/sys/block/%s/device/wwpn", device);
path = util_path_sysfs("block/%s/device/wwpn", device);
fh = fopen(path, "r");
if (fh == NULL)
ERR_EXIT_ERRNO("Could not open \"%s\"", path);
@@ -44,19 +49,20 @@ void fcp_wwpn_get(const char *device, char *wwpn)
ERR_EXIT("Could not lookup WWPN \"%s\"", path);
util_strlcpy(wwpn, buf, 20);
fclose(fh);
free(path);
}
/*
* Return the lun of a device
*/
void fcp_lun_get(const char *device, char *lun)
{
char path[PATH_MAX], buf[20];
char buf[20];
char *path;
FILE *fh;
int rc;
snprintf(path, sizeof(path), "/sys/block/%s/device/fcp_lun", device);
path = util_path_sysfs("block/%s/device/fcp_lun", device);
fh = fopen(path, "r");
if (fh == NULL)
ERR_EXIT_ERRNO("Could not open \"%s\"", path);
@@ -65,6 +71,7 @@ void fcp_lun_get(const char *device, char *lun)
ERR_EXIT("Could not lookup LUN \"%s\"", path);
util_strlcpy(lun, buf, 20);
fclose(fh);
free(path);
}
/*
@@ -72,11 +79,12 @@ void fcp_lun_get(const char *device, char *lun)
*/
void fcp_busid_get(const char *device, char *devno)
{
char buf[4096], path[PATH_MAX];
char buf[4096];
char *path;
FILE *fh;
int rc;
snprintf(path, sizeof(path), "/sys/block/%s/device/hba_id", device);
path = util_path_sysfs("block/%s/device/hba_id", device);
fh = fopen(path, "r");
if (fh == NULL)
ERR_EXIT_ERRNO("Could not open \"%s\"", path);
@@ -85,4 +93,5 @@ void fcp_busid_get(const char *device, char *devno)
ERR_EXIT("Could not find device \"%s\"", path);
strcpy(devno, buf);
fclose(fh);
free(path);
}

View File

@@ -12,7 +12,6 @@
#ifndef IPL_TOOLS_H
#define IPL_TOOLS_H
#include <ctype.h>
#include <errno.h>
#include <fcntl.h>
#include <getopt.h>
@@ -36,41 +35,38 @@ extern struct globals {
/*
* Commands
*/
extern void cmd_lsshut(int argc, char *argv[]);
extern void cmd_chshut(int argc, char *argv[]);
extern void cmd_lsreipl(int argc, char *argv[]);
extern void cmd_chreipl(int argc, char *argv[]);
void cmd_lsshut(int argc, char *argv[]);
void cmd_chshut(int argc, char *argv[]);
void cmd_lsreipl(int argc, char *argv[]);
void cmd_chreipl(int argc, char *argv[]);
extern void print_ccw(int show_ipl);
extern void print_fcp(int show_ipl, int dump);
extern void print_nvme(int show_ipl, int dump);
extern void print_nss(int show_ipl);
extern void print_eckd(int show_ipl, const char *name);
void print_ccw(int show_ipl);
void print_fcp(int show_ipl, int dump);
void print_nvme(int show_ipl, int dump);
void print_nss(int show_ipl);
void print_eckd(int show_ipl, const char *name);
/*
* Helper
*/
extern int is_lpar(void);
extern int is_root(void);
int is_lpar(void);
int is_root(void);
extern void strlow(char *s);
void write_str(char *string, char *file);
int write_str_errno(char *string, char *file);
char *read_fw_str(const char *file);
void print_fw_str(const char *fmt, const char *dir, const char *file);
extern void write_str(char *string, char *file);
extern int write_str_errno(char *string, char *file);
extern void read_str(char *string, const char *file, size_t len);
extern void read_fw_str(char *string, const char *file, size_t len);
extern void print_fw_str(const char *fmt, const char *dir, const char *file);
extern void __noreturn print_version_exit(void);
extern void __noreturn print_help_hint_exit(void);
void __noreturn print_version_exit(void);
void __noreturn print_help_hint_exit(void);
/*
* FCP
*/
extern int fcp_is_device(const char *devno);
extern void fcp_lun_get(const char *device, char *lun);
extern void fcp_wwpn_get(const char *device, char *wwpn);
extern void fcp_busid_get(const char *device, char *devno);
int fcp_is_device(const char *devno);
void fcp_lun_get(const char *device, char *lun);
void fcp_wwpn_get(const char *device, char *wwpn);
void fcp_busid_get(const char *device, char *devno);
/*
* NVME
@@ -79,16 +75,16 @@ extern void fcp_busid_get(const char *device, char *devno);
#define NVME_DEV_MAX_LEN 15 /* "nvme" + u32 in decimal + null */
#define NVME_PATH_MAX (PATH_MAX + NAME_MAX + 1)
extern void nvme_fid_get(const char *device, char *fid);
extern void nvme_nsid_get(const char *device, char *nsid);
void nvme_fid_get(const char *device, char *fid);
void nvme_nsid_get(const char *device, char *nsid);
int nvme_is_device(char *fid_str, char *nsid_str);
/*
* CCW
*/
extern int ccw_is_device(const char *devno);
extern int ccw_is_virtio_device(const char *device);
extern void ccw_busid_get(const char *device, char *devno);
int ccw_is_device(const char *devno);
int ccw_is_virtio_device(const char *device);
void ccw_busid_get(const char *device, char *devno);
/*
* Shutdown trigger

View File

@@ -16,6 +16,7 @@
#include "lib/util_libc.h"
#include "lib/util_file.h"
#include "lib/util_path.h"
#include "ipl_tools.h"
static void nvme_dev_from_bdev(char *dev_name)
@@ -31,8 +32,9 @@ static void nvme_dev_from_bdev(char *dev_name)
*/
void nvme_fid_get(const char *device, char *fid)
{
char path[PATH_MAX], buf[FID_MAX_LEN];
char nvme_dev[NVME_DEV_MAX_LEN];
char buf[FID_MAX_LEN];
char *path;
/*
* An NVMe may present multiple namespaces and thus block devices, even
@@ -42,24 +44,27 @@ void nvme_fid_get(const char *device, char *fid)
util_strlcpy(nvme_dev, device, sizeof(nvme_dev));
nvme_dev_from_bdev(nvme_dev);
snprintf(path, PATH_MAX, "/sys/class/nvme/%s/device/function_id", nvme_dev);
path = util_path_sysfs("class/nvme/%s/device/function_id", nvme_dev);
if (util_file_read_line(buf, FID_MAX_LEN, path))
ERR_EXIT_ERRNO("Could not read from \"%s\"", path);
util_strlcpy(fid, buf, FID_MAX_LEN);
free(path);
}
/*
* Return the nsid of a device
*/
void nvme_nsid_get(const char *device, char *nsid)
{
char path[PATH_MAX], buf[FID_MAX_LEN];
char buf[FID_MAX_LEN];
char *path;
snprintf(path, PATH_MAX, "/sys/block/%s/nsid", device);
path = util_path_sysfs("block/%s/nsid", device);
if (util_file_read_line(buf, FID_MAX_LEN, path))
ERR_EXIT_ERRNO("Could not read from \"%s\"", path);
util_strlcpy(nsid, buf, FID_MAX_LEN);
free(path);
}
static int next_entry(DIR *dir, char *in_path, char *out_path,
@@ -93,13 +98,14 @@ static int next_entry(DIR *dir, char *in_path, char *out_path,
static int nvme_getdev_by_fid(char *fidstr, char *devpath)
{
char temp_path[PATH_MAX+19], real_path[PATH_MAX];
char *sys_path = "/sys/class/nvme";
char temp_path[PATH_MAX + 19], real_path[PATH_MAX];
u_int64_t target_fid, curfid;
char *sys_path;
DIR *dir;
char *end;
int rc = -1;
sys_path = util_path_sysfs("class/nvme");
target_fid = strtoul(fidstr, &end, 16);
if (*end)
ERR_EXIT("Invalid function_id given %s", fidstr);
@@ -126,6 +132,7 @@ static int nvme_getdev_by_fid(char *fidstr, char *devpath)
}
closedir(dir);
free(sys_path);
return rc;
}

View File

@@ -16,6 +16,7 @@
#include <sys/stat.h>
#include <sys/sysmacros.h>
#include <unistd.h>
#include <ctype.h>
#include "ipl_tools.h"
#include "proc.h"

View File

@@ -12,7 +12,6 @@
#ifndef PROC_H
#define PROC_H
#include <ctype.h>
#include <sys/types.h>
struct proc_part_entry {

View File

@@ -9,7 +9,8 @@
* it under the terms of the MIT license. See LICENSE for details.
*/
#include "lib/util_libc.h"
#include "lib/util_path.h"
#include "lib/util_file.h"
#include "ipl_tools.h"
/*
@@ -46,47 +47,18 @@ int is_root(void)
return 0;
}
/*
* Convert a string to lower case
*/
void strlow(char *s)
{
while (*s) {
*s = tolower(*s);
s++;
}
}
/*
* Read a string from a particular file
*/
void read_str(char *string, const char *path, size_t len)
{
size_t rc;
FILE *fh;
fh = fopen(path, "rb");
if (fh == NULL)
ERR_EXIT_ERRNO("Could not open \"%s\"", path);
rc = fread(string, 1, len - 1, fh);
if (rc == 0 && ferror(fh))
ERR_EXIT_ERRNO("Could not read \"%s\"", path);
fclose(fh);
string[rc] = 0;
if (string[strlen(string) - 1] == '\n')
string[strlen(string) - 1] = 0;
}
/*
* Read a string from a particular /sys/firmware file
*/
void read_fw_str(char *string, const char *file, size_t len)
char *read_fw_str(const char *file)
{
char *string;
char *path;
util_asprintf(&path, "/sys/firmware/%s", file);
read_str(string, path, len);
path = util_path_sysfs("firmware/%s", file);
string = util_file_read_text_file(path, 1);
free(path);
return string;
}
/*
@@ -94,11 +66,13 @@ void read_fw_str(char *string, const char *file, size_t len)
*/
void print_fw_str(const char *fmt, const char *dir, const char *file)
{
char path[PATH_MAX], str[4096];
char path[PATH_MAX];
char *str;
snprintf(path, sizeof(path), "%s/%s", dir, file);
read_fw_str(str, path, sizeof(str));
str = read_fw_str(path);
printf(fmt, str);
free(str);
}
/*
@@ -106,17 +80,19 @@ void print_fw_str(const char *fmt, const char *dir, const char *file)
*/
void write_str(char *string, char *file)
{
char path[PATH_MAX], value[4096];
char value[4096];
char *path;
int fh;
path = util_path_sysfs("firmware/%s", file);
snprintf(value, sizeof(value), "%s\n", string);
snprintf(path, sizeof(path), "/sys/firmware/%s", file);
fh = open(path, O_WRONLY);
if (fh < 0)
ERR_EXIT_ERRNO("Could not open \"%s\"", file);
if (write(fh, value, strlen(value)) < 0)
ERR_EXIT_ERRNO("Could not set \"%s\"", file);
close(fh);
free(path);
}
/*
@@ -124,17 +100,18 @@ void write_str(char *string, char *file)
*/
int write_str_errno(char *string, char *file)
{
char path[PATH_MAX], value[4096];
char value[4096];
char *path;
int fh;
path = util_path_sysfs("firmware/%s", file);
snprintf(value, sizeof(value), "%s\n", string);
snprintf(path, sizeof(path), "/sys/firmware/%s", file);
fh = open(path, O_WRONLY);
if (fh < 0)
return errno;
if (write(fh, value, strlen(value)) < 0)
return errno;
close(fh);
free(path);
return 0;
}

View File

@@ -7,7 +7,8 @@ examples = libcpumf_example
all: $(lib)
examples: $(lib) $(examples)
objects = libcpumf_pmutype.o libcpumf_cpuset.o libcpumf_support.o
objects = libcpumf_pmutype.o libcpumf_cpuset.o libcpumf_support.o \
libcpumf_ctrset.o
$(lib): $(objects)

View File

@@ -5,20 +5,23 @@
*/
#include <errno.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <limits.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "lib/libcpumf.h"
#include "lib/util_file.h"
#include "lib/util_libc.h"
#include "lib/util_path.h"
int libcpumf_cpuset(const char *parm, cpu_set_t *mask)
{
char *cp, *buffer = strdup(parm);
char *cp, *buffer = util_strdup(parm);
char *cp2 = buffer;
int to, from, rc;
if (!buffer) /* Errno set to ENOMEM */
return -1;
/* Check for invalid characters, such as 11.12 instead 11-12
* but allow blanks and newline. Newline is appended
* when the string is taken from sysfs files, for example
@@ -26,7 +29,8 @@ int libcpumf_cpuset(const char *parm, cpu_set_t *mask)
*/
if (strspn(buffer, "0123456789-,\n ") != strlen(buffer)) {
errno = EINVAL;
return -1;
rc = -1;
goto out;
}
CPU_ZERO(mask);
for (; (cp = strtok(buffer, ",")); buffer = NULL) {
@@ -51,25 +55,19 @@ int libcpumf_cpuset(const char *parm, cpu_set_t *mask)
}
rc = 0;
out:
free(buffer);
free(cp2);
return rc;
}
int libcpumf_cpuset_fn(const char *filename, cpu_set_t *mask)
{
char *txt = NULL;
ssize_t ret = -1;
size_t len = 0;
FILE *fp;
char *path = util_path_sysfs(filename);
char txt[PATH_MAX];
int ret = util_file_read_line(txt, sizeof(txt), "%s", path);
fp = fopen(filename, "r");
if (!fp)
return ret;
free(path);
/* Read out file, one line expected */
ret = getline(&txt, &len, fp);
fclose(fp);
if (ret > 0)
if (!ret)
ret = libcpumf_cpuset(txt, mask);
free(txt);
return ret;
}

View File

@@ -0,0 +1,53 @@
/* Copyright IBM Corp. 2022, 2024
*
* s390-tools is free software; you can redistribute it and/or modify
* it under the terms of the MIT license. See LICENSE for details.
*/
#include <errno.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <stdbool.h>
#include "lib/libcpumf.h"
int libcpumf_ctrset(int ctr, int cfvn, int csvn)
{
/* Governs basic and problem state counters */
switch (cfvn) {
case 1:
if (ctr >= 0 && ctr < 32)
return CPUMF_CTRSET_BASIC;
if (ctr >= 32 && ctr < 38)
return CPUMF_CTRSET_PROBLEM_STATE;
break;
case 3:
if (ctr >= 0 && ctr < 32)
return CPUMF_CTRSET_BASIC;
if (ctr >= 32 && ctr < 34)
return CPUMF_CTRSET_PROBLEM_STATE;
break;
}
/* Governs crypto, extended and MT-Diagnositc counters */
switch (csvn) {
case 1 ... 5:
if (ctr >= 64 && ctr < 80)
return CPUMF_CTRSET_CRYPTO;
if ((csvn == 1 && ctr >= 128 && ctr < 160) ||
(csvn == 2 && ctr >= 128 && ctr < 176) ||
(ctr >= 128 && ctr < 256))
return CPUMF_CTRSET_EXTENDED;
break;
case 6 ... 8:
if (ctr >= 64 && ctr < 84)
return CPUMF_CTRSET_CRYPTO;
if (ctr >= 128 && ctr < 288)
return CPUMF_CTRSET_EXTENDED;
break;
}
if (csvn >= 3 && ctr >= 448 && ctr < 496)
return CPUMF_CTRSET_MT_DIAG;
return CPUMF_CTRSET_NONE;
}

View File

@@ -1,4 +1,4 @@
/* Copyright IBM Corp. 2022
/* Copyright IBM Corp. 2022, 2025
*
* s390-tools is free software; you can redistribute it and/or modify
* it under the terms of the MIT license. See LICENSE for details.
@@ -18,16 +18,13 @@
int libcpumf_pmutype(const char *dirname)
{
int ret = -1;
FILE *file;
char *fn;
int ret;
ret = asprintf(&fn, "%s/type", dirname);
if (ret == -1) /* No memory, errno set */
return ret;
fn = util_path_sysfs("%s/type", dirname);
file = fopen(fn, "r");
free(fn);
ret = -1; /* Errno set on file open error */
if (file) {
/* Read out a single number from that file */
if (fscanf(file, "%u", &ret) != 1)

View File

@@ -13,6 +13,7 @@
#include <sys/stat.h>
#include "lib/libcpumf.h"
#include "lib/util_path.h"
#define SERVICELEVEL "/proc/service_levels"
@@ -129,34 +130,42 @@ bool libcpumf_have_sfb(void)
bool libcpumf_sfb_info(unsigned long *min, unsigned long *max)
{
int rc = false;
char *path;
FILE *fp;
fp = fopen(S390_CPUMSF_BUFFERSZ, "r");
path = util_path_sysfs(S390_CPUMSF_BUFFERSZ);
fp = fopen(path, "r");
if (!fp)
err(EXIT_FAILURE, "%s", S390_CPUMSF_BUFFERSZ);
err(EXIT_FAILURE, "%s", path);
if (fscanf(fp, "%lu,%lu", min, max) == 2)
rc = true;
fclose(fp);
free(path);
return rc;
}
static bool libcpumf_have_pai_sysfs(char *p)
{
char *path;
bool ret;
path = util_path_sysfs(p);
ret = util_path_exists(path);
free(path);
return ret;
}
bool libcpumf_have_pai_crypto(void)
{
struct stat statbuf;
return (stat(S390_SYSFS_PAI_CRYPTO, &statbuf) == -1) ? false : true;
return libcpumf_have_pai_sysfs(S390_SYSFS_PAI_CRYPTO);
}
bool libcpumf_have_pai_ext(void)
{
struct stat statbuf;
return (stat(S390_SYSFS_PAI_EXT, &statbuf) == -1) ? false : true;
return libcpumf_have_pai_sysfs(S390_SYSFS_PAI_EXT);
}
bool libcpumf_have_pai_nnpa(void)
{
struct stat statbuf;
return (stat(S390_SYSFS_PAI_NNPA, &statbuf) == -1) ? false : true;
return libcpumf_have_pai_sysfs(S390_SYSFS_PAI_NNPA);
}

View File

@@ -2432,7 +2432,7 @@ out:
kmip_node_free(iv);
kmip_node_free(tag);
kmip_node_free(ffl);
kmip_node_free(ffl);
kmip_node_free(ifl);
kmip_node_free(cnt);
kmip_node_free(icv);
kmip_node_free(salt);

View File

@@ -1471,7 +1471,7 @@ retry:
chain = SSL_get_peer_cert_chain(conn->plain_tls.ssl);
if (chain == NULL) {
kmip_debug(debug, "SSL_get_peer_cert_chain failed");
return -EIO;
rc = -EIO;
goto out;
}

View File

@@ -210,6 +210,8 @@ struct kmip_node *kmip_new_request_header(const struct kmip_version *version,
out:
kmip_node_free(ver);
kmip_node_free(max);
kmip_node_free(ccorr);
kmip_node_free(scorr);
kmip_node_free(async);
kmip_node_free(err);
kmip_node_free(ord);

View File

@@ -1369,7 +1369,7 @@ int kmip_get_get_response_payload(const struct kmip_node *node,
return 0;
error:
if (*unique_id != NULL) {
if (unique_id != NULL && *unique_id != NULL) {
kmip_node_free(*unique_id);
*unique_id = NULL;
}

View File

@@ -351,7 +351,6 @@ int kmip_connection_tls_init(struct kmip_connection *conn, bool debug)
if (tok == NULL) {
kmip_debug(debug, "malformed IPv6 address");
rc = -EINVAL;
free(hostname);
goto out;
}
tok++;

View File

@@ -1501,6 +1501,9 @@ int SK_EP11_reencipher_key(const struct sk_ext_ep11_lib *ep11_lib,
return -ENODEV;
}
memset(&rb, 0, sizeof(rb));
memset(&lrb, 0, sizeof(lrb));
rb.domain = domain;
lrb.domain = domain;

View File

@@ -97,6 +97,9 @@ const char *util_arch_machine_type_to_str(int type)
case UTIL_ARCH_MACHINE_TYPE_Z16:
case UTIL_ARCH_MACHINE_TYPE_Z16_A02:
return "IBM z16";
case UTIL_ARCH_MACHINE_TYPE_Z17:
case UTIL_ARCH_MACHINE_TYPE_Z17_2:
return "IBM z17";
default:
return "Unknown machine type";
}

View File

@@ -616,6 +616,10 @@ char *util_file_read_fd(FILE *fd, int chomp)
if (util_file_read_fd_buf(fd, (void **) &buffer, &done))
return NULL;
/* Prevent over-read if buffer is larger than amount of read characters */
if (buffer)
done = MIN(done, strnlen(buffer, done));
/* Check if this is a text file at all (required to filter out
* binary sysfs attributes).
*/

View File

@@ -307,9 +307,13 @@ static void add_key(const char *name, bool persist)
key.persist = persist;
util_add_array(&f.keys, &f.num_keys, key);
if (f.type == FMT_CSV) {
hdr = csv_quote(name);
util_rec_def(f.csv_rec, name, UTIL_REC_ALIGN_LEFT, 0, hdr);
free(hdr);
if (f.quote_all) {
hdr = csv_quote(name);
util_rec_def(f.csv_rec, name, UTIL_REC_ALIGN_LEFT, 0, hdr);
free(hdr);
} else {
util_rec_def(f.csv_rec, name, UTIL_REC_ALIGN_LEFT, 0, name);
}
util_rec_set(f.csv_rec, name, "\"\"");
f.csv_hdr = true;
}

View File

@@ -356,3 +356,34 @@ void zpci_free_dev_list(struct util_list *zpci_list)
}
util_list_free(zpci_list);
}
/**
* Find a PCI device given the name of a netdev
*
* This function allows finding a PCI device when only the name of one
* of its netdevs is known.
*
* @param[in] zpci_list The device list to search
* @param[in] netdev_name The name of the netdev
* @param[out] netdev Pointer to store the netdev or NULL if
* only the PCI device is needed
*
* @return The PCI device if one is found NULL otherwise
*/
struct zpci_dev *zpci_find_by_netdev(struct util_list *zpci_list, char *netdev_name,
struct zpci_netdev **netdev)
{
struct zpci_dev *zdev = NULL;
int i;
util_list_iterate(zpci_list, zdev) {
for (i = 0; i < zdev->num_netdevs; i++) {
if (!strcmp(zdev->netdevs[i].name, netdev_name)) {
if (netdev)
*netdev = &zdev->netdevs[i];
return zdev;
}
}
}
return NULL;
}

View File

@@ -171,8 +171,6 @@ static const char help_text[] =
"-h, --help Print this help, then exit\n"
"-v, --version Print version information, then exit\n"
"-a, --attach Run in foreground\n"
"-i, --interval=<seconds> Sample interval\n"
"\n"
"Please report bugs to: linux390@de.ibm.com\n";
"-i, --interval=<seconds> Sample interval\n";
#endif

View File

@@ -16,6 +16,19 @@
. if !'\\$1'' .IP "\fB\-\-\\$1\fP \\*[args]" 4
. PD
..
.\" Macro for inserting code line.
.\" .CL <text>
.de CL
. ds pfont \fP
. nh
. na
. ft CW
\\$*
. ft \\*[pfont]
. ad
. hy
. br
..
.
.TH opticsmon 8 "Oct 2024" s390-tools zpcictl
.
@@ -67,8 +80,7 @@ on".
.B Example: Extract module information for the first adapter
.CL
opticsmon --module-info | jq -r '.adapters[0].netdevs[0].optics.module_info' | base64 -d | hexdump -C
.CL opticsmon --module-info | jq -r '.adapters[0].netdevs[0].optics.module_info' | base64 -d | hexdump -C
.PP
.PP
.

View File

@@ -274,38 +274,33 @@ static int oneshot_mode(struct opticsmon_ctx *ctx)
void on_link_change(struct zpci_netdev *netdev, void *arg)
{
struct opticsmon_ctx *ctx = arg;
struct zpci_dev *zdev;
int i, reloads = 1;
struct zpci_netdev *found_netdev;
struct zpci_dev *zdev = NULL;
int reloads = 1;
if (!ctx->zpci_list || util_list_is_empty(ctx->zpci_list))
zpci_list_reload(&ctx->zpci_list);
reload:
util_list_iterate(ctx->zpci_list, zdev) {
for (i = 0; i < zdev->num_netdevs; i++) {
if (!strcmp(zdev->netdevs[i].name, netdev->name)) {
reloads--;
do {
if (ctx->zpci_list) {
zdev = zpci_find_by_netdev(ctx->zpci_list, netdev->name, &found_netdev);
if (zdev) {
/* Skip data collection if operational state is
* unchanged
*/
if (zdev->netdevs[i].operstate == netdev->operstate)
continue;
if (found_netdev->operstate == netdev->operstate)
return;
/* Update operation state for VFs even though
* they are skipped just for a consistent view
*/
zdev->netdevs[i].operstate = netdev->operstate;
found_netdev->operstate = netdev->operstate;
/* Only collect optics data for PFs */
if (!zpci_is_vf(zdev))
dump_adapter_data(ctx, zdev);
return;
}
}
}
/* Might be a new device, reload list of devices and retry */
if (reloads) {
/* Could be uninitalized list or a new device, retry after reload */
zpci_list_reload(&ctx->zpci_list);
reloads--;
goto reload;
}
} while (reloads > 0);
}
#define MAX_EVENTS 8

584
rust/Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -26,6 +26,3 @@ unstable_features = "warn"
unused = "warn"
unused_import_braces = "warn"
unused_qualifications = "warn"
[profile.release]
lto = true

View File

@@ -14,7 +14,7 @@ ifneq (${HAVE_CARGO},0)
CARGO_TARGETS :=
BUILD_TARGETS = $(CARGO_TARGETS)
INSTALL_TARGETS := install-rust-tools install-man
INSTALL_TARGETS := install-rust-tools install-man install-shell-completions
CARGO_TEST_TARGETS = $(addsuffix .test, $(CARGO_TARGETS))
ifeq ($(HOST_ARCH),s390x)
@@ -86,7 +86,6 @@ install: $(INSTALL_TARGETS)
$(INSTALL) -d -m 755 $(DESTDIR)$(USRBINDIR)
$(INSTALL) -g $(GROUP) -o $(OWNER) -m 755 pvattest/tools/pvextract-hdr $(DESTDIR)$(USRBINDIR)
$(INSTALL) -d -m 755 $(DESTDIR)$(PVIMG_PKGDATADIR)
$(INSTALL) -g $(GROUP) -o $(OWNER) -m 755 pvimg/tools/check_hostkeydoc $(DESTDIR)$(PVIMG_PKGDATADIR)
$(MAKE) -C pvimg/boot install
ln -sf pvimg $(DESTDIR)$(USRBINDIR)/genprotimg
@@ -115,8 +114,21 @@ install-man:
$(INSTALL) -m 644 $(target)/man/*.1 -t $(DESTDIR)$(MANDIR)/man1;)
$(foreach target,$(PV_TARGETS),\
$(INSTALL) -m 644 $(target)/man/*.1 -t $(DESTDIR)$(MANDIR)/man1;)
ln -sf pvimg-create.1 $(DESTDIR)$(MANDIR)/man1/genprotimg.1
.PHONY: all install clean skip-build install-rust-tools print-rust-targets install-man rust-test
install-shell-completions: install-bash-completion install-zsh-completion
install-bash-completion: $(PV_TARGETS)
$(INSTALL) -d -m 755 $(DESTDIR)$(BASHCOMPLETIONDIR)
$(foreach target,$(PV_TARGETS),\
$(INSTALL) -m 0644 --preserve-timestamps -- $(shell find $(shell ls --sort=time --reverse --directory target/release/build/$(target)-*/out/ |tail -n1) -name '*.bash') $(DESTDIR)$(BASHCOMPLETIONDIR);)
install-zsh-completion: $(PV_TARGETS)
$(INSTALL) -d -m 755 $(DESTDIR)$(ZSHCOMPLETIONDIR)
$(foreach target,$(PV_TARGETS),\
$(INSTALL) -m 0644 --preserve-timestamps -- $(shell find $(shell ls --sort=time --reverse --directory target/release/build/$(target)-*/out/ |tail -n1) -regex '.*/_[a-zA-Z0-9]+') $(DESTDIR)$(ZSHCOMPLETIONDIR);)
.PHONY: all install clean skip-build install-rust-tools print-rust-targets install-man rust-test install-bash-completion install-zsh-completion install-shell-completions
.check-cargo:
ifeq ($(shell command -v $(CARGO)),)

View File

@@ -1,20 +1,20 @@
[package]
name = "cpacfinfo"
version = "0.11.0"
version = "0.12.0"
edition.workspace = true
license.workspace = true
rust-version.workspace = true
[dependencies]
anyhow = "1.0.71"
clap = { version = "4.1", features = ["derive"] }
anyhow = "1.0.95"
clap = { version = "4.5", features = ["derive"] }
libc = "0.2"
serde = { version = "1.0.164", features = ["derive"] }
serde = { version = "1.0.217", features = ["derive"] }
serde_json = "1.0"
utils = { path = "../utils" }
zerocopy = { version="0.7", features = ["derive"] }
zerocopy = { version="0.8", features = ["derive"] }
[build-dependencies]
cc = "1.0"
clap = { version ="4.1", features = ["derive"]}
lazy_static = "1.1"
cc = "1.2"
clap = { version ="4.5", features = ["derive"]}
lazy_static = "1.5"

View File

@@ -124,8 +124,8 @@ Possible values for
Displays the CPACF subfunctions available on the system.
.B cpacfinfo \-f/\-\-functions
is the same as
.B cpacfinfo \-f/\-\-functions \-a/\-\-available
. If
\fBcpacfinfo \-f/\-\-functions \-a/\-\-available\fR.
If
.B -n/\-\-not-available
is specified no available functions are shown. To get available as well as functions not available use
.B cpacfinfo \-f/\-\-functions \-a/\-\-available \-n/\-\-not-available.

View File

@@ -9,7 +9,6 @@ use std::ops::Index;
use std::result::Result;
use zerocopy::FromBytes;
use zerocopy::FromZeroes;
use crate::msa::InstructionKind;
use crate::msa::QueryAuthInfo;
@@ -33,7 +32,7 @@ pub const QAI_PARAM_SIZE_IN_BYTES: usize = 256;
/// Query authentication information format identifier
const FORMAT_0: u8 = 0;
#[derive(FromBytes, FromZeroes)]
#[derive(FromBytes)]
#[repr(C)]
struct QaiFmt0 {
res00: [u8; 6],
@@ -175,7 +174,7 @@ pub fn check_sysfs() -> bool {
/// -----------------------------------------------------------------
fn parse_qai_format_0(qai: &mut QueryAuthInfo, param: &[u8]) {
// parse param to temporary struct to ease further conversion
let tmp = QaiFmt0::read_from_prefix(param).expect("programming error");
let (tmp, _) = QaiFmt0::read_from_prefix(param).expect("programming error");
// parse from temporary struct
qai.hash_len = tmp.hash_length;

View File

@@ -1,6 +1,6 @@
[package]
name = "s390_pv"
version = "0.11.0"
version = "0.12.0"
edition.workspace = true
license.workspace = true
rust-version.workspace = true
@@ -14,18 +14,18 @@ readme = "README.md"
workspace = true
[dependencies]
byteorder = "1.3"
curl = "0.4.44"
byteorder = "1.5"
curl = "0.4.47"
enum_dispatch = "0.3.13"
foreign-types = "0.3.1"
log = { version = "0.4.6", features = ["std", "release_max_level_debug"] }
openssl = "0.10.66"
openssl-sys = "0.9.92"
serde = { version = "1.0.139", features = ["derive"] }
thiserror = "1.0.33"
zerocopy = { version="0.7", features = ["derive"] }
foreign-types = "0.3.2"
log = { version = "0.4.25", features = ["std", "release_max_level_debug"] }
openssl = "0.10.70"
openssl-sys = "0.9.105"
serde = { version = "1.0.217", features = ["derive"] }
thiserror = "2.0.11"
zerocopy = { version="0.8", features = ["derive"] }
pv_core = { path = "../pv_core", package = "s390_pv_core", version = "0.11.0" }
pv_core = { path = "../pv_core", package = "s390_pv_core", version = "0.12.0" }
[dev-dependencies]
serde_test = "1.0.139"
serde_test = "1.0.177"

View File

@@ -8,7 +8,7 @@ use std::{
};
use log::{debug, warn};
use zerocopy::{AsBytes, BigEndian, FromBytes, FromZeroes, U32, U64};
use zerocopy::{BigEndian, FromBytes, Immutable, IntoBytes, KnownLayout, U32, U64};
// (SE) boot request control block aka SE header
use crate::{assert_size, request::MagicValue, static_assert, Error, Result, PAGESIZE};
@@ -21,7 +21,7 @@ use crate::{assert_size, request::MagicValue, static_assert, Error, Result, PAGE
/// Tweak List Digest (tld)
/// SE-Header Tag (tag)
#[repr(C)]
#[derive(Debug, Clone, Copy, AsBytes, PartialEq, Eq, FromBytes, FromZeroes)]
#[derive(Debug, Clone, Copy, IntoBytes, PartialEq, Eq, FromBytes, Immutable, KnownLayout)]
pub struct BootHdrTags {
pld: [u8; BootHdrHead::DIGEST_SIZE],
ald: [u8; BootHdrHead::DIGEST_SIZE],
@@ -40,8 +40,8 @@ impl TryFrom<Vec<u8>> for BootHdrTags {
type Error = Error;
fn try_from(value: Vec<u8>) -> Result<Self, Self::Error> {
Self::ref_from(&value)
.ok_or_else(|| Error::InvBootHdrSize(value.len()))
Self::ref_from_bytes(&value)
.map_err(|_| Error::InvBootHdrSize(value.len()))
.copied()
}
}
@@ -49,7 +49,7 @@ impl TryFrom<Vec<u8>> for BootHdrTags {
/// Struct representing the Secure Execution boot image metadata
#[allow(unused)]
#[repr(packed)]
#[derive(Debug, Clone, FromBytes, FromZeroes, AsBytes, PartialEq, Eq)]
#[derive(Debug, Clone, FromBytes, IntoBytes, PartialEq, Eq, Immutable, KnownLayout)]
pub struct SeImgMetaData {
/// Magic value
magic: [u8; 8],
@@ -106,7 +106,7 @@ impl SeImgMetaData {
/// Gets the bytes of this value.
#[inline(always)]
pub fn as_bytes(&self) -> &[u8] {
<Self as AsBytes>::as_bytes(self)
<Self as IntoBytes>::as_bytes(self)
}
/// Returns the version of this [`SeImgMetaData`].
@@ -154,7 +154,7 @@ where
// read in the header
img.read_exact(&mut img_metadata_bytes)?;
// Cannot fail because the buffer has the same size as SeImgMetaData.
let img_metadata = SeImgMetaData::ref_from(&img_metadata_bytes).unwrap();
let img_metadata = SeImgMetaData::ref_from_bytes(&img_metadata_bytes).unwrap();
let img_metadata_version = img_metadata.version();
if img_metadata_version != SeImgMetaData::V1 {
warn!("Unknown Secure Execution boot image version {img_metadata_version}");
@@ -226,8 +226,8 @@ impl BootHdrTags {
}
let hdr_head = match BootHdrHead::read_from_prefix(hdr.as_mut_slice()) {
Some(hdr) => hdr,
None => {
Ok((hdr, _)) => hdr,
Err(_) => {
debug!("Boot hdr is too small");
return Err(Error::InvBootHdr);
}
@@ -260,7 +260,7 @@ impl BootHdrTags {
}
#[repr(C)]
#[derive(Debug, Clone, FromBytes, FromZeroes)]
#[derive(Debug, Clone, FromBytes)]
struct BootHdrHead {
magic: U64<BigEndian>,
version: U32<BigEndian>,
@@ -388,7 +388,7 @@ mod tests {
0, 1, 96, 0,
];
assert_eq!(metadata.as_bytes(), &data);
assert_eq!(SeImgMetaData::ref_from(&data), Some(&metadata));
assert_eq!(SeImgMetaData::ref_from_bytes(&data), Ok(&metadata));
assert_eq!(metadata.version(), SeImgMetaData::V1);
}

View File

@@ -29,7 +29,6 @@ pub type Aes256XtsKey = Confidential<[u8; SymKeyType::AES_256_XTS_KEY_LEN]>;
/// SHA-512 digest length (in bytes)
pub const SHA_512_HASH_LEN: usize = 64;
#[allow(dead_code)]
pub(crate) const SHA_256_HASH_LEN: u32 = 32;
#[allow(dead_code)]
@@ -60,6 +59,8 @@ impl SymKeyType {
pub const AES_256_XTS_KEY_LEN: usize = 64;
/// AES256-XTS tweak length (in bytes)
pub const AES_256_XTS_TWEAK_LEN: usize = 16;
/// AES256 GCM Block length
pub const AES_256_GCM_BLOCK_LEN: usize = 16;
/// Returns the tag length of the [`SymKeyType`] if it is an AEAD key
pub const fn tag_len(&self) -> Option<usize> {
@@ -360,7 +361,7 @@ pub fn encrypt_aead(
aad: &[u8],
conf: &[u8],
) -> Result<AeadEncryptionResult> {
let tag_len = key.key_type().tag_len().ok_or_else(|| Error::NoAeadKey)?;
let tag_len = key.key_type().tag_len().ok_or(Error::NoAeadKey)?;
let nid = key.key_type().into();
let cipher = Cipher::from_nid(nid).ok_or(Error::UnsupportedCipher(nid))?;

View File

@@ -55,7 +55,7 @@ pub enum Error {
#[error("Decryption failed. Probably due to a GCM tag mismatch.")]
GcmTagMismatch,
#[error("Invalid {0} user-data for signing provided. Max {} bytes allowed", .0.max())]
#[error("Invalid {0} user-data for signing provided. Max {max} bytes allowed", max=.0.max())]
AsrcbInvSgnUserData(UserDataType),
#[error("Unsupported user data signing key provided. Only EC(secp521r1) and RSA(2048 & 3072 bit) are supported")]
@@ -106,6 +106,20 @@ pub enum Error {
)]
AddDataMissing(&'static str),
#[error("An ASCII string was expected, but non-ASCII characters were received.")]
NonAscii,
#[error("Incorrect {what} for a {kind}. Is: {value}; expected: {exp}")]
RetrInvKey {
what: &'static str,
kind: String,
value: String,
exp: String,
},
#[error("Invalid data from OpenSSL")]
InvalSslData,
// errors from other crates
#[error(transparent)]
PvCore(#[from] pv_core::Error),

View File

@@ -37,6 +37,7 @@ mod brcb;
mod crypto;
mod error;
mod openssl_extensions;
mod pem_utils;
mod req;
mod utils;
mod uvattest;
@@ -71,6 +72,11 @@ pub mod attest {
};
}
/// Definitions and functions to write objects in PEM format
pub mod pem {
pub use crate::pem_utils::Pem;
}
/// Miscellaneous functions and definitions
pub mod misc {
pub use pv_core::misc::*;
@@ -98,7 +104,12 @@ pub mod request {
/// Reexports some useful OpenSSL symbols
pub mod openssl {
pub use openssl::{error::ErrorStack, hash::DigestBytes, pkey, x509};
pub use openssl::{error::ErrorStack, hash::DigestBytes, nid::Nid, pkey, x509};
// rust-OpenSSL does not define these NIDs
#[allow(missing_docs)]
pub const NID_ED25519: Nid = Nid::from_raw(openssl_sys::NID_ED25519);
#[allow(missing_docs)]
pub const NID_ED448: Nid = Nid::from_raw(openssl_sys::NID_ED448);
}
pub use pv_core::request::*;
@@ -112,6 +123,7 @@ pub mod secret {
asrcb::{AddSecretFlags, AddSecretRequest, AddSecretVersion},
ext_secret::ExtSecret,
guest_secret::GuestSecret,
retr_secret::{IbmProtectedKey, RetrievedSecret},
user_data::verify_asrcb_and_get_user_data,
};
}

View File

@@ -0,0 +1,85 @@
// SPDX-License-Identifier: MIT
//
// Copyright IBM Corp. 2024
use core::slice;
use openssl::error::ErrorStack;
use openssl_sys::BIO_new_mem_buf;
use std::ffi::c_int;
use std::{marker::PhantomData, ptr};
pub struct BioMem(*mut openssl_sys::BIO);
impl Drop for BioMem {
fn drop(&mut self) {
// SAFETY: Pointer is valid. The pointer value is dropped after the free.
unsafe {
openssl_sys::BIO_free_all(self.0);
}
}
}
impl BioMem {
pub fn new() -> Result<Self, ErrorStack> {
openssl_sys::init();
// SAFETY: Returns a valid pointer or null. null-case is tested right after this.
let bio = unsafe { openssl_sys::BIO_new(openssl_sys::BIO_s_mem()) };
match bio.is_null() {
true => Err(ErrorStack::get()),
false => Ok(Self(bio)),
}
}
pub fn as_ptr(&self) -> *mut openssl_sys::BIO {
self.0
}
/// Copies the content of this slice into a Vec
pub fn to_vec(&self) -> Vec<u8> {
let buf;
// SAFTEY: BIO provides a continuous memory that can be used to build a slice.
unsafe {
let mut ptr = ptr::null_mut();
let len = openssl_sys::BIO_get_mem_data(self.0, &mut ptr);
buf = slice::from_raw_parts(ptr as *const _ as *const _, len as usize)
}
buf.to_vec()
}
}
pub struct BioMemSlice<'a>(*mut openssl_sys::BIO, PhantomData<&'a [u8]>);
impl Drop for BioMemSlice<'_> {
fn drop(&mut self) {
// SAFETY: Pointer is valid. The pointer value is dropped after the free.
unsafe {
openssl_sys::BIO_free_all(self.0);
}
}
}
impl<'a> BioMemSlice<'a> {
pub fn new(buf: &'a [u8]) -> Result<BioMemSlice<'a>, ErrorStack> {
openssl_sys::init();
// SAFETY: `buf` is a slice (i.e. pointer+size) pointing to a valid memory region.
// So the resulting bio is valid. Lifetime of the slice is connected by this Rust
// structure.
assert!(buf.len() <= c_int::MAX as usize);
let bio = unsafe {
{
let r = BIO_new_mem_buf(buf.as_ptr() as *const _, buf.len() as c_int);
match r.is_null() {
true => Err(ErrorStack::get()),
false => Ok(r),
}
}?
};
Ok(BioMemSlice(bio, PhantomData))
}
pub fn as_ptr(&self) -> *mut openssl_sys::BIO {
self.0
}
}

View File

@@ -6,8 +6,10 @@
/// Extensions to the rust-openssl crate
mod akid;
mod bio;
mod crl;
mod stackable_crl;
pub use akid::*;
pub use bio::*;
pub use crl::*;

View File

@@ -2,16 +2,14 @@
//
// Copyright IBM Corp. 2023
use std::{marker::PhantomData, ptr};
use crate::openssl_extensions::bio::BioMemSlice;
use foreign_types::{ForeignType, ForeignTypeRef};
use openssl::{
error::ErrorStack,
stack::Stackable,
x509::{X509Crl, X509CrlRef},
};
use openssl_sys::BIO_new_mem_buf;
use std::ffi::c_int;
use std::ptr;
#[derive(Debug)]
pub struct StackableX509Crl(*mut openssl_sys::X509_CRL);
@@ -62,44 +60,11 @@ impl Stackable for StackableX509Crl {
type StackType = openssl_sys::stack_st_X509_CRL;
}
pub struct MemBioSlice<'a>(*mut openssl_sys::BIO, PhantomData<&'a [u8]>);
impl Drop for MemBioSlice<'_> {
fn drop(&mut self) {
unsafe {
openssl_sys::BIO_free_all(self.0);
}
}
}
impl<'a> MemBioSlice<'a> {
pub fn new(buf: &'a [u8]) -> Result<MemBioSlice<'a>, ErrorStack> {
openssl_sys::init();
assert!(buf.len() <= c_int::MAX as usize);
let bio = unsafe {
{
let r = BIO_new_mem_buf(buf.as_ptr() as *const _, buf.len() as c_int);
if r.is_null() {
Err(ErrorStack::get())
} else {
Ok(r)
}
}?
};
Ok(MemBioSlice(bio, PhantomData))
}
pub fn as_ptr(&self) -> *mut openssl_sys::BIO {
self.0
}
}
impl StackableX509Crl {
pub fn stack_from_pem(pem: &[u8]) -> Result<Vec<X509Crl>, ErrorStack> {
unsafe {
openssl_sys::init();
let bio = MemBioSlice::new(pem)?;
let bio = BioMemSlice::new(pem)?;
let mut crls = vec![];
loop {

222
rust/pv/src/pem_utils.rs Normal file
View File

@@ -0,0 +1,222 @@
// SPDX-License-Identifier: MIT
//
// Copyright IBM Corp. 2024
use crate::Result;
use crate::{openssl_extensions::BioMem, Error};
use openssl::error::ErrorStack;
use pv_core::request::Confidential;
use std::{
ffi::{c_char, CString},
fmt::Display,
};
mod ffi {
use openssl_sys::BIO;
use std::ffi::{c_char, c_int, c_long, c_uchar};
extern "C" {
pub fn PEM_write_bio(
bio: *mut BIO,
name: *const c_char,
header: *const c_char,
data: *const c_uchar,
len: c_long,
) -> c_int;
}
}
/// Thin wrapper around [`CString`] only containing ASCII chars.
#[derive(Debug)]
struct AsciiCString(CString);
impl AsciiCString {
/// Convert from string
///
/// # Returns
/// Error if string is not ASCII or contains null chars
pub(crate) fn from_str(s: &str) -> Result<Self> {
match s.is_ascii() {
true => Ok(Self(CString::new(s).map_err(|_| Error::NonAscii)?)),
false => Err(Error::NonAscii),
}
}
fn as_ptr(&self) -> *const c_char {
self.0.as_ptr()
}
}
/// Helper struct to construct the PEM format
#[derive(Debug)]
struct InnerPem<'d> {
name: AsciiCString,
header: Option<AsciiCString>,
data: &'d [u8],
}
impl<'d> InnerPem<'d> {
fn new(name: &str, header: Option<String>, data: &'d [u8]) -> Result<Self> {
Ok(Self {
name: AsciiCString::from_str(name)?,
header: match header {
Some(h) => Some(AsciiCString::from_str(&h)?),
None => None,
},
data,
})
}
/// Generate PEM representation of the data
fn to_pem(&self) -> Result<Vec<u8>> {
let bio = BioMem::new()?;
let hdr_ptr = match self.header {
// avoid moving variable -> use reference
Some(ref h) => h.as_ptr(),
None => std::ptr::null(),
};
// SAFETY:
// All pointers point to valid C strings or memory regions
let rc = unsafe {
ffi::PEM_write_bio(
bio.as_ptr(),
self.name.as_ptr(),
hdr_ptr,
self.data.as_ptr(),
self.data.len() as std::ffi::c_long,
)
};
match rc {
1 => Err(Error::InternalSsl("Could not write PEM", ErrorStack::get())),
_ => Ok(bio.to_vec()),
}
}
}
/// Data in PEM format
///
/// Displays into a printable PEM structure.
/// Must be constructed from another structure in this library.
///
/// ```rust,ignore
/// let pem: Pem = ...;
/// println!("PEM {pem}");
/// ```
/// ```PEM
///-----BEGIN <name>-----
///<header>
///
///<Base64 formatted binary data>
///-----END <name>-----
#[derive(Debug)]
pub struct Pem {
pem: Confidential<String>,
}
#[allow(unused)]
impl Pem {
/// Create a new PEM structure.
///
/// # Errors
///
/// This function will return an error if name or header contain non-ASCII chars, or OpenSSL
/// could not generate the PEM (very likely due to OOM).
pub(crate) fn new<D, H>(name: &str, header: H, data: D) -> Result<Self>
where
D: AsRef<[u8]>,
H: Into<Option<String>>,
{
let mut header = header.into();
let header = match header {
Some(h) if h.ends_with('\n') => Some(h),
Some(h) if h.is_empty() => None,
Some(mut h) => {
h.push('\n');
Some(h)
}
None => None,
};
let inner_pem = InnerPem::new(name, header, data.as_ref())?;
// Create the PEM format eagerly so that to_string/display cannot fail because of ASCII or OpenSSL Errors
// Both error should be very unlikely
// OpenSSL should be able to create PEM if there is enough memory and produce a non-null
// terminated ASCII-string
// Unwrap succeeds it's all ASCII
// Std lib implements all the conversations without a copy
let pem = CString::new(inner_pem.to_pem()?)
.map_err(|_| Error::NonAscii)?
.into_string()
.unwrap()
.into();
Ok(Self { pem })
}
/// Converts the PEM-data into a byte vector.
///
/// This consumes the `PEM`.
#[inline]
#[must_use = "`self` will be dropped if the result is not used"]
pub fn into_bytes(self) -> Confidential<Vec<u8>> {
self.pem.into_inner().into_bytes().into()
}
}
impl Display for Pem {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
self.pem.value().fmt(f)
}
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn no_data() {
const EXP: &str =
"-----BEGIN PEM test-----\ntest hdr value: 17\n\n-----END PEM test-----\n";
let test_pem = Pem::new("PEM test", "test hdr value: 17".to_string(), []).unwrap();
let pem_str = test_pem.to_string();
assert_eq!(pem_str, EXP);
}
#[test]
fn no_hdr() {
const EXP: &str =
"-----BEGIN PEM test-----\ndmVyeSBzZWNyZXQga2V5\n-----END PEM test-----\n";
let test_pem = Pem::new("PEM test", None, "very secret key").unwrap();
let pem_str = test_pem.to_string();
assert_eq!(pem_str, EXP);
}
#[test]
fn some_data() {
const EXP: &str= "-----BEGIN PEM test-----\ntest hdr value: 17\n\ndmVyeSBzZWNyZXQga2V5\n-----END PEM test-----\n";
let test_pem = Pem::new(
"PEM test",
"test hdr value: 17".to_string(),
"very secret key",
)
.unwrap();
let pem_str = test_pem.to_string();
assert_eq!(pem_str, EXP);
}
#[test]
fn data_linebreak() {
const EXP: &str= "-----BEGIN PEM test-----\ntest hdr value: 17\n\ndmVyeSBzZWNyZXQga2V5\n-----END PEM test-----\n";
let test_pem = Pem::new(
"PEM test",
"test hdr value: 17\n".to_string(),
"very secret key",
)
.unwrap();
let pem_str = test_pem.to_string();
assert_eq!(pem_str, EXP);
}
}

View File

@@ -12,7 +12,7 @@ use openssl::{
pkey::{PKey, PKeyRef, Private, Public},
};
use pv_core::request::{RequestMagic, RequestVersion};
use zerocopy::{AsBytes, BigEndian, FromBytes, FromZeroes, U32};
use zerocopy::{BigEndian, FromBytes, Immutable, IntoBytes, KnownLayout, U32};
use crate::{
assert_size,
@@ -358,7 +358,7 @@ ecdh_from!(Public);
/// Representation of the shared parts of the request header.
/// Used by [`ReqEncrCtx`]
#[repr(C)]
#[derive(Debug, Copy, Clone, AsBytes, FromBytes, FromZeroes)]
#[derive(Debug, Copy, Clone, IntoBytes, FromBytes, Immutable)]
struct RequestHdr {
magic: [u8; 8],
rqvn: U32<BigEndian>,
@@ -449,7 +449,7 @@ impl<'a> BinReqValues<'a> {
/// Does minimal sanity test, just tests to prevent panics.
/// `req` may be larger than the actual request.
pub(crate) fn get(req: &'a [u8]) -> Result<Self> {
let hdr = RequestHdr::read_from_prefix(req).ok_or(Error::BinRequestSmall)?;
let (hdr, _) = RequestHdr::read_from_prefix(req).map_err(|_| Error::BinRequestSmall)?;
let rql = hdr.rql.get() as usize;
let sea = hdr.sea.get() as usize;
@@ -506,9 +506,9 @@ impl<'a> BinReqValues<'a> {
/// [`FromBytes::ref_from_prefix`]
pub(crate) fn req_dep_aad<T>(&self) -> Option<&T>
where
T: FromBytes + Sized,
T: FromBytes + Sized + Immutable + KnownLayout,
{
T::ref_from_prefix(self.req_dep_aad)
T::ref_from_prefix(self.req_dep_aad).map(|s| s.0).ok()
}
/// Returns a reference to the tag of this [`BinReqValues`].
@@ -615,7 +615,7 @@ mod tests {
#[test]
fn req_hdr2() {
let mut hdr = RequestHdr::new(0x200, 0x1234, [0x11; 12], 15, 44, Some(TEST_MAGIC));
let hdr_bin = hdr.as_bytes_mut();
let hdr_bin = hdr.as_mut_bytes();
let hdr_bin_exp = [
0x12, 0x34, 0x56, 0x89, 0xab, 0xcd, 0xef, 0, // magic
0, 0, 2, 0, // vers

View File

@@ -4,7 +4,7 @@
use openssl::pkey::{PKey, Public};
use std::mem::size_of;
use zerocopy::{AsBytes, BigEndian, FromBytes, FromZeroes, U32};
use zerocopy::{BigEndian, FromBytes, Immutable, IntoBytes, KnownLayout, U32};
use crate::{
assert_size,
@@ -276,7 +276,7 @@ impl From<AttestationVersion> for RequestVersion {
/// Authenticated additional Data of an [`AttestationRequest`]
#[repr(C)]
#[derive(Debug, AsBytes, FromZeroes, FromBytes, Clone, Copy)]
#[derive(Debug, IntoBytes, FromBytes, Clone, Copy, Immutable, KnownLayout)]
pub struct AttestationAuthenticated {
flags: AttestationFlags,
mai: U32<BigEndian>,
@@ -310,7 +310,7 @@ impl AttestationAuthenticated {
/// Attestation flags
#[repr(C)]
#[derive(Default, Debug, AsBytes, FromZeroes, FromBytes, Clone, Copy)]
#[derive(Default, Debug, IntoBytes, FromBytes, Clone, Copy, Immutable)]
pub struct AttestationFlags(UvFlags);
static_assert!(AttestationFlags::FLAG_TO_ADD_SIZE.len() < 64);
@@ -393,7 +393,7 @@ impl AttestationFlags {
}
#[repr(C)]
#[derive(Debug, AsBytes)]
#[derive(Debug, IntoBytes, Immutable)]
struct ReqConfData {
meas_key: [u8; 64],
nonce: AttNonce,

View File

@@ -12,7 +12,7 @@ use openssl::{
pkey::{PKeyRef, Private},
};
use std::mem::size_of;
use zerocopy::{AsBytes, BigEndian, U16, U32};
use zerocopy::{BigEndian, IntoBytes, U16, U32};
#[cfg(doc)]
use crate::attest::AttestationRequest;

View File

@@ -10,4 +10,5 @@
pub mod asrcb;
pub mod ext_secret;
pub mod guest_secret;
pub mod retr_secret;
pub mod user_data;

View File

@@ -2,7 +2,7 @@
//
// Copyright IBM Corp. 2023
use super::user_data::UserData;
use super::{guest_secret::ListableSecretHdr, user_data::UserData};
use crate::{
assert_size,
crypto::{hkdf_rfc_5869, AeadEncryptionResult},
@@ -17,12 +17,12 @@ use openssl::{
md::Md,
pkey::{PKey, Private, Public},
};
use pv_core::{request::RequestVersion, secret::AddSecretMagic};
use zerocopy::AsBytes;
use pv_core::{request::RequestVersion, secret::AddSecretMagic, uv::SecretId};
use zerocopy::{Immutable, IntoBytes};
/// Authenticated data w/o user data
#[repr(C)]
#[derive(Debug, Clone, Copy, AsBytes)]
#[derive(Debug, Clone, Copy, IntoBytes, Immutable)]
struct ReqAuthData {
flags: UvFlags,
boot_tags: BootHdrTags,
@@ -283,6 +283,13 @@ impl AddSecretRequest {
.map(|res| res.into_buf())
}
/// Get a copy of the secret ID if any
pub fn bin_id(asrcb: &[u8]) -> Result<Option<SecretId>> {
AddSecretMagic::try_from_bytes(asrcb)?;
BinReqValues::get(asrcb)
.map(|req| req.req_dep_aad::<ListableSecretHdr>().map(|a| a.id.clone()))
}
/// Get a copy of the add secret request tag
pub fn bin_tag(asrcb: &[u8]) -> Result<Vec<u8>> {
AddSecretMagic::try_from_bytes(asrcb)?;

View File

@@ -4,20 +4,35 @@
#[allow(unused_imports)] // used for more convenient docstring
use super::asrcb::AddSecretRequest;
use crate::assert_size;
use crate::{
crypto::{hash, random_array},
request::Confidential,
Result,
assert_size,
crypto::{hash, random_array, SymKeyType},
request::{
openssl::{NID_ED25519, NID_ED448},
Confidential,
},
uv::{
AesSizes, AesXtsSizes, EcCurves, HmacShaSizes, ListableSecretType, RetrievableSecret,
RetrieveCmd, SecretId,
},
Error, Result,
};
use byteorder::BigEndian;
use openssl::hash::MessageDigest;
use pv_core::uv::{ListableSecretType, SecretId};
use openssl::{
hash::MessageDigest,
nid::Nid,
pkey::{Id, PKey, PKeyRef, Private},
};
use pv_core::static_assert;
use serde::{Deserialize, Serialize};
use std::{convert::TryInto, fmt::Display};
use zerocopy::{AsBytes, U16, U32};
use std::fmt::Display;
use zerocopy::{BigEndian, KnownLayout};
use zerocopy::{FromBytes, Immutable, IntoBytes, U16, U32};
const ASSOC_SECRET_SIZE: usize = 32;
const CCK_SIZE: usize = 32;
/// Maximum size of a plain-text secret payload (8190)
pub(crate) const MAX_SIZE_PLAIN_PAYLOAD: usize = RetrieveCmd::MAX_SIZE - 2;
static_assert!(MAX_SIZE_PLAIN_PAYLOAD == 8190);
/// A Secret to be added in [`AddSecretRequest`]
#[derive(Debug, Serialize, Deserialize, PartialEq, Eq)]
@@ -36,13 +51,69 @@ pub enum GuestSecret {
#[serde(skip)]
secret: Confidential<[u8; ASSOC_SECRET_SIZE]>,
},
/// Retrievable key
///
/// Create Retrievables using [`GuestSecret::retrievable`]
/// Secret size is always valid for the type/kind
Retrievable {
/// Retrievable secret type
kind: RetrievableSecret,
/// Name of the secret
name: String,
/// SHA256 hash of [`GuestSecret::RetrievableKey::name`]
id: SecretId,
/// Confidential actual retrievable secret
#[serde(skip)]
secret: Confidential<Vec<u8>>,
},
/// CCK update
///
/// Create CCK updates using [`GuestSecret::update_cck`]
UpdateCck {
/// Confidential actual CCK (32 bytes)
#[serde(skip)]
secret: Confidential<[u8; CCK_SIZE]>,
},
}
macro_rules! retr_constructor {
($(#[$err:meta])* | $(#[$kind:meta])* => $type: ty, $func: ident) => {
/// Create a new
$(#[$kind])*
/// [`GuestSecret::Retrievable`] secret.
///
/// * `name` - Name of the secret. Will be hashed into a 32 byte id
/// * `secret` - the secret value
///
/// # Errors
///
$(#[$err])*
pub fn $func(name: &str, secret: $type) -> Result<Self> {
let (kind, secret) = $func(secret)?;
Ok(Self::Retrievable {
kind,
name: name.to_string(),
id: Self::name_to_id(name)?,
secret,
})
}
};
}
impl GuestSecret {
/// Hashes the name with sha256
pub fn name_to_id(name: &str) -> Result<SecretId> {
let id: [u8; SecretId::ID_SIZE] = hash(MessageDigest::sha256(), name.as_bytes())?
.to_vec()
.try_into()
.unwrap();
Ok(id.into())
}
/// Create a new [`GuestSecret::Association`].
///
/// * `name` - Name of the secret. Will be hashed into a 32 byte id
/// * `secret` - Value of the secret. Ranom if [`Option::None`]
/// * `secret` - Value of the secret. Random if [`Option::None`]
///
/// # Errors
///
@@ -51,10 +122,6 @@ impl GuestSecret {
where
O: Into<Option<[u8; ASSOC_SECRET_SIZE]>>,
{
let id: [u8; SecretId::ID_SIZE] = hash(MessageDigest::sha256(), name.as_bytes())?
.to_vec()
.try_into()
.unwrap();
let secret = match secret.into() {
Some(s) => s,
None => random_array()?,
@@ -62,16 +129,51 @@ impl GuestSecret {
Ok(Self::Association {
name: name.to_string(),
id: id.into(),
id: Self::name_to_id(name)?,
secret: secret.into(),
})
}
retr_constructor!(#[doc = r"This function will return an error if the secret is larger than 8 pages"]
| #[doc = r"plaintext"] => Confidential<Vec<u8>>, plaintext);
retr_constructor!(#[doc = r"This function will return an error if OpenSSL cannot create a hash or the secret size is invalid"]
| #[doc = r"AES Key"] => Confidential<Vec<u8>>, aes);
retr_constructor!(#[doc = r"This function will return an error if OpenSSL cannot create a hash or the secret size is invalid"]
| #[doc = r"AES-XTS Key"] => Confidential<Vec<u8>>, aes_xts);
retr_constructor!(#[doc = r"This function will return an error if OpenSSL cannot create a hash or the secret size is invalid"]
| #[doc = r"HMAC-SHA Key"] => Confidential<Vec<u8>>, hmac_sha);
retr_constructor!(#[doc = r"This function will return an error if OpenSSL cannot create a hash or the curve is invalid"]
| #[doc = r"EC PRIVATE Key"] => PKey<Private>, ec);
/// Create a new [`GuestSecret::UpdateCck`].
///
/// * `secret` - New CCK.
pub fn update_cck(secret: [u8; CCK_SIZE]) -> Self {
Self::UpdateCck {
secret: secret.into(),
}
}
/// Use the name as ID, do not hash it
pub fn no_hash_name(&mut self) {
match self {
Self::Null | Self::UpdateCck { .. } => (),
Self::Association {
name, ref mut id, ..
}
| Self::Retrievable {
name, ref mut id, ..
} => id.clone_from(&SecretId::from_string(name)),
}
}
/// Reference to the confidential data
pub(crate) fn confidential(&self) -> &[u8] {
pub fn confidential(&self) -> &[u8] {
match &self {
Self::Null => &[],
Self::Association { secret, .. } => secret.value().as_slice(),
Self::Retrievable { secret, .. } => secret.value(),
Self::UpdateCck { secret, .. } => secret.value(),
}
}
@@ -79,7 +181,8 @@ impl GuestSecret {
pub(crate) fn auth(&self) -> SecretAuth {
match &self {
Self::Null => SecretAuth::Null,
// Panic: every non null secret type is listable -> no panic
Self::UpdateCck { .. } => SecretAuth::UpdateCck,
// Panic: other secret types are list-able -> no panic
listable => {
SecretAuth::Listable(ListableSecretHdr::from_guest_secret(listable).unwrap())
}
@@ -92,6 +195,8 @@ impl GuestSecret {
// Null is not listable, but the ListableSecretType provides the type constant (1)
Self::Null => ListableSecretType::NULL,
Self::Association { .. } => ListableSecretType::ASSOCIATION,
Self::Retrievable { kind, .. } => kind.into(),
Self::UpdateCck { .. } => ListableSecretType::UPDATE_CCK,
}
}
@@ -100,25 +205,181 @@ impl GuestSecret {
match self {
Self::Null => 0,
Self::Association { secret, .. } => secret.value().len() as u32,
Self::Retrievable { secret, .. } => secret.value().len() as u32,
Self::UpdateCck { secret } => secret.value().len() as u32,
}
}
/// Returns the ID of the secret type (if any)
fn id(&self) -> Option<SecretId> {
match self {
Self::Null => None,
Self::Association { id, .. } => Some(id.to_owned()),
Self::Null | Self::UpdateCck { .. } => None,
Self::Association { id, .. } | Self::Retrievable { id, .. } => Some(id.to_owned()),
}
}
}
type RetrKeyInfo = (RetrievableSecret, Confidential<Vec<u8>>);
fn extend_to_multiple(mut key: Vec<u8>, multiple: usize) -> Confidential<Vec<u8>> {
match key.len().checked_rem(multiple) {
Some(0) | None => key,
Some(m) => {
key.resize(key.len() + multiple - m, 0);
key
}
}
.into()
}
/// Get a plain-text key
///
/// ```none
/// size U16<BigEndian> | payload (0-8190) bytes
/// ```
fn plaintext(inp: Confidential<Vec<u8>>) -> Result<RetrKeyInfo> {
let key_len = inp.value().len();
if key_len > MAX_SIZE_PLAIN_PAYLOAD {
return Err(Error::RetrInvKey {
what: "key size",
value: key_len.to_string(),
kind: RetrievableSecret::PlainText.to_string(),
exp: RetrievableSecret::PlainText.expected(),
});
}
let mut key = Vec::with_capacity(2 + inp.value().len());
let key_len: U16<BigEndian> = (key_len as u16).into();
key.extend_from_slice(key_len.as_bytes());
key.extend_from_slice(inp.value());
let key = extend_to_multiple(key, SymKeyType::AES_256_GCM_BLOCK_LEN);
Ok((RetrievableSecret::PlainText, key))
}
/// Get an AES-key
fn aes(key: Confidential<Vec<u8>>) -> Result<RetrKeyInfo> {
let key_len = key.value().len() as u32;
let bit_size = bitsize(key_len);
match AesSizes::from_bits(bit_size) {
Some(size) => Ok((RetrievableSecret::Aes(size), key)),
None => {
// Use some AES type to get exp sizes and name
let kind = RetrievableSecret::Aes(AesSizes::Bits128);
Err(Error::RetrInvKey {
what: "key size",
value: bit_size.to_string(),
kind: format!("{kind:#}"),
exp: kind.expected(),
})
}
}
}
/// Get an AES-XTS-key
fn aes_xts(key: Confidential<Vec<u8>>) -> Result<RetrKeyInfo> {
let key_len = key.value().len() as u32;
let bit_size = bitsize(key_len / 2);
match AesXtsSizes::from_bits(bit_size) {
Some(size) => Ok((RetrievableSecret::AesXts(size), key)),
None => {
// Use some AES-XTS type to get exp sizes and name
let kind = RetrievableSecret::AesXts(AesXtsSizes::Bits128);
Err(Error::RetrInvKey {
what: "key size",
value: bit_size.to_string(),
kind: format!("{kind:#}"),
exp: kind.expected(),
})
}
}
}
/// Get an HMAC-SHA-key
fn hmac_sha(key: Confidential<Vec<u8>>) -> Result<RetrKeyInfo> {
let key_len = key.value().len() as u32;
let size = bitsize(key_len / 2);
match HmacShaSizes::from_sha_size(size) {
Some(size) => Ok((RetrievableSecret::HmacSha(size), key)),
None => {
// Use some HMAC type to get exp sizes and name
let kind = RetrievableSecret::HmacSha(HmacShaSizes::Sha256);
Err(Error::RetrInvKey {
what: "key size",
value: size.to_string(),
kind: format!("{kind:#}"),
exp: kind.expected(),
})
}
}
}
/// Get an EC-private-key
fn ec(key: PKey<Private>) -> Result<RetrKeyInfo> {
// reads & left-pads Edward EC keys
fn pad_ed_key(pkey: &PKeyRef<Private>, curve: &EcCurves) -> Result<Vec<u8>> {
let raw_key = pkey.raw_private_key()?;
match raw_key.len().cmp(&curve.exp_key_size()) {
std::cmp::Ordering::Less => {
let mut key = Vec::with_capacity(curve.exp_key_size());
key.extend_from_slice(&vec![0u8; curve.exp_key_size() - raw_key.len()]);
key.extend_from_slice(&raw_key);
Ok(key)
}
std::cmp::Ordering::Equal => Ok(raw_key),
std::cmp::Ordering::Greater => Err(Error::InvalSslData),
}
}
let nid = match key.id() {
Id::EC => key.ec_key()?.group().curve_name().unwrap_or(Nid::UNDEF),
id @ (Id::ED25519 | Id::ED448) => Nid::from_raw(id.as_raw()),
_ => Nid::UNDEF,
};
let kind = match nid {
Nid::X9_62_PRIME256V1 => EcCurves::Secp256R1,
Nid::SECP384R1 => EcCurves::Secp384R1,
Nid::SECP521R1 => EcCurves::Secp521R1,
NID_ED25519 => EcCurves::Ed25519,
NID_ED448 => EcCurves::Ed448,
nid => {
// Use some EC type to get exp sizes and name
let ec = RetrievableSecret::Ec(EcCurves::Secp521R1);
return Err(Error::RetrInvKey {
what: "curve or format",
kind: format!("{ec:#}"),
value: nid.long_name()?.to_string(),
exp: ec.expected(),
});
}
};
let key = match key.id() {
Id::EC => key
.ec_key()?
.private_key()
.to_vec_padded(kind.exp_key_size() as i32)?,
// ED keys are not handled via the EC struct in OpenSSL.
Id::ED25519 | Id::ED448 => pad_ed_key(&key, &kind)?,
_ => unreachable!(),
};
Ok((RetrievableSecret::Ec(kind), key.into()))
}
#[inline(always)]
const fn bitsize(bytesize: u32) -> u32 {
bytesize * 8
}
impl Display for GuestSecret {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Null => write!(f, "Meta"),
gs => {
let kind: U16<BigEndian> = gs.kind().into();
let st: ListableSecretType = kind.into();
let st: ListableSecretType = kind.get().into();
write!(f, "{st}")
}
}
@@ -129,44 +390,97 @@ impl Display for GuestSecret {
pub(crate) enum SecretAuth {
Null,
Listable(ListableSecretHdr),
UpdateCck,
}
impl SecretAuth {
const NULL_HDR: NullSecretHdr = NullSecretHdr::new();
const UPDATE_CCK_HDR: UpdateCckHdr = UpdateCckHdr::new();
pub fn get(&self) -> &[u8] {
match self {
Self::Null => &[0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
Self::Null => Self::NULL_HDR.as_bytes(),
Self::Listable(h) => h.as_bytes(),
Self::UpdateCck => Self::UPDATE_CCK_HDR.as_bytes(),
}
}
}
#[repr(C)]
#[derive(Debug, AsBytes)]
#[derive(Debug, IntoBytes, FromBytes, Immutable, KnownLayout)]
struct NullSecretHdr {
res0: u16,
kind: U16<BigEndian>,
secret_len: U32<BigEndian>,
res8: u64,
}
assert_size!(NullSecretHdr, 0x10);
impl NullSecretHdr {
const fn new() -> Self {
Self {
res0: 0,
kind: U16::new(ListableSecretType::NULL),
secret_len: U32::ZERO,
res8: 0,
}
}
}
#[repr(C)]
#[derive(Debug, IntoBytes, FromBytes, Immutable, KnownLayout)]
pub(crate) struct ListableSecretHdr {
res0: u16,
kind: U16<BigEndian>,
secret_len: U32<BigEndian>,
res8: u64,
id: SecretId,
pub(crate) id: SecretId,
}
assert_size!(ListableSecretHdr, 0x30);
impl ListableSecretHdr {
fn from_guest_secret(gs: &GuestSecret) -> Option<Self> {
let id = gs.id()?;
Some(Self {
res0: 0,
kind: gs.kind().into(),
secret_len: gs.secret_len().into(),
res8: 0,
id,
id: gs.id()?,
})
}
}
#[repr(C)]
#[derive(Debug, IntoBytes, Default, Immutable)]
struct UpdateCckHdr {
res0: u16,
kind: U16<BigEndian>,
secret_len: U32<BigEndian>,
res8: u64,
res10: [u8; 0x20],
}
assert_size!(UpdateCckHdr, 0x30);
impl UpdateCckHdr {
const fn new() -> Self {
Self {
res0: 0,
kind: U16::new(ListableSecretType::UPDATE_CCK),
secret_len: U32::new(CCK_SIZE as u32),
res8: 0,
res10: [0; 0x20],
}
}
}
#[cfg(test)]
mod test {
use super::HmacShaSizes as HmacSizes;
use super::RetrievableSecret::*;
use super::*;
use openssl::ec::{EcGroup, EcKey};
use pv_core::uv::AesSizes;
use serde_test::{assert_tokens, Token};
#[test]
@@ -187,8 +501,127 @@ mod test {
assert_eq!(secret, exp);
}
macro_rules! retr_test {
($name: ident, $func: ident, $size: expr, $exp_kind: expr) => {
#[test]
fn $name() {
let secret_value = vec![0x11; $size];
let name = "test retr secret".to_string();
let secret = GuestSecret::$func(&name, secret_value.clone().into()).unwrap();
let exp_id = [
0x61, 0x2c, 0xd6, 0x3e, 0xa8, 0xf2, 0xc1, 0x15, 0xc1, 0xe, 0x15, 0xb8, 0x8a,
0x90, 0x16, 0xc1, 0x55, 0xef, 0x9c, 0x7c, 0x2c, 0x8e, 0x56, 0xd0, 0x78, 0x4c,
0x8a, 0x1d, 0xc9, 0x3a, 0x80, 0xba,
];
let exp = GuestSecret::Retrievable {
kind: $exp_kind,
name,
id: exp_id.into(),
secret: secret_value.into(),
};
assert_eq!(exp, secret);
}
};
}
retr_test!(retr_aes_128, aes, 16, Aes(AesSizes::Bits128));
retr_test!(retr_aes_192, aes, 24, Aes(AesSizes::Bits192));
retr_test!(retr_aes_256, aes, 32, Aes(AesSizes::Bits256));
retr_test!(retr_aes_xts_128, aes_xts, 32, AesXts(AesXtsSizes::Bits128));
retr_test!(retr_aes_xts_256, aes_xts, 64, AesXts(AesXtsSizes::Bits256));
retr_test!(retr_aes_hmac_256, hmac_sha, 64, HmacSha(HmacSizes::Sha256));
retr_test!(retr_aes_hmac_512, hmac_sha, 128, HmacSha(HmacSizes::Sha512));
#[test]
fn ap_asc_parse() {
fn update_cck() {
let new_cck = [11; 32];
let req = GuestSecret::update_cck(new_cck);
let exp = GuestSecret::UpdateCck {
secret: new_cck.into(),
};
assert_eq!(req, exp);
}
#[test]
fn plaintext_no_pad() {
let key = vec![0, 14, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7];
let name = "PLAINTEXT_PAD".to_string();
let secret = GuestSecret::plaintext(&name, key[2..].to_vec().into()).unwrap();
let exp_id = [
15, 123, 176, 210, 135, 231, 220, 232, 148, 93, 198, 195, 165, 212, 214, 129, 45, 1,
94, 11, 167, 18, 151, 15, 120, 254, 13, 109, 173, 186, 37, 74,
];
let exp = GuestSecret::Retrievable {
kind: PlainText,
name,
id: exp_id.into(),
secret: key.into(),
};
assert_eq!(secret, exp);
}
#[test]
fn plaintext_pad() {
let key = vec![0, 10, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 0, 0, 0, 0];
let name = "PLAINTEXT_PAD".to_string();
let secret = GuestSecret::plaintext(&name, key[2..12].to_vec().into()).unwrap();
let exp_id = [
15, 123, 176, 210, 135, 231, 220, 232, 148, 93, 198, 195, 165, 212, 214, 129, 45, 1,
94, 11, 167, 18, 151, 15, 120, 254, 13, 109, 173, 186, 37, 74,
];
let exp = GuestSecret::Retrievable {
kind: PlainText,
name,
id: exp_id.into(),
secret: key.into(),
};
assert_eq!(secret, exp);
}
#[track_caller]
fn gen_ec(nid: Nid) -> PKey<Private> {
let group = EcGroup::from_curve_name(nid).unwrap();
let key = EcKey::generate(&group).unwrap();
PKey::from_ec_key(key).unwrap()
}
#[track_caller]
fn test_ec(grp: Nid, exp_kind: EcCurves, exp_len: usize) {
let key = match grp {
NID_ED25519 => PKey::generate_ed25519().unwrap(),
NID_ED448 => PKey::generate_ed448().unwrap(),
nid => gen_ec(nid),
};
let (kind, key) = ec(key).unwrap();
assert_eq!(kind, Ec(exp_kind));
assert_eq!(key.value().len(), exp_len);
}
#[test]
fn retr_ec() {
test_ec(Nid::X9_62_PRIME256V1, EcCurves::Secp256R1, 32);
test_ec(Nid::SECP384R1, EcCurves::Secp384R1, 48);
test_ec(Nid::SECP521R1, EcCurves::Secp521R1, 80);
test_ec(NID_ED25519, EcCurves::Ed25519, 32);
test_ec(NID_ED448, EcCurves::Ed448, 64);
}
#[test]
fn retr_ec_pad() {
let pkey = PKey::generate_ed448().unwrap();
let (_, key) = ec(pkey).unwrap();
assert_eq!(key.value()[..7], [0; 7]);
let pkey = gen_ec(Nid::SECP521R1);
let (_, key) = ec(pkey).unwrap();
assert_eq!(key.value()[..14], [0; 14]);
}
#[test]
fn asc_parse() {
let id = [
0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef, 0x01, 0x23, 0x45, 0x67, 0x89, 0xab,
0xcd, 0xef, 0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef, 0x01, 0x23, 0x45, 0x67,
@@ -217,6 +650,57 @@ mod test {
);
}
#[test]
fn retrievable_parse() {
let id = [
0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef, 0x01, 0x23, 0x45, 0x67, 0x89, 0xab,
0xcd, 0xef, 0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef, 0x01, 0x23, 0x45, 0x67,
0x89, 0xab, 0xcd, 0xef,
];
let asc = GuestSecret::Retrievable {
kind: PlainText,
name: "test123".to_string(),
id: id.into(),
secret: vec![].into(),
};
assert_tokens(
&asc,
&[
Token::StructVariant {
name: "GuestSecret",
variant: "Retrievable",
len: 3,
},
Token::String("kind"),
Token::String("3 (PLAINTEXT)"),
Token::String("name"),
Token::String("test123"),
Token::String("id"),
Token::String("0x0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef"),
Token::StructVariantEnd,
],
);
}
#[test]
fn update_cck_parse() {
let cck = GuestSecret::UpdateCck {
secret: [0; 32].into(),
};
assert_tokens(
&cck,
&[
Token::StructVariant {
name: "GuestSecret",
variant: "UpdateCck",
len: 0,
},
Token::StructVariantEnd,
],
)
}
#[test]
fn guest_secret_bin_null() {
let gs = GuestSecret::Null;
@@ -228,7 +712,7 @@ mod test {
}
#[test]
fn guest_secret_bin_ap() {
fn guest_secret_bin_asoc() {
let gs = GuestSecret::Association {
name: "test".to_string(),
id: [1; 32].into(),
@@ -241,4 +725,34 @@ mod test {
assert_eq!(exp, gs_bytes_auth.get());
assert_eq!(&[2; 32], gs.confidential());
}
#[test]
fn guest_secret_bin_retr() {
let gs = GuestSecret::Retrievable {
kind: PlainText,
name: "test".to_string(),
id: [1; 32].into(),
secret: vec![2; 32].into(),
};
let auth = gs.auth();
let gs_bytes_auth = auth.get();
let mut exp = vec![0u8, 0, 0, 3, 0, 0, 0, 0x20, 0, 0, 0, 0, 0, 0, 0, 0];
exp.extend([1; 32]);
assert_eq!(exp, gs_bytes_auth);
assert_eq!(&[2; 32], gs.confidential());
}
#[test]
fn guest_secret_bin_cck() {
let gs = GuestSecret::UpdateCck {
secret: [2; 32].into(),
};
let gs_bytes_auth = gs.auth();
let mut exp = vec![0u8, 0, 0, 0x16, 0, 0, 0, 0x20];
exp.extend([0; 40]);
assert_eq!(exp, gs_bytes_auth.get());
assert_eq!(&[2; 32], gs.confidential());
}
}

View File

@@ -0,0 +1,236 @@
// SPDX-License-Identifier: MIT
//
// Copyright IBM Corp. 2024
use crate::{crypto::SymKeyType, pem::Pem, uvsecret::guest_secret::MAX_SIZE_PLAIN_PAYLOAD, Result};
use log::warn;
use pv_core::{
request::Confidential,
uv::{ListableSecretType, RetrievableSecret, RetrieveCmd},
};
use zerocopy::BigEndian;
use zerocopy::{FromBytes, U16};
/// An IBM Protected Key
///
/// A protected key, writeable as pem.
///
/// Will convert into PEM as:
/// ```PEM
///-----BEGIN IBM PROTECTED KEY-----
///kind: <name>
///
///<protected key in base64>
///-----END IBM PROTECTED KEY-----
/// ```
#[derive(Debug, PartialEq, Eq)]
pub struct IbmProtectedKey {
kind: ListableSecretType,
key: Confidential<Vec<u8>>,
}
impl IbmProtectedKey {
/// Get the binary representation of the key.
pub fn data(&self) -> &[u8] {
self.key.value()
}
/// Converts a [`IbmProtectedKey`] into a vector.
pub fn into_bytes(self) -> Confidential<Vec<u8>> {
self.key
}
/// Get the data in PEM format.
///
/// # Errors
///
/// This function will return an error if the PEM conversion failed (very unlikely).
pub fn to_pem(&self) -> Result<Pem> {
Pem::new(
"IBM PROTECTED KEY",
format!("kind: {}", self.kind),
self.key.value(),
)
}
fn new<K>(kind: ListableSecretType, key: K) -> Self
where
K: Into<Confidential<Vec<u8>>>,
{
Self {
kind,
key: key.into(),
}
}
}
impl From<RetrieveCmd> for RetrievedSecret {
fn from(value: RetrieveCmd) -> Self {
let kind = value.meta_data().stype();
let key = value.into_key();
match kind {
ListableSecretType::Retrievable(RetrievableSecret::PlainText) => {
// Will not run into default, retrieve has a granularity of 16 bytes and 16 bytes is the
// minimum size
let len = U16::<BigEndian>::read_from_prefix(key.value())
.unwrap_or_default()
.0
.get() as usize;
// Test if the plain text secret has a size:
// 1. len <= 8190
// 2. first two bytes are max 15 less than buffer-size+2 i.e. smaller than the
// block length
// 3. bytes after len + 2 are zero
match len <= MAX_SIZE_PLAIN_PAYLOAD
&& key.value().len() - (len + 2) < SymKeyType::AES_256_GCM_BLOCK_LEN
&& key.value()[len + 2..].iter().all(|c| *c == 0)
{
false => Self::Plaintext(key),
true => Self::Plaintext(key.value()[2..len + 2].to_vec().into()),
}
}
kind => {
match kind {
ListableSecretType::Retrievable(_) => (),
_ => warn!("Retrieved an unretrievable Secret! Will continue; interpreting it as a protected key."),
}
Self::ProtectedKey(IbmProtectedKey::new(kind, key))
}
}
}
}
/// A retrieved Secret.
#[derive(Debug, PartialEq, Eq)]
pub enum RetrievedSecret {
/// A plaintext secret
Plaintext(Confidential<Vec<u8>>),
/// An [`IbmProtectedKey`]
ProtectedKey(IbmProtectedKey),
}
impl RetrievedSecret {
/// Create a new IBM PROTECTED KEY object
pub fn from_cmd(cmd: RetrieveCmd) -> Self {
cmd.into()
}
/// Get the binary representation of the key.
pub fn data(&self) -> &[u8] {
match self {
RetrievedSecret::Plaintext(p) => p.value(),
RetrievedSecret::ProtectedKey(p) => p.data(),
}
}
/// Converts a [`IbmProtectedKey`] into a vector.
pub fn into_bytes(self) -> Confidential<Vec<u8>> {
match self {
RetrievedSecret::Plaintext(p) => p,
RetrievedSecret::ProtectedKey(p) => p.into_bytes(),
}
}
/// Get the data in PEM format.
///
/// # Errors
///
/// This function will return an error if the PEM conversion failed (very unlikely).
pub fn to_pem(&self) -> Result<Pem> {
match self {
RetrievedSecret::Plaintext(p) => Pem::new("PLAINTEXT SECRET", None, p.value()),
RetrievedSecret::ProtectedKey(p) => p.to_pem(),
}
}
}
#[cfg(test)]
mod test {
use super::*;
use pv_core::uv::*;
fn mk_retr(secret: &[u8]) -> RetrievedSecret {
let entry = SecretEntry::new(
0,
ListableSecretType::Retrievable(RetrievableSecret::PlainText),
SecretId::default(),
secret.len() as u32,
);
let mut cmd = RetrieveCmd::from_entry(entry).unwrap();
cmd.data().unwrap().copy_from_slice(secret);
RetrievedSecret::from_cmd(cmd)
}
#[test]
fn from_retr_cmd() {
let secret = vec![0, 10, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0xa, 0, 0, 0, 0];
let prot_key = mk_retr(&secret);
let exp = RetrievedSecret::Plaintext(secret[2..12].to_vec().into());
assert_eq!(prot_key, exp);
}
#[test]
fn from_retr_inv_size() {
let secret = vec![0x20; 32];
let prot_key = mk_retr(&secret);
let exp = RetrievedSecret::Plaintext(secret.into());
assert_eq!(prot_key, exp);
}
#[test]
fn from_retr_inv_no_zero_after_end() {
let secret = vec![0, 10, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0xa, 1, 0, 0, 0];
let prot_key = mk_retr(&secret);
let exp = RetrievedSecret::Plaintext(secret.into());
assert_eq!(prot_key, exp);
}
#[test]
fn from_retr_inv_to_much_padding() {
let secret = vec![
0, 10, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0xa, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0,
];
let prot_key = mk_retr(&secret);
let exp = RetrievedSecret::Plaintext(secret.into());
assert_eq!(prot_key, exp);
}
#[test]
fn from_retr_0_size() {
let secret = vec![0x00; 32];
let prot_key = mk_retr(&secret);
let exp = RetrievedSecret::Plaintext(secret.into());
assert_eq!(prot_key, exp);
}
#[test]
fn plain_text_pem() {
let exp = "\
-----BEGIN PLAINTEXT SECRET-----\n\
ERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERER\n\
-----END PLAINTEXT SECRET-----\n";
let prot = RetrievedSecret::Plaintext(vec![17; 48].into());
let pem = prot.to_pem().unwrap();
let pem_str = pem.to_string();
assert_eq!(pem_str, exp);
}
#[test]
fn prot_key_pem() {
let exp = "\
-----BEGIN IBM PROTECTED KEY-----\n\
kind: AES-128-KEY\n\n\
ERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERER\n\
-----END IBM PROTECTED KEY-----\n";
let prot = IbmProtectedKey::new(
ListableSecretType::Retrievable(RetrievableSecret::Aes(AesSizes::Bits128)),
vec![17; 48],
);
let pem = prot.to_pem().unwrap();
let pem_str = pem.to_string();
assert_eq!(pem_str, exp);
}
}

View File

@@ -15,7 +15,7 @@ use crate::{
};
use openssl::hash::MessageDigest;
use openssl::nid::Nid;
use zerocopy::{AsBytes, BigEndian, FromBytes, FromZeroes, U16};
use zerocopy::{BigEndian, FromBytes, IntoBytes, KnownLayout, U16};
/// User data.
///
@@ -59,7 +59,7 @@ pub(super) enum UserData {
}
#[repr(C)]
#[derive(Debug, AsBytes, FromBytes, FromZeroes)]
#[derive(Debug, IntoBytes, FromBytes, KnownLayout)]
struct EcUserData {
data: [u8; 256],
signature: [u8; EC_SIGN_MAX_SIZE],
@@ -175,7 +175,7 @@ impl UserData {
// insert signature
if let UserDataType::SgnEcSECP521R1 = self.data_type() {
// Panic: will not panic buffer is 512+ bytes long
let buf_ec = EcUserData::mut_from_prefix(&mut buf[user_data_offset..]).unwrap();
let (buf_ec, _) = EcUserData::mut_from_prefix(&mut buf[user_data_offset..]).unwrap();
buf_ec.set_signature(&sgn);
} else {
// Panic: will not panic buffer is 512+ bytes long
@@ -328,12 +328,15 @@ impl VerifiedUserData {
let (ret, sgn) = match kind {
UserDataType::SgnEcSECP521R1 => {
let EcUserData {
data,
signature,
sgn_size,
..
} = EcUserData::mut_from_prefix(buf).unwrap();
let (
EcUserData {
data,
signature,
sgn_size,
..
},
_,
) = EcUserData::mut_from_prefix(buf).unwrap();
let data_len: usize = data.len();
let data = data.to_vec();
let mut signature = signature.to_vec();
@@ -480,7 +483,7 @@ mod test {
buf[..0x80].copy_from_slice(data);
user_data.sign(&mut buf, 0).unwrap();
let buf_ec = EcUserData::mut_from(&mut buf).unwrap();
let buf_ec = EcUserData::mut_from_bytes(&mut buf).unwrap();
let EcUserData {
data,
signature,

View File

@@ -1,6 +1,6 @@
[package]
name = "s390_pv_core"
version = "0.11.0"
version = "0.12.0"
edition.workspace = true
license.workspace = true
rust-version.workspace = true
@@ -16,13 +16,14 @@ readme = "README.md"
workspace = true
[dependencies]
libc = "0.2.49"
log = { version = "0.4.6", features = ["std", "release_max_level_debug"] }
thiserror = "1.0.33"
zerocopy = {version = "0.7", features = ["derive"]}
serde = { version = "1.0.139", features = ["derive"]}
byteorder = "1.3"
libc = "0.2.169"
log = { version = "0.4.25", features = ["std", "release_max_level_debug"] }
thiserror = "2.0.11"
zerocopy = {version = "0.8", features = ["derive"]}
serde = { version = "1.0.217", features = ["derive"]}
byteorder = "1.5"
regex = "1.10"
[dev-dependencies]
serde_test = "1.0.139"
lazy_static = "1.1"
serde_test = "1.0.177"
lazy_static = "1.5"

View File

@@ -0,0 +1,468 @@
// SPDX-License-Identifier: MIT
//
// Copyright IBM Corp. 2023
//
//! AP support functions
//
use crate::{
utils::{pv_guest_bit_set, read_file_string, write_file},
Error, Result,
};
use regex::Regex;
use std::fmt;
use std::thread;
use std::time;
const PATH_SYS_DEVICES_AP: &str = "/sys/devices/ap";
/// Regular expression for AP queue directories
pub const RE_QUEUE_DIR: &str = r"^([[:xdigit:]]{2})\.([[:xdigit:]]{4})$";
const RE_CARD_TYPE: &str = r"^CEX([3-8])([ACP])$";
const RE_EP11_MKVP: &str = r"WK\s+CUR:\s+(\S+)\s+(\S+)";
const RE_CCA_AES_MKVP: &str = r"AES\s+CUR:\s+(\S+)\s+(\S+)";
const RE_CCA_APKA_MKVP: &str = r"APKA\s+CUR:\s+(\S+)\s+(\S+)";
const SYS_BUS_AP_BIND_POLL_MS: u64 = 500;
const SYS_BUS_AP_BIND_TIMEOUT_MS: u64 = 10000;
const SYS_BUS_AP_ASSOC_POLL_MS: u64 = 500;
const SYS_BUS_AP_ASSOC_TIMEOUT_MS: u64 = 10000;
/// APQN mode
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum ApqnMode {
/// Accelerator mode
Accel,
/// EP11 (Enterprise PKCS #11) coprocessor mode
Ep11,
/// Common Cryptographic Architecture (CCA) coprocessor mode
Cca,
}
/// Info on an APQN configured for accelerator
#[derive(Debug, Clone)]
pub struct ApqnInfoAccel {
// empty
}
/// Info on an APQN configured for EP11 coprocessor
#[derive(Debug, Clone)]
pub struct ApqnInfoEp11 {
/// Serial number of the Crypto Express adapter as a case-sensitive ASCII string
pub serialnr: String,
/// Master key verification pattern as hex string
pub mkvp: String, // may be an empty string if no WK set
}
/// Info on an APQN configured for CCA coprocessor
#[derive(Debug, Clone)]
#[allow(dead_code)]
pub struct ApqnInfoCca {
/// Serial number of the Crypto Express adapter as a case-sensitive ASCII string
pub serialnr: String,
/// Master key verification pattern as hex string for AES
pub mkvp_aes: String, // may be an empty string if no MK set
/// Master key verification pattern as hex string for asymmetric public key algorithms
pub mkvp_apka: String, // may be an empty string if no MK set
}
/// Info for an APQN's mode
#[derive(Debug, Clone)]
pub enum ApqnInfo {
/// Info on an APQN configured for accelerator
Accel(ApqnInfoAccel),
/// Info on an APQN configured for EP11 coprocessor
Ep11(ApqnInfoEp11),
/// Info on an APQN configured for CCA coprocessor
#[allow(dead_code)]
Cca(ApqnInfoCca),
}
macro_rules! parse_error {
($subject:expr, $content:expr) => {
Error::ParseError {
subject: $subject,
content: $content,
}
};
}
impl ApqnInfo {
fn accel_info(_carddir: &str, _queuedir: &str) -> Result<Self> {
Ok(Self::Accel(ApqnInfoAccel {}))
}
fn cca_info(carddir: &str, queuedir: &str) -> Result<Self> {
let serialnr_str = read_file_string(format!("{carddir}/serialnr"), "serialnr")?;
let serialnr = serialnr_str.trim().to_string();
let mkvps = read_file_string(format!("{carddir}/{queuedir}/mkvps"), "mkvps")?;
let mut aes_mkvp = String::new();
let re_cca_aes_mkvp = Regex::new(RE_CCA_AES_MKVP).unwrap();
if !re_cca_aes_mkvp.is_match(&mkvps) {
return Err(parse_error!(format!("APQN {queuedir} MKVPs"), mkvps));
} else {
let caps = re_cca_aes_mkvp.captures(&mkvps).unwrap();
if caps.get(1).unwrap().as_str().to_lowercase() == "valid" {
aes_mkvp = caps.get(2).unwrap().as_str().to_lowercase();
if aes_mkvp.starts_with("0x") {
aes_mkvp = String::from(&aes_mkvp[2..]);
}
}
}
let mut apka_mkvp = String::new();
let re_cca_apka_mkvp = Regex::new(RE_CCA_APKA_MKVP).unwrap();
if !re_cca_apka_mkvp.is_match(&mkvps) {
return Err(parse_error!(format!("APQN {queuedir} MKVPs"), mkvps));
} else {
let caps = re_cca_apka_mkvp.captures(&mkvps).unwrap();
if caps.get(1).unwrap().as_str().to_lowercase() == "valid" {
apka_mkvp = caps.get(2).unwrap().as_str().to_lowercase();
if apka_mkvp.starts_with("0x") {
apka_mkvp = String::from(&apka_mkvp[2..]);
}
}
}
Ok(Self::Cca(ApqnInfoCca {
serialnr,
mkvp_aes: aes_mkvp,
mkvp_apka: apka_mkvp,
}))
}
fn ep11_info(carddir: &str, queuedir: &str) -> Result<Self> {
let serialnr_str = read_file_string(format!("{carddir}/serialnr"), "serialnr")?;
let serialnr = serialnr_str.trim().to_string();
let mkvps = read_file_string(format!("{carddir}/{queuedir}/mkvps"), "mkvps")?;
let mut mkvp = String::new();
let re_ep11_mkvp = Regex::new(RE_EP11_MKVP).unwrap();
if !re_ep11_mkvp.is_match(&mkvps) {
return Err(parse_error!(format!("APQN {queuedir} MKVPs"), mkvps));
} else {
let caps = re_ep11_mkvp.captures(&mkvps).unwrap();
if caps.get(1).unwrap().as_str().to_lowercase() == "valid" {
mkvp = caps.get(2).unwrap().as_str().to_lowercase();
if mkvp.starts_with("0x") {
mkvp = String::from(&mkvp[2..]);
}
if mkvp.len() > 32 {
mkvp = String::from(&mkvp[..32])
}
}
}
Ok(Self::Ep11(ApqnInfoEp11 { serialnr, mkvp }))
}
/// Get mode-specific info
pub fn info(mode: &ApqnMode, carddir: &str, queuedir: &str) -> Result<Self> {
match mode {
ApqnMode::Accel => Self::accel_info(carddir, queuedir),
ApqnMode::Cca => Self::cca_info(carddir, queuedir),
ApqnMode::Ep11 => Self::ep11_info(carddir, queuedir),
}
}
}
/// `Apqn` encodes an adjunct processor queue number.
#[derive(Debug, Clone)]
pub struct Apqn {
/// Name of the APQN
#[allow(dead_code)]
pub name: String,
/// Card number
pub card: u32,
/// Domain number
pub domain: u32,
/// CryptoExpress generation
pub gen: u32,
/// Mode that adapter is configured to use
pub mode: ApqnMode,
/// Mode-specific info
pub info: Option<ApqnInfo>,
}
impl TryFrom<&str> for Apqn {
type Error = Error;
/// Create an `Apqn` struct from a CARD.DOMAIN-formatted APQN
/// string, such as `28.0014`. Will not populate `info` upon
/// failure to read it. Other failures to read required information
/// are treated as an Error.
/// # Panics
/// Panics if the compilation of a static regular expression fails
/// or a regex capture that is already format-checked does not
/// parse, e.g. when the capture `([[:xdigit:]]{2})` does not
/// parse as hex string.
fn try_from(name: &str) -> Result<Self> {
let re_card_type = Regex::new(RE_CARD_TYPE).unwrap();
let re_queue_dir = Regex::new(RE_QUEUE_DIR).unwrap();
let caps = re_queue_dir
.captures(name)
.ok_or_else(|| parse_error!("queue".to_string(), name.to_string()))?;
let cardstr = caps.get(1).unwrap().as_str();
let card = u32::from_str_radix(cardstr, 16).unwrap();
let domstr = caps.get(2).unwrap().as_str();
let domain = u32::from_str_radix(domstr, 16).unwrap();
let path = format!("{PATH_SYS_DEVICES_AP}/card{cardstr}");
let card_type =
read_file_string(format!("{path}/type"), "card type").map(|s| s.trim().to_string())?;
let caps = re_card_type
.captures(&card_type)
.ok_or_else(|| parse_error!("card type".to_string(), card_type.to_string()))?;
let gen = caps.get(1).unwrap().as_str().parse::<u32>().unwrap();
let mode = match caps.get(2).unwrap().as_str().parse::<char>().unwrap() {
'A' => ApqnMode::Accel,
'C' => ApqnMode::Cca,
'P' => ApqnMode::Ep11,
_ => unreachable!("Code inconsistency between regex RE_CARD_TYPE and evaluation code."),
};
// the UV blocks requests to CCA cards within SE guest with AP
// pass-through support. However, filter out CCA cards as
// these cards cause hangs during information gathering.
if mode == ApqnMode::Cca && pv_guest_bit_set() {
return Err(Error::CcaSeIncompatible(card));
}
match read_file_string(format!("{path}/{name}/online"), "AP queue online status")
.map(|s| s.trim().parse::<i32>())
{
Ok(Ok(1)) => {}
_ => return Err(Error::ApOffline { card, domain }),
}
// For the MKVP and serialnr to fetch from the APQN within a SE
// guest the APQN needs to be bound to the guest. So if the APQN
// is not bound, temporarily bind it here until the info has
// been retrieved.
let mut tempbound = false;
if pv_guest_bit_set() {
let cbs = get_apqn_bind_state(card, domain)?;
if cbs == BindState::Unbound {
set_apqn_bind_state(card, domain, BindState::Bound)?;
tempbound = true;
}
}
let info = ApqnInfo::info(&mode, &path, name).ok();
if tempbound {
set_apqn_bind_state(card, domain, BindState::Unbound)?;
}
Ok(Apqn {
name: name.to_string(),
card,
domain,
gen,
mode,
info,
})
}
}
impl fmt::Display for Apqn {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "({},{})", self.card, self.domain)
}
}
impl Apqn {
/// Read bind state of the APQN.
pub fn bind_state(&self) -> Result<BindState> {
get_apqn_bind_state(self.card, self.domain)
}
/// Set bind state of the APQN.
pub fn set_bind_state(&self, state: BindState) -> Result<()> {
set_apqn_bind_state(self.card, self.domain, state)
}
/// Read associate state of the APQN.
pub fn associate_state(&self) -> Result<AssocState> {
get_apqn_associate_state(self.card, self.domain)
}
/// Set associate state of the APQN.
pub fn set_associate_state(&self, state: AssocState) -> Result<()> {
set_apqn_associate_state(self.card, self.domain, state)
}
}
/// Bind state of an APQN
#[derive(Debug, PartialEq, Eq)]
pub enum BindState {
/// APQN is bound
Bound,
/// APQN is unbound
Unbound,
/// APQN does not support bind
NotSupported,
}
/// Query bind state for this APQN.
///
/// Returns a BindState enum as defined above or on failure
/// an error string. Does NOT print any error messages.
pub fn get_apqn_bind_state(card: u32, dom: u32) -> Result<BindState> {
let path = format!(
"{}/card{:02x}/{:02x}.{:04x}/se_bind",
PATH_SYS_DEVICES_AP, card, card, dom
);
let state_str = read_file_string(path, "se_bind attribute")?;
let state = state_str.trim();
match state {
"bound" => Ok(BindState::Bound),
"unbound" => Ok(BindState::Unbound),
"-" => Ok(BindState::NotSupported),
_ => Err(Error::UnknownBindState(state.to_string())),
}
}
/// Bind or unbind an APQN.
///
/// The action is determined by the BindState given in.
/// But of course only Bound and Unbound is supported - otherwise
/// this function panics!
/// The function actively loops over the bind state until
/// the requested bind state is reached or a timeout has
/// occurred (SYS_BUS_AP_BIND_TIMEOUT_MS).
/// On success () is returned, on failure an error string
/// is returned. Does NOT print any error messages.
/// # Panics
/// Panics if a desired bind state other than Bound or Unbound is given.
pub fn set_apqn_bind_state(card: u32, dom: u32, state: BindState) -> Result<()> {
let ctx = "bind APQN";
let path = format!(
"{}/card{:02x}/{:02x}.{:04x}/se_bind",
PATH_SYS_DEVICES_AP, card, card, dom
);
match state {
BindState::Bound => write_file(path, 1.to_string(), ctx),
BindState::Unbound => write_file(path, 0.to_string(), ctx),
_ => panic!("set_apqn_bind_state called with invalid BindState."),
}?;
let mut ms: u64 = 0;
loop {
thread::sleep(time::Duration::from_millis(SYS_BUS_AP_BIND_POLL_MS));
ms += SYS_BUS_AP_BIND_POLL_MS;
if ms >= SYS_BUS_AP_BIND_TIMEOUT_MS {
break Err(Error::Timeout(format!(
"setting APQN({card},{dom}) bind state"
)));
}
let newstate = get_apqn_bind_state(card, dom)?;
if newstate == state {
return Ok(());
}
}
}
/// Association state of an APQN
#[derive(Debug, PartialEq, Eq)]
pub enum AssocState {
/// Associated with index
Associated(u16),
/// Association pending
AssociationPending,
/// Not associated
Unassociated,
/// APQN does not support association
NotSupported,
}
/// Query association state for this APQN.
///
/// Returns an AssocState enum as defined above or on failure
/// an error string. Does NOT print any error messages.
pub fn get_apqn_associate_state(card: u32, dom: u32) -> Result<AssocState> {
let path = format!(
"{}/card{:02x}/{:02x}.{:04x}/se_associate",
PATH_SYS_DEVICES_AP, card, card, dom
);
let state_str = read_file_string(path, "se_associate attribute")?;
let state = state_str.trim();
match state.strip_prefix("associated ") {
Some(prefix) => Ok(AssocState::Associated(prefix.parse()?)),
_ => match state {
"association pending" => Ok(AssocState::AssociationPending),
"unassociated" => Ok(AssocState::Unassociated),
"-" => Ok(AssocState::NotSupported),
_ => Err(Error::UnknownAssocState(state.to_string())),
},
}
}
fn set_apqn_associate_state_associate(card: u32, dom: u32, idx: u16) -> Result<()> {
let path = format!(
"{}/card{:02x}/{:02x}.{:04x}/se_associate",
PATH_SYS_DEVICES_AP, card, card, dom
);
write_file(path, idx.to_string(), "associate APQN")?;
let mut ms: u64 = 0;
loop {
thread::sleep(time::Duration::from_millis(SYS_BUS_AP_ASSOC_POLL_MS));
ms += SYS_BUS_AP_ASSOC_POLL_MS;
if ms >= SYS_BUS_AP_ASSOC_TIMEOUT_MS {
break Err(Error::Timeout(format!(
"setting APQN({card},{dom}) association index {idx} state",
)));
}
match get_apqn_associate_state(card, dom)? {
AssocState::Associated(i) if i == idx => return Ok(()),
AssocState::Associated(i) => {
return Err(Error::WrongAssocState {
card,
domain: dom,
desired: idx,
actual: i,
})
}
_ => {}
}
}
}
fn set_apqn_associate_state_unbind(card: u32, dom: u32) -> Result<()> {
let bindpath = format!(
"{}/card{:02x}/{:02x}.{:04x}/se_bind",
PATH_SYS_DEVICES_AP, card, card, dom
);
write_file(bindpath, 0.to_string(), "unbind APQN")?;
let mut ms: u64 = 0;
loop {
thread::sleep(time::Duration::from_millis(SYS_BUS_AP_ASSOC_POLL_MS));
ms += SYS_BUS_AP_ASSOC_POLL_MS;
if ms >= SYS_BUS_AP_ASSOC_TIMEOUT_MS {
break Err(Error::Timeout(format!(
"setting APQN({card},{dom}) association unbind state",
)));
}
let newstate = get_apqn_associate_state(card, dom)?;
if newstate == AssocState::Unassociated {
return Ok(());
}
}
}
/// Associate or Unassociate an APQN.
///
/// The action is determined by the AssocState given in.
/// But of course only Associated and Unassociated is supported
/// otherwise this function panics!
/// The function actively loops over the association state until
/// the requested state is reached or a timeout has
/// occurred (SYS_BUS_AP_ASSOC_TIMEOUT_MS).
/// The unassociate is in fact a unbind. So the code triggers
/// an unbind and then loops over the sysfs se_associate until
/// "unassociated" is reached.
/// On success () is returned, on failure an error string
/// is returned. Does NOT print any error messages.
/// # Panics
/// Panics if a desired bind state other than Associated or
/// Unassociated is given.
pub fn set_apqn_associate_state(card: u32, dom: u32, state: AssocState) -> Result<()> {
match state {
AssocState::Associated(idx) => set_apqn_associate_state_associate(card, dom, idx),
AssocState::Unassociated => set_apqn_associate_state_unbind(card, dom),
_ => panic!("set_apqn_associate_state called with invalid AssocState."),
}
}

View File

@@ -4,6 +4,8 @@
use std::path::PathBuf;
use crate::uv::SecretId;
/// Result type for this crate
pub type Result<T, E = Error> = std::result::Result<T, E>;
@@ -70,6 +72,41 @@ pub enum Error {
#[error("The attestation request does not specify a measurement size or measurement data.")]
BinArcbNoMeasurement,
#[error(
"The secret with the ID {id} cannot be retrieved. The requested size is too large ({size})"
)]
InvalidRetrievableSecretType { id: SecretId, size: usize },
#[error("Unknown bind state '{0}'.")]
UnknownBindState(String),
#[error("Unknown association state '{0}'.")]
UnknownAssocState(String),
#[error(
"APQN({card:02x},{domain:04x}) is associated with {actual} but it should be {desired}."
)]
WrongAssocState {
card: u32,
domain: u32,
desired: u16,
actual: u16,
},
#[error("Timeout on {0}.")]
Timeout(String),
#[error(
"CCA card {0:02x} cannot be used with Secure Execution, as this combination is unsupported"
)]
CcaSeIncompatible(u32),
#[error("APQN({card:02x}{domain:04x}) is offline.")]
ApOffline { card: u32, domain: u32 },
#[error("Failure parsing {subject} '{content}'.")]
ParseError { subject: String, content: String },
// errors from other crates
#[error(transparent)]
Io(#[from] std::io::Error),

View File

@@ -2,6 +2,7 @@
//
// Copyright IBM Corp. 2023, 2024
#![doc = include_str!("../README.md")]
mod apdevice;
mod confidential;
mod error;
mod macros;
@@ -20,9 +21,11 @@ pub mod attest {
/// Miscellaneous functions and definitions
pub mod misc {
pub use crate::utils::pv_guest_bit_set;
pub use crate::utils::{create_file, open_file, read_exact_file, read_file, write_file};
pub use crate::utils::{decode_hex, parse_hex, to_u16, to_u32, try_parse_u128, try_parse_u64};
pub use crate::utils::{create_file, open_file};
pub use crate::utils::{decode_hex, encode_hex, parse_hex};
pub use crate::utils::{read, write};
pub use crate::utils::{read_exact_file, read_file, read_file_string, write_file};
pub use crate::utils::{to_u16, to_u32, try_parse_u128, try_parse_u64};
pub use crate::utils::{Flags, Lsb0Flags64, Msb0Flags64};
}
@@ -32,7 +35,9 @@ pub mod misc {
/// [`crate::uv::UvCmd`]
pub mod uv {
pub use crate::uvdevice::attest::AttestationCmd;
pub use crate::uvdevice::secret::{AddCmd, ListCmd, LockCmd};
pub use crate::uvdevice::retr_secret::RetrievableSecret;
pub use crate::uvdevice::retr_secret::{AesSizes, AesXtsSizes, EcCurves, HmacShaSizes};
pub use crate::uvdevice::secret::{AddCmd, ListCmd, LockCmd, RetrieveCmd};
pub use crate::uvdevice::secret_list::{ListableSecretType, SecretEntry, SecretId, SecretList};
pub use crate::uvdevice::{ConfigUid, UvCmd, UvDevice, UvDeviceInfo, UvFlags, UvcSuccess};
}
@@ -68,5 +73,29 @@ pub mod secret {
pub use crate::uvsecret::UserDataType;
}
/// Functionalities for the AP bus
pub mod ap {
pub use crate::apdevice::Apqn;
pub use crate::apdevice::RE_QUEUE_DIR;
pub use crate::apdevice::{get_apqn_bind_state, set_apqn_bind_state};
/// AP modes
pub mod apqn_mode {
pub use crate::apdevice::ApqnMode::{self, *};
}
/// AP info for each state
pub mod apqn_info {
pub use crate::apdevice::ApqnInfo::{self, *};
pub use crate::apdevice::{ApqnInfoAccel, ApqnInfoCca, ApqnInfoEp11};
}
/// AP bind states
pub mod bind_state {
pub use crate::apdevice::BindState::{self, *};
}
/// AP association states
pub mod assoc_state {
pub use crate::apdevice::AssocState::{self, *};
}
}
// Internal definitions/ imports
const PAGESIZE: usize = 0x1000;

View File

@@ -7,11 +7,11 @@ use std::{
path::Path,
};
use zerocopy::{AsBytes, BigEndian, FromBytes, FromZeroes, U64};
use zerocopy::{BigEndian, FromBytes, Immutable, IntoBytes, U64};
use crate::{
macros::{bail_spec, file_error},
Error, FileAccessErrorType, FileIoErrorType, Result,
Error, FileAccessErrorType, Result,
};
/// Trait that describes bitflags, represented by `T`.
@@ -34,7 +34,7 @@ pub trait Flags<T>: From<T> + for<'a> From<&'a T> {
///
/// Wraps an u64 to set/get individual bits
#[repr(C)]
#[derive(Debug, Clone, Copy, Default, AsBytes, FromZeroes, FromBytes)]
#[derive(Debug, Clone, Copy, Default, IntoBytes, FromBytes, Eq, PartialEq, Immutable)]
pub struct Msb0Flags64(U64<BigEndian>);
impl Flags<u64> for Msb0Flags64 {
#[track_caller]
@@ -82,7 +82,7 @@ impl From<Msb0Flags64> for u64 {
///
/// Wraps an u64 to set/get individual bits
#[repr(C)]
#[derive(Debug, Clone, Copy, Default, AsBytes, FromZeroes, FromBytes)]
#[derive(Debug, Clone, Copy, Default, IntoBytes, FromBytes, Immutable)]
pub struct Lsb0Flags64(U64<BigEndian>);
impl Flags<u64> for Lsb0Flags64 {
#[track_caller]
@@ -262,6 +262,17 @@ pub fn read_exact_file<P: AsRef<Path>, const COUNT: usize>(
Ok(buf)
}
/// Read content from a file as string and add context in case of an error
///
/// * `path` - Path to file
/// * `ctx` - Error context string in case of an error
///
/// # Errors
/// Passes through any kind of error `std::fs::read` produces
pub fn read_file_string<P: AsRef<Path>>(path: P, ctx: &str) -> Result<String> {
std::fs::read_to_string(&path).map_err(|e| file_error!(Read, ctx, path, e))
}
/// Read content from a file and add context in case of an error
///
/// * `path` - Path to file
@@ -284,12 +295,8 @@ pub fn read_file<P: AsRef<Path>>(path: P, ctx: &str) -> Result<Vec<u8>> {
/// Passes through any kind of error `std::fs::read` produces
pub fn read<R: Read, P: AsRef<Path>>(rd: &mut R, path: P, ctx: &str) -> Result<Vec<u8>> {
let mut buf = vec![];
rd.read_to_end(&mut buf).map_err(|e| Error::FileIo {
ty: FileIoErrorType::Write,
ctx: ctx.to_string(),
path: path.as_ref().to_path_buf(),
source: e,
})?;
rd.read_to_end(&mut buf)
.map_err(|e| file_error!(Read, ctx, path, e))?;
Ok(buf)
}
@@ -302,12 +309,7 @@ pub fn read<R: Read, P: AsRef<Path>>(rd: &mut R, path: P, ctx: &str) -> Result<V
/// # Errors
/// Passes through any kind of error `std::fs::write` produces
pub fn write_file<D: AsRef<[u8]>, P: AsRef<Path>>(path: P, data: D, ctx: &str) -> Result<()> {
std::fs::write(path.as_ref(), data.as_ref()).map_err(|e| Error::FileIo {
ty: FileIoErrorType::Write,
ctx: ctx.to_string(),
path: path.as_ref().to_path_buf(),
source: e,
})
std::fs::write(path.as_ref(), data.as_ref()).map_err(|e| file_error!(Write, ctx, path, e))
}
/// Write content to a [`std::io::Write`] and add context in case of an error
@@ -324,12 +326,8 @@ pub fn write<D: AsRef<[u8]>, P: AsRef<Path>, W: Write>(
path: P,
ctx: &str,
) -> Result<()> {
wr.write_all(data.as_ref()).map_err(|e| Error::FileIo {
ty: FileIoErrorType::Write,
ctx: ctx.to_string(),
path: path.as_ref().to_path_buf(),
source: e,
})
wr.write_all(data.as_ref())
.map_err(|e| file_error!(Write, ctx, path, e))
}
macro_rules! usize_to_ui {
@@ -355,6 +353,15 @@ usize_to_ui! {
#[doc = r"u16"]
=> u16, to_u16}
/// Converts the u8 slice into (lowercase) hexstring
pub fn encode_hex<S: AsRef<[u8]>>(s: S) -> String {
let slice = s.as_ref();
let string = String::with_capacity(2 * slice.len());
slice
.iter()
.fold(string, |acc, e| acc + &format!("{e:02x}"))
}
/// Converts the hexstring into a byte vector.
///
/// # Errors
@@ -504,6 +511,14 @@ mod tests {
fn lsb_flags_unset_panic() {
Lsb0Flags64::default().unset_bit(64)
}
#[test]
fn encode_hex() {
let arr = [0x12, 0x34, 0x56, 0xac, 0xbe, 0xf0];
let exp = "123456acbef0";
assert_eq!(super::encode_hex(arr), exp);
}
#[test]
fn parse_hex() {
let s = "123456acbef0";

View File

@@ -3,8 +3,8 @@
// Copyright IBM Corp. 2024
use crate::{request::MagicValue, Error};
use byteorder::{BigEndian, ByteOrder};
use zerocopy::U32;
use zerocopy::{BigEndian, ByteOrder};
/// The magic value used to identify an attestation request
///

View File

@@ -25,6 +25,7 @@ mod info;
mod test;
pub(crate) use ffi::uv_ioctl;
pub mod attest;
pub mod retr_secret;
pub mod secret;
pub mod secret_list;
@@ -58,11 +59,13 @@ fn ioctl_raw(raw_fd: RawFd, cmd: c_ulong, cb: &mut IoctlCb) -> Result<()> {
rc = ioctl(raw_fd, cmd, cb.as_ptr_mut());
}
// NOTE io::Error handles all errnos ioctl uses
let errno = std::io::Error::last_os_error();
debug!("ioctl resulted with {cb:?}");
match rc {
0 => Ok(()),
// NOTE io::Error handles all errnos ioctl uses
_ => Err(std::io::Error::last_os_error().into()),
_ => Err(errno.into()),
}
}
@@ -73,7 +76,7 @@ fn rc_fmt<C: UvCmd>(rc: u16, rrc: u16, cmd: &mut C) -> &'static str {
(0x0002, _) => Some("invalid UV command"),
(0x0005, _) => Some("request has an invalid size"),
(0x0030, _) => Some("home address space control bit has R-bit set to one. This may be due to a corrupted host or a guest kernel bug. STOP using this guest"),
(0x0031, _) => Some("adress translation exception. This may be due to a corrupted host or a guest kernel bug. STOP using this guest"),
(0x0031, _) => Some("address translation exception. This may be due to a corrupted host or a guest kernel bug. STOP using this guest"),
(0x0032, _) => Some("request contains virtual address translating to an invalid address. This may be due to a corrupted host or a guest kernel bug. STOP using this guest"),
(UvDevice::RC_MORE_DATA, _) => unreachable!("This is no Error!!!!"),
(UvDevice::RC_SUCCESS, _) => unreachable!("This is no Error!!!!"),
@@ -162,6 +165,16 @@ pub enum UvcSuccess {
RC_MORE_DATA = UvDevice::RC_MORE_DATA,
}
impl UvcSuccess {
/// Returns true if there is more data available
pub fn more_data(&self) -> bool {
match self {
Self::RC_SUCCESS => false,
Self::RC_MORE_DATA => true,
}
}
}
/// The `UvDevice` is a (virtual) device on s390 machines to send Ultravisor commands(UVCs) from
/// userspace.
///

View File

@@ -4,7 +4,7 @@
use super::{ffi, AttestationUserData, ConfigUid, UvCmd};
use crate::{Error, Result};
use std::ptr;
use zerocopy::{AsBytes, FromZeroes};
use zerocopy::{FromZeros, IntoBytes};
/// _Retrieve Attestation Measurement_ UVC
///
@@ -222,7 +222,7 @@ impl UvCmd for AttestationCmd {
}
fn data(&mut self) -> Option<&mut [u8]> {
Some(self.uvio_attest.as_bytes_mut())
Some(self.uvio_attest.as_mut_bytes())
}
}

View File

@@ -2,18 +2,24 @@
//
// Copyright IBM Corp. 2023
// This file is a rustified copy of linux/arch/s390/include/uapi/asm/uvdevice.h
// There might be things that are not needed here but nontheless defined in that header.
// Those two files should be in sync -> there might be unused/dead code.
//
// The `UVIO_IOCTL_*` and `UVIO_SUPP_*` macros
#![allow(dead_code)]
use std::mem::size_of;
use crate::{assert_size, static_assert};
use zerocopy::{AsBytes, FromBytes, FromZeroes};
use zerocopy::{FromBytes, IntoBytes};
pub const UVIO_ATT_ARCB_MAX_LEN: usize = 0x100000;
pub const UVIO_ATT_MEASUREMENT_MAX_LEN: usize = 0x8000;
pub const UVIO_ATT_ADDITIONAL_MAX_LEN: usize = 0x8000;
pub const UVIO_ADD_SECRET_MAX_LEN: usize = 0x100000;
#[allow(unused)]
// here for completeness
pub const UVIO_LIST_SECRETS_LEN: usize = 0x1000;
pub const UVIO_RETR_SECRET_MAX_LEN: usize = 0x2000;
// equal to ascii 'u'
pub const UVIO_TYPE_UVC: u8 = 117u8;
@@ -23,6 +29,7 @@ pub const UVIO_IOCTL_ATT_NR: u8 = 1;
pub const UVIO_IOCTL_ADD_SECRET_NR: u8 = 2;
pub const UVIO_IOCTL_LIST_SECRETS_NR: u8 = 3;
pub const UVIO_IOCTL_LOCK_SECRETS_NR: u8 = 4;
pub const UVIO_IOCTL_RETR_SECRET_NR: u8 = 5;
/// Uvdevice IOCTL control block
/// Programs can use this struct to communicate with the uvdevice via IOCTLs
@@ -58,7 +65,7 @@ assert_size!(uvio_ioctl_cb, 0x40);
/// Note that bit 0 (`UVIO_IOCTL_UVDEV_INFO_NR`) is always zero for `supp_uv_cmds`
/// as there is no corresponding UV-call.
#[repr(C)]
#[derive(Debug, Copy, Clone, AsBytes, FromZeroes, FromBytes)]
#[derive(Debug, Copy, Clone, IntoBytes, FromBytes)]
pub struct uvio_uvdev_info {
pub supp_uvio_cmds: u64,
pub supp_uv_cmds: u64,
@@ -88,7 +95,7 @@ pub const UVIO_ATT_UID_LEN: usize = 0x10;
///
/// All numbers are in big-endian!
#[repr(C)]
#[derive(Debug, AsBytes, FromZeroes, FromBytes)]
#[derive(Debug, IntoBytes, FromBytes)]
pub struct uvio_attest {
pub arcb_addr: u64, // in
pub meas_addr: u64, // out

View File

@@ -9,7 +9,7 @@ use crate::{
Result,
};
use std::fmt::Display;
use zerocopy::{AsBytes, FromZeroes};
use zerocopy::{FromZeros, IntoBytes};
/// Information of supported functions by the uvdevice
///
@@ -74,7 +74,7 @@ impl UvCmd for uvio_uvdev_info {
const UV_IOCTL_NR: u8 = ffi::UVIO_IOCTL_UVDEV_INFO_NR;
fn data(&mut self) -> Option<&mut [u8]> {
Some(self.as_bytes_mut())
Some(self.as_mut_bytes())
}
fn rc_fmt(&self, _: u16, _: u16) -> Option<&'static str> {

View File

@@ -0,0 +1,392 @@
// SPDX-License-Identifier: MIT
//
// Copyright IBM Corp. 2024
use crate::uv::{ListableSecretType, RetrieveCmd};
use serde::{Deserialize, Serialize, Serializer};
use std::fmt::Display;
/// Allowed sizes for AES keys
#[non_exhaustive]
#[derive(PartialEq, Eq, Debug)]
pub enum AesSizes {
/// 128 bit key
Bits128,
/// 192 bit key
Bits192,
/// 256 bit key
Bits256,
}
impl AesSizes {
/// Construct the key-size from the bit-size.
///
/// Returns [`None`] if the bit-size is not supported.
pub fn from_bits(bits: u32) -> Option<Self> {
match bits {
128 => Some(Self::Bits128),
192 => Some(Self::Bits192),
256 => Some(Self::Bits256),
_ => None,
}
}
/// Returns the bit-size for the key-type
const fn bit_size(&self) -> u32 {
match self {
Self::Bits128 => 128,
Self::Bits192 => 192,
Self::Bits256 => 256,
}
}
}
impl Display for AesSizes {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.bit_size())
}
}
/// Allowed sizes for AES-XTS keys
#[non_exhaustive]
#[derive(PartialEq, Eq, Debug)]
pub enum AesXtsSizes {
/// Two AES 128 bit keys
Bits128,
/// Two AES 256 bit keys
Bits256,
}
impl AesXtsSizes {
/// Construct the key-size from the bit-size.
///
/// It's a key containing two keys; bit-size is half the number of bits it has
/// Returns [`None`] if the bit-size is not supported.
pub fn from_bits(bits: u32) -> Option<Self> {
match bits {
128 => Some(Self::Bits128),
256 => Some(Self::Bits256),
_ => None,
}
}
/// Returns the bit-size for the key-type
///
/// It's a key containing two keys: bit-size is half the number of bits it has
const fn bit_size(&self) -> u32 {
match self {
Self::Bits128 => 128,
Self::Bits256 => 256,
}
}
}
impl Display for AesXtsSizes {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.bit_size())
}
}
/// Allowed sizes for HMAC-SHA keys
#[non_exhaustive]
#[derive(PartialEq, Eq, Debug)]
pub enum HmacShaSizes {
/// SHA 256 bit
Sha256,
/// SHA 512 bit
Sha512,
}
impl HmacShaSizes {
/// Construct the key-size from the sha-size.
///
/// FW expects maximum resistance keys (double the SHA size).
/// The `sha_size` is half of the number of bits in the key
/// Returns [`None`] if the `sha_size` is not supported.
pub fn from_sha_size(sha_size: u32) -> Option<Self> {
match sha_size {
256 => Some(Self::Sha256),
512 => Some(Self::Sha512),
_ => None,
}
}
/// Returns the sha-size for the key-type
///
/// FW expects maximum resistance keys (double the SHA size).
/// The `sha_size` is half of the number of bits in the key
const fn sha_size(&self) -> u32 {
match self {
Self::Sha256 => 256,
Self::Sha512 => 512,
}
}
}
impl Display for HmacShaSizes {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.sha_size())
}
}
/// Allowed curves for EC private keys
#[non_exhaustive]
#[derive(PartialEq, Eq, Debug)]
pub enum EcCurves {
/// secp256r1 or prime256v1 curve
Secp256R1,
/// secp384p1 curve
Secp384R1,
/// secp521r1 curve
Secp521R1,
/// ed25519 curve
Ed25519,
/// ed448 curve
Ed448,
}
impl EcCurves {
/// Returns the expected key-byte-size for this curve.
pub const fn exp_key_size(&self) -> usize {
match self {
Self::Secp256R1 => 32,
Self::Secp384R1 => 48,
Self::Secp521R1 => 80,
Self::Ed25519 => 32,
Self::Ed448 => 64,
}
}
}
// The names have to stay constant, otherwise the PEM contains invalid types
impl Display for EcCurves {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Secp256R1 => write!(f, "SECP256R1"),
Self::Secp384R1 => write!(f, "SECP384R1"),
Self::Secp521R1 => write!(f, "SECP521R1"),
Self::Ed25519 => write!(f, "ED25519"),
Self::Ed448 => write!(f, "ED448"),
}
}
}
/// Retrievable Secret types
#[non_exhaustive]
#[derive(PartialEq, Eq, Debug)]
pub enum RetrievableSecret {
/// Plain-text secret
PlainText,
/// Protected AES key
Aes(AesSizes),
/// Protected AES-XTS key
AesXts(AesXtsSizes),
/// Protected HMAC-SHA key
HmacSha(HmacShaSizes),
/// Protected EC-private key
Ec(EcCurves),
}
// The names have to stay constant, otherwise the PEM contains invalid/unknown types
impl Display for RetrievableSecret {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
// Alternate representation: Omit sizes/curves
if f.alternate() {
match self {
Self::PlainText => write!(f, "PLAINTEXT"),
Self::Aes(_) => write!(f, "AES-KEY"),
Self::AesXts(_) => write!(f, "AES-XTS-KEY"),
Self::HmacSha(_) => write!(f, "HMAC-SHA-KEY"),
Self::Ec(_) => write!(f, "EC-PRIVATE-KEY"),
}
} else {
match self {
Self::PlainText => write!(f, "PLAINTEXT"),
Self::Aes(s) => write!(f, "AES-{s}-KEY"),
Self::AesXts(s) => write!(f, "AES-XTS-{s}-KEY"),
Self::HmacSha(s) => write!(f, "HMAC-SHA-{s}-KEY"),
Self::Ec(c) => write!(f, "EC-{c}-PRIVATE-KEY"),
}
}
}
}
impl RetrievableSecret {
/// Report expected input types
pub fn expected(&self) -> String {
match self {
Self::PlainText => format!("less than {}", RetrieveCmd::MAX_SIZE),
Self::Aes(_) => "128, 192, or 256".to_string(),
Self::AesXts(_) => "128 or 256".to_string(),
Self::HmacSha(_) => "256 or 512".to_string(),
Self::Ec(_) => "secp256r1, secp384r1, secp521r1, ed25519, or ed448".to_string(),
}
}
}
impl From<&RetrievableSecret> for u16 {
fn from(value: &RetrievableSecret) -> Self {
match value {
RetrievableSecret::PlainText => ListableSecretType::PLAINTEXT,
RetrievableSecret::Aes(AesSizes::Bits128) => ListableSecretType::AES_128_KEY,
RetrievableSecret::Aes(AesSizes::Bits192) => ListableSecretType::AES_192_KEY,
RetrievableSecret::Aes(AesSizes::Bits256) => ListableSecretType::AES_256_KEY,
RetrievableSecret::AesXts(AesXtsSizes::Bits128) => ListableSecretType::AES_128_XTS_KEY,
RetrievableSecret::AesXts(AesXtsSizes::Bits256) => ListableSecretType::AES_256_XTS_KEY,
RetrievableSecret::HmacSha(HmacShaSizes::Sha256) => {
ListableSecretType::HMAC_SHA_256_KEY
}
RetrievableSecret::HmacSha(HmacShaSizes::Sha512) => {
ListableSecretType::HMAC_SHA_512_KEY
}
RetrievableSecret::Ec(EcCurves::Secp256R1) => ListableSecretType::ECDSA_P256_KEY,
RetrievableSecret::Ec(EcCurves::Secp384R1) => ListableSecretType::ECDSA_P384_KEY,
RetrievableSecret::Ec(EcCurves::Secp521R1) => ListableSecretType::ECDSA_P521_KEY,
RetrievableSecret::Ec(EcCurves::Ed25519) => ListableSecretType::ECDSA_ED25519_KEY,
RetrievableSecret::Ec(EcCurves::Ed448) => ListableSecretType::ECDSA_ED448_KEY,
}
}
}
// serializes to: <secret type nb> (String name)
impl Serialize for RetrievableSecret {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
let id: u16 = self.into();
serializer.serialize_str(&format!("{id} ({self})"))
}
}
/// deserializes from the secret type nb only
impl<'de> Deserialize<'de> for RetrievableSecret {
fn deserialize<D>(de: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
struct RetrSecretVisitor;
impl serde::de::Visitor<'_> for RetrSecretVisitor {
type Value = RetrievableSecret;
fn expecting(&self, fmt: &mut std::fmt::Formatter) -> std::fmt::Result {
fmt.write_str(
"a retrievable secret type: `<number> (String name)` number in [3,10]|[17,21]",
)
}
fn visit_str<E>(self, s: &str) -> Result<Self::Value, E>
where
E: serde::de::Error,
{
let (n, _) = s.split_once(' ').ok_or(serde::de::Error::invalid_value(
serde::de::Unexpected::Str(s),
&self,
))?;
let id: u16 = n.parse().map_err(|_| {
serde::de::Error::invalid_value(serde::de::Unexpected::Str(n), &self)
})?;
let listable: ListableSecretType = id.into();
match listable {
ListableSecretType::Retrievable(r) => Ok(r),
_ => Err(serde::de::Error::invalid_value(
serde::de::Unexpected::Unsigned(id.into()),
&self,
)),
}
}
}
de.deserialize_str(RetrSecretVisitor)
}
}
#[cfg(test)]
mod test {
use serde_test::{assert_tokens, Token};
use super::*;
#[test]
fn retr_serde_plain() {
let retr = RetrievableSecret::PlainText;
assert_tokens(&retr, &[Token::Str("3 (PLAINTEXT)")]);
}
#[test]
fn retr_serde_aes() {
let retr = RetrievableSecret::Aes(AesSizes::Bits192);
assert_tokens(&retr, &[Token::Str("5 (AES-192-KEY)")]);
}
#[test]
fn retr_serde_aes_xts() {
let retr = RetrievableSecret::AesXts(AesXtsSizes::Bits128);
assert_tokens(&retr, &[Token::Str("7 (AES-XTS-128-KEY)")]);
}
#[test]
fn retr_serde_hmac() {
let retr = RetrievableSecret::HmacSha(HmacShaSizes::Sha256);
assert_tokens(&retr, &[Token::Str("9 (HMAC-SHA-256-KEY)")]);
}
#[test]
fn retr_serde_es() {
let retr = RetrievableSecret::Ec(EcCurves::Secp521R1);
assert_tokens(&retr, &[Token::Str("19 (EC-SECP521R1-PRIVATE-KEY)")]);
}
// Ensure that the string representation of the retrievable types stay constant, or PEM will have
// different, incompatible types
#[test]
fn stable_type_names() {
assert_eq!("PLAINTEXT", RetrievableSecret::PlainText.to_string());
assert_eq!(
"AES-128-KEY",
RetrievableSecret::Aes(AesSizes::Bits128).to_string()
);
assert_eq!(
"AES-192-KEY",
RetrievableSecret::Aes(AesSizes::Bits192).to_string()
);
assert_eq!(
"AES-256-KEY",
RetrievableSecret::Aes(AesSizes::Bits256).to_string()
);
assert_eq!(
"AES-XTS-128-KEY",
RetrievableSecret::AesXts(AesXtsSizes::Bits128).to_string()
);
assert_eq!(
"AES-XTS-256-KEY",
RetrievableSecret::AesXts(AesXtsSizes::Bits256).to_string()
);
assert_eq!(
"HMAC-SHA-256-KEY",
RetrievableSecret::HmacSha(HmacShaSizes::Sha256).to_string()
);
assert_eq!(
"HMAC-SHA-512-KEY",
RetrievableSecret::HmacSha(HmacShaSizes::Sha512).to_string()
);
assert_eq!(
"EC-SECP256R1-PRIVATE-KEY",
RetrievableSecret::Ec(EcCurves::Secp256R1).to_string()
);
assert_eq!(
"EC-SECP384R1-PRIVATE-KEY",
RetrievableSecret::Ec(EcCurves::Secp384R1).to_string()
);
assert_eq!(
"EC-SECP521R1-PRIVATE-KEY",
RetrievableSecret::Ec(EcCurves::Secp521R1).to_string()
);
assert_eq!(
"EC-ED25519-PRIVATE-KEY",
RetrievableSecret::Ec(EcCurves::Ed25519).to_string()
);
assert_eq!(
"EC-ED448-PRIVATE-KEY",
RetrievableSecret::Ec(EcCurves::Ed448).to_string()
);
}
}

View File

@@ -3,8 +3,15 @@
// Copyright IBM Corp. 2023
use super::ffi;
use crate::{request::MagicValue, uv::UvCmd, uvsecret::AddSecretMagic, Error, Result, PAGESIZE};
use std::io::Read;
use crate::{
request::{Confidential, MagicValue},
uv::{SecretEntry, UvCmd},
uvsecret::AddSecretMagic,
Error, Result, PAGESIZE,
};
use log::debug;
use std::{io::Read, mem::size_of_val};
use zerocopy::IntoBytes;
/// _List Secrets_ Ultravisor command.
///
@@ -17,6 +24,17 @@ impl ListCmd {
Self(vec![0; size])
}
/// Create a new list secrets command with `pages` capacity.
///
/// * `pages` - number pf pages to allocate for this IOCTL
///
/// # Panic
/// This function will trigger a panic if the allocation size is larger than [`usize::MAX`].
/// Very likely an OOM situation occurs way before this!
pub fn with_pages(pages: usize) -> Self {
Self::with_size(pages * PAGESIZE)
}
/// Create a new list secrets command with a one page capacity
pub fn new() -> Self {
Self::with_size(PAGESIZE)
@@ -116,3 +134,89 @@ impl UvCmd for LockCmd {
}
}
}
/// Retrieve a secret value from UV store
#[derive(Debug)]
pub struct RetrieveCmd {
entry: SecretEntry,
key: Confidential<Vec<u8>>,
}
impl RetrieveCmd {
/// Maximum size of a retrieved key (=2 pages)
pub const MAX_SIZE: usize = ffi::UVIO_RETR_SECRET_MAX_LEN;
/// Create a retrieve-secret UVC from a [`SecretEntry`].
///
/// This uses the index of the secret entry for the UVC.
pub fn from_entry(entry: SecretEntry) -> Result<Self> {
entry.try_into()
}
/// Transform a [`RetrieveCmd`] into a key-vector.
///
/// Only makes sense to call after a successful UVC execution.
pub fn into_key(self) -> Confidential<Vec<u8>> {
self.key
}
/// Get the secret entry
///
/// Get the secret entry that is used as metadata to retrieve the secret
pub fn meta_data(&self) -> &SecretEntry {
&self.entry
}
}
impl TryFrom<SecretEntry> for RetrieveCmd {
type Error = Error;
fn try_from(entry: SecretEntry) -> Result<Self> {
let len = entry.secret_size() as usize;
// Next to impossible if the secret entry is a valid response from UV
if len > Self::MAX_SIZE {
return Err(Error::InvalidRetrievableSecretType {
id: entry.secret_id().to_owned(),
size: len,
});
}
// Ensure that an u16 fits into the buffer.
let size = std::cmp::max(size_of_val(&entry.index()), len);
debug!("Create a buf with {} elements", size);
let mut buf = vec![0; size];
// The IOCTL expects the secret index in the first two bytes of the buffer. They will be
// overwritten in the response
entry.index_be().write_to_prefix(&mut buf).unwrap();
Ok(Self {
entry,
key: buf.into(),
})
}
}
impl UvCmd for RetrieveCmd {
const UV_IOCTL_NR: u8 = ffi::UVIO_IOCTL_RETR_SECRET_NR;
fn rc_fmt(&self, rc: u16, _: u16) -> Option<&'static str> {
match rc {
// should not appear (TM), software creates request from a list item
0x0009 => Some("the allocated buffer is to small to store the secret"),
// should not appear (TM), kernel allocates the memory
0x0102 => {
Some("access exception recognized when accessing retrieved secret storage area")
}
// should not appear (TM), software creates request from a list item
0x010f => Some("the Secret Store is empty"),
// should not appear (TM), software creates request from a list item
0x0110 => Some("the Secret Store does not contain a secret with the specified index"),
0x0111 => Some("the secret is not retrievable"),
_ => None,
}
}
fn data(&mut self) -> Option<&mut [u8]> {
Some(self.key.value_mut())
}
}

View File

@@ -2,23 +2,30 @@
//
// Copyright IBM Corp. 2024
use crate::assert_size;
use crate::{misc::to_u16, uv::ListCmd, uvdevice::UvCmd, Error, Result};
use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt};
use crate::{
assert_size,
uv::{AesSizes, AesXtsSizes, EcCurves, HmacShaSizes, ListCmd, RetrievableSecret},
uvdevice::UvCmd,
Error, Result,
};
use serde::{Deserialize, Serialize, Serializer};
use std::{
fmt::Display,
cmp::min,
ffi::CStr,
fmt::{Debug, Display, LowerHex, UpperHex},
io::{Cursor, Read, Seek, Write},
mem::size_of,
slice::Iter,
vec::IntoIter,
};
use zerocopy::{AsBytes, FromBytes, FromZeroes, U16, U32};
use zerocopy::{BigEndian, ByteOrder};
use zerocopy::{FromBytes, Immutable, IntoBytes, KnownLayout, U16, U32};
/// The 32 byte long ID of an UV secret
///
/// (de)serializes itself in/from a hex-string
#[repr(C)]
#[derive(PartialEq, Eq, AsBytes, FromZeroes, FromBytes, Debug, Clone)]
#[derive(PartialEq, Eq, IntoBytes, FromBytes, Debug, Clone, Default, Immutable, KnownLayout)]
pub struct SecretId([u8; Self::ID_SIZE]);
assert_size!(SecretId, SecretId::ID_SIZE);
@@ -26,10 +33,37 @@ impl SecretId {
/// Size in bytes of the [`SecretId`]
pub const ID_SIZE: usize = 32;
/// Create a [`SecretId`] forom a buffer.
/// Create a [`SecretId`] from a buffer.
pub fn from(buf: [u8; Self::ID_SIZE]) -> Self {
buf.into()
}
/// Create a Id from a string
///
/// Uses the first 31 bytes from `name` as id
/// Does not hash anything. Byte 32 is the NUL char
pub fn from_string(name: &str) -> Self {
let len = min(name.len(), Self::ID_SIZE - 1);
let mut res = Self::default();
res.0[0..len].copy_from_slice(&name.as_bytes()[0..len]);
res
}
/// Tries to represent the Id as printable-ASCII string
pub fn as_ascii(&self) -> Option<&str> {
if let Ok(t) = CStr::from_bytes_until_nul(&self.0) {
if let Ok(t) = t.to_str() {
if !t.is_empty()
&& t.chars()
.all(|c| c.is_ascii_whitespace() | c.is_ascii_graphic())
&& self.0[t.len()..].iter().all(|b| *b == 0)
{
return Some(t);
}
}
};
None
}
}
impl Serialize for SecretId {
@@ -37,8 +71,8 @@ impl Serialize for SecretId {
where
S: Serializer,
{
// calls Display at one point
ser.serialize_str(&self.to_string())
// calls LowerHex at one point
ser.serialize_str(&format!("{self:#x}"))
}
}
@@ -51,12 +85,36 @@ impl<'de> Deserialize<'de> for SecretId {
}
}
impl UpperHex for SecretId {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
if f.alternate() {
write!(f, "0x")?;
}
for b in self.0 {
write!(f, "{b:02X}")?;
}
Ok(())
}
}
impl LowerHex for SecretId {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
if f.alternate() {
write!(f, "0x")?;
}
for b in self.0 {
write!(f, "{b:02x}")?;
}
Ok(())
}
}
impl Display for SecretId {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let mut s = String::with_capacity(32 * 2 + 2);
s.push_str("0x");
let s = self.0.iter().fold(s, |acc, e| acc + &format!("{e:02x}"));
write!(f, "{s}")
if let Some(s) = self.as_ascii() {
write!(f, "{s} | ")?;
}
write!(f, "{self:#x}")
}
}
@@ -74,7 +132,7 @@ impl AsRef<[u8]> for SecretId {
/// A secret in a [`SecretList`]
#[repr(C)]
#[derive(Debug, PartialEq, Eq, AsBytes, FromZeroes, FromBytes, Serialize)]
#[derive(Debug, Clone, PartialEq, Eq, IntoBytes, FromBytes, Serialize, Immutable)]
pub struct SecretEntry {
#[serde(serialize_with = "ser_u16")]
index: U16<BigEndian>,
@@ -94,11 +152,11 @@ impl SecretEntry {
/// Create a new entry for a [`SecretList`].
///
/// The content of this entry will very likely not represent the status of the guest in the
/// Ultravisor. Use of [`SecretList::decode`] in any non-test environments is encuraged.
/// Ultravisor. Use of [`SecretList::decode`] in any non-test environments is encouraged.
pub fn new(index: u16, stype: ListableSecretType, id: SecretId, secret_len: u32) -> Self {
Self {
index: index.into(),
stype: stype.into(),
stype: U16::new(stype.into()),
len: secret_len.into(),
res_8: 0,
id,
@@ -110,9 +168,14 @@ impl SecretEntry {
self.index.get()
}
/// Returns the secret type of this [`SecretEntry`].
/// Returns the index of this [`SecretEntry`] in BE.
pub(crate) fn index_be(&self) -> &U16<BigEndian> {
&self.index
}
/// Returns the secret type of this [`SecretEntry`]
pub fn stype(&self) -> ListableSecretType {
self.stype.into()
self.stype.get().into()
}
/// Returns a reference to the id of this [`SecretEntry`].
@@ -120,33 +183,74 @@ impl SecretEntry {
/// The slice is guaranteed to be 32 bytes long.
/// ```rust
/// # use s390_pv_core::uv::SecretEntry;
/// # use zerocopy::FromZeroes;
/// # use zerocopy::FromZeros;
/// # let secr = SecretEntry::new_zeroed();
/// # assert_eq!(secr.id().len(), 32);
/// ```
pub fn id(&self) -> &[u8] {
self.id.as_ref()
}
/// Get the id as [`SecretId`] reference
pub(crate) fn secret_id(&self) -> &SecretId {
&self.id
}
/// Returns the secret size of this [`SecretEntry`].
pub fn secret_size(&self) -> u32 {
self.len.get()
}
}
impl Display for SecretEntry {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let stype: ListableSecretType = self.stype.into();
let stype: ListableSecretType = self.stype.get().into();
writeln!(f, "{} {}:", self.index, stype)?;
write!(f, " ")?;
for b in self.id.as_ref() {
write!(f, "{b:02x}")?;
}
Ok(())
write!(f, " {}", self.id)
}
}
#[repr(C)]
#[derive(
Debug, FromBytes, IntoBytes, Clone, PartialEq, Eq, Default, Serialize, Immutable, KnownLayout,
)]
struct SecretListHdr {
#[serde(skip)]
num_secrets_stored: U16<BigEndian>,
#[serde(serialize_with = "ser_u16")]
total_num_secrets: U16<BigEndian>,
#[serde(skip)]
next_secret_idx: U16<BigEndian>,
#[serde(skip)]
reserved_06: u16,
#[serde(skip)]
reserved_08: u64,
}
impl SecretListHdr {
fn new(num_secrets_stored: u16, total_num_secrets: u16, next_secret_idx: u16) -> Self {
Self {
num_secrets_stored: num_secrets_stored.into(),
total_num_secrets: total_num_secrets.into(),
next_secret_idx: next_secret_idx.into(),
reserved_06: 0,
reserved_08: 0,
}
}
}
assert_size!(SecretListHdr, 16);
/// List of secrets used to parse the [`crate::uv::ListCmd`] result.
///
/// The list should not hold more than 0xffffffff elements
#[derive(Debug, PartialEq, Eq, Serialize)]
/// The list should ONLY be created from an UV-Call result using either:
/// - [`TryInto::try_into`] from [`ListCmd`]
/// - [`SecretList::decode`]
/// Any other ways can create invalid lists that do not represent the UV secret store.
/// The list must not hold more than [`u32::MAX`] elements
#[derive(Debug, PartialEq, Eq, Serialize, Default)]
pub struct SecretList {
total_num_secrets: usize,
#[serde(flatten)]
hdr: SecretListHdr,
secrets: Vec<SecretEntry>,
}
@@ -182,10 +286,14 @@ impl SecretList {
/// The content of this list will very likely not represent the status of the guest in the
/// Ultravisor. Use of [`SecretList::decode`] in any non-test environments is encuraged.
pub fn new(total_num_secrets: u16, secrets: Vec<SecretEntry>) -> Self {
Self {
total_num_secrets: total_num_secrets as usize,
Self::new_with_hdr(
SecretListHdr::new(total_num_secrets, total_num_secrets, 0),
secrets,
}
)
}
fn new_with_hdr(hdr: SecretListHdr, secrets: Vec<SecretEntry>) -> Self {
Self { hdr, secrets }
}
/// Returns an iterator over the slice.
@@ -209,19 +317,18 @@ impl SecretList {
///
/// This number may be not equal to the provided number of [`SecretEntry`]
pub fn total_num_secrets(&self) -> usize {
self.total_num_secrets
self.hdr.total_num_secrets.get() as usize
}
/// Find the first [`SecretEntry`] that has the provided [`SecretId`]
pub fn find(&self, id: &SecretId) -> Option<SecretEntry> {
self.iter().find(|e| e.id() == id.as_ref()).cloned()
}
/// Encodes the list in the same binary format the UV would do
pub fn encode<T: Write>(&self, w: &mut T) -> Result<()> {
let num_s = to_u16(self.secrets.len()).ok_or(Error::ManySecrets)?;
w.write_u16::<BigEndian>(num_s)?;
w.write_u16::<BigEndian>(
self.total_num_secrets
.try_into()
.map_err(|_| Error::ManySecrets)?,
)?;
w.write_all(&[0u8; 12])?;
let hdr = self.hdr.as_bytes();
w.write_all(hdr)?;
for secret in &self.secrets {
w.write_all(secret.as_bytes())?;
}
@@ -230,19 +337,20 @@ impl SecretList {
/// Decodes the list from the binary format of the UV into this internal representation
pub fn decode<R: Read + Seek>(r: &mut R) -> std::io::Result<Self> {
let num_s = r.read_u16::<BigEndian>()?;
let total_num_secrets = r.read_u16::<BigEndian>()? as usize;
let mut v: Vec<SecretEntry> = Vec::with_capacity(num_s as usize);
r.seek(std::io::SeekFrom::Current(12))?; // skip reserved bytes
let mut buf = [0u8; size_of::<SecretListHdr>()];
r.read_exact(&mut buf)?;
let hdr = SecretListHdr::ref_from_bytes(&buf).unwrap();
let mut buf = [0u8; SecretEntry::STRUCT_SIZE];
for _ in 0..num_s {
let mut v = Vec::with_capacity(hdr.num_secrets_stored.get() as usize);
for _ in 0..hdr.num_secrets_stored.get() {
r.read_exact(&mut buf)?;
// cannot fail. buffer has the same size as the secret entry
let secr = SecretEntry::read_from(buf.as_slice()).unwrap();
let secr = SecretEntry::read_from_bytes(buf.as_slice()).unwrap();
v.push(secr);
}
Ok(Self {
total_num_secrets,
hdr: hdr.clone(),
secrets: v,
})
}
@@ -258,7 +366,7 @@ impl TryFrom<ListCmd> for SecretList {
impl Display for SecretList {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
writeln!(f, "Total number of secrets: {}", self.total_num_secrets)?;
writeln!(f, "Total number of secrets: {}", self.total_num_secrets())?;
if !self.secrets.is_empty() {
writeln!(f)?;
}
@@ -283,51 +391,119 @@ fn ser_u16<S: Serializer>(v: &U16<BigEndian>, ser: S) -> Result<S::Ok, S::Error>
pub enum ListableSecretType {
/// Association Secret
Association,
/// Retrievable key
Retrievable(RetrievableSecret),
/// Invalid secret type, that should never appear in a list
///
/// 0 is reserved
/// 1 is Null secret, with no id and not listable
/// 1 is Null secret, with no id and not list-able
/// 21 is Update CCK secret, with no id and not list-able
Invalid(u16),
/// Unknown secret type
Unknown(u16),
}
impl ListableSecretType {
/// UV type id for an association secret
pub const ASSOCIATION: u16 = 0x0002;
/// UV type id for a null secret
pub const NULL: u16 = 0x0001;
const RESERVED_0: u16 = 0x0000;
/// UV secret-type id for a null secret
pub const NULL: u16 = 0x0001;
/// UV secret-type id for an association secret
pub const ASSOCIATION: u16 = 0x0002;
/// UV secret-type id for a plain text secret
pub const PLAINTEXT: u16 = 0x0003;
/// UV secret-type id for an aes-128-key secret
pub const AES_128_KEY: u16 = 0x0004;
/// UV secret-type id for an aes-192-key secret
pub const AES_192_KEY: u16 = 0x0005;
/// UV secret-type id for an aes-256-key secret
pub const AES_256_KEY: u16 = 0x0006;
/// UV secret-type id for an aes-xts-128-key secret
pub const AES_128_XTS_KEY: u16 = 0x0007;
/// UV secret-type id for an aes-xts-256-key secret
pub const AES_256_XTS_KEY: u16 = 0x0008;
/// UV secret-type id for an hmac-sha-256-key secret
pub const HMAC_SHA_256_KEY: u16 = 0x0009;
/// UV secret-type id for an hmac-sha-512-key secret
pub const HMAC_SHA_512_KEY: u16 = 0x000a;
// 0x000b - 0x0010 reserved
/// UV secret-type id for an ecdsa-p256-private-key secret
pub const ECDSA_P256_KEY: u16 = 0x0011;
/// UV secret-type id for an ecdsa-p384-private-key secret
pub const ECDSA_P384_KEY: u16 = 0x0012;
/// UV secret-type id for an ecdsa-p521-private-key secret
pub const ECDSA_P521_KEY: u16 = 0x0013;
/// UV secret-type id for an ed25519-private-key secret
pub const ECDSA_ED25519_KEY: u16 = 0x0014;
/// UV secret-type id for an ed448-private-key secret
pub const ECDSA_ED448_KEY: u16 = 0x0015;
/// UV secret-type id for a new customer communication key
pub const UPDATE_CCK: u16 = 0x0016;
}
impl Display for ListableSecretType {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Association => write!(f, "Association"),
Self::Invalid(n) => write!(f, "Invalid({n})"),
Self::Unknown(n) => write!(f, "Unknown({n})"),
Self::Invalid(n) => write!(f, "Invalid(0x{n:04x})"),
Self::Unknown(n) => write!(f, "Unknown(0x{n:04x})"),
Self::Retrievable(r) => write!(f, "{r}"),
}
}
}
impl From<U16<BigEndian>> for ListableSecretType {
fn from(value: U16<BigEndian>) -> Self {
match value.get() {
impl<O: ByteOrder> From<U16<O>> for ListableSecretType {
fn from(value: U16<O>) -> Self {
value.get().into()
}
}
impl From<u16> for ListableSecretType {
fn from(value: u16) -> Self {
match value {
Self::RESERVED_0 => Self::Invalid(Self::RESERVED_0),
Self::NULL => Self::Invalid(Self::NULL),
Self::ASSOCIATION => Self::Association,
Self::PLAINTEXT => Self::Retrievable(RetrievableSecret::PlainText),
Self::AES_128_KEY => Self::Retrievable(RetrievableSecret::Aes(AesSizes::Bits128)),
Self::AES_192_KEY => Self::Retrievable(RetrievableSecret::Aes(AesSizes::Bits192)),
Self::AES_256_KEY => Self::Retrievable(RetrievableSecret::Aes(AesSizes::Bits256)),
Self::AES_128_XTS_KEY => {
Self::Retrievable(RetrievableSecret::AesXts(AesXtsSizes::Bits128))
}
Self::AES_256_XTS_KEY => {
Self::Retrievable(RetrievableSecret::AesXts(AesXtsSizes::Bits256))
}
Self::HMAC_SHA_256_KEY => {
Self::Retrievable(RetrievableSecret::HmacSha(HmacShaSizes::Sha256))
}
Self::HMAC_SHA_512_KEY => {
Self::Retrievable(RetrievableSecret::HmacSha(HmacShaSizes::Sha512))
}
Self::ECDSA_P256_KEY => Self::Retrievable(RetrievableSecret::Ec(EcCurves::Secp256R1)),
Self::ECDSA_P384_KEY => Self::Retrievable(RetrievableSecret::Ec(EcCurves::Secp384R1)),
Self::ECDSA_P521_KEY => Self::Retrievable(RetrievableSecret::Ec(EcCurves::Secp521R1)),
Self::ECDSA_ED25519_KEY => Self::Retrievable(RetrievableSecret::Ec(EcCurves::Ed25519)),
Self::ECDSA_ED448_KEY => Self::Retrievable(RetrievableSecret::Ec(EcCurves::Ed448)),
Self::UPDATE_CCK => Self::Invalid(Self::UPDATE_CCK),
n => Self::Unknown(n),
}
}
}
impl From<ListableSecretType> for U16<BigEndian> {
impl<O: ByteOrder> From<ListableSecretType> for U16<O> {
fn from(value: ListableSecretType) -> Self {
Self::new(value.into())
}
}
impl From<ListableSecretType> for u16 {
fn from(value: ListableSecretType) -> Self {
match value {
ListableSecretType::Association => ListableSecretType::ASSOCIATION,
ListableSecretType::Invalid(n) | ListableSecretType::Unknown(n) => n,
ListableSecretType::Retrievable(r) => (&r).into(),
}
.into()
}
}
@@ -341,15 +517,18 @@ where
type Value = [u8; SecretId::ID_SIZE];
fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
formatter.write_str("a `32 bytes long hexstring` prepended with 0x")
formatter.write_str("a `32 bytes (=64 character) long hexstring` prepended with 0x")
}
fn visit_str<E>(self, s: &str) -> Result<Self::Value, E>
where
E: serde::de::Error,
{
if s.len() != SecretId::ID_SIZE * 2 + 2 {
return Err(serde::de::Error::invalid_length(s.len(), &self));
if s.len() != SecretId::ID_SIZE * 2 + "0x".len() {
return Err(serde::de::Error::invalid_length(
s.len().saturating_sub("0x".len()),
&self,
));
}
let nb = s.strip_prefix("0x").ok_or_else(|| {
serde::de::Error::invalid_value(serde::de::Unexpected::Str(s), &self)
@@ -366,10 +545,12 @@ where
#[cfg(test)]
mod test {
use std::io::{BufReader, BufWriter, Cursor};
use serde_test::{assert_ser_tokens, assert_tokens, Token};
use zerocopy::FromZeros;
use super::*;
use std::io::{BufReader, BufWriter, Cursor};
#[test]
fn dump_secret_entry() {
@@ -398,8 +579,8 @@ mod test {
let buf = [
0x00u8, 0x01, // num secr stored
0x01, 0x12, // total num secrets
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, // reserved
0x01, 0x01, // next valid idx
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // reserved
// secret
0x00, 0x01, 0x00, 0x02, // idx + type
0x00, 0x00, 0x00, 0x20, // len
@@ -410,16 +591,16 @@ mod test {
0x00, 0x00, 0x00, 0x00,
];
let exp = SecretList {
total_num_secrets: 0x112,
secrets: vec![SecretEntry {
let exp = SecretList::new_with_hdr(
SecretListHdr::new(0x001, 0x112, 0x101),
vec![SecretEntry {
index: 1.into(),
stype: 2.into(),
len: 32.into(),
res_8: 0,
id: SecretId::from([0; 32]),
}],
};
);
let mut br = BufReader::new(Cursor::new(buf));
let sl = SecretList::decode(&mut br).unwrap();
@@ -431,8 +612,8 @@ mod test {
const EXP: &[u8] = &[
0x00, 0x01, // num secr stored
0x01, 0x12, // total num secrets
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, // reserved
0x01, 0x01, // next valid idx
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // reserved
// secret
0x00, 0x01, 0x00, 0x02, // idx + type
0x00, 0x00, 0x00, 0x20, // len
@@ -443,16 +624,16 @@ mod test {
0x00, 0x00, 0x00, 0x00,
];
let sl = SecretList {
total_num_secrets: 0x112,
secrets: vec![SecretEntry {
let sl = SecretList::new_with_hdr(
SecretListHdr::new(0x001, 0x112, 0x101),
vec![SecretEntry {
index: 1.into(),
stype: 2.into(),
len: 32.into(),
res_8: 0,
id: SecretId::from([0; 32]),
}],
};
);
let mut buf = [0u8; 0x40];
{
@@ -501,4 +682,120 @@ mod test {
)],
)
}
#[test]
fn secret_list_ser() {
let list = SecretList::new_with_hdr(
SecretListHdr::new(0x001, 0x112, 0x101),
vec![SecretEntry {
index: 1.into(),
stype: 2.into(),
len: 32.into(),
res_8: 0,
id: SecretId::from([0; 32]),
}],
);
assert_ser_tokens(
&list,
&[
Token::Map { len: None },
Token::String("total_num_secrets"),
Token::U16(0x112),
Token::String("secrets"),
Token::Seq { len: Some(1) },
Token::Struct {
name: "SecretEntry",
len: (4),
},
Token::String("index"),
Token::U16(1),
Token::String("stype"),
Token::U16(2),
Token::String("len"),
Token::U32(32),
Token::String("id"),
Token::String("0x0000000000000000000000000000000000000000000000000000000000000000"),
Token::StructEnd,
Token::SeqEnd,
Token::MapEnd,
],
)
}
#[test]
fn secret_id_display() {
let text = "Fancy secret ID";
let id = SecretId::from_string(text);
let exp =
"Fancy secret ID | 0x46616e6379207365637265742049440000000000000000000000000000000000";
assert_eq!(id.to_string(), exp);
}
#[test]
fn secret_id_long_name() {
let text = "the most fanciest secret ID you ever seen in the time the universe exists";
let id = SecretId::from_string(text);
let exp =
"the most fanciest secret ID you | 0x746865206d6f73742066616e63696573742073656372657420494420796f7500";
assert_eq!(id.to_string(), exp);
}
#[test]
fn secret_id_no_ascii_name() {
let text = [0; 32];
let id = SecretId::from(text);
let exp = "0x0000000000000000000000000000000000000000000000000000000000000000";
assert_eq!(id.to_string(), exp);
}
#[test]
fn secret_id_no_ascii_name2() {
let text = [
0x25, 0x55, 3, 4, 50, 0, 6, 0, 8, 0, 0, 0, 0, 0, 0, 0, 90, 0, 0xa, 0, 0, 0, 0, 0xf, 0,
0, 0, 0, 0, 0, 0, 0,
];
let id = SecretId::from(text);
assert_eq!(id.as_ascii(), None);
}
#[test]
fn secret_id_no_ascii_name3() {
let text = [
0x25, 0x55, 0, 4, 50, 0, 6, 0, 8, 0, 0, 0, 0, 0, 0, 0, 90, 0, 0xa, 0, 0, 0, 0, 0xf, 0,
0, 0, 0, 0, 0, 0, 0,
];
let id = SecretId::from(text);
assert_eq!(id.as_ascii(), None);
}
#[test]
fn secret_id_hex() {
let id_str = "Nice Test 123";
let id = SecretId::from_string(id_str);
let s = format!("{id:#x}");
assert_eq!(
s,
"0x4e69636520546573742031323300000000000000000000000000000000000000"
);
let s = format!("{id:x}");
assert_eq!(
s,
"4e69636520546573742031323300000000000000000000000000000000000000"
);
let s = format!("{id:#X}");
assert_eq!(
s,
"0x4E69636520546573742031323300000000000000000000000000000000000000"
);
let s = format!("{id:X}");
assert_eq!(
s,
"4E69636520546573742031323300000000000000000000000000000000000000"
);
}
}

View File

@@ -7,9 +7,9 @@ use crate::{
request::{MagicValue, RequestMagic},
Error, Result,
};
use byteorder::{BigEndian, ByteOrder};
use byteorder::ByteOrder;
use std::{fmt::Display, mem::size_of};
use zerocopy::{AsBytes, U16};
use zerocopy::{BigEndian, Immutable, IntoBytes, U16};
/// The magic value used to identify an `AddSecretRequest`.
///
@@ -25,7 +25,7 @@ use zerocopy::{AsBytes, U16};
/// # }
/// ```
#[repr(C)]
#[derive(Debug, Clone, Copy, PartialEq, Eq, AsBytes)]
#[derive(Debug, Clone, Copy, PartialEq, Eq, IntoBytes, Immutable)]
pub struct AddSecretMagic {
magic: [u8; 6], // [0x61, 0x73, 0x72, 0x63, 0x62, 0x4D]
kind: U16<BigEndian>,
@@ -56,7 +56,7 @@ impl AddSecretMagic {
}
// Panic: Will not panic, bytes is at least 8 elements long
let kind = BigEndian::read_u16(&bytes[6..8]);
let kind = byteorder::BigEndian::read_u16(&bytes[6..8]);
let kind = UserDataType::try_from(kind)?;
Ok(Self::from(kind))
}

View File

@@ -2,23 +2,23 @@
name = "pvapconfig"
description = "A tool to configure the AP resources inside a SE guest based on UV secrets and an AP config file."
authors = ["Harald Freudenberger <freude@linux.ibm.com>"]
version = "0.11.0"
version = "0.12.0"
edition.workspace = true
license.workspace = true
rust-version.workspace = true
[dependencies]
clap = { version ="4.1", features = ["derive", "wrap_help"]}
lazy_static = "1.1"
openssl = { version = "0.10.66" }
clap = { version ="4.5", features = ["derive", "wrap_help"]}
lazy_static = "1.5"
openssl = { version = "0.10.70" }
pv_core = { path = "../pv_core", package = "s390_pv_core"}
rand = "0.8"
regex = "1.7"
serde = { version = "1.0.139", features = ["derive"] }
rand = "0.9"
regex = "1.11"
serde = { version = "1.0.217", features = ["derive"] }
serde_yaml = "0.9"
utils = { path = "../utils" }
[build-dependencies]
clap = { version ="4.1", features = ["derive", "wrap_help"]}
clap_complete = "4.1"
lazy_static = "1.1"
clap = { version ="4.5", features = ["derive", "wrap_help"]}
clap_complete = "4.5"
lazy_static = "1.5"

View File

@@ -91,6 +91,9 @@ All AP config entries need to be satisfied to have pvapconfig
terminate with success. Without this option one applied AP config
entry is enough to meet the expectations.
.TP 8
.B \-\-unbind
Unbind all available APQNs.
.TP 8
.B \-v, \-\-verbose
Print out informational messages about what pvapconfig is actually
doing.
@@ -104,7 +107,7 @@ entries. Each entry may hold this information:
- mode: AP queue mode information, required, either "EP11" or "Accel".
.TP 2
- mkvp: AP queue Master Key Verification Pattern (MKVP), required for
EP11, hex string optional prepented with 0x. The MKVP hex string value
EP11, hex string optional prepended with 0x. The MKVP hex string value
may hold either 16 bytes (32 hex characters) or 32 bytes (64 hex
characters) but only the leftmost 16 bytes hold MKVP information and
thus the rest is ignored.
@@ -117,7 +120,7 @@ lowercase character(s) count different.
"CEX7" or "CEX8" for Accelerator, string "CEC8" for EP11, optional. If
given specifies the minimal accepted Crypto card generation.
.TP 2
- secretid: Secret id, hex string with optional 0x prepented, required
- secretid: Secret id, hex string with optional 0x prepended, required
for EP11, ignored for Accel. Details see the following text.
.TP 2
- name: ASCII string, optional, but see details below.

View File

@@ -6,33 +6,22 @@
//
use crate::helper::*;
use regex::Regex;
use std::fmt;
use pv_core::ap::*;
use pv_core::misc::read_file_string;
use std::path::Path;
use std::slice::Iter;
use std::thread;
use std::time;
const RE_CARD_DIR: &str = r"^card([[:xdigit:]]{2})$";
const PATH_SYS_BUS_AP: &str = "/sys/bus/ap";
const PATH_SYS_BUS_AP_FEATURES: &str = "/sys/bus/ap/features";
const PATH_SYS_BUS_AP_BINDINGS: &str = "/sys/bus/ap/bindings";
const PATH_SYS_DEVICES_AP: &str = "/sys/devices/ap";
const RE_CARD_DIR: &str = r"^card([[:xdigit:]]{2})$";
const RE_QUEUE_DIR: &str = r"^([[:xdigit:]]{2})\.([[:xdigit:]]{4})$";
const RE_CARD_TYPE: &str = r"^CEX([3-8])([ACP])$";
const RE_EP11_MKVP: &str = r"WK\s+CUR:\s+(\S+)\s+(\S+)";
const RE_CCA_AES_MKVP: &str = r"AES\s+CUR:\s+(\S+)\s+(\S+)";
const RE_CCA_APKA_MKVP: &str = r"APKA\s+CUR:\s+(\S+)\s+(\S+)";
const SYS_BUS_AP_BINDINGS_POLL_MS: u64 = 500;
const SYS_BUS_AP_BIND_POLL_MS: u64 = 500;
const SYS_BUS_AP_BIND_TIMEOUT_MS: u64 = 10000;
const SYS_BUS_AP_ASSOC_POLL_MS: u64 = 500;
const SYS_BUS_AP_ASSOC_TIMEOUT_MS: u64 = 10000;
/// Check if AP bus support is available.
/// Returns Result with Ok(()) or Err(failurestring).
pub fn check_ap_bus_support() -> Result<(), String> {
@@ -49,14 +38,8 @@ pub fn check_ap_bus_support() -> Result<(), String> {
/// When APSB support is available returns Result
/// with Ok(()) or otherwise Err(failurestring).
pub fn ap_bus_has_apsb_support() -> Result<(), String> {
if !Path::new(PATH_SYS_BUS_AP_FEATURES).is_file() {
return Err(format!(
"AP bus features support missing (file {PATH_SYS_BUS_AP_FEATURES} does not exist)."
));
}
let features = sysfs_read_string(PATH_SYS_BUS_AP_FEATURES).map_err(|err| {
format!("Failure reading AP bus features from {PATH_SYS_BUS_AP_FEATURES} ({err:?}).")
})?;
let features =
read_file_string(PATH_SYS_BUS_AP_FEATURES, "AP bus features").map_err(|e| e.to_string())?;
match features.find("APSB") {
Some(_) => Ok(()),
None => Err("Missing AP bus feature APSB (SE AP pass-through not enabled ?).".to_string()),
@@ -79,17 +62,14 @@ pub fn ap_bus_has_apsb_support() -> Result<(), String> {
pub fn wait_for_ap_bus_bindings_complete() -> bool {
let mut counter = 0;
loop {
match sysfs_read_string(PATH_SYS_BUS_AP_BINDINGS) {
match read_file_string(PATH_SYS_BUS_AP_BINDINGS, "AP bus bindings") {
Ok(s) => {
if s.contains("complete") {
return true;
}
}
Err(err) => {
eprintln!(
"Failure reading AP bus bindings from {} ({:?}).",
PATH_SYS_BUS_AP_BINDINGS, err
);
eprintln!("{err}");
return false;
}
}
@@ -101,225 +81,34 @@ pub fn wait_for_ap_bus_bindings_complete() -> bool {
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum ApqnMode {
Accel,
Ep11,
Cca,
}
#[derive(Debug, Clone)]
pub struct ApqnInfoAccel {
// empty
}
#[derive(Debug, Clone)]
pub struct ApqnInfoEp11 {
pub serialnr: String,
pub mkvp: String, // may be an empty string if no WK set
}
#[derive(Debug, Clone)]
#[allow(dead_code)]
pub struct ApqnInfoCca {
pub serialnr: String,
pub mkvp_aes: String, // may be an empty string if no MK set
pub mkvp_apka: String, // may be an empty string if no MK set
}
#[derive(Debug, Clone)]
pub enum ApqnInfo {
Accel(ApqnInfoAccel),
Ep11(ApqnInfoEp11),
#[allow(dead_code)]
Cca(ApqnInfoCca),
}
impl ApqnInfo {
fn accel_info(_carddir: &str, _queuedir: &str) -> Result<Self, String> {
Ok(Self::Accel(ApqnInfoAccel {}))
}
fn cca_info(carddir: &str, queuedir: &str) -> Result<Self, String> {
let serialnr = match sysfs_read_string(&format!("{carddir}/serialnr")) {
Ok(r) => r,
Err(err) => {
return Err(format!(
"Failure reading serialnr from {carddir}/serialnr: {:?}.",
err
))
}
};
let mkvps = match sysfs_read_string(&format!("{carddir}/{queuedir}/mkvps")) {
Ok(r) => r,
Err(err) => {
return Err(format!(
"Failure reading mkvps from {carddir}/{queuedir}/mkvps: {:?}.",
err
))
}
};
let mut aes_mkvp = String::new();
let re_cca_aes_mkvp = Regex::new(RE_CCA_AES_MKVP).unwrap();
if !re_cca_aes_mkvp.is_match(&mkvps) {
return Err(format!(
"APQN {} failure parsing mkvps string '{}'.",
queuedir, mkvps
));
} else {
let caps = re_cca_aes_mkvp.captures(&mkvps).unwrap();
let valid = caps.get(1).unwrap().as_str().to_lowercase();
if valid != "valid" {
eprintln!(
"Warning: APQN {} has no valid AES master key set.",
queuedir
);
} else {
aes_mkvp = caps.get(2).unwrap().as_str().to_lowercase();
if aes_mkvp.starts_with("0x") {
aes_mkvp = String::from(&aes_mkvp[2..]);
}
}
}
let mut apka_mkvp = String::new();
let re_cca_apka_mkvp = Regex::new(RE_CCA_APKA_MKVP).unwrap();
if !re_cca_apka_mkvp.is_match(&mkvps) {
return Err(format!(
"APQN {} failure parsing mkvps string '{}'.",
queuedir, mkvps
));
} else {
let caps = re_cca_apka_mkvp.captures(&mkvps).unwrap();
let valid = caps.get(1).unwrap().as_str().to_lowercase();
if valid != "valid" {
eprintln!(
"Warning: APQN {} has no valid APKA master key set.",
queuedir
);
} else {
apka_mkvp = caps.get(2).unwrap().as_str().to_lowercase();
if apka_mkvp.starts_with("0x") {
apka_mkvp = String::from(&apka_mkvp[2..]);
}
}
}
Ok(Self::Cca(ApqnInfoCca {
serialnr,
mkvp_aes: aes_mkvp,
mkvp_apka: apka_mkvp,
}))
}
fn ep11_info(carddir: &str, queuedir: &str) -> Result<Self, String> {
let serialnr = match sysfs_read_string(&format!("{carddir}/serialnr")) {
Ok(r) => r,
Err(err) => {
return Err(format!(
"Failure reading serialnr from {carddir}/serialnr: {:?}.",
err
))
}
};
let mkvps = match sysfs_read_string(&format!("{carddir}/{queuedir}/mkvps")) {
Ok(r) => r,
Err(err) => {
return Err(format!(
"Failure reading mkvps from {carddir}/{queuedir}/mkvps: {:?}.",
err
))
}
};
let mut mkvp = String::new();
let re_ep11_mkvp = Regex::new(RE_EP11_MKVP).unwrap();
if !re_ep11_mkvp.is_match(&mkvps) {
return Err(format!(
"APQN {} failure parsing mkvps string '{}'.",
queuedir, mkvps
));
} else {
let caps = re_ep11_mkvp.captures(&mkvps).unwrap();
let valid = caps.get(1).unwrap().as_str().to_lowercase();
if valid != "valid" {
eprintln!("Warning: APQN {} has no valid wrapping key set.", queuedir);
} else {
mkvp = caps.get(2).unwrap().as_str().to_lowercase();
if mkvp.starts_with("0x") {
mkvp = String::from(&mkvp[2..]);
}
if mkvp.len() > 32 {
mkvp = String::from(&mkvp[..32])
}
}
}
Ok(Self::Ep11(ApqnInfoEp11 { serialnr, mkvp }))
}
fn info(mode: &ApqnMode, carddir: &str, queuedir: &str) -> Result<Self, String> {
match mode {
ApqnMode::Accel => Self::accel_info(carddir, queuedir),
ApqnMode::Cca => Self::cca_info(carddir, queuedir),
ApqnMode::Ep11 => Self::ep11_info(carddir, queuedir),
}
}
}
#[derive(Debug, Clone)]
pub struct Apqn {
#[allow(dead_code)]
pub name: String,
pub card: u32,
pub domain: u32,
pub gen: u32,
pub mode: ApqnMode,
pub info: Option<ApqnInfo>,
}
impl fmt::Display for Apqn {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "({},{})", self.card, self.domain)
}
}
impl Apqn {
pub fn bind_state(&self) -> Result<BindState, String> {
get_apqn_bind_state(self.card, self.domain)
}
pub fn set_bind_state(&self, state: BindState) -> Result<(), String> {
set_apqn_bind_state(self.card, self.domain, state)
}
pub fn associate_state(&self) -> Result<AssocState, String> {
get_apqn_associate_state(self.card, self.domain)
}
pub fn set_associate_state(&self, state: AssocState) -> Result<(), String> {
set_apqn_associate_state(self.card, self.domain, state)
}
}
/// Wrapper object around Vector of Apqns
#[derive(Debug)]
pub struct ApqnList(Vec<Apqn>);
impl ApqnList {
/// Create from APQN vector.
#[cfg(test)] // only used in test code
pub fn from_apqn_vec(apqns: Vec<Apqn>) -> Self {
Self(apqns)
}
/// Converts to an APQN vector.
#[cfg(test)] // only used in test code
pub fn to_apqn_vec(&self) -> Vec<Apqn> {
self.0.clone()
}
/// Iter over APQN list
pub fn iter(&self) -> Iter<'_, Apqn> {
self.0.iter()
}
/// Length of the APQN list
pub fn len(&self) -> usize {
self.0.len()
}
/// Check if APQN list is empty.
pub fn is_empty(&self) -> bool {
self.0.is_empty()
}
@@ -327,19 +116,12 @@ impl ApqnList {
/// Scan AP bus devices in sysfs and construct the Apqnlist.
///
/// The list is a vector of struct Apqn for each APQN found in sysfs
/// which is online and the card type matches to the regular expression
/// RE_CARD_TYPE.
/// that this struct can be created from.
/// On success a vector of struct Apqn is returned. This list may be
/// empty if there are no APQNs available or do not match to the conditions.
/// On failure None is returned.
/// Fatal errors which should never happened like unable to compile a
/// static regular expression will result in calling panic.
/// # Panics
/// Panics if the compilation of a static regular expression fails.
pub fn gather_apqns() -> Option<Self> {
let mut apqns: Vec<Apqn> = Vec::new();
let re_card_type = Regex::new(RE_CARD_TYPE).unwrap();
let re_queue_dir = Regex::new(RE_QUEUE_DIR).unwrap();
let card_dirs =
match sysfs_get_list_of_subdirs_matching_regex(PATH_SYS_DEVICES_AP, RE_CARD_DIR) {
Ok(r) => r,
@@ -353,33 +135,6 @@ impl ApqnList {
};
for dir in card_dirs {
let path = format!("{PATH_SYS_DEVICES_AP}/{dir}");
let card_type = match sysfs_read_string(&format!("{path}/type")) {
Ok(r) => r,
Err(err) => {
eprintln!("Failure reading card type from {} ({:?}).", path, err);
return None;
}
};
if !re_card_type.is_match(&card_type) {
eprintln!("Failure parsing card type string '{}'.", card_type);
return None;
}
let caps = re_card_type.captures(&card_type).unwrap();
let gen = caps.get(1).unwrap().as_str().parse::<u32>().unwrap();
let mode = match caps.get(2).unwrap().as_str().parse::<char>().unwrap() {
'A' => ApqnMode::Accel,
'C' => ApqnMode::Cca,
'P' => ApqnMode::Ep11,
_ => panic!("Code inconsistence between regex RE_CARD_TYPE and evaluation code."),
};
if pv_core::misc::pv_guest_bit_set() {
// the UV blocks requests to CCA cards within SE guest with
// AP pass-through support. However, filter out CCA cards as these
// cards cause hangs during information gathering.
if mode == ApqnMode::Cca {
continue;
}
}
let queue_dirs = match sysfs_get_list_of_subdirs_matching_regex(&path, RE_QUEUE_DIR) {
Ok(r) => r,
Err(err) => {
@@ -391,76 +146,31 @@ impl ApqnList {
}
};
for queue_dir in queue_dirs {
let _online = match sysfs_read_i32(&format!("{path}/{queue_dir}/online")) {
Ok(1) => true,
_ => continue,
};
let caps = re_queue_dir.captures(&queue_dir).unwrap();
let cardstr = caps.get(1).unwrap().as_str();
let card = u32::from_str_radix(cardstr, 16).unwrap();
let domstr = caps.get(2).unwrap().as_str();
let dom = u32::from_str_radix(domstr, 16).unwrap();
// For the mpvk and serialnr to fetch from the APQN within a SE
// guest the APQN needs to be bound to the guest. So if the APQN
// is not bound, temporarily bind it here until the info has
// been retrieved.
let mut tempbound = false;
if pv_core::misc::pv_guest_bit_set() {
let cbs = match get_apqn_bind_state(card, dom) {
Ok(bs) => bs,
Err(err) => {
eprintln!(
"Error: Failure reading APQN ({},{}) bind state: {}",
card, dom, err
);
BindState::NotSupported
}
};
if cbs == BindState::Unbound {
let r = set_apqn_bind_state(card, dom, BindState::Bound);
if r.is_err() {
eprintln!(
"Warning: Failure to temp. bind APQN ({},{}): {}",
card,
dom,
r.unwrap_err()
);
continue;
} else {
tempbound = true;
}
};
};
let info = match ApqnInfo::info(&mode, &path, &queue_dir) {
Err(err) => {
// print the error but continue with info set to None
eprintln!(
"Warning: Failure to gather info for APQN ({},{}): {}",
card, dom, err
);
None
}
Ok(i) => Some(i),
};
if tempbound {
let r = set_apqn_bind_state(card, dom, BindState::Unbound);
if r.is_err() {
eprintln!(
"Warning: Failure to unbind temp. bound APQN ({},{}): {}",
card,
dom,
r.unwrap_err()
);
let apqn: Apqn = match (&queue_dir as &str).try_into() {
Ok(apqn) => apqn,
Err(e) => {
eprintln!("{e}");
continue;
}
};
apqns.push(Apqn {
name: queue_dir.clone(),
card,
domain: dom,
gen,
mode: mode.clone(),
info,
});
// Warn about non-fatal errors
if apqn.info.is_none() {
eprintln!("Warning: Failure gathering info for APQN {queue_dir}");
}
if let Some(apqn_info::Cca(ref cca_info)) = apqn.info {
if cca_info.mkvp_aes.is_empty() {
eprintln!("Warning: APQN {queue_dir} has no valid AES master key set.");
}
if cca_info.mkvp_apka.is_empty() {
eprintln!("Warning: APQN {queue_dir} has no valid APKA master key set.");
}
}
if let Some(apqn_info::Ep11(ref ep11_info)) = apqn.info {
if ep11_info.mkvp.is_empty() {
eprintln!("Warning: APQN {queue_dir} has no valid wrapping key set.");
}
}
apqns.push(apqn);
}
}
Some(Self(apqns))
@@ -483,16 +193,16 @@ impl ApqnList {
for a2 in self.0.iter() {
if a1.card == a2.card
&& a1.domain < a2.domain
&& a1.mode == ApqnMode::Ep11
&& a1.mode == apqn_mode::Ep11
&& a1.info.is_some()
&& a2.info.is_some()
{
let i1 = match a1.info.as_ref().unwrap() {
ApqnInfo::Ep11(i) => i,
apqn_info::Ep11(i) => i,
_ => continue,
};
let i2 = match a2.info.as_ref().unwrap() {
ApqnInfo::Ep11(i) => i,
apqn_info::Ep11(i) => i,
_ => continue,
};
if i1.mkvp.is_empty() || i2.mkvp.is_empty() {
@@ -509,217 +219,6 @@ impl ApqnList {
}
}
#[derive(PartialEq, Eq)]
pub enum BindState {
Bound,
Unbound,
NotSupported,
}
/// Query bind state for this APQN.
///
/// Returns a BindState enum as defined above or on failure
/// an error string. Does NOT print any error messages.
pub fn get_apqn_bind_state(card: u32, dom: u32) -> Result<BindState, String> {
let path = format!(
"{}/card{:02x}/{:02x}.{:04x}/se_bind",
PATH_SYS_DEVICES_AP, card, card, dom
);
match sysfs_read_string(&path) {
Err(err) => Err(format!(
"Failure reading se_bind attribute for APQN({},{}): {:?}.",
card, dom, err
)),
Ok(str) => match str.as_str() {
"bound" => Ok(BindState::Bound),
"unbound" => Ok(BindState::Unbound),
"-" => Ok(BindState::NotSupported),
_ => Err(format!("Unknown bind state '{str}'.")),
},
}
}
/// Bind or unbind an APQN.
///
/// The action is determined by the BindState given in.
/// But of course only Bound and Unbound is supported - otherwise
/// this function panics!
/// The function actively loops over the bind state until
/// the requested bind state is reached or a timeout has
/// occurred (SYS_BUS_AP_BIND_TIMEOUT_MS).
/// On success () is returned, on failure an error string
/// is returned. Does NOT print any error messages.
/// # Panics
/// Panics if a desired bind state other than Bound or Unbound is given.
pub fn set_apqn_bind_state(card: u32, dom: u32, state: BindState) -> Result<(), String> {
let path = format!(
"{}/card{:02x}/{:02x}.{:04x}/se_bind",
PATH_SYS_DEVICES_AP, card, card, dom
);
let r = match state {
BindState::Bound => sysfs_write_i32(&path, 1),
BindState::Unbound => sysfs_write_i32(&path, 0),
_ => panic!("set_apqn_bind_state called with invalid BindState."),
};
if r.is_err() {
return Err(format!(
"Failure writing se_bind attribute for APQN({},{}): {:?}.",
card,
dom,
r.unwrap_err()
));
}
let mut ms: u64 = 0;
loop {
thread::sleep(time::Duration::from_millis(SYS_BUS_AP_BIND_POLL_MS));
ms += SYS_BUS_AP_BIND_POLL_MS;
if ms >= SYS_BUS_AP_BIND_TIMEOUT_MS {
break Err(format!(
"Timeout setting APQN({},{}) bind state.",
card, dom
));
}
let newstate = get_apqn_bind_state(card, dom)?;
if newstate == state {
return Ok(());
}
}
}
#[derive(PartialEq, Eq)]
pub enum AssocState {
Associated(u16),
AssociationPending,
Unassociated,
NotSupported,
}
/// Query association state for this APQN.
///
/// Returns an AssocState enum as defined above or on failure
/// an error string. Does NOT print any error messages.
pub fn get_apqn_associate_state(card: u32, dom: u32) -> Result<AssocState, String> {
let path = format!(
"{}/card{:02x}/{:02x}.{:04x}/se_associate",
PATH_SYS_DEVICES_AP, card, card, dom
);
match sysfs_read_string(&path) {
Err(err) => Err(format!(
"Failure reading se_associate attribute for APQN({},{}: {:?}",
card, dom, err
)),
Ok(str) => {
if let Some(prefix) = str.strip_prefix("associated ") {
let value = &prefix.parse::<u16>();
match value {
Ok(v) => Ok(AssocState::Associated(*v)),
Err(_) => Err(format!("Invalid association index in '{str}'.")),
}
} else {
match str.as_str() {
"association pending" => Ok(AssocState::AssociationPending),
"unassociated" => Ok(AssocState::Unassociated),
"-" => Ok(AssocState::NotSupported),
_ => Err(format!("Unknown association state '{str}'.")),
}
}
}
}
}
fn set_apqn_associate_state_associate(card: u32, dom: u32, idx: u16) -> Result<(), String> {
let path = format!(
"{}/card{:02x}/{:02x}.{:04x}/se_associate",
PATH_SYS_DEVICES_AP, card, card, dom
);
let r = sysfs_write_i32(&path, idx as i32);
if r.is_err() {
return Err(format!(
"Failure writing se_associate attribute for APQN({},{}): {:?}.",
card,
dom,
r.unwrap_err()
));
}
let mut ms: u64 = 0;
loop {
thread::sleep(time::Duration::from_millis(SYS_BUS_AP_ASSOC_POLL_MS));
ms += SYS_BUS_AP_ASSOC_POLL_MS;
if ms >= SYS_BUS_AP_ASSOC_TIMEOUT_MS {
break Err(format!(
"Timeout setting APQN({},{}) association idx {} state.",
card, dom, idx
));
}
let newstate = get_apqn_associate_state(card, dom)?;
if let AssocState::Associated(i) = newstate {
if idx == i {
return Ok(());
} else {
return Err(format!(
"Failure: APQN({},{}) is associated with {} but it should be {}.",
card, dom, i, idx
));
}
}
}
}
fn set_apqn_associate_state_unbind(card: u32, dom: u32) -> Result<(), String> {
let bindpath = format!(
"{}/card{:02x}/{:02x}.{:04x}/se_bind",
PATH_SYS_DEVICES_AP, card, card, dom
);
let r = sysfs_write_i32(&bindpath, 0);
if r.is_err() {
return Err(format!(
"Failure writing se_bind attribute for APQN({},{}): {:?}.",
card,
dom,
r.unwrap_err()
));
}
let mut ms: u64 = 0;
loop {
thread::sleep(time::Duration::from_millis(SYS_BUS_AP_ASSOC_POLL_MS));
ms += SYS_BUS_AP_ASSOC_POLL_MS;
if ms >= SYS_BUS_AP_ASSOC_TIMEOUT_MS {
break Err(format!(
"Timeout setting APQN({},{}) association unbind state.",
card, dom
));
}
let newstate = get_apqn_associate_state(card, dom)?;
if newstate == AssocState::Unassociated {
return Ok(());
}
}
}
/// Associate or Unassociate an APQN.
///
/// The action is determined by the AssocState given in.
/// But of course only Associated and Unassociated is supported
/// otherwise this function panics!
/// The function actively loops over the association state until
/// the requested state is reached or a timeout has
/// occurred (SYS_BUS_AP_ASSOC_TIMEOUT_MS).
/// The unassociate is in fact a unbind. So the code triggers
/// an unbind and then loops over the sysfs se_associate until
/// "unassociated" is reached.
/// On success () is returned, on failure an error string
/// is returned. Does NOT print any error messages.
/// # Panics
/// Panics if a desired bind state other than Associated or
/// Unassociated is given.
pub fn set_apqn_associate_state(card: u32, dom: u32, state: AssocState) -> Result<(), String> {
match state {
AssocState::Associated(idx) => set_apqn_associate_state_associate(card, dom, idx),
AssocState::Unassociated => set_apqn_associate_state_unbind(card, dom),
_ => panic!("set_apqn_associate_state called with invalid AssocState."),
}
}
#[cfg(test)]
mod tests {
@@ -771,16 +270,16 @@ mod tests {
let v = l.to_apqn_vec();
for a in v {
match a.mode {
ApqnMode::Accel => {
apqn_mode::Accel => {
// fail if no ApqnInfo is attached
assert!(a.info.is_some());
}
ApqnMode::Ep11 => {
apqn_mode::Ep11 => {
// fail if no ApqnInfo is attached
assert!(a.info.is_some());
let info = a.info.unwrap();
let i = match &info {
ApqnInfo::Ep11(i) => i,
apqn_info::Ep11(i) => i,
_ => panic!("ApqnInfo attached onto Ep11 APQN is NOT ApqnInfoEp11 ?!?"),
};
// fail if no serialnr
@@ -788,12 +287,12 @@ mod tests {
// mkvp is either empty (no WK set) or has exact 32 characters
assert!(i.mkvp.is_empty() || i.mkvp.len() == 32);
}
ApqnMode::Cca => {
apqn_mode::Cca => {
// fail if no ApqnInfo is attached
assert!(a.info.is_some());
let info = a.info.unwrap();
let i = match &info {
ApqnInfo::Cca(i) => i,
apqn_info::Cca(i) => i,
_ => panic!("ApqnInfo attached onto Cca APQN is NOT ApqnInfoCca ?!?"),
};
// fail if no serialnr

View File

@@ -29,6 +29,10 @@ pub struct Cli {
#[arg(long = "strict")]
pub strict: bool,
/// Unbind all available APQNs.
#[arg(long, conflicts_with_all = ["config", "strict"])]
pub unbind: bool,
/// Provide more detailed output.
#[arg(short, long)]
pub verbose: bool,

View File

@@ -6,6 +6,7 @@
//
use openssl::sha::sha256;
use pv_core::misc::encode_hex;
use regex::Regex;
use serde::{Deserialize, Serialize};
use serde_yaml::{self};
@@ -48,7 +49,7 @@ impl ApConfigEntry {
return Ok(());
}
let hash = sha256(self.name.as_bytes());
let hashstr = crate::helper::u8_to_hexstring(&hash);
let hashstr = encode_hex(hash);
// if there is a secretid given, this must match to the hash
if !self.secretid.is_empty() {
if self.secretid != hashstr {
@@ -149,6 +150,7 @@ impl ApConfigEntry {
}
/// Wrapper object around Vector of ApConfigEntry
#[derive(Default)]
pub struct ApConfigList(Vec<ApConfigEntry>);
impl ApConfigList {
@@ -377,15 +379,15 @@ mod tests {
#[test]
fn test_sha256() {
assert!(
crate::helper::u8_to_hexstring(&sha256("Hello".as_bytes()))
encode_hex(sha256("Hello".as_bytes()))
== "185f8db32271fe25f561a6fc938b2e264306ec304eda518007d1764826381969"
);
assert!(
crate::helper::u8_to_hexstring(&sha256("SECRET1".as_bytes()))
encode_hex(sha256("SECRET1".as_bytes()))
== "03153249db7ce46b0330ffb1a760b59710531af08ec4d7f8424a6870fae49360"
);
assert!(
crate::helper::u8_to_hexstring(&sha256("SECRET2".as_bytes()))
encode_hex(sha256("SECRET2".as_bytes()))
== "258499e710e0bd3bb878d6bac7e478b30f3f3e72566989f638c4143d14f6c0b6"
);
}

View File

@@ -8,81 +8,12 @@
use regex::Regex;
use std::error::Error;
use std::fs;
use std::fs::{File, OpenOptions};
use std::io::{Read, Write};
use std::fs::OpenOptions;
use std::io::Write;
use std::path::PathBuf;
pub const PATH_PVAPCONFIG_LOCK: &str = "/run/lock/pvapconfig.lock";
/// Convert u8 slice to (lowercase) hex string
pub fn u8_to_hexstring(slice: &[u8]) -> String {
let s = String::with_capacity(2 * slice.len());
slice.iter().fold(s, |acc, e| acc + &format!("{e:02x}"))
}
/// Convert hexstring to u8 vector
/// The hexstring may contain whitespaces which are ignored.
/// If there are other characters in there or if the number
/// of hex characters is uneven panic() is called.
/// # Panics
/// Panics if the given string contains characters other than
/// hex digits and whitespace. Panics if the number of hex digits
/// is not even.
#[cfg(test)] // currently only used in test code
pub fn hexstring_to_u8(hex: &str) -> Vec<u8> {
let mut s = String::new();
for c in hex.chars() {
if c.is_ascii_hexdigit() {
s.push(c);
} else if c.is_whitespace() {
// ignore
} else {
panic!("Invalid character '{c}'");
}
}
if s.len() % 2 == 1 {
panic!("Uneven # of hex characters in '{s}'");
}
let mut hex_bytes = s.as_bytes().iter().map_while(|b| match b {
b'0'..=b'9' => Some(b - b'0'),
b'a'..=b'f' => Some(b - b'a' + 10),
b'A'..=b'F' => Some(b - b'A' + 10),
_ => None,
});
let mut bytes = Vec::with_capacity(s.len());
while let (Some(h), Some(l)) = (hex_bytes.next(), hex_bytes.next()) {
bytes.push(h << 4 | l)
}
bytes
}
/// Read sysfs file into string
pub fn sysfs_read_string(fname: &str) -> Result<String, Box<dyn Error>> {
let mut file = File::open(fname)?;
let mut content = String::new();
file.read_to_string(&mut content)?;
let trimmed_content = String::from(content.trim());
Ok(trimmed_content)
}
/// Write string into sysfs file
pub fn sysfs_write_string(fname: &str, value: &str) -> Result<(), Box<dyn Error>> {
let mut file = OpenOptions::new().write(true).open(fname)?;
file.write_all(value.as_bytes())?;
Ok(())
}
/// Read sysfs file content and parse as i32 value
pub fn sysfs_read_i32(fname: &str) -> Result<i32, Box<dyn Error>> {
let content = sysfs_read_string(fname)?;
Ok(content.parse::<i32>()?)
}
/// Write an i32 value into a sysfs file
pub fn sysfs_write_i32(fname: &str, value: i32) -> Result<(), Box<dyn Error>> {
sysfs_write_string(fname, &value.to_string())
}
/// For a given (sysfs) directory construct a list of all subdirs
/// and give it back as a vector of strings. If there is no subdir,
/// the vector is empty.
@@ -203,29 +134,6 @@ mod tests {
// Only very simple tests
const TEST_BYTES: [u8; 8] = [0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef];
const TEST_HEXSTR: &str = "0123456789abcdef";
#[test]
fn test_u8_to_hexstring() {
let str = u8_to_hexstring(&TEST_BYTES);
assert!(str == TEST_HEXSTR);
}
#[test]
fn test_hexstring_to_u8() {
let bytes = hexstring_to_u8(TEST_HEXSTR);
assert!(bytes.as_slice() == TEST_BYTES);
}
#[test]
fn test_sysfs_read_string() {
let r = sysfs_read_string("/proc/cpuinfo");
assert!(r.is_ok());
}
#[test]
fn test_sysfs_read_i32() {
let r = sysfs_read_i32("/proc/sys/kernel/random/entropy_avail");
assert!(r.is_ok());
}
#[test]
fn test_sysfs_get_list_of_subdirs() {
let r = sysfs_get_list_of_subdirs("/proc/self");
@@ -244,24 +152,6 @@ mod tests {
}
}
#[test]
fn test_sysfs_write_i32() {
let temp_dir =
TemporaryDirectory::new().expect("creating a temporary directory should work");
let test_path = temp_dir.path().join("test");
let test_path = test_path.as_os_str().to_str().expect("should work");
let mut file = File::create(test_path).unwrap();
let _ = file.write_all(b"XYZ");
drop(file);
let r = sysfs_read_i32(test_path);
assert!(r.is_err());
let r = sysfs_write_i32(test_path, 999);
assert!(r.is_ok());
let r = sysfs_read_i32(test_path);
assert!(r.is_ok());
let v = r.unwrap();
assert!(v == 999);
}
#[test]
fn test_lockfile() {
let temp_dir =
TemporaryDirectory::new().expect("creating a temporary directory should work");

View File

@@ -12,10 +12,12 @@ mod config;
mod helper;
mod uv;
use ap::{Apqn, ApqnList};
use ap::ApqnList;
use cli::ARGS;
use config::{ApConfigEntry, ApConfigList};
use helper::{LockFile, PATH_PVAPCONFIG_LOCK};
use pv_core::ap::{self as pvap, Apqn};
use pv_core::misc::encode_hex;
use pv_core::uv::{ListableSecretType, SecretList};
use std::process::ExitCode;
use utils::print_version;
@@ -84,42 +86,48 @@ fn main() -> ExitCode {
on_error_print_and_exit!(r);
info!("UV support and environment is ok.\n");
// read configuration
let configfile: &str = match &cli::ARGS.config {
Some(f) => f,
_ => cli::PATH_DEFAULT_CONFIG_FILE,
};
info!(
"Reading AP configuration entries from file '{}'...\n",
configfile
);
let apconfig: ApConfigList = match ApConfigList::read_and_validate_yaml_file(configfile) {
Ok(apcfg) => apcfg,
Err(err) => println_and_exit_failure!("{}", err),
};
if apconfig.is_empty() {
println!(
"No AP configuration entries in config file '{}': Nothing to do.",
let mut apconfig: ApConfigList = Default::default();
if !cli::ARGS.unbind {
// read configuration
let configfile: &str = match &cli::ARGS.config {
Some(f) => f,
_ => cli::PATH_DEFAULT_CONFIG_FILE,
};
info!(
"Reading AP configuration entries from file '{}'...\n",
configfile
);
return ExitCode::SUCCESS;
}
info!("Found {} AP configuration entries.\n", apconfig.len());
apconfig = match ApConfigList::read_and_validate_yaml_file(configfile) {
Ok(apcfg) => apcfg,
Err(err) => println_and_exit_failure!("{}", err),
};
if apconfig.is_empty() {
println!(
"No AP configuration entries in config file '{}': Nothing to do.",
configfile
);
return ExitCode::SUCCESS;
}
info!("Found {} AP configuration entries.\n", apconfig.len());
};
// get list of secrets from UV
info!("Fetching list of secrets from UV...\n");
let secrets: SecretList = match uv::gather_secrets() {
Err(e) => println_and_exit_failure!("{}", e),
Ok(los) => los,
};
info!("Fetched {} Secret entries from UV.\n", secrets.len());
let mut secrets = SecretList::new(0, Vec::new());
if !cli::ARGS.unbind {
info!("Fetching list of secrets from UV...\n");
secrets = match uv::gather_secrets() {
Err(e) => println_and_exit_failure!("{}", e),
Ok(los) => los,
};
info!("Fetched {} Secret entries from UV.\n", secrets.len());
}
// Warning if no UV secrets given but AP config entries require it
let non_accel_apc = apconfig
.iter()
.filter(|apc| apc.mode != config::STR_MODE_ACCEL)
.count();
if non_accel_apc > 0 && secrets.is_empty() {
if !cli::ARGS.unbind && non_accel_apc > 0 && secrets.is_empty() {
println!(
"Warning: No UV Secrets given but at least one AP config entry requires a Secret."
);
@@ -151,7 +159,7 @@ fn main() -> ExitCode {
Ok(n) => n,
};
if n == 0 {
if !cli::ARGS.unbind && n == 0 {
println_and_exit_failure!(
"None out of {} AP config entries could be applied.",
apconfig.len()
@@ -164,11 +172,13 @@ fn main() -> ExitCode {
);
}
info!(
"Successfully applied {} out of {} AP config entries.\n",
n,
apconfig.len()
);
if !cli::ARGS.unbind {
info!(
"Successfully applied {} out of {} AP config entries.\n",
n,
apconfig.len()
);
}
ExitCode::SUCCESS
}
@@ -224,14 +234,14 @@ fn do_ap_config(
continue;
}
match apqn.mode {
ap::ApqnMode::Accel => {
pvap::apqn_mode::Accel => {
// check bind state of this APQN
let bind_state_ok = match apqn.bind_state() {
Err(err) => {
eprintln!("Warning: Failure reading APQN {apqn} bind state: {err}");
false
}
Ok(ap::BindState::Bound) => true,
Ok(pvap::bind_state::Bound) => true,
Ok(_) => false,
};
if !bind_state_ok {
@@ -246,7 +256,7 @@ fn do_ap_config(
resolved_entries += 1;
break;
}
ap::ApqnMode::Ep11 => {
pvap::apqn_mode::Ep11 => {
// check association state of this APQN
let (assoc_state_ok, assoc_idx) = match apqn.associate_state() {
Err(err) => {
@@ -255,7 +265,7 @@ fn do_ap_config(
);
(false, 0)
}
Ok(ap::AssocState::Associated(idx)) => (true, idx),
Ok(pvap::assoc_state::Associated(idx)) => (true, idx),
Ok(_) => (false, 0),
};
if !assoc_state_ok {
@@ -266,7 +276,7 @@ fn do_ap_config(
se.stype() == ListableSecretType::Association
&& se.id().len() == uv::AP_ASSOC_SECRET_ID_SIZE
&& se.index() == assoc_idx
&& helper::u8_to_hexstring(se.id()) == apc.secretid
&& encode_hex(se.id()) == apc.secretid
});
if r.is_none() {
continue;
@@ -297,10 +307,10 @@ fn do_ap_config(
}
match apqn.bind_state() {
Err(err) => eprintln!("Warning: Failure reading APQN {apqn} bind state: {err}"),
Ok(ap::BindState::Bound) => {
Ok(pvap::bind_state::Bound) => {
info!("Unbind APQN {apqn} as this bind/associate does not match to any AP config entry.\n");
if !ARGS.dryrun() {
if let Err(err) = apqn.set_bind_state(ap::BindState::Unbound) {
if let Err(err) = apqn.set_bind_state(pvap::bind_state::Unbound) {
return Err(format!("Failure unbinding APQN {apqn}: {err}"));
}
}
@@ -330,15 +340,15 @@ fn do_ap_config(
continue;
}
match apqn.mode {
ap::ApqnMode::Accel => {
pvap::apqn_mode::Accel => {
// try to bind this accelerator APQN
if ARGS.verbose() || fntest {
println!("Bind APQN {apqn} to match to AP config entry {cistr}.");
}
if !(ARGS.dryrun() || fntest) {
if let Err(err) = apqn.set_bind_state(ap::BindState::Bound) {
if let Err(err) = apqn.set_bind_state(pvap::bind_state::Bound) {
// bind failed, unbind/reset this apqn, return with failure
let _ = apqn.set_bind_state(ap::BindState::Unbound);
let _ = apqn.set_bind_state(pvap::bind_state::Unbound);
return Err(format!("Failure binding APQN {apqn}: {err}"));
}
}
@@ -347,13 +357,13 @@ fn do_ap_config(
resolved_entries += 1;
break;
}
ap::ApqnMode::Ep11 => {
pvap::apqn_mode::Ep11 => {
// EP11 needs bind and associate, but before doing this let's
// check out which secret index to use with the associate
let se = match secrets.iter().find(|&se| {
se.stype() == ListableSecretType::Association
&& se.id().len() == uv::AP_ASSOC_SECRET_ID_SIZE
&& helper::u8_to_hexstring(se.id()) == apc.secretid
&& encode_hex(se.id()) == apc.secretid
}) {
None => {
eprintln!("Warning: Secret id '{}' from config entry {} not found in UV secrets list.",
@@ -369,9 +379,9 @@ fn do_ap_config(
);
}
if !(ARGS.dryrun() || fntest) {
if let Err(err) = apqn.set_bind_state(ap::BindState::Bound) {
if let Err(err) = apqn.set_bind_state(pvap::bind_state::Bound) {
// bind failed, unbind/reset this apqn, return with failure
let _ = apqn.set_bind_state(ap::BindState::Unbound);
let _ = apqn.set_bind_state(pvap::bind_state::Unbound);
return Err(format!("Failure binding APQN {}: {}", apqn, err));
}
}
@@ -383,7 +393,7 @@ fn do_ap_config(
);
}
if !(ARGS.dryrun() || fntest) {
let apas = ap::AssocState::Associated(se.index());
let apas = pvap::assoc_state::Associated(se.index());
apqn.set_associate_state(apas)
.map_err(|err| format!("Failure associating APQN {apqn}: {err}"))?;
}
@@ -408,7 +418,7 @@ fn do_ap_config(
/// Please note this can not happen, as mingen is already checked via RE
/// during storing the value into mingen.
fn config_and_apqn_match(apc: &ApConfigEntry, apqn: &Apqn) -> bool {
if apc.mode == config::STR_MODE_ACCEL && apqn.mode == ap::ApqnMode::Accel {
if apc.mode == config::STR_MODE_ACCEL && apqn.mode == pvap::apqn_mode::Accel {
// config and apqn are accelerators
// maybe check mingen
if !apc.mingen.is_empty() {
@@ -418,10 +428,10 @@ fn config_and_apqn_match(apc: &ApConfigEntry, apqn: &Apqn) -> bool {
}
}
return true;
} else if apc.mode == config::STR_MODE_EP11 && apqn.mode == ap::ApqnMode::Ep11 {
} else if apc.mode == config::STR_MODE_EP11 && apqn.mode == pvap::apqn_mode::Ep11 {
// config and apqn are ep11
let info = match &apqn.info {
Some(ap::ApqnInfo::Ep11(i)) => i,
Some(pvap::apqn_info::Ep11(i)) => i,
_ => return false,
};
// maybe check mingen
@@ -450,79 +460,78 @@ fn config_and_apqn_match(apc: &ApConfigEntry, apqn: &Apqn) -> bool {
mod tests {
use super::*;
use helper::hexstring_to_u8;
use pv_core::uv::SecretEntry;
use pv_core::{misc::decode_hex, uv::SecretEntry};
// This is more or less only a test for the do_ap_config() function
// However, this is THE main functionality of the whole application.
fn make_test_apqns() -> Vec<Apqn> {
vec![
ap::Apqn {
pvap::Apqn {
name: String::from("10.0007"),
card: 16,
domain: 7,
gen: 8,
mode: ap::ApqnMode::Accel,
info: Option::Some(ap::ApqnInfo::Accel(ap::ApqnInfoAccel {})),
mode: pvap::apqn_mode::Accel,
info: Option::Some(pvap::apqn_info::Accel(pvap::apqn_info::ApqnInfoAccel {})),
},
ap::Apqn {
pvap::Apqn {
name: String::from("11.0008"),
card: 17,
domain: 8,
gen: 8,
mode: ap::ApqnMode::Ep11,
info: Option::Some(ap::ApqnInfo::Ep11(ap::ApqnInfoEp11 {
mode: pvap::apqn_mode::Ep11,
info: Option::Some(pvap::apqn_info::Ep11(pvap::apqn_info::ApqnInfoEp11 {
serialnr: String::from("93AADFK719460083"),
mkvp: String::from("db3c3b3c3f097dd55ec7eb0e7fdbcb93"),
})),
},
ap::Apqn {
pvap::Apqn {
name: String::from("12.0009"),
card: 18,
domain: 9,
gen: 8,
mode: ap::ApqnMode::Ep11,
info: Option::Some(ap::ApqnInfo::Ep11(ap::ApqnInfoEp11 {
mode: pvap::apqn_mode::Ep11,
info: Option::Some(pvap::apqn_info::Ep11(pvap::apqn_info::ApqnInfoEp11 {
serialnr: String::from("93AADHZU42082261"),
mkvp: String::from("4a27bb66520ac85f6073a7f678d262c0"),
})),
},
ap::Apqn {
pvap::Apqn {
name: String::from("12.000a"),
card: 18,
domain: 10,
gen: 8,
mode: ap::ApqnMode::Ep11,
info: Option::Some(ap::ApqnInfo::Ep11(ap::ApqnInfoEp11 {
mode: pvap::apqn_mode::Ep11,
info: Option::Some(pvap::apqn_info::Ep11(pvap::apqn_info::ApqnInfoEp11 {
serialnr: String::from("93AADHZU42082261"),
mkvp: String::from("383d2a9ab781f35343554c5b3d9337cd"),
})),
},
ap::Apqn {
pvap::Apqn {
name: String::from("13.000d"),
card: 19,
domain: 13,
gen: 8,
mode: ap::ApqnMode::Ep11,
info: Option::Some(ap::ApqnInfo::Ep11(ap::ApqnInfoEp11 {
mode: pvap::apqn_mode::Ep11,
info: Option::Some(pvap::apqn_info::Ep11(pvap::apqn_info::ApqnInfoEp11 {
serialnr: String::from("87HU397G150TZGR"),
mkvp: String::new(),
})),
},
ap::Apqn {
pvap::Apqn {
name: String::from("13.000f"),
card: 19,
domain: 15,
gen: 8,
mode: ap::ApqnMode::Ep11,
mode: pvap::apqn_mode::Ep11,
info: Option::None,
},
]
}
fn make_assoc_secretentry(idx: u16, hexidstr: &str) -> SecretEntry {
let id = hexstring_to_u8(hexidstr);
let id = decode_hex(hexidstr).unwrap();
let idlen: u32 = id.len().try_into().unwrap();
let idarray: [u8; 32] = id.try_into().unwrap();
SecretEntry::new(idx, ListableSecretType::Association, idarray.into(), idlen)

View File

@@ -5,9 +5,9 @@
//! UV related functions for pvapconfig
//
use pv_core::misc::read_file_string;
use pv_core::uv::{ListCmd, SecretList, UvDevice, UvcSuccess};
use regex::Regex;
use std::path::Path;
/// The byte size of association secret of type 2 in struct SecretEntry
pub const AP_ASSOC_SECRET_ID_SIZE: usize = 32;
@@ -25,21 +25,8 @@ const RE_UV_FAC_BIT_LIST_SECRETS: u32 = 30;
/// Panics if the compilation of a static regular expression fails.
/// Panics if RE_UV_FACILITIES does not match.
pub fn has_list_secrets_facility() -> Result<(), String> {
if !Path::new(PATH_SYS_FW_UV_FACILITIES).is_file() {
return Err(format!(
"UV facilities sysfs attribute not found (file {} does not exist).",
PATH_SYS_FW_UV_FACILITIES
));
}
let facstr = match crate::helper::sysfs_read_string(PATH_SYS_FW_UV_FACILITIES) {
Ok(s) => s,
Err(err) => {
return Err(format!(
"Failure reading UV facilities from {PATH_SYS_FW_UV_FACILITIES} ({:?}).",
err
))
}
};
let facstr =
read_file_string(PATH_SYS_FW_UV_FACILITIES, "UV facilities").map_err(|e| e.to_string())?;
let re_uv_facilities = Regex::new(RE_UV_FACILITIES).unwrap();
if !re_uv_facilities.is_match(&facstr) {
Err(format!("Failure parsing UV facilities entry '{facstr}'."))

Some files were not shown because too many files have changed in this diff Show More