pax_global_header00006660000000000000000000000064132341043110014502gustar00rootroot0000000000000052 comment=f4eb7e4907d3f50155c46e648bbca1478517bc93 s390-tools-2.3.0/000077500000000000000000000000001323410431100133405ustar00rootroot00000000000000s390-tools-2.3.0/.gitignore000066400000000000000000000033261323410431100153340ustar00rootroot00000000000000# # Ignore compiler generated files # *.o *.a *.o.d # # Ignore generated executables and other generated files # cmsfs-fuse/cmsfs-fuse cpacfstats/cpacfstats cpacfstats/cpacfstatsd cpumf/bin/chcpumf cpumf/bin/cpumf_helper cpumf/bin/lscpumf cpuplugd/cpuplugd dasdfmt/dasdfmt dasdinfo/dasdinfo dasdview/dasdview dump2tar/src/dump2tar fdasd/fdasd hmcdrvfs/hmcdrvfs hyptop/hyptop ip_watcher/xcec-bridge ipl_tools/chreipl ipl_tools/chshut ipl_tools/lsreipl ipl_tools/lsshut iucvterm/bin/chiucvallow iucvterm/bin/ts-shell iucvterm/etc/ts-shell.conf iucvterm/po/iucvterm.pot iucvterm/src/iucvconn iucvterm/src/iucvtty iucvterm/src/ttyrun iucvterm/test/test_afiucv libutil/util_base_example libutil/util_file_example libutil/util_libc_example libutil/util_opt_command_example libutil/util_opt_example libutil/util_panic_example libutil/util_path_example libutil/util_prg_example libutil/util_rec_example libutil/util_scandir_example libzds/libzds.a mon_tools/mon_fsstatd mon_tools/mon_procd osasnmpd/osasnmpd qetharp/qetharp qethqoat/qethqoat systemd/cpacfstatsd.service systemd/iucvtty-login@.service systemd/ttyrun-getty@.service tape390/tape390_crypt tape390/tape390_display tunedasd/src/tunedasd vmconvert/vmconvert vmcp/vmcp vmur/vmur zconf/chp/chchp zconf/chp/lschp zconf/css/lscss zconf/qeth/lsqeth zconf/scm/lsscm zconf/zcrypt/chzcrypt zconf/zcrypt/lszcrypt zdev/src/chzdev zdev/src/chzdev_usage.c zdev/src/lszdev zdev/src/lszdev_usage.c zdsfs/zdsfs zdump/zgetdump zfcpdump/cpioinit zfcpdump/zfcpdump_part zfcpdump/zfcpdump_part.rd ziomon/ziomon_mgr ziomon/ziomon_util ziomon/ziomon_zfcpdd ziomon/ziorep_traffic ziomon/ziorep_utilization zipl/boot/*.bin zipl/boot/*.exec zipl/boot/data.h zipl/src/chreipl_helper.device-mapper zipl/src/zipl zkey/zkey s390-tools-2.3.0/AUTHORS.md000066400000000000000000000035541323410431100150160ustar00rootroot00000000000000List of all individuals having contributed content to s390-tools ---------------------------------------------------------------- - Alexey Ishchuk - Andreas Herrmann - Andre Wild - Antoinette Kaschner - Arnd Bergmann - Axel Wirbser - Benjamin Block - Carsten Otte - Christian Borntraeger - Christian Ehrhardt - Christof Schmitt - Claudio Imbrenda - Clemens von Mann - cvs2git - Dan Horak - Despina Papadopoulou - Eberhard Pasch - Einar Lueck - Erwin Vicari - Eugene Crosser - Eugene Dvurechenski - Eugene (jno) Dvurechenski - Farhan Ali - Fedor Loshakov - Felix Beck - Frank Blaschka - Frank Munzert - Frank Pavlic - Fritz Elfert - Gerald Schaefer - Gerhard Tonn - Hannes Reinecke - Hans-Joachim Picht - Hans Wippel - Harald Freudenberger - Heiko Carstens - Hendrik Brueckner - Holger Dengler - Holger Smolinski - Hongjie Yang - Horst Hummel - Ingo Franzki - Ingo Tuchscherer - Jan Glauber - Jan Hoeppner - Jan Willeke - Jean-Baptiste Joret - Jens Remus - Jochen Roehrig - Julian Wiedmann - Karsten Graul - Kittipon Meesompop - Klaus-Dieter Wacker - Lakhvich Dmitriy - Mark Dettinger - Martin Kammerer - Martin Peschke - Martin Petermann - Martin Schwidefsky - Maxim Shchetynin - Melissa Howland - Michael - Michael Ernst - Michael Holzheu - Michael Mueller - Mijo Safradin - Mikhail Zaslonko - Peter Oberparleiter - Peter Tiedemann - Philipp Kern - Philipp Rudo - Rafael Fonseca - Raimund Schroeder - Ralph Wuerthner - Rene Trumpp - Rolf Schaefer - root - Sa Liu - Sascha Silbe - Sebastian Ott - Seshagiri N Ippili - Seshagiri N. Ippili - Simon Sturm - Stefan Bader - Stefan Haberland - Stefan Raspl - Stefan Reimbold - Stefan Weinhuber - Steffen Maier - Steffen Thoss - Susanne Wintenberger - Sven Schuetz - Swen Schillig - Taraka R. Bodireddy - Thomas Heidrich - Thomas Richter - Thomas Spatzier - Thomas Weber - Ursula Braun - Utz Bacher - Viktor Mihajlovski - Volker Sameske - Wolfgang Taphorn s390-tools-2.3.0/CHANGELOG.md000066400000000000000000000051531323410431100151550ustar00rootroot00000000000000Release history for s390-tools (MIT version) -------------------------------------------- * __v2.3.0 (2018-01-30)__ For Linux kernel version: 4.15 Changes of existing tools: - lscpumf: Add support for IBM z14 hardware counters - libdasd: Introduce libdasd and use for dasd tools - zipl: Always build and link without PIE Bug Fixes: - zgetdump: Fix handling of DASD multi-volume dump for partitions above 4 GB - zdev: Fix zdev dracut module aborting on unknown root device * __v2.2.0 (2017-12-07)__ For Linux kernel version: 4.14 Removed tools: - lsmem/chmem: Moved to util-linux >= 2.30 Changes of existing tools: - lszcrypt: Add CEX6S support - cpuplugd/mon_tools: Improve systemctl start error handling - systemd: Install also the unit configuration files Bug Fixes: - build process: Fix parallel build for libutil - cpi: Add missing Install section to service unit - lsluns: Do not scan (all) if filters match nothing - lsluns: Enhance usage statement and man page - zdev: Use correct path to vmcp binary - ziomon: Re-add missing line in ziomon_fcpconf - ziomon: Fix non-zero return code in ziomon_util - zipl: Remove invalid dasdview command line option * __v2.1.0 (2017-09-25)__ For Linux kernel version: 4.13 Added new tools: - netboot: Scripts for building a PXE-style netboot image for KVM - 90-cpi.rules/cpictl: New udev rule to update CPI when KVM is used Changes of existing tools: - lsqeth/zdev: Add VNIC Characteristics support Bug Fixes: - chzcrypt: Corrected handling of insufficient permissions - cpacfstats: Add size setting to perf event - fdasd: Skip partition check with the force option - ttyrun: Fix deprecated BindTo usage in ttyrun-getty@.service.in - lszcrypt: Fix core dump caused by stack overwrite - lszcrypt: Fix random domain printout when no config available - zdev: Fix segfault with unknown qeth attribute - zdev: Fix IPv6 NDP proxy description - zdev: Fix zdev dracut module temp file location - zkey: Correctly detect abbreviated commands - zkey: Validate XTS key: ignore domain and card - zkey: Use octal values instead of S_IRWX* constants - zkey: Properly set umask to prohibit permissions to group and others - zkey: Add -ldl to LDLIBS (not LDFLAGS) - znetconf: Re-add missing line in lsznet.raw - Fix several gcc 7 warnings * __v2.0.0 (2017-08-21)__ - Publish package under the MIT license with the same contents as the already available s390-tools-1.39.0 Previous releases of s390-tools can be found on the IBM Developer Works web pages: - https://www.ibm.com/developerworks/linux/linux390/s390-tools.html s390-tools-2.3.0/CODINGSTYLE.md000066400000000000000000000050651323410431100154540ustar00rootroot00000000000000Coding guidelines for s390-tools ================================ For s390-tools the preferred language is C. We provide libraries, e.g. [`libutil`](libutil) that should be used by all tools if possible. The coding style is based on the Linux [kernel guidelines]. Therefore, use the [checkpatch] tool for verification before you submit a patch. [kernel guidelines]: https://www.kernel.org/doc/html/latest/process/coding-style.html [checkpatch]: https://github.com/torvalds/linux/blob/master/scripts/checkpatch.pl Below we describe some additional things that we want you to consider when writing new code for s390-tools. This package started in 2001 and has a long "tradition" - therefore, older tools might not follow all recommendations. Note that when changing existing code, consistency could have priority over applying rules. Standard abbreviations ---------------------- The abbreviations below are recommended to be used in the source code. | __Short Name__ | __Long Name__ | |:----------------|:--------------------------------------------------| | attr | Attribute | | blk | Block | | buf | Buffer | | col | Column | | count | Count | | desc | Description | | dir | Directory | | fd | File descriptor (open) | | fp | File pointer (fopen) | | len | Length | | lib | Library | | mod | Module | | nr | Number | | parm | Parameter | | path | File path | | ptr | Pointer | | rc | Return code | | size | Size | | src | Source | | str | String | | sym | Symbol | s390-tools-2.3.0/CONTRIBUTING.md000066400000000000000000000204761323410431100156020ustar00rootroot00000000000000Contributing to s390-tools ========================== License ------- All contributions have to be submitted under the MIT license. See also the [LICENSE](LICENSE) file. Developer's Certificate of Origin and Signed-off-by --------------------------------------------------- The sign-off is a simple line at the end of the explanation for the patch, which certifies that you wrote it or otherwise have the right to pass it on as an open-source patch. With the Signed-off-by line you certify the below: ``` Developer's Certificate of Origin 1.1 By making a contribution to this project, I certify that: (a) The contribution was created in whole or in part by me and I have the right to submit it under the open source license indicated in the file; or (b) The contribution is based upon previous work that, to the best of my knowledge, is covered under an appropriate open source license and I have the right under that license to submit that work with modifications, whether created in whole or in part by me, under the same open source license (unless I am permitted to submit under a different license), as indicated in the file; or (c) The contribution was provided directly to me by some other person who certified (a), (b) or (c) and I have not modified it. (d) I understand and agree that this project and the contribution are public and that a record of the contribution (including all personal information I submit with it, including my sign-off) is maintained indefinitely and may be redistributed consistent with this project or the open source license(s) involved. ``` If you can certify the above, just add a line stating the following at the bottom of each of your commit messages: ``` Signed-off-by: Random Developer ``` Please use your real name and a valid e-mail address (no pseudonyms or anonymous contributions). Submitting code --------------- The preferred way is to create GitHub pull requests for your code contributions. Please create separate pull requests for each logical enhancement, new feature, or fix. Before you submit your code please consider our recommendations in the [CODINGSTYLE](CODINGSTYLE.md) document. GitHub workflow for contributions --------------------------------- In the examples below we use this fictive identity: - Name: Random Developer - E-mail: random@developer.example.org - GitHub ID: random-developer ### Setup GitHub and local git 1. Create a fork of this repository by clicking the `Fork` button on the top right of the [s390-tools](https://github.com/ibm-s390-tools/s390-tools) main page 2. Clone your forked repository to your local development system ``` $ git clone https://github.com/random-developer/s390-tools.git ``` 3. Configure a remote called "upstream" pointing to the official s390-tools repository on GitHub ``` $ cd s390-tools ~/s390-tools $ git remote add upstream https://github.com/ibm-s390-tools/s390-tools.git ``` 4. Verify your remotes ``` ~/s390-tools $ git remote -v origin https://github.com/random-developer/s390-tools.git (fetch) origin https://github.com/random-developer/s390-tools.git (push) upstream https://github.com/ibm-s390-tools/s390-tools.git (fetch) upstream https://github.com/ibm-s390-tools/s390-tools.git (push) ``` You now have two remotes: The "origin" remote points to your fork and the "upstream" remote to the official s390-tools repository. 5. Configure your git user name and e-mail ``` ~/s390-tools $ git config user.name "Random Developer" ~/s390-tools $ git config user.email "random@developer.example.com" ``` ### Create a pull request 1. Create and checkout a new branch for your contribution ``` ~/s390-tools $ git checkout -b contrib-doc-pr ``` 2. Make your changes to the code ``` ~/s390-tools $ vim CONTRIBUTING.md ``` 3. Build and test your contribution ``` ~/s390-tools $ make clean all ~/s390-tools $ # Whatever you have to do for testing ``` 4. Commit your changes ``` ~/s390-tools $ git add CONTRIBUTING.md ~/s390-tools $ git commit -s ``` Provide a meaningful commit message including your "Signed-off-by" line to each commit: ``` CONTRIBUTING: Outline steps to submit code Explain in more detail how to submit s390-tools contributions as GitHub pull requests. Signed-off-by: Random Developer ``` 5. Use the [checkpatch] tool to validate your commits ``` ~/s390-tools $ checkpatch.pl --no-tree --git master..HEAD ``` Interpret the checkpatch messages wisely - e.g. the 80 character rule can be ignored for printf format strings. [checkpatch]: https://github.com/torvalds/linux/blob/master/scripts/checkpatch.pl 6. Push the changes to your fork of the repository ``` ~/s390-tools $ git push origin contrib-doc-pr ``` 7. Go to the GitHub website of your s390-tools fork and create a pull request for your branch "contrib-doc-pr" ### Update a pull request during review If there are changes requested during the review process, you have to update your code in the pull request. To retain the existing review comments, add commits on top of your pull request branch. Depending on the size and number of changes, a rebase of the pull request might be required. This will be communicated during the review. 1. Update your code with new commits ``` ~/s390-tools $ vi CONTRIBUTING.md ~/s390-tools $ git add CONTRIBUTING.md ~/s390-tools $ git commit -s -m "CONTRIBUTING: Add update PR info" ``` 2. Update your pull request by pushing changes ``` ~/s390-tools $ git push origin contrib-doc-pr ``` ### Finalize a pull request After the review process is finished or if you are explicitly asked for it, you have to create a clean commit series. 1. Save branch to "contrib-doc-pr.v1" ``` $ cd s390-tools ~/s390-tools $ git branch contrib-doc-pr.v1 ``` 2. Use interactive git rebase to merge commits, adjust commit messages, and rebase onto your local master branch ``` ~/s390-tools $ git rebase -i master ``` An editor is started and shows the following: ``` pick 2c73b9fc CONTRIBUTING: Outline steps to submit code pick fcfb0412 CONTRIBUTING: Add update PR info ``` To merge the update into the original commit, replace "pick fcfb0412" with "squash fcfb0412". ``` pick 2c73b9fc CONTRIBUTING: Outline steps to submit code squash fcfb0412 CONTRIBUTING: Add update PR info ``` Save the document and exit the editor to finish the merge. Another editor window is presented to modify the commit message. You now could change the commit message as follows: ``` CONTRIBUTING: Outline steps to submit code Explain in more detail how to submit s390-tools contributions as GitHub pull requests and how to update already submitted pull requests. Signed-off-by: Random Developer ``` With interactive rebasing you can also change the order of commits and modify commit messages with "reword". 3. Use `git push` with the force option to replace the existing pull request with your locally modified commits ``` ~/s390-tools $ git push --force origin contrib-doc-pr ``` ### Rebase a pull request If changes are made to the master branch in the official s390-tools repository you may be asked to rebase your branch with your contribution onto it. This can be required to prevent any merge conflicts that might arise when integrating your contribution. 1. Fetch all upstream changes from the official s390-tools repository, rebase your local master branch and update the master branch on your fork ``` ~/s390-tools $ git fetch upstream ~/s390-tools $ git checkout master ~/s390-tools $ git rebase upstream/master ~/s390-tools $ git push origin master ``` 2. Rebase your branch with your contribution onto the master branch of the official s390-tools repository ``` ~/s390-tools $ git checkout contrib-doc-pr ~/s390-tools $ git rebase master ``` 3. Use `git push` with the force option to replace the existing pull request with your locally modified commits ``` ~/s390-tools $ git push --force origin contrib-doc-pr ``` s390-tools-2.3.0/LICENSE000066400000000000000000000020371323410431100143470ustar00rootroot00000000000000Copyright IBM Corp. 2001, 2017 Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. s390-tools-2.3.0/Makefile000066400000000000000000000030421323410431100147770ustar00rootroot00000000000000ARCH := $(shell uname -m | sed -e s/i.86/i386/ -e s/sun4u/sparc64/ -e s/arm.*/arm/ -e s/sa110/arm/) # Include common definitions include common.mak LIB_DIRS = libvtoc libu2s libutil libzds libdasd libvmdump libccw TOOL_DIRS = zipl zdump fdasd dasdfmt dasdview tunedasd \ tape390 osasnmpd qetharp ip_watcher qethconf scripts zconf \ vmconvert 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 SUB_DIRS = $(LIB_DIRS) $(TOOL_DIRS) all: $(TOOL_DIRS) clean: $(TOOL_DIRS) install: $(TOOL_DIRS) # # For simple "make" we explicitly set the MAKECMDGOALS to "all". # ifeq ($(MAKECMDGOALS),) MAKECMDGOALS = all endif # # We have to build the libraries before the tools are built. Otherwise # the tools would trigger parallel "make -C" builds for libraries in # case of "make -j". # # MAKECMDGOALS contains the list of goals, e.g. "clean all". We use # "foreach" to generate a ";" separated list of "make -C ". # For example the the expansion for "make clean all" is: # # $(MAKE) -C $@ [..] clean ; $(MAKE) -C $@ [...] all ; # # This ensures that the commandline targets are serialized and also "make -j" # works as expected, e.g. "make clean all -j 20". # $(TOOL_DIRS): $(LIB_DIRS) $(foreach goal,$(MAKECMDGOALS), \ $(MAKE) -C $@ TOPDIR=$(TOPDIR) ARCH=$(ARCH) $(goal) ;) .PHONY: $(TOOL_DIRS) $(LIB_DIRS): $(foreach goal,$(MAKECMDGOALS), \ $(MAKE) -C $@ TOPDIR=$(TOPDIR) ARCH=$(ARCH) $(goal) ;) .PHONY: $(LIB_DIRS) s390-tools-2.3.0/README.md000066400000000000000000000344111323410431100146220ustar00rootroot00000000000000 s390-tools ========== The s390-tools package contains the source tree of a set of user space utilities for use with the s390 Linux kernel and device drivers. The package also contains the following files: * [CONTRIBUTING](CONTRIBUTING.md): Contribution guidelines * [LICENSE](LICENSE): The MIT license that applies to this package * [CHANGELOG](CHANGELOG.md): The history of s390-tools versions * [AUTHORS](AUTHORS.md): A list of all authors of the s390-tools package * [CODINGSTYLE](CODINGSTYLE.md): Recommendations for writing s390-tools code Package contents ---------------- * dasdfmt: Low-level format ECKD DASDs with the classical Linux disk layout or the new z/OS compatible disk layout. * fdasd: Create or modify partitions on ECKD DASDs formatted with the z/OS compatible disk layout. * dasdview: Display DASD and VTOC information or dump the contents of a DASD to the console. * dasdinfo: Display unique DASD ID, either UID or volser. * udev rules: - 59-dasd.rules: rules for unique DASD device nodes created in /dev/disk/. - 57-osasnmpd.rules: udev rules for osasnmpd. - 60-readahead.rules: udev rules to set increased "default max readahead". - 40-z90crypt.rules: udev rules for z90crypt driver - 90-cpi.rules: udev rule to update Control-Program-Information when KVM is used. * systemd units: - cpi.service: Unit to apply CPI settings - dumpconf.service: Unit to configure dump on panic for s390 - mon_fsstatd.service: Unit for mon_fsstatd - mon_procd.service: Unit for mon_procd - iucvtty-login@.service: Instance unit to manage iucvtty instances - ttyrun-getty@.service: Instance unit to manage ttyrun * zipl: Make DASDs or tapes bootable for system IPL or system dump. * zgetdump: Retrieve system dumps from either tapes or DASDs. * qetharp: Read and flush the ARP cache on OSA Express network cards. * tape390_display: Display information on the message display facility of a s390 tape device. * tape390_crypt: Control and query crypto settings for 3592 tape devices. * osasnmpd: NET-SNMP subagent implementing MIBs provided by OSA-Express features Fast Ethernet, Gigabit Ethernet, 10 Gigabit Ethernet. * qethconf: bash shell script simplifying the usage of qeth IPA (IP address takeover), VIPA (Virtual IP address) and Proxy ARP. * dbginfo.sh: Shell script collecting useful information about the current system for debugging purposes. * zfcpdump: Dump tool to create system dumps on fibre channel attached SCSI disk partitions. It is installed using the "zipl -d" command. * ip_watcher: Provides HiperSockets Network Concentrator functionality. It looks for addresses in the HiperSockets and sets them as Proxy ARP on the OSA cards. It also adds routing entries for all IP addresses configured on active HiperSockets devices. Use start_hsnc.sh to start HiperSockets Network Concentrator. * tunedasd: Adjust tunable parameters on DASD devices. * vmconvert: Convert system dumps created by the z/VM VMDUMP command into dumps with LKCD format. These LKCD dumps can then be analyzed with the dump analysis tool lcrash. * vmcp: Send commands from Linux as a z/VM guest to the z/VM control program (CP). Call vmcp with the CP command as an argument. The response of z/VM is written to the standard output. * vmur: Work with z/VM spool file queues (reader, punch, printer). * zfcpdbf: Display debug data of zfcp. zfcp provides traces via the s390 debug feature. Those traces are filtered with the zfcpdbf script, i.e. merge several traces, make it more readable etc. * 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. - chccwdev: Modify generic attributes of channel attached devices. - lscss: List channel subsystem devices. - lsdasd: List channel attached direct access storage devices (DASD). - lsqeth: List all qeth-based network devices with their corresponding settings. - lstape: List tape devices, both channel and FCP attached. - lszfcp: Show sysfs information about zfcp adapters, ports and units that are online. - lschp: List information about available channel-paths. - lsscm: List information about available Storage Class Memory Increments. - chchp: Modify channel-path state. - lsluns: List LUNs discovered in the FC SAN, or show encryption state of attached LUNs. - lszcrypt: Show Information about zcrypt devices and configuration. - chzcrypt: Modify the zcrypt configuration. - znetconf: List and configure network devices for s390 network adapters. - cio_ignore: Query and modify the contents of the CIO device driver blacklist. - dasdstat: Configure and format the debugfs based DASD statistics data. * zkey: Use the zkey tool to generate secure AES keys that are enciphered with a master key of an IBM cryptographic adapter in CCA coprocessor mode. You can also use the zkey tool to validate and re-encipher secure AES keys. * dumpconf: Configure the dump device used for system dump in case a kernel panic occurs. This tool can also be used by the "dumpconf" systemd unit or as System V init script in /etc/init.d. Prerequisite for dumpconf is a Linux kernel with the "dump on panic" feature. * mon_statd: Linux - z/VM monitoring daemons. - mon_fsstatd: Daemon that writes file system utilization data to the z/VM monitor stream. - mon_procd: Daemon that writes process information data to the z/VM monitor stream. * cpuplugd: Manages CPU and memory resources based on a set of rules. Depending on the workload, CPUs can be enabled or disabled. The amount of memory can be increased or decreased exploiting the CMM1 feature. * ipl_tools: Tool set to configure and list re-IPL and shutdown actions. - lsreipl: List information of re-IPL device. - chreipl: Change re-IPL device settings. - lsshut: List the actions that are configured as responses to of halt, poff, reboot or panic. - chshut: Change the actions that are to result from of halt, poff, reboot or panic. * ziomon tools: Tool set to collect data for zfcp performance analysis and report. * iucvterm: z/VM IUCV terminal applications. A set of applications to provide terminal access via the z/VM Inter-User Communication Vehicle (IUCV). The terminal access does not require an active TCP/IP connection between two Linux guest operating systems. - iucvconn: Application to establish a terminal connection via z/VM IUCV. - iucvtty: Application to provide terminal access via z/VM IUCV. - ts-shell: Terminal server shell to authorize and control IUCV terminal connections for individual Linux users. * ttyrun: Depending on your setup, Linux on s390 might or might not provide a particular terminal or console. The ttyrun tool safely starts getty programs and prevents respawns through the init program, if a terminal is not available. * cmsfs-fuse: Read and write files stored on a z/VM CMS disk. The cmsfs-fuse file system translates the record-based EDF file system on the CMS disk to UNIX semantics. It is possible to mount a CMS disk and use common Linux tools to access the files on the disk. * hmcdrvfs: Provide (read-only) access to files stored on a (assigned) DVD inserted in a HMC drive. The command creates a FUSE.HMCDRVFS filesystem at the specified mount point. The feature works with either the LPAR or the z/VM hypervisor. But note especially for z/VM that the DVD must be assigned to the associated system image (LPAR). * hyptop: Provide a dynamic real-time view of a s390 hypervisor environment. The tool works with the z/VM and LPAR hypervisor. Depending on the available data it shows e.g. CPU and memory consumption of active LPARs or z/VM virtual guests. The tool provides a curses based user interface similar to the popular Linux 'top' command. * qethqoat: Query the OSA address table and display physical and logical device information. * zdsfs: Mount a z/OS DASD as Linux file system. * CPU-measurement facilities (CPU-MF) tools: Use the lscpumf tool to display information about the CPU-measurement counter and sampling facilities. Use the chcpumf tool to control the sampling facility support. * cpacfstats: The cpacfstats tools provide a client/server application set to monitor and maintain CPACF activity counters. * zdev: Provides two tools to modify (chzdev) and display (lszdev) the persistent configuration of devices and device drivers that are specific to the s390 platform. * dump2tar: dump2tar is a tool for creating a tar archive from the contents of arbitrary files. It works even when the size of the actual file content is not known beforehand (e.g. FIFOs, sysfs files). * netboot: Provides simple tools to create a binary that can be used to implement simple network boot setups following the PXELINUX conventions. For more information refer to the following publications: * "Device Drivers, Features, and Commands" chapter "Useful Linux commands" * "Using the dump tools" Dependencies ------------ The s390-tools package has several build and runtime requirements. If your build system does not have the required support, you can disable parts of the s390-tools build with "`make HAVE_=0`", for example "`make HAVE_FUSE=0`". The following table provides an overview of the used libraries and build options: | __LIBRARY__ | __BUILD OPTION__ | __TOOLS__ | |----------------|:------------------:|:-------------------------------------:| | fuse | `HAVE_FUSE` | cmsfs-fuse, zdsfs, hmcdrvfs, zgetdump | | zlib | `HAVE_ZLIB` | zgetdump, dump2tar | | ncurses | `HAVE_NCURSES` | hyptop | | pfm | `HAVE_PFM` | cpacfstats | | net-snmp | `HAVE_SNMP` | osasnmpd | | glibc-static | `HAVE_LIBC_STATIC` | zfcpdump | This table lists additional build or install options: | __COMPONENT__ | __OPTION__ | __TOOLS__ | |----------------|:----------------:|:-------------------------------:| | dracut | `HAVE_DRACUT` | zdev | The s390-tools build process uses "pkg-config" if available and hard-coded compiler and linker options otherwise. Build and runtime requirements for specific tools ------------------------------------------------- In the following more details on the build an runtime requirements of the different tools are provided: * osasnmpd: You need at least the NET-SNMP 5.1.x package (net-snmp-devel.rpm) installed, before building the osasnmpd subagent. For more information on NET-SNMP refer to: http://net-snmp.sourceforge.net * lsluns: For executing the lsluns script the sg_luns and sg_inq commands must be available. The sg_luns and sg_inq executables are part of the SCSI generic device driver package (sg3 utils/sg utils). * ziomon tools: For running the ziomon tools the following tools/packages are required: - Packages: blktrace, multipath-tools, sg3-utils - Tools: rsync, tar, lsscsi * cmsfs-fuse/zdsfs/hmcdrvfs/zgetdump: The tools cmsfs-fuse, zdsfs, hmcdrvfs, and zgetdump depend on FUSE. FUSE is provided by installing the fuse and libfuse packages and by a kernel compiled with `CONFIG_FUSE_FS`. For compiling the s390-tools package the fuse-devel package is required. The cmsfs-fuse tool requires FUSE version 2.8.1 or newer for full functionality. For further information about FUSE see: http://fuse.sourceforge.net * hyptop: The ncurses-devel package is required to build hyptop. The libncurses package is required to run hyptop. IMPORTANT: When running hyptop on a System z10 LPAR, the required minimum microcode code level is the following: Driver 79 MCL N24404.008 in the SE-LPAR stream * ip_watcher/xcec-bridge: As of s390-tools-1.10.0, the minimum required kernel level is 2.6.26. For running ip_watcher these programs are required: - qetharp (s390-tools) - qethconf (s390-tools) - route (net-tools) * zfcpdbf: As of s390-tools-1.13.0, the minimum required kernel level is 2.6.38. * cpacfstats: For building the cpacfstats tools you need libpfm version 4 or newer installed (libpfm-devel.rpm). Tip: you may skip the cpacfstats build by adding `HAVE_PFN=0` to the make invocation. To run the cpacfstats daemon the kernel needs to have performance events enabled (check for `CONFIG_PERF_EVENTS=y`) and you need libpfm version 4 or newer installed. A new group 'cpacfstats' needs to be created and all users intending to use the tool should be added to this group. * zdev: Depending on the boot loader and initial RAM-disk mechanism used by a Linux distribution, specific steps may be required to make changes to the root device configuration persistent. zdev encapsulates this root device logic in a helper script called 'zdev-root-update': This script is invoked whenever the root device configuration is changed and it must ensure that the persistent root device configuration is put into effect during boot. zdev provides a sample implementation for zdev-root-update which can be selected during the 'make install' step using the `HAVE_DRACUT` variable: - `HAVE_DRACUT=1` installs a zdev-root-update helper that works with zipl as boot loader and dracut as initial RAM-disk provider. Distributors with different boot or RAM-disk mechanisms should provide a custom zdev-root-update helper script. Some functions of zdev require that the following programs are available: - modprobe (kmod) - udevadm (systemd) The following programs are not required but when available will improve the functionality of the zdev tools: - lsblk (util-linux) - findmnt (util-linux) - vmcp (s390-tools) - ip (iproute2) * znetconf: For running znetconf these programs are required: - modprobe (kmod) - vmcp (s390-tools) s390-tools-2.3.0/bin/000077500000000000000000000000001323410431100141105ustar00rootroot00000000000000s390-tools-2.3.0/bin/mk-authors000077500000000000000000000055721323410431100161410ustar00rootroot00000000000000#!/bin/bash # # mk-authors - Update the s390-tools authors file (AUTHORS.md) # # Invocation examples: # # $ mk-authors # $ mk-authors -t v2.0.0 # # Copyright IBM Corp. 2017 # # s390-tools is free software; you can redistribute it and/or modify # it under the terms of the MIT license. See LICENSE for details. cmd=$(basename $0) authors_file="AUTHORS.md" cleanup() { test -f $authors_file_tmp && rm -f $authors_file_tmp } trap cleanup EXIT # # Print usage information # print_usage() { cat <&2 exit 1 } # # Parse options # opts=$(getopt -o ht: -l help,tag: -n $cmd -- "$@") if [ $? != 0 ]; then print_parse_error_and_exit fi # Use eval to remove getopt quotes eval set -- $opts while [ -n $1 ]; do case $1 in -h | --help) print_usage exit 0 ;; -t | --tag) release_tag_last=$2 shift 2 ;; --) shift break ;; *) break ;; esac done if [ $# != 0 ]; then echo "$cmd: Invalid argument $1" >&2 print_parse_error_and_exit fi # # Get git root and last release tag # git_root=$(git rev-parse --show-toplevel 2> /dev/null) if [ $? -ne 0 ]; then echo "$cmd: Use tool within s390-tools git repository" >&2 print_parse_error_and_exit fi authors_file_path=$git_root/$authors_file # Get last release tag if not specified via -t if [ -z $release_tag_last ]; then tag_commit=$(git rev-list --tags --max-count=1) release_tag_last=$(git describe --tags $tag_commit) fi # Verify release tag git show $release_tag_last &> /dev/null if [ $? -ne 0 ]; then echo "$cmd: Cannot find tag '$release_tag_last'" >&2 print_parse_error_and_exit fi # # Update authors file # # Create temporary file for new author list authors_file_tmp=$(mktemp /tmp/authors.XXXXXX) echo "$cmd: Adding new authors since tag: $release_tag_last" { # Print authors list without header tail -n +4 $authors_file_path # Add the new authors git log --format="- %an" $release_tag_last.. # Then sort everything and remove duplicates } | sort | uniq > $authors_file_tmp # Create new AUTHORS file with header ... cat < $authors_file_path List of all individuals having contributed content to s390-tools ---------------------------------------------------------------- EoHEADER # ... and add new content cat $authors_file_tmp >> $authors_file_path cat < #include #include #include #include #include #include "lib/zt_common.h" #include "cmsfs-fuse.h" #include "edf.h" #include "helper.h" /* * Hint where to look for the next free block (level 0 only). * Updated if a free block is found. If the level 0 amap bitmap * block is exhausted we still scan all amap blocks. */ struct amap_alloction_hint { /* addr of amap bitmap block to check */ off_t amap_addr; /* disk addr of the last allocated or freed block */ off_t addr; /* offset to start of the amap data block */ off_t offset; }; static struct amap_alloction_hint amap_hint; static void update_amap_hint(off_t amap_addr, off_t addr) { amap_hint.amap_addr = amap_addr; amap_hint.addr = addr; } /* * Get L1 block number from address. */ static int amap_blocknumber(off_t addr) { return addr / BYTES_PER_BLOCK; } /* * Get the block number for a specific level. */ static int amap_blocknumber_level(int level, off_t addr) { int entry = amap_blocknumber(addr); while (level-- > 1) entry /= PTRS_PER_BLOCK; return entry; } /* * Return address of to the allocation map for a block number > 0. */ static off_t get_amap_addr(int level, off_t addr, off_t ptr) { int block = amap_blocknumber_level(level, addr) % PTRS_PER_BLOCK; if (cmsfs.amap_levels == 0) return cmsfs.amap; if (level--) { ptr = get_fixed_pointer(ptr + (off_t) block * PTR_SIZE); if (!ptr) DIE("amap invalid ptr at addr: %llx\n", (unsigned long long) ptr + (off_t) block * PTR_SIZE); return get_amap_addr(level, addr, ptr); } return ptr; } /* * Mark disk address as allocated in alloc map. */ static void amap_block_set(off_t amap, int bit) { u8 entry; int rc; rc = _read(&entry, sizeof(entry), amap); BUG(rc < 0); /* already used */ BUG(entry & (1 << (7 - bit))); entry |= (1 << (7 - bit)); rc = _write(&entry, sizeof(entry), amap); BUG(rc < 0); } /* * Mark disk address as free in alloc map. Unaligned addr is tolerated. */ static void amap_block_clear(off_t addr) { off_t amap = get_amap_addr(cmsfs.amap_levels, addr, cmsfs.amap); int rc, block = amap_blocknumber(addr); off_t disk_addr = addr; unsigned int byte, bit; u8 entry; if (block > 0) addr -= (off_t) block * BYTES_PER_BLOCK; addr >>= BITS_PER_DATA_BLOCK; byte = addr / 8; bit = addr % 8; rc = _read(&entry, sizeof(entry), amap + byte); BUG(rc < 0); /* already cleared */ BUG(!(entry & (1 << (7 - bit)))); entry &= ~(1 << (7 - bit)); rc = _write(&entry, sizeof(entry), amap + byte); BUG(rc < 0); /* * If the freed addr is lower set the hint to it to ensure * the amap bitmap is packed from the start. That way we do not * need an extra check if the bitmap entry is above disk end, the * check if we overflow the total block limit is sufficient. */ if (disk_addr < amap_hint.addr) update_amap_hint(amap + byte, disk_addr); } /* * Return the first free bit in one byte. */ static inline int find_first_empty_bit(u8 entry) { u8 i; for (i = 0; i < 8; i++) if (!(entry & 1 << (7 - i))) return i; /* unreachable */ return -1; } /* * Return the number of bytes addressed by one pointer entry for the * specified level. */ static off_t bytes_per_level(int level) { off_t mult = BYTES_PER_BLOCK; if (!level) return 0; level--; while (level--) mult *= PTRS_PER_BLOCK; return mult; } static inline int get_amap_entry_bit(off_t amap) { u8 entry; int rc; rc = _read(&entry, sizeof(entry), amap); BUG(rc < 0); if (entry == 0xff) return -1; return find_first_empty_bit(entry); } static off_t __get_free_block_fast(void) { off_t addr, amap = amap_hint.amap_addr & ~DATA_BLOCK_MASK; int bit, i = amap_hint.amap_addr & DATA_BLOCK_MASK; for (; i < cmsfs.blksize; i++) { bit = get_amap_entry_bit(amap + i); if (bit == -1) continue; /* Calculate the addr for the free block we've found. */ addr = (off_t) amap_blocknumber(amap_hint.addr) * BYTES_PER_BLOCK; addr += i * 8 * cmsfs.blksize; addr += bit * cmsfs.blksize; amap_block_set(amap + i, bit); update_amap_hint(amap + i, addr); return addr; } return 0; } /* * Look for the first unallocated block and return addr of allocated block. */ static off_t __get_free_block(int level, off_t amap, off_t addr) { off_t ptr; int bit, i; if (level > 0) { for (i = 0; i < PTRS_PER_BLOCK; i++) { ptr = get_fixed_pointer(amap); if (!ptr) return 0; ptr = __get_free_block(level - 1, ptr, addr + i * bytes_per_level(level)); if (ptr) return ptr; amap += PTR_SIZE; } return 0; } for (i = 0; i < cmsfs.blksize; i++) { bit = get_amap_entry_bit(amap + i); if (bit == -1) continue; amap_block_set(amap + i, bit); /* add byte offset */ addr += i * 8 * cmsfs.blksize; /* add bit offset */ addr += bit * cmsfs.blksize; update_amap_hint(amap + i, addr); return addr; } return 0; } /* * Allocate a free block and increment label block counter. */ off_t get_free_block(void) { off_t addr = 0; if (cmsfs.used_blocks + cmsfs.reserved_blocks >= cmsfs.total_blocks) return -ENOSPC; if (amap_hint.amap_addr) addr = __get_free_block_fast(); if (!addr) addr = __get_free_block(cmsfs.amap_levels, cmsfs.amap, 0); BUG(!addr); cmsfs.used_blocks++; return addr; } /* * Allocate a zero-filled block and increment label block counter. */ off_t get_zero_block(void) { off_t addr = get_free_block(); int rc; if (addr < 0) return -ENOSPC; rc = _zero(addr, cmsfs.blksize); if (rc < 0) return rc; return addr; } /* * Free a block and decrement label block counter. */ void free_block(off_t addr) { if (addr) { amap_block_clear(addr); cmsfs.used_blocks--; } } s390-tools-2.3.0/cmsfs-fuse/cmsfs-fuse.1000066400000000000000000000161071323410431100175550ustar00rootroot00000000000000.\" Copyright IBM Corp. 2010, 2017 .\" s390-tools is free software; you can redistribute it and/or modify .\" it under the terms of the MIT license. See LICENSE for details. .\" .TH CMSFS-FUSE 1 "February 2010" "s390-tools" .SH NAME cmsfs-fuse \- File system for z/VM CMS disks .SH SYNOPSIS .SS mounting: .TP \fBcmsfs-fuse\fP DEVICE MOUNTPOINT [OPTIONS] .SS unmounting: .TP \fBfusermount\fP -u MOUNTPOINT .SH DESCRIPTION Use the \fBcmsfs-fuse\fP command to provide read and write access to files stored on a z/VM CMS disk. The cmsfs-fuse file system translates the record-based EDF file system on the CMS disk to UNIX semantics. After mounting the CMS disk, you can use common Linux tools to access the files on the disk. You can enable automatic conversions of text files from EBCDIC to ASCII. Attention: You can inadvertently damage files and lose data when directly writing to files within the cmsfs-fuse file system. To avoid problems when writing, multiple restrictions must be observed, especially with regard to linefeeds (see section RESTRICTIONS). If you are unsure about how to safely write to a file on the cmsfs-fuse file system, copy the file to a location outside the cmsfs-fuse file system, edit the file, and then copy it back to its original location. .SH OPTIONS .SS "general options:" .TP \fB\-o\fR opt,[opt...] Fuse or mount command options. For fuse options see below, for mount options see \fBmount(8)\fP. .TP \fB\-h\fR or \fB\-\-help\fR Print usage information, then exit. .TP \fB\-v\fR or \fB\-\-version\fR Print version information, then exit. .SS "cmsfs-fuse options:" .TP \fB\-a\fR or \fB\-\-ascii\fR Interpret all files on the CMS disk as text files and convert them from EBCDIC to ASCII. .TP \fB--from\fR The codepage of the files on the CMS disk. If this option is not specified the default codepage CP1047 is used. For a list of all available codepages see iconv --list. .TP \fB--to\fR The codepage to which CMS files should be converted to. If this option is not specified the default codepage ISO-8859-1 is used. For a list of all available codepages see iconv --list. .TP \fB\-t\fR or \fB\-\-filetype\fR Interpret files on the CMS disk as text files based on the file type and convert them from EBCDIC to ASCII. The file types that are treated as text files are taken from a configuration file (see section CONFIGURATION FILES). .SS "Applicable FUSE options (version 2.8):" .TP \fB\-d\fR or \fB\-o\fR debug Enable debug output (implies \fB\-f\fR) .TP \fB\-f\fR Foreground operation .TP \fB\-o\fR allow_other Allow access by other users .TP \fB\-o\fR allow_root Allow access by root .TP \fB\-o\fR nonempty Allow mounts over non\-empty file/dir .TP \fB\-o\fR default_permissions Enable permission checking by kernel .TP .TP \fB\-o\fR max_read=N Set maximum size of read requests .TP \fB\-o\fR kernel_cache Cache files in kernel .TP \fB\-o\fR [no]auto_cache Enable caching based on modification times .TP \fB\-o\fR umask=M Set file permissions (octal) .TP \fB\-o\fR uid=N Set file owner .TP \fB\-o\fR gid=N Set file group .TP \fB\-o\fR max_write=N Set maximum size of write requests .TP \fB\-o\fR max_readahead=N Set maximum readahead .TP \fB\-o\fR async_read Perform reads asynchronously (default) .TP \fB\-o\fR sync_read Perform reads synchronously .TP \fB\-o big_writes\fR Enable write operations with more than 4 KB .SH EXTENDED ATTRIBUTES Use the following extended attributes to handle the CMS characteristics of a file: \fBuser.record_format\fR: The format of a file. Allowed values are F for fixed record length files and V for variable record length files. This attribute can be set only if the file is empty. \fBuser.record_lrecl\fR: The record length of a file. This attribute can be set only for a fixed record length file and if the file is empty. A valid record length is an integer in the range 1-65535. \fBuser.file_mode\fR: The file mode of a file which is interpreted by CMS. The file mode consists of a mode letter from A-Z and mode number from 0-6. New files are created by default as variable files with file mode A1. .SH RESTRICTIONS \fBrename\fR and \fBcreat\fR: Uppercase file names are enforced. \fBtruncate\fR: Only shrinking of a file is supported. For fixed length record files, the new file size must be a multiple of the record length. \fBunlink\fR: Creating a file with the name of a previously unlinked file which is still in use is not supported and will fail with -ENOENT. \fBwrite\fR: Writes are supported only at the end of the file. A write on a fixed length record file always writes a multiple of the record length. If additional bytes are added, the bytes are filled with zero in binary mode or with spaces in ASCII mode. Sparse files are not supported. If the cp tool is used to write files to a CMS disk the option "--sparse=never" must be specified. If ASCII translation is enabled for a file a linefeed character determines the end of a record. The following restrictions must be observed for writing files in ASCII mode: For fixed record length files a linefeed must occur exactly after a record of the length specified in the fixed record length. For variable record length files a linefeed must occur after the maximum record length is reached or earlier. If a record of a variable record length file consists only of a linefeed character cmsfs-fuse adds a space to this record since empty records are not supported by the CMS file system. .SH CONFIGURATION FILES cmsfs-fuse uses a configuration file for automatic translation based on the file type. Upon startup, cmsfs-fuse evaluates the file .cmsfs-fuse/filetypes.conf in the user's home directory. If the file does not exist cmsfs-fuse evaluates the file /etc/cmsfs-fuse/filetypes.conf. The filetypes.conf file contains the CMS file types that are automaticaly translated to ASCII if cmsfs-fuse is started with the -t option. The syntax of the configuration file is one file type per line. Lines that start with a # followed by a space are treated as comments and are ignored. The file type is 8 characters long and must consist of valid CMS file name characters only. The default file types in the configuration file were taken from the z/VM TCPIP.DATA file (z/VM version 5.4.0). .SH EXAMPLES To mount the CMS disk with the name dasde enter: .br # cmsfs-fuse /dev/dasde /mnt .br To mount the CMS disk with the name dasde and enable automatic translation of known text files enter: .br # cmsfs-fuse -t /dev/dasde /mnt To mount the CMS disk with the name dasde and enable automatic translation of all files to UTF-8 enter: .br # cmsfs-fuse --to=UTF-8 -a /dev/dasde /mnt To unmount the CMS disk mounted on /mnt enter: .br # fusermount -u /mnt To show the record format of file PROFILE.EXEC assuming the CMS disk was mounted on /mnt: # getfattr -n user.record_format /mnt/PROFILE.EXEC The following example assumes that an empty, fixed record format file, PROFILE.EXEC, can be accessed on a CMS disk that has been mounted on /mnt. To set the record length of PROFILE.EXEC to 80 bytes: # setfattr -n user.record_lrecl -v 80 /mnt/PROFILE.EXEC .SH SEE ALSO attr (5), getfattr (1), setfattr(1), iconv(1) and Linux on System z: Device Drivers, Features and Commands s390-tools-2.3.0/cmsfs-fuse/cmsfs-fuse.c000066400000000000000000003125601323410431100176410ustar00rootroot00000000000000/* * cmsfs-fuse - CMS EDF filesystem support for Linux * * Main function * * Copyright IBM Corp. 2010, 2017 * * s390-tools is free software; you can redistribute it and/or modify * it under the terms of the MIT license. See LICENSE for details. */ #define FUSE_USE_VERSION 26 #include #include #include #include #include #include #include #include #ifdef HAVE_SETXATTR #include #endif #include #include #include #include #include #include #include #include #include #include #include #include #include #include "lib/util_base.h" #include "lib/util_list.h" #include "lib/zt_common.h" #include "cmsfs-fuse.h" #include "ebcdic.h" #include "edf.h" #include "helper.h" struct cmsfs cmsfs; static struct util_list open_file_list; static struct util_list text_type_list; FILE *logfile; #define FSNAME_MAX_LEN 200 #define MAX_FNAME 18 #define CMSFS_OPT(t, p, v) { t, offsetof(struct cmsfs, p), v } enum { KEY_HELP, KEY_VERSION, }; static const struct fuse_opt cmsfs_opts[] = { CMSFS_OPT("-a", mode, TEXT_MODE), CMSFS_OPT("--ascii", mode, TEXT_MODE), CMSFS_OPT("-t", mode, TYPE_MODE), CMSFS_OPT("--filetype", mode, TYPE_MODE), CMSFS_OPT("--from=%s", codepage_from, 0), CMSFS_OPT("--to=%s", codepage_to, 0), FUSE_OPT_KEY("-h", KEY_HELP), FUSE_OPT_KEY("--help", KEY_HELP), FUSE_OPT_KEY("-v", KEY_VERSION), FUSE_OPT_KEY("--version", KEY_VERSION), FUSE_OPT_END }; static void usage(const char *progname) { fprintf(stderr, "Usage: %s DEVICE MOUNTPOINT [OPTIONS]\n" "\n" "Use the cmsfs-fuse command to read and write files stored on a z/VM CMS disk.\n" "\n" "General options:\n" " -o opt,[opt...] Mount options\n" " -h --help Print help, then exit\n" " -v --version Print version, then exit\n" " -t --filetype ASCII translation based on file type\n" " -a --ascii Force ascii translation\n" " --from= Codepage used on the CMS disk\n" " --to= Codepage used for conversion to Linux\n" "\n", progname); } static char CODEPAGE_EDF[] = "CP1047"; static char CODEPAGE_LINUX[] = "ISO-8859-1"; #define READDIR_FILE_ENTRY -1 #define READDIR_END_OF_DIR -2 #define READDIR_DIR_ENTRY -3 #define READDIR_MAP_ENTRY -4 #define LINEFEED_OFFSET ((struct record *) -1) #define LINEFEED_ASCII 0xa #define LINEFEED_EBCDIC 0x25 #define LINEFEED_NOT_FOUND -1 #define FILLER_EBCDIC 0x40 #define FILLER_ASCII 0x20 #define RSS_HEADER_STARTED 0x1 #define RSS_HEADER_COMPLETE 0x2 #define RSS_DATA_BLOCK_STARTED 0x4 #define RSS_DATA_BLOCK_EXT 0x8 #define RWS_HEADER_STARTED 0x1 #define RWS_HEADER_COMPLETE 0x2 #define RWS_RECORD_INCOMPLETE 0x4 #define RWS_RECORD_COMPLETE 0x8 #define BWS_BLOCK_NOT_INIT 0x0 #define BWS_BLOCK_NEW 0x1 #define BWS_BLOCK_USED 0x2 #define WCACHE_MAX (MAX_RECORD_LEN + 1) struct block { off_t disk_addr; unsigned int disp; int hi_record_nr; }; struct record_ext { /* start addr of the extension */ off_t disk_start; /* length of extension in this disk block */ int len; /* null block start flag */ int null_block_started; /* corresponding disk block number */ int block_nr; struct record_ext *prev; struct record_ext *next; }; struct record { /* length of the complete record */ unsigned int total_len; /* offset of first record block on the disk */ off_t disk_start; /* bytes in first record block */ int first_block_len; /* logical offset, dependent on line feed mode */ off_t file_start; /* null block start flag */ int null_block_started; /* spanned record extension */ struct record_ext *ext; /* corresponding disk block number */ int block_nr; }; struct file; struct file_operations { int (*cache_data) (struct file *f, off_t addr, int level, int *block, unsigned int *disp, int *record, off_t *total); int (*write_data) (struct file *f, const char *buf, int len, size_t size, int rlen); int (*delete_pointers) (struct file *f, int level, off_t addr); int (*write_pointers) (struct file *f, int level, off_t dst, int offset); }; static struct file_operations fops_fixed; static struct file_operations fops_variable; struct io_operations { int (*read) (void *buf, size_t size, off_t addr); int (*write) (const void *buf, size_t size, off_t addr); }; static struct io_operations io_ops; struct write_state { int block_state; /* number of free bytes in the current block */ int block_free; int var_record_state; /* remaining record bytes for a write request */ int var_record_len; /* only used for var records hi_record_nr by now */ int var_records_written; }; /* * File object for operations that follow open */ struct file { /* pointer to the fst entry */ struct fst_entry *fst; /* fst address on disk */ off_t fst_addr; /* translate mode enabled */ int translate; /* linefeed mode enabled */ int linefeed; /* list of records */ struct record *rlist; /* record scan state machine flag */ int record_scan_state; /* next record for sequential reads */ int next_record_hint; /* counter for null bytes to detect block start */ int null_ctr; /* list of disk blocks */ struct block *blist; /* disk address of next byte to write */ off_t write_ptr; /* the filesize while the file is opened */ off_t session_size; /* number of null blocks for fixed files */ int nr_null_blocks; /* number of written padding bytes for a fixed file */ int pad_bytes; /* old levels value, needed to rewrite pointers */ int old_levels; /* state information needed for a write request */ struct write_state *wstate; /* path name for open and unlink */ char path[MAX_FNAME + 1]; /* counter for pseudo null length records */ int null_records; /* write cache for text mode */ char *wcache; /* used bytes in write cache */ int wcache_used; /* committed written bytes to FUSE */ int wcache_commited; /* dirty flag for file meta data */ int ptr_dirty; /* fops pointers */ struct file_operations *fops; /* pointers per block constant */ int ptr_per_block; /* open list head */ struct util_list_node list; /* usage counter for all openers */ int use_count; /* usage counter for all writers */ int write_count; /* unlink flag */ int unlinked; }; struct xattr { char name[20]; size_t size; }; /* * Record format: 'F' (fixed) or 'V' (variable), 1 byte * Record lrecl: 0-65535, 5 bytes * Record mode: [A-Z][0-6], 2 bytes */ static struct xattr xattr_format = { .name = "user.record_format", .size = 1 }; static struct xattr xattr_lrecl = { .name = "user.record_lrecl", .size = 5 }; static struct xattr xattr_mode = { .name = "user.file_mode", .size = 2 }; #define SHOW_UNLINKED 0 #define HIDE_UNLINKED 1 #define WALK_FLAG_LOOKUP 0x1 #define WALK_FLAG_READDIR 0x2 #define WALK_FLAG_LOCATE_EMPTY 0x4 #define WALK_FLAG_CACHE_DBLOCKS 0x8 struct walk_file { int flag; char *name; char *type; void *buf; off_t addr; fuse_fill_dir_t filler; off_t *dlist; int dlist_used; }; /* * Prototypes */ static struct file *create_file_object(struct fst_entry *fst, int *rc); static void destroy_file_object(struct file *f); static unsigned long dec_to_hex(unsigned long long num) { unsigned long res; asm volatile("cvb %0,%1" : "=d" (res) : "m" (num)); return res & 0xffffffff; } static unsigned int hex_to_dec(unsigned int num) { unsigned long long res; asm volatile("cvd %1,%0" : "=m" (res) : "d" (num)); return res & 0xffffffff; } static void setup_iconv(iconv_t *conv, const char *from, const char *to) { *conv = iconv_open(to, from); if (*conv == ((iconv_t) -1)) DIE("Could not initialize conversion table %s->%s.\n", from, to); } static inline struct file *get_fobj(struct fuse_file_info *fi) { return (struct file *)(unsigned long) fi->fh; } static int access_ok(size_t size, off_t addr) { if (((addr + (off_t) size - 1) & ~DATA_BLOCK_MASK) > (addr & ~DATA_BLOCK_MASK)) DIE("crossing disk blocks, addr: %x size: %d\n", (int) addr, (int) size); if ((unsigned long long) addr < (unsigned long long) cmsfs.label || addr > cmsfs.size) return 0; return 1; } static int read_memory(void *buf, size_t size, off_t addr) { memcpy(buf, cmsfs.map + addr, size); return 0; } static int read_syscall(void *buf, size_t size, off_t addr) { int rc; rc = pread(cmsfs.fd, buf, size, addr); if (rc < 0) perror("pread failed"); return rc; } int _read(void *buf, size_t size, off_t addr) { if (!access_ok(size, addr)) return -EIO; return io_ops.read(buf, size, addr); } static int write_syscall(const void *buf, size_t size, off_t addr) { char *zbuf; int rc; if (buf == NULL) { zbuf = malloc(size); if (zbuf == NULL) return -ENOMEM; memset(zbuf, 0, size); rc = pwrite(cmsfs.fd, zbuf, size, addr); free(zbuf); } else rc = pwrite(cmsfs.fd, buf, size, addr); if (rc < 0) perror("pwrite failed"); return rc; } static int write_memory(const void *buf, size_t size, off_t addr) { if (buf == NULL) memset(cmsfs.map + addr, 0, size); else memcpy(cmsfs.map + addr, buf, size); return 0; } int _write(const void *buf, size_t size, off_t addr) { if (!access_ok(size, addr)) return -EIO; return io_ops.write(buf, size, addr); } int _zero(off_t addr, size_t size) { return _write(NULL, size, addr); } static off_t get_filled_block(void) { off_t addr = get_free_block(); if (addr < 0) return -ENOSPC; memset(cmsfs.map + addr, FILLER_EBCDIC, cmsfs.blksize); return addr; } static int get_fop(off_t addr) { struct fst_entry fst; int rc; rc = _read(&fst, sizeof(fst), addr); BUG(rc < 0); return ABS(fst.fop); } static int get_levels(off_t addr) { struct fst_entry fst; int rc; rc = _read(&fst, sizeof(fst), addr); BUG(rc < 0); return fst.levels; } static int get_files_count(off_t addr) { struct fst_entry fst; int rc; rc = _read(&fst, sizeof(fst), addr); BUG(rc < 0); /* ignore director and allocmap entries */ return fst.nr_records - 2; } static int get_order(int shift) { int count = 0; while (!(shift & 0x1)) { shift >>= 1; count++; } return count; } /* * Read pointer from fixed size pointer block and return * absolute address on disk. */ off_t get_fixed_pointer(off_t addr) { struct fixed_ptr ptr; int rc; if (!addr) return NULL_BLOCK; rc = _read(&ptr, sizeof(ptr), addr); if (rc < 0) return -EIO; if (!ptr.next) return NULL_BLOCK; else return ABS((off_t)ptr.next); } /* * Read variable pointer from block and return absolute address on disk * and highest record number. */ static off_t get_var_pointer(off_t addr, int *max_record, unsigned int *disp) { struct var_ptr vptr; off_t ptr = 0; int rc; BUG(!addr); rc = _read(&vptr, VPTR_SIZE, addr); if (rc < 0) return -EIO; ptr = (off_t) vptr.next; *max_record = vptr.hi_record_nr; *disp = vptr.disp; if (!ptr) { if (vptr.hi_record_nr) return NULL_BLOCK; else return VAR_FILE_END; } else return ABS(ptr); } int is_edf_char(int c) { switch (c) { case 'A' ... 'Z': break; case 'a' ... 'z': break; case '0' ... '9': break; case '#': break; case '@': break; case '+': break; case '$': break; case '-': break; case ':': break; case '_': break; default: return 0; } return 1; } /* * Force conversion to upper case since lower case file names although * valid are not accepted by many CMS tools. */ static void str_toupper(char *str) { int i; for (i = 0; i < (int) strlen(str); i++) str[i] = toupper(str[i]); } /* * Set the FST date to the specified date. */ static void update_fst_date(struct fst_entry *fst, struct tm *tm) { unsigned int num; int i; if (tm->tm_year >= 100) fst->flag |= FST_FLAG_CENTURY; else fst->flag &= ~FST_FLAG_CENTURY; fst->date[0] = tm->tm_year; fst->date[1] = tm->tm_mon + 1; fst->date[2] = tm->tm_mday; fst->date[3] = tm->tm_hour; fst->date[4] = tm->tm_min; fst->date[5] = tm->tm_sec; /* convert hex to decimal */ for (i = 0; i < 6; i++) { num = fst->date[i]; num = hex_to_dec(num); fst->date[i] = num >> 4; } } /* * Set the FST date to the current date. */ static int set_fst_date_current(struct fst_entry *fst) { struct timeval tv; struct tm tm; /* convert timespec to tm */ memset(&tm, 0, sizeof(struct tm)); if (gettimeofday(&tv, NULL) < 0) { perror(COMP "gettimeofday failed"); return -EINVAL; } if (localtime_r(&tv.tv_sec, &tm) == NULL) return -EINVAL; update_fst_date(fst, &tm); return 0; } /* * Check if the file is on the opened list. */ static struct file *file_open(const char *name) { char uc_name[MAX_FNAME]; struct file *f; strncpy(uc_name, name, MAX_FNAME); str_toupper(uc_name); util_list_iterate(&open_file_list, f) if (strncmp(f->path + 1, uc_name, MAX_FNAME) == 0) return f; return NULL; } /* * Check if the file is open and unlinked. */ static int file_unlinked(const char *name) { struct file *f = file_open(name); if (f && f->unlinked) return 1; else return 0; } /* * Convert EDF date to time_t. */ static time_t fst_date_to_time_t(char *date, int century) { unsigned long long num; unsigned int res[6]; struct tm tm; time_t time; int i; /* * date : YY MM DD HH MM SS (decimal!) * century: 0=19, 1=20, dead=21 * convert decimal to hex */ for (i = 0; i < 6; i++) { num = date[i]; num <<= 4; num += 0xc; /* plus */ res[i] = dec_to_hex(num); } memset(&tm, 0, sizeof(tm)); tm.tm_year = res[0]; tm.tm_mon = res[1]; tm.tm_mday = res[2]; tm.tm_hour = res[3]; tm.tm_min = res[4]; tm.tm_sec = res[5]; /* see man 3 tzset */ tm.tm_isdst = -1; /* prepare for mktime */ tm.tm_mon--; if (century == FST_FLAG_CENTURY) tm.tm_year += 100; time = mktime(&tm); if (time == -1) { fprintf(stderr, COMP "mktime failed!\n"); memset(&time, 0, sizeof(time)); } return time; } /* * Read one FST entry into *fst from offset on disk addr and detect type. * * Return values: * ret > 0 : disk address of additional FOP block * ret = -1 : file entry filled * ret = -2 : end of directory * ret = -3 : directory entry * ret = -4 : allocmap entry */ static int readdir_entry(struct fst_entry *fst, off_t addr) { int rc; BUG(addr & (sizeof(struct fst_entry) - 1)); rc = _read(fst, sizeof(*fst), addr); BUG(rc < 0); if (is_directory(fst->name, fst->type)) { /* check for multi-block directory */ if (ABS(fst->fop) != addr) return ABS(fst->fop); return READDIR_DIR_ENTRY; } if (is_allocmap(fst->name, fst->type)) return READDIR_MAP_ENTRY; if (is_file(fst->name, fst->type)) return READDIR_FILE_ENTRY; return READDIR_END_OF_DIR; } /* * Return number of characters excluding trailing spaces. */ static inline int strip_right(const char *str, int size) { while (str[size - 1] == 0x20) size--; return size; } /* * Convert ASCII name to EBCDIC name. */ static int encode_edf_name(const char *name, char *fname, char *ftype) { int dot_pos, tlen; char *tmp; /* * name is ascii string "FILE.EXT" * readdir_entry returns fst.name fst.type as EBCDIC including spaces * pre-fill name and type with ascii spaces, remove dot and convert * to EBCDIC. */ memset(fname, 0x20, 8); memset(ftype, 0x20, 8); tmp = index(name, '.'); /* filenames without a dot are invalid! */ if (tmp == NULL) return -EINVAL; dot_pos = tmp - name; if (dot_pos == 0 || dot_pos > 8) return -EINVAL; memcpy(fname, name, dot_pos); ebcdic_enc(fname, fname, 8); tlen = strlen(name) - (dot_pos + 1); if (tlen == 0 || tlen > 8) return -EINVAL; memcpy(ftype, name + dot_pos + 1, tlen); ebcdic_enc(ftype, ftype, 8); return 0; } /* * Convert EBCDIC name to ASCII name. */ static void decode_edf_name(char *file, char *fname, char *ftype) { int len, pos = 0; ebcdic_dec(fname, fname, 8); ebcdic_dec(ftype, ftype, 8); /* strip spaces but only from the end */ len = strip_right(fname, 8); memcpy(file, fname, len); /* add dot */ pos += len; file[pos] = '.'; pos++; len = strip_right(ftype, 8); memcpy(&file[pos], ftype, len); pos += len; /* terminate string */ file[pos] ='\0'; } static int edf_name_valid(const char *name) { int name_len, i; char *dot; /* name must contain . */ dot = index(name, '.'); if (dot == NULL) return -EINVAL; name_len = dot - name; for (i = 0; i < name_len; i++) if (!is_edf_char(name[i])) return -EINVAL; for (i = name_len + 1; i < (int) strlen(name); i++) if (!is_edf_char(name[i])) return -EINVAL; return 0; } /* * Summarize the number of bytes used in the last data block. */ static int walk_last_var_data_block(off_t addr, off_t *total) { ssize_t left = cmsfs.blksize; u16 len; int rc; /* subtract displacement */ left -= addr & DATA_BLOCK_MASK; while (left >= (int) sizeof(len)) { rc = _read(&len, sizeof(len), addr); if (rc < 0) return rc; /* * Null length means no more records follow. * Assumption: the last block is zero-padded. */ if (!len) return 0; /* add length of record with the header length */ *total += len + sizeof(len); left -= len + sizeof(len); /* point to next record */ addr += len + sizeof(len); } return 0; } /* * Return struct record for record number nr. */ static struct record *get_record(struct file *f, int nr) { BUG(nr > f->fst->nr_records - 1); return &f->rlist[nr]; } static int skip_header_byte(struct file *f) { if (f->fst->record_format == RECORD_LEN_FIXED) return 0; if (f->record_scan_state == RSS_HEADER_STARTED) return 1; else return 0; } static void set_record_len_upper(struct file *f, int record, u8 len) { struct record *r = &f->rlist[record]; if (f->record_scan_state != RSS_DATA_BLOCK_STARTED && f->record_scan_state != RSS_DATA_BLOCK_EXT) DIE("%s: internal error\n", __func__); r->total_len = len << 8; f->record_scan_state = RSS_HEADER_STARTED; } static void set_record_len_lower(struct file *f, int record, u8 len) { struct record *r = &f->rlist[record]; if (f->record_scan_state != RSS_HEADER_STARTED) DIE("%s: internal error\n", __func__); r->total_len += len; f->record_scan_state = RSS_HEADER_COMPLETE; } static void set_record_len(struct file *f, int record, u16 len) { struct record *r = &f->rlist[record]; if (f->fst->nr_records && f->fst->nr_records == record) DIE("%s: record nr: %d out of bounds\n", __func__, record); if (f->record_scan_state != RSS_DATA_BLOCK_STARTED && f->record_scan_state != RSS_DATA_BLOCK_EXT) DIE("%s: internal error\n", __func__); r->total_len = len; f->record_scan_state = RSS_HEADER_COMPLETE; } static void set_record(struct file *f, int *record, off_t addr, int len, off_t *total, int block) { struct record *r = &f->rlist[*record]; if (f->record_scan_state != RSS_HEADER_COMPLETE) DIE("%s: internal error\n", __func__); r->first_block_len = len; r->disk_start = addr; r->block_nr = block; if (addr == NULL_BLOCK) { if (f->null_ctr % cmsfs.blksize == 0) r->null_block_started = 1; f->null_ctr += len; } else f->null_ctr = 0; /* add previous record linefeed but not for the first record */ if (f->linefeed && *record) (*total)++; r->file_start = *total; (*total) += r->total_len; f->record_scan_state = RSS_DATA_BLOCK_STARTED; } static void add_record_ext(struct record *rec, struct record_ext *ext) { struct record_ext *tmp; int i = 0; if (rec->ext == NULL) { rec->ext = ext; ext->prev = NULL; ext->next = NULL; } else { tmp = rec->ext; i++; while (tmp->next != NULL) { i++; tmp = tmp->next; } tmp->next = ext; ext->prev = tmp; ext->next = NULL; } } static void set_record_extension(struct file *f, int *record, off_t addr, int len, int block) { struct record *rec = &f->rlist[*record]; struct record_ext *ext; if (f->record_scan_state != RSS_DATA_BLOCK_STARTED && f->record_scan_state != RSS_DATA_BLOCK_EXT) DIE("%s: interal error\n", __func__); BUG(*record >= f->fst->nr_records); ext = malloc(sizeof(struct record_ext)); if (ext == NULL) DIE_PERROR("malloc failed\n"); memset(ext, 0, sizeof(*ext)); ext->len = len - skip_header_byte(f); ext->disk_start = addr + skip_header_byte(f); ext->block_nr = block; if (ext->disk_start == NULL_BLOCK) { if (f->null_ctr % cmsfs.blksize == 0) ext->null_block_started = 1; f->null_ctr += len; } else f->null_ctr = 0; add_record_ext(rec, ext); f->record_scan_state = RSS_DATA_BLOCK_EXT; } /* check for file end by record number */ static int end_of_file(struct file *f, int record) { if (record == f->fst->nr_records - 1) return 1; return 0; } static int walk_fixed_data_block(struct file *f, off_t addr, int *record, off_t *total, int block, int disp) { off_t offset = (off_t ) block * cmsfs.blksize + disp; int len = (f->fst->record_len > cmsfs.blksize) ? cmsfs.blksize : f->fst->record_len; int left = cmsfs.blksize - disp; int first = 0; if (offset % f->fst->record_len) { if (f->fst->record_len - offset >= cmsfs.blksize) first = cmsfs.blksize; else first = (f->fst->record_len % cmsfs.blksize) - (offset % len); } if (first) { BUG(first > left); set_record_extension(f, record, addr, first, block); left -= first; if (addr != NULL_BLOCK) addr += first; } while (left >= len) { /* * Increment record number only after adding a possible * extension. *record starts with -1 so the first is 0. */ if (end_of_file(f, *record)) return 1; (*record)++; set_record_len(f, *record, f->fst->record_len); set_record(f, record, addr, len, total, block); left -= len; if (addr != NULL_BLOCK) addr += len; } /* partial record left */ if (left > 0) { if (end_of_file(f, *record)) return 1; (*record)++; set_record_len(f, *record, f->fst->record_len); set_record(f, record, addr, left, total, block); return 0; } return 0; } static int get_record_unused_bytes(struct file *f, int nr) { struct record *rec = get_record(f, nr); struct record_ext *rext; int used = 0; /* no data bytes yet */ if (f->record_scan_state == RSS_HEADER_COMPLETE) return rec->total_len; used = rec->first_block_len; /* only first block */ if (f->record_scan_state == RSS_DATA_BLOCK_STARTED) goto out; rext = rec->ext; while (rext != NULL) { used += rext->len; rext = rext->next; } out: return rec->total_len - used; } static int walk_var_data_block(struct file *f, off_t addr, unsigned int disp, int *record, off_t *total, int block, int skip) { ssize_t left = cmsfs.blksize - skip; int last, rc; u8 half_len; u16 len; /* * If records are skipped on this block there is no record extension, * overwrite disp and start with scanning the record. */ if (skip) disp = 0; /* * disp set means 1 or 2 header bytes and possibly data bytes on the * last block or a null block. */ if (disp) { if (addr == NULL_BLOCK) { last = cmsfs.blksize; /* * Special case: last block can be a null block with * not all bytes used on it. */ if (f->fst->nr_blocks - 1 == block) last = get_record_unused_bytes(f, *record); /* * Special case: record header on last block wo. data. * That means no record data yet for this block. */ if (f->record_scan_state == RSS_HEADER_COMPLETE) set_record(f, record, addr, last, total, block); else set_record_extension(f, record, addr, last, block); return 0; } if (disp == VAR_RECORD_SPANNED) len = cmsfs.blksize; else len = disp; /* split header -> read second length byte */ if (f->record_scan_state == RSS_HEADER_STARTED) { rc = _read(&half_len, sizeof(half_len), addr); if (rc < 0) return rc; set_record_len_lower(f, *record, half_len); left--; len--; addr++; } if (f->record_scan_state == RSS_HEADER_COMPLETE) set_record(f, record, addr, len, total, block); else set_record_extension(f, record, addr, len, block); if (disp == VAR_RECORD_SPANNED) return 0; left -= len; addr += len; } /* at least one data byte */ while (left >= (int) sizeof(len) + 1) { rc = _read(&len, sizeof(len), addr); if (rc < 0) return rc; /* * Null length means no more records follow. * Assumption: the last block is zero-padded. */ if (!len) return 0; /* * Increment record number only after adding a possible * extension. *record starts with -1 so the first is 0. */ (*record)++; set_record_len(f, *record, len); /* account consumed header bytes */ left -= sizeof(len); addr += sizeof(len); /* limit to block end */ if (len > left) len = left; /* add length of record including the header length */ set_record(f, record, addr, len, total, block); left -= len; /* point to next record header */ addr += len; } /* 2 header bytes left */ if (left == 2) { rc = _read(&len, sizeof(len), addr); if (rc < 0) return rc; if (!len) return 0; (*record)++; set_record_len(f, *record, len); return 0; } /* split header */ if (left == 1) { if (end_of_file(f, *record)) return 0; rc = _read(&half_len, sizeof(half_len), addr); if (rc < 0) return rc; (*record)++; set_record_len_upper(f, *record, half_len); f->record_scan_state = RSS_HEADER_STARTED; } return 0; } static int cache_fixed_data_block(struct file *f, off_t addr, int *block, int *record, off_t *total, int disp) { /* * Cannot distinguish null block pointers from not existing pointers, * so this fn is called for the whole pointer block and maybe for * non-existing blocks and records too. Check and bail out if EOF. */ if (*block >= f->fst->nr_blocks || *record >= f->fst->nr_records) return 0; f->blist[*block].disk_addr = addr & ~DATA_BLOCK_MASK; walk_fixed_data_block(f, addr, record, total, *block, disp); /* record starts with 0 but on-disk record number with 1 */ f->blist[*block].hi_record_nr = *record + 1; (*block)++; return 0; } /* * Walk all pointer blocks of a fixed file and call function for every * data block respecting the sequence of the data. */ static int cache_file_fixed(struct file *f, off_t addr, int level, int *block, unsigned int *disp, int *record, off_t *total) { int left = f->ptr_per_block; off_t ptr; if (level > 0) { level--; while (left--) { ptr = get_fixed_pointer(addr); if (ptr < 0) return ptr; /* * In difference to variable record format a null pointer * may be valid for a null block so we cannot determine * the file end from a pointer entry. Check for the number * of scanned blocks instead. */ if (*block >= f->fst->nr_blocks || *record >= f->fst->nr_records) return 0; cache_file_fixed(f, ptr, level, block, disp, record, total); /* don't increment for null block pointers */ if (addr) addr += PTR_SIZE; } return 0; } return cache_fixed_data_block(f, addr, block, record, total, 0); } static int cache_variable_data_block(struct file *f, off_t addr, int *block, int *record, int disp, off_t *total, int skip) { int rc; /* * Cannot distinguish null block pointers from not existing pointers, * so this fn is called for the whole pointer block and maybe for * non-existing blocks and records too. Check and bail out if EOF. */ if (*block >= f->fst->nr_blocks || *record >= f->fst->nr_records) return 0; f->blist[*block].disk_addr = addr & ~DATA_BLOCK_MASK; rc = walk_var_data_block(f, addr, disp, record, total, *block, skip); if (rc < 0) return rc; /* record starts with 0 but on-disk record number with 1 */ f->blist[*block].hi_record_nr = *record + 1; f->blist[*block].disp = disp; (*block)++; return 0; } /* * Walk all pointer blocks of a variable file and call function for every * data block respecting the sequence of the data. */ static int cache_file_variable(struct file *f, off_t addr, int level, int *block, unsigned int *disp, int *record, off_t *total) { int nr, left = f->ptr_per_block; off_t ptr; if (level > 0) { level--; /* 4 or 8 bytes are left at the end (offset) which we ignore */ while (left--) { ptr = get_var_pointer(addr, &nr, disp); if (ptr < 0) return ptr; if (ptr == VAR_FILE_END) return 0; cache_file_variable(f, ptr, level, block, disp, record, total); addr += VPTR_SIZE; } return 0; } return cache_variable_data_block(f, addr, block, record, *disp, total, 0); } static int locate_last_data_vptr(off_t addr, int level, struct fst_entry *fst, struct var_ptr *vptr) { int last, rc; if (!level) return 0; level--; /* read offset pointer from the end of the var pointer block */ rc = _read(&last, sizeof(last), addr + cmsfs.blksize - sizeof(last)); if (rc < 0) return rc; if (last % VPTR_SIZE || last > cmsfs.blksize) return -EIO; rc = _read(vptr, VPTR_SIZE, addr + last); if (rc < 0) return rc; if (vptr->hi_record_nr != fst->nr_records) return -EIO; /* vptr should contain the highest data block pointer */ if (!level) return 0; if (vptr->next == NULL_BLOCK) return 0; return locate_last_data_vptr(ABS(vptr->next), level, fst, vptr); } static int is_textfile(struct fst_entry *fst) { char type[MAX_TYPE_LEN]; struct filetype *ft; if (!fst) return 0; memset(type, 0, sizeof(type)); ebcdic_dec(type, fst->type, 8); util_list_iterate(&text_type_list, ft) if (strncmp(ft->name, type, strlen(ft->name)) == 0) return 1; return 0; } /* * Decide if linefeeds are needed for this file type. */ static int linefeed_mode_enabled(struct fst_entry *fst) { if (cmsfs.mode == BINARY_MODE) return 0; if (cmsfs.mode == TEXT_MODE) return 1; return is_textfile(fst); } /* * Workaround glibc 2.9 bug with less than 3 files and give room for some * new files. If cache is full it will be purged and rebuild. */ static int max_cache_entries(void) { return cmsfs.files + 10 + cmsfs.files / 4; } static void resize_htab(void) { int i; for (i = 0; i < cmsfs.fcache_used; i++) free(cmsfs.fcache[i].str); hdestroy_r(&cmsfs.htab); free(cmsfs.fcache); cmsfs.fcache_used = 0; cmsfs.fcache_max = max_cache_entries(); cmsfs.fcache = calloc(cmsfs.fcache_max, sizeof(struct fcache_entry)); if (!hcreate_r(cmsfs.fcache_max, &cmsfs.htab)) DIE("hcreate failed\n"); } static void cache_fst_addr(off_t addr, const char *file) { struct fcache_entry *fce; ENTRY e, *eptr; e.key = strdup(file); again: if (hsearch_r(e, FIND, &eptr, &cmsfs.htab) == 0) { /* cache it */ if (cmsfs.fcache_used == cmsfs.fcache_max - 1) { DEBUG("hsearch: hash table full: %d\n", cmsfs.fcache_used); resize_htab(); goto again; } fce = &cmsfs.fcache[cmsfs.fcache_used]; cmsfs.fcache_used++; fce->fst_addr = addr; fce->str = e.key; e.data = fce; if (hsearch_r(e, ENTER, &eptr, &cmsfs.htab) == 0) DIE("hsearch: hash table full\n"); } else free(e.key); } static void update_htab_entry(off_t addr, const char *file) { struct fcache_entry *fce; ENTRY e, *eptr; e.key = strdup(file); if (hsearch_r(e, FIND, &eptr, &cmsfs.htab) == 0) { /* not yet cached, nothing to do */ free(e.key); return; } else { /* update it */ fce = eptr->data; fce->fst_addr = addr; e.data = fce; if (hsearch_r(e, ENTER, &eptr, &cmsfs.htab) == 0) DIE("%s: hash table full\n", __func__); } } static void invalidate_htab_entry(const char *name) { struct fcache_entry *fce; ENTRY e, *eptr; e.key = strdup(name); if (hsearch_r(e, FIND, &eptr, &cmsfs.htab) == 0) { /* nothing to do if not cached */ free(e.key); return; } fce = eptr->data; fce->fst_addr = 0; e.data = fce; if (hsearch_r(e, ENTER, &eptr, &cmsfs.htab) == 0) DIE("hsearch: hash table full\n"); } /* * For each FST entry in a directory block do action. * * Return: * hit == NULL : lookup file not found * hit != NULL : lookup file found, addr of the fst entry */ static void walk_dir_block(struct fst_entry *fst, struct walk_file *walk, int level, off_t *hit) { off_t ptr, addr = walk->addr; char file[MAX_FNAME]; int ret, left; /* handle higher level directory pointer blocks */ if (level > 0) { level--; left = PTRS_PER_BLOCK; while (left--) { ptr = get_fixed_pointer(addr); BUG(ptr < 0); if (!ptr) break; walk->addr = ptr; walk_dir_block(fst, walk, level, hit); if (hit != NULL && *hit) return; addr += PTR_SIZE; } return; } if (walk->flag == WALK_FLAG_CACHE_DBLOCKS) { walk->dlist[walk->dlist_used++] = walk->addr; return; } left = cmsfs.blksize / sizeof(struct fst_entry); while (left--) { ret = readdir_entry(fst, walk->addr); /* directory and allocmap type are skipped */ if (ret == READDIR_FILE_ENTRY) { if (walk->flag == WALK_FLAG_LOOKUP) { if ((memcmp(fst->name, walk->name, 8) == 0) && (memcmp(fst->type, walk->type, 8) == 0)) { /* got it */ *hit = walk->addr; return; } } if (walk->flag == WALK_FLAG_READDIR) { memset(file, 0, sizeof(file)); decode_edf_name(file, fst->name, fst->type); if (!file_unlinked(file)) { cache_fst_addr(walk->addr, file); walk->filler(walk->buf, file, NULL, 0); } } } if (ret == READDIR_END_OF_DIR) { if (walk->flag == WALK_FLAG_LOCATE_EMPTY) { *hit = walk->addr; return; } break; } walk->addr += sizeof(struct fst_entry); }; return; } static void walk_directory(struct fst_entry *fst, struct walk_file *walk, off_t *hit) { if (cmsfs.dir_levels == 0) walk->addr = cmsfs.fdir; else walk->addr = get_fop(cmsfs.fdir); walk_dir_block(fst, walk, cmsfs.dir_levels, hit); } /* * Check FST record format only when reading FST entry from disk. */ static int check_fst_valid(struct fst_entry *fst) { if (fst->record_format != RECORD_LEN_FIXED && fst->record_format != RECORD_LEN_VARIABLE) return 0; else return 1; } /* * Locate the file's fst_entry in any of the directory blocks. */ static off_t lookup_file(const char *name, struct fst_entry *fst, int flag) { struct fcache_entry *fce; char uc_name[MAX_FNAME]; char fname[8], ftype[8]; struct walk_file walk; ENTRY e, *eptr; off_t faddr = 0; int rc; strncpy(uc_name, name, MAX_FNAME); str_toupper(uc_name); if (flag == HIDE_UNLINKED && file_unlinked(uc_name)) return 0; e.key = strdup(uc_name); /* already cached ? */ if (hsearch_r(e, FIND, &eptr, &cmsfs.htab)) { fce = eptr->data; /* check if fst is valid, may be zero for a stale entry */ if (!fce->fst_addr) goto renew; /* read in the fst entry */ rc = _read(fst, sizeof(*fst), fce->fst_addr); BUG(rc < 0); if (!check_fst_valid(fst)) DIE("Invalid file format in file: %s\n", uc_name); free(e.key); return fce->fst_addr; } renew: free(e.key); if (encode_edf_name(uc_name, fname, ftype)) return 0; memset(&walk, 0, sizeof(walk)); walk.flag = WALK_FLAG_LOOKUP; walk.name = fname; walk.type = ftype; walk_directory(fst, &walk, &faddr); if (!faddr) return 0; if (!check_fst_valid(fst)) DIE("Invalid file format in file: %s\n", uc_name); cache_fst_addr(faddr, uc_name); return faddr; } static int cache_file(struct file *f) { int block = 0, record = -1; unsigned int disp = 0; off_t total = 0; return f->fops->cache_data(f, ABS(f->fst->fop), f->fst->levels, &block, &disp, &record, &total); } /* * Caveat: for fixed files nr_blocks is excluding null blocks, * for variable files nr_blocks is including null blocks. * Add null blocks for fixed files so allocation and file end * checks work identical for both variants. */ static void workaround_nr_blocks(struct file *f) { int nr; if (f->fst->record_format == RECORD_LEN_VARIABLE) return; nr = (off_t) f->fst->nr_records * (off_t) f->fst->record_len / cmsfs.blksize; if ((off_t) f->fst->nr_records * (off_t) f->fst->record_len % cmsfs.blksize) nr++; f->nr_null_blocks = nr - f->fst->nr_blocks; f->fst->nr_blocks = nr; } static off_t get_file_size_fixed(struct fst_entry *fst) { return (off_t) fst->nr_records * (off_t) fst->record_len; } static off_t get_file_size_variable(struct fst_entry *fst) { struct var_ptr vptr; off_t total = 0; off_t ptr; int rc; if (fst->levels > 0) { rc = locate_last_data_vptr(ABS(fst->fop), fst->levels, fst, &vptr); if (rc < 0) return rc; if (vptr.next == 0) { /* * Last block is a null block. No more records can * follow, so the displacement value points to EOF. */ total = vptr.disp; goto skip_scan; } ptr = ABS(vptr.next); if (vptr.disp != VAR_RECORD_SPANNED) { ptr += vptr.disp; /* count displacement as used space */ total += vptr.disp; } else { total += cmsfs.blksize; goto skip_scan; } } else ptr = ABS(fst->fop); /* now count the remaining used space in the last block */ rc = walk_last_var_data_block(ptr, &total); if (rc < 0) return rc; skip_scan: /* * Add the full blocks. For variable record file nr_blocks contains * also null blocks. */ if (fst->nr_blocks) total += ((off_t) fst->nr_blocks - 1) * cmsfs.blksize; return total; } /* * Return the file size as it is on the disk. Includes headers for * variable records. */ static off_t get_file_size(struct fst_entry *fst) { if (fst->record_format == RECORD_LEN_FIXED) return get_file_size_fixed(fst); else if (fst->record_format == RECORD_LEN_VARIABLE) return get_file_size_variable(fst); return 0; } static off_t get_file_size_logical(struct fst_entry *fst) { off_t total; if (fst->nr_records == 0) return 0; if (!fst->fop) return -EIO; total = get_file_size(fst); if (total < 0) return -EIO; /* subtract the record headers */ if (fst->record_format == RECORD_LEN_VARIABLE) total -= (off_t) fst->nr_records * VAR_RECORD_HEADER_SIZE; if (linefeed_mode_enabled(fst)) total += fst->nr_records; return total; } static int cmsfs_getattr(const char *path, struct stat *stbuf) { int mask = (cmsfs.allow_other) ? 0444 : 0440; struct fst_entry fst; if (!cmsfs.readonly) mask |= ((cmsfs.allow_other) ? 0222 : 0220); memset(stbuf, 0, sizeof(*stbuf)); stbuf->st_uid = getuid(); stbuf->st_gid = getgid(); stbuf->st_blksize = cmsfs.blksize; if (strcmp(path, "/") == 0) { stbuf->st_mode = S_IFDIR | mask | ((cmsfs.allow_other) ? 0111 : 0110); stbuf->st_nlink = 2; readdir_entry(&fst, cmsfs.fdir); /* date */ stbuf->st_mtime = fst_date_to_time_t(&fst.date[0], fst.flag & FST_FLAG_CENTURY); stbuf->st_atime = stbuf->st_ctime = stbuf->st_mtime; /* size */ stbuf->st_size = (off_t) fst.record_len * (off_t) fst.nr_records; stbuf->st_blocks = MAX(stbuf->st_size, cmsfs.blksize); stbuf->st_blocks = ((stbuf->st_blocks + cmsfs.data_block_mask) & ~cmsfs.data_block_mask) >> 9; } else { if (!lookup_file(path + 1, &fst, HIDE_UNLINKED)) return -ENOENT; stbuf->st_mode = S_IFREG | mask; stbuf->st_nlink = 1; /* date */ stbuf->st_mtime = stbuf->st_atime = stbuf->st_ctime = fst_date_to_time_t(&fst.date[0], fst.flag & FST_FLAG_CENTURY); /* size */ stbuf->st_size = get_file_size_logical(&fst); if (stbuf->st_size < 0) return -EIO; /* * Include potential sparse blocks for variable files which * are included in nr_blocks to avoid scanning the whole file. */ stbuf->st_blocks = (off_t) fst.nr_blocks * cmsfs.nr_blocks_512; } return 0; } static int cmsfs_readdir(const char *path, void *buf, fuse_fill_dir_t filler, off_t offset, struct fuse_file_info *fi) { struct walk_file walk; struct fst_entry fst; (void) offset; (void) fi; /* * Offset is ignored and 0 passed to the filler fn so the whole * directory is read at once. */ /* EDF knows only the root directory */ if (strcmp(path, "/") != 0) return -ENOENT; filler(buf, ".", NULL, 0); filler(buf, "..", NULL, 0); memset(&walk, 0, sizeof(walk)); /* readdir is possible without open so fi->fh is not set */ walk.flag = WALK_FLAG_READDIR; walk.buf = buf; walk.filler = filler; walk_directory(&fst, &walk, NULL); return 0; } static int cmsfs_open(const char *path, struct fuse_file_info *fi) { struct fst_entry fst; struct file *f; off_t fst_addr; int rc = 0; /* * open flags: * O_DIRECTORY: FUSE captures open on / so not needed. * O_NOATIME: ignored because there is no atime in EDF. * O_NOFOLLOW: can be ignored since EDF has no links. * O_SYNC: ignored since IO is always sync. * O_TRUNC, O_CREAT, O_EXCL: avoided by FUSE. */ fst_addr = lookup_file(path + 1, &fst, SHOW_UNLINKED); if (!fst_addr) return -ENOENT; f = file_open(path + 1); if (f == NULL) { f = create_file_object(&fst, &rc); if (f == NULL) return rc; f->fst_addr = fst_addr; /* * Store file size in file object. Needed for write of fixed record * length files when the write is not a multiple of the record length. * In this case a second write would fail since the file size would * be calculated by lrecl * nr_records. Use session_size therefore. */ f->session_size = get_file_size_logical(&fst); if (f->session_size < 0) return -EIO; f->wcache = malloc(WCACHE_MAX); if (f->wcache == NULL) return -ENOMEM; strncpy(f->path, path, MAX_FNAME + 1); str_toupper(f->path); f->use_count = 1; util_list_add_head(&open_file_list, f); } else f->use_count++; if (fi->flags & O_RDWR || fi->flags & O_WRONLY) f->write_count++; fi->fh = (uint64_t)(unsigned long) f; return 0; } static void set_fdir_date_current(void) { struct fst_entry fst; int rc; rc = _read(&fst, sizeof(fst), cmsfs.fdir); BUG(rc < 0); set_fst_date_current(&fst); rc = _write(&fst, sizeof(fst), cmsfs.fdir); BUG(rc < 0); } static void increase_file_count(void) { struct fst_entry fst; int rc; rc = _read(&fst, sizeof(fst), cmsfs.fdir); BUG(rc < 0); fst.nr_records = ++cmsfs.files + 2; set_fst_date_current(&fst); rc = _write(&fst, sizeof(fst), cmsfs.fdir); BUG(rc < 0); } static void decrease_file_count(void) { struct fst_entry fst; int rc; rc = _read(&fst, sizeof(fst), cmsfs.fdir); BUG(rc < 0); fst.nr_records = --cmsfs.files + 2; set_fst_date_current(&fst); rc = _write(&fst, sizeof(fst), cmsfs.fdir); BUG(rc < 0); } static off_t get_reserved_block(void) { off_t addr; if (cmsfs.reserved_blocks > 0) cmsfs.reserved_blocks--; addr = get_zero_block(); BUG(addr < 0); return addr; } static void cache_dblocks(struct walk_file *walk) { double dblocks; /* calculate number of data blocks used for FST entries */ dblocks = (cmsfs.files + 2) * sizeof(struct fst_entry); dblocks = ceil(dblocks / cmsfs.blksize); /* add a spare one in case of create file */ dblocks++; memset(walk, 0, sizeof(*walk)); walk->flag = WALK_FLAG_CACHE_DBLOCKS; walk->dlist = calloc(dblocks, sizeof(off_t)); if (walk->dlist == NULL) DIE_PERROR("malloc failed"); walk_directory(NULL, walk, NULL); } static void free_dblocks(struct walk_file *walk) { free(walk->dlist); } static void purge_dblock_ptrs(int level, off_t addr) { int left = PTRS_PER_BLOCK; off_t ptr, start = addr; if (!level) return; level--; while (left--) { ptr = get_fixed_pointer(addr); BUG(ptr < 0); if (ptr != NULL_BLOCK) purge_dblock_ptrs(level, ptr); /* don't increment for null block pointers */ if (addr) addr += PTR_SIZE; } free_block(start); } /* * Return total number of pointer entries for level. */ static int pointers_per_level(struct file *f, int level, int nr_blocks) { double entries = nr_blocks; if (!level || nr_blocks < 2) return 0; if (level == 1) return nr_blocks; level--; while (level--) entries = ceil(entries / f->ptr_per_block); return (int) entries; } static int per_level_fixed(int level, int entries) { double val = entries; while (level--) val = ceil(val / PTRS_PER_BLOCK); return (int) val; } static void rewrite_dir_ptr_block(struct walk_file *walk, int level, off_t dst, int start) { struct fixed_ptr ptr; int rc, i, end; off_t addr; if (!level) return; end = MIN(start + PTRS_PER_BLOCK, per_level_fixed(level - 1, walk->dlist_used)); BUG(start > end); for (i = start; i < end; i++) { if (level == 1) { addr = walk->dlist[i]; if (addr) ptr.next = REL(addr); else ptr.next = 0; } else { addr = get_zero_block(); BUG(addr < 0); ptr.next = REL(addr); } rc = _write(&ptr, sizeof(ptr), dst); BUG(rc < 0); dst += sizeof(ptr); rewrite_dir_ptr_block(walk, level - 1, addr, i * PTRS_PER_BLOCK); } } static int update_dir_levels(int blocks) { int levels = 1; if (blocks < 2) return 0; while (blocks / (PTRS_PER_BLOCK + 1)) { levels++; blocks /= PTRS_PER_BLOCK; } return levels; } static void rewrite_dblock_ptrs(struct walk_file *walk) { int rc, nr_blocks = walk->dlist_used; struct fst_entry fst; off_t dst; BUG(!nr_blocks); /* read in the directory FST */ rc = _read(&fst, sizeof(fst), cmsfs.fdir); BUG(rc < 0); if (nr_blocks == 1) { fst.fop = REL(cmsfs.fdir); fst.levels = 0; cmsfs.dir_levels = fst.levels; goto store; } dst = get_zero_block(); BUG(dst < 0); fst.fop = REL(dst); fst.levels = update_dir_levels(walk->dlist_used); cmsfs.dir_levels = fst.levels; rewrite_dir_ptr_block(walk, fst.levels, dst, 0); store: rc = _write(&fst, sizeof(fst), cmsfs.fdir); BUG(rc < 0); } /* * Update used block count in disk label. */ static void update_block_count(void) { int rc; rc = _write(&cmsfs.used_blocks, sizeof(unsigned int), cmsfs.label + 32); BUG(rc < 0); } static int cmsfs_create(const char *path, mode_t mode, struct fuse_file_info *fi) { char fname[8], ftype[8]; char uc_name[MAX_FNAME]; struct walk_file walk; struct fst_entry fst; off_t fst_addr = 0; int rc; /* no permissions in EDF */ (void) mode; /* * Note: creating a file that was unlinked but not yet deleted from * disk is not supported. That means the unlinked file can still be * opened. */ if (lookup_file(path + 1, &fst, SHOW_UNLINKED)) return cmsfs_open(path, fi); if (cmsfs.readonly) return -EACCES; rc = edf_name_valid(path + 1); if (rc) return rc; /* force uppercase */ strncpy(uc_name, path + 1, MAX_FNAME); str_toupper(uc_name); rc = encode_edf_name(uc_name, fname, ftype); if (rc) return rc; /* find free fst entry */ memset(&walk, 0, sizeof(walk)); walk.flag = WALK_FLAG_LOCATE_EMPTY; walk_directory(&fst, &walk, &fst_addr); /* no free slot found, allocate new directory block */ if (!fst_addr) { /* * Be conservative and check if enough blocks for all * directory levels are available. */ if (cmsfs.total_blocks - cmsfs.used_blocks < cmsfs.dir_levels + 2) return -ENOSPC; fst_addr = get_zero_block(); if (fst_addr < 0) return fst_addr; cache_dblocks(&walk); /* add the newly allocated block to dlist */ walk.dlist[walk.dlist_used++] = fst_addr; purge_dblock_ptrs(cmsfs.dir_levels, get_fop(cmsfs.fdir)); rewrite_dblock_ptrs(&walk); free_dblocks(&walk); update_block_count(); } /* * Fill fst entry. Default template: * format: variable * record_len: 0 * mode: A1 * flag: 0, century bit (0x8) set by following utimens * fop: 0 * nr_records: 0 * nr_blocks: 0 * levels: 0 * ptr_size: 0xc for variable format * date: set to current date */ memset(&fst, 0, sizeof(fst)); memcpy(fst.name, fname, 8); memcpy(fst.type, ftype, 8); ebcdic_enc((char *) &fst.mode, "A1", 2); fst.record_format = RECORD_LEN_VARIABLE; fst.ptr_size = sizeof(struct var_ptr); rc = set_fst_date_current(&fst); if (rc != 0) return rc; rc = _write(&fst, sizeof(fst), fst_addr); BUG(rc < 0); cache_fst_addr(fst_addr, uc_name); increase_file_count(); return cmsfs_open(path, fi); } static int purge_pointer_block_fixed(struct file *f, int level, off_t addr) { int left = f->ptr_per_block; off_t ptr, start = addr; if (!level) return 0; level--; while (left--) { if (!level) break; ptr = get_fixed_pointer(addr); if (ptr < 0) return ptr; if (ptr != NULL_BLOCK) purge_pointer_block_fixed(f, level, ptr); /* don't increment for null block pointers */ if (addr) addr += PTR_SIZE; } free_block(start); return 0; } static int purge_pointer_block_variable(struct file *f, int level, off_t addr) { int nr, left = f->ptr_per_block; off_t ptr, start = addr; unsigned int disp; if (!level) return 0; level--; while (left--) { if (!level) break; ptr = get_var_pointer(addr, &nr, &disp); if (ptr < 0) return ptr; if (ptr == VAR_FILE_END) break; if (ptr != NULL_BLOCK) purge_pointer_block_variable(f, level, ptr); /* don't increment for null block pointers */ if (addr) addr += VPTR_SIZE; } free_block(start); return 0; } /* * Store the back pointer for a variable pointer block. * Pointer is offset of last VPTR to block start. */ static int store_back_pointer(off_t dst, int entries) { unsigned int back; back = (entries - 1) * VPTR_SIZE; return _write(&back, sizeof(back), ((dst | DATA_BLOCK_MASK) + 1) - sizeof(back)); } /* * Rewrite one pointer block starting from the highest level. */ static int rewrite_pointer_block_fixed(struct file *f, int level, off_t dst, int start) { struct fixed_ptr ptr; int i, end, rc = 0; off_t addr; if (!level) return 0; /* * start is always the first entry of a pointer block, * end is the last used entry in this pointer block. */ end = MIN(start + f->ptr_per_block, per_level_fixed(level - 1, f->fst->nr_blocks)); BUG(start > end); for (i = start; i < end; i++) { if (level == 1) { addr = f->blist[i].disk_addr; if (addr) ptr.next = REL(addr); else ptr.next = 0; } else { addr = get_reserved_block(); ptr.next = REL(addr); } rc = _write(&ptr, sizeof(ptr), dst); if (rc < 0) return rc; dst += sizeof(ptr); rc = rewrite_pointer_block_fixed(f, level - 1, addr, i * f->ptr_per_block); if (rc < 0) return rc; } return rc; } static int get_first_block_nr(int level, int entry) { while (level-- > 1) entry *= VPTRS_PER_BLOCK; return entry; } static int get_last_block_nr(int level, int entry, int nr_blocks) { while (level-- > 1) entry *= VPTRS_PER_BLOCK; entry--; if (entry > nr_blocks - 1) entry = nr_blocks - 1; return entry; } static int per_level_var(int level, int entries) { double val = entries; while (level--) val = ceil(val / VPTRS_PER_BLOCK); return (int) val; } /* * Rewrite one pointer block starting from the highest level. */ static int rewrite_pointer_block_variable(struct file *f, int level, off_t dst, int start) { int i, bnr, end, rc = 0; struct var_ptr ptr; off_t addr; if (!level) return 0; /* * start is always the first entry of a pointer block, * end is the last used entry in this pointer block. */ end = MIN(start + f->ptr_per_block, per_level_var(level - 1, f->fst->nr_blocks)); BUG(start > end); for (i = start; i < end; i++) { if (level == 1) { addr = f->blist[i].disk_addr; if (addr) ptr.next = REL(addr); else ptr.next = 0; } else { addr = get_reserved_block(); ptr.next = REL(addr); } bnr = get_first_block_nr(level, i); ptr.disp = f->blist[bnr].disp; bnr = get_last_block_nr(level, i + 1, f->fst->nr_blocks); ptr.hi_record_nr = f->blist[bnr].hi_record_nr; rc = _write(&ptr, sizeof(ptr), dst); if (rc < 0) return rc; dst += sizeof(ptr); rc = rewrite_pointer_block_variable(f, level - 1, addr, i * f->ptr_per_block); if (rc < 0) return rc; } return store_back_pointer(dst, end - start); } /* * Update fop and pointer blocks if needed. */ static int rewrite_pointers(struct file *f) { struct record *rec; off_t dst; if (f->fst->nr_blocks == 0) { f->fst->fop = 0; return 0; } if (f->fst->nr_blocks == 1) { rec = get_record(f, 0); if (rec->disk_start == NULL_BLOCK) f->fst->fop = 0; else f->fst->fop = REL(rec->disk_start); return 0; } /* allocate root block for fst */ dst = get_reserved_block(); f->fst->fop = REL(dst); return f->fops->write_pointers(f, f->fst->levels, dst, 0); } /* * Guess position in record table. */ static int guess_record_number(struct file *f, off_t offset) { int nr; if (f->linefeed) nr = (offset / (f->fst->record_len + 1)); else nr = (offset / f->fst->record_len); if (nr >= f->fst->nr_records) nr = f->fst->nr_records - 1; return nr; } static int offset_is_linefeed(off_t offset, struct record *prev, struct record *next) { if ((offset < next->file_start) && (offset >= prev->file_start + prev->total_len)) return 1; return 0; } static int offset_in_record(off_t offset, struct record *rec) { if (offset >= rec->file_start && offset < rec->file_start + rec->total_len) return 1; return 0; } static void set_hint(struct file *f, int hint) { f->next_record_hint = hint; /* limit hint to last record */ if (f->next_record_hint >= f->fst->nr_records) f->next_record_hint = f->fst->nr_records - 1; } /* * Find record by file offset. * * Returns: record number in *nr * > 0 : ptr to found record * -1 : linefeed offset * NULL : error */ static struct record *find_record(struct file *f, off_t offset, int *nr) { int i, start, step, max = f->fst->nr_records; struct record *rec; /* * next_record_hint is a guess which is optimal for sequential * single-threaded reads. */ i = f->next_record_hint; rec = &f->rlist[i]; if (offset_in_record(offset, rec)) { /* increment hint for sequential read, fails for extensions */ set_hint(f, i + 1); *nr = i; return rec; } /* look out for previous record linefeed from sequential read hint */ if (f->linefeed && i > 0) if (offset_is_linefeed(offset, &f->rlist[i - 1], rec)) return LINEFEED_OFFSET; start = guess_record_number(f, offset); /* because of potential linefeed we need to check the next record */ rec = &f->rlist[start]; if (offset < rec->file_start) step = -1; else step = 1; for (i = start; i >= 0 && i < max; i += step) { rec = &f->rlist[i]; if (offset_in_record(offset, rec)) { set_hint(f, i + 1); *nr = i; return rec; } /* last record reached, only one linefeed can follow */ if (i == max - 1) { if (f->linefeed && (offset == rec->file_start + rec->total_len)) return LINEFEED_OFFSET; else return NULL; } /* check for linefeed byte between two records */ if (step == 1) { if (offset_is_linefeed(offset, rec, &f->rlist[i + 1])) return LINEFEED_OFFSET; } else /* * No need to check if i > 0 since no linefeed before * record 0 possible. */ if (offset_is_linefeed(offset, &f->rlist[i - 1], rec)) return LINEFEED_OFFSET; } DEBUG("find: record not found!\n"); return NULL; } /* * Get disk address and block size from a record. */ static void get_block_data_from_record(struct record *rec, off_t offset, off_t *addr, int *chunk) { int record_off = offset - rec->file_start; struct record_ext *rext; if (record_off >= rec->first_block_len) { record_off -= rec->first_block_len; rext = rec->ext; BUG(rext == NULL); while (record_off >= rext->len) { record_off -= rext->len; rext = rext->next; BUG(rext == NULL); } /* found the right record extension */ if (rext->disk_start == NULL_BLOCK) *addr = NULL_BLOCK; else *addr = rext->disk_start + record_off; *chunk = rext->len - record_off; } else { if (rec->disk_start == NULL_BLOCK) *addr = NULL_BLOCK; else *addr = rec->disk_start + record_off; *chunk = rec->first_block_len - record_off; } } static int convert_text(iconv_t conv, char *buf, int size) { size_t out_count = size; size_t in_count = size; char *data_ptr = buf; int rc; rc = iconv(conv, &data_ptr, &in_count, &data_ptr, &out_count); if ((rc == -1) || (in_count != 0)) { DEBUG("Code page translation EBCDIC-ASCII failed\n"); return -EIO; } return 0; } static int cmsfs_read(const char *path, char *buf, size_t size, off_t offset, struct fuse_file_info *fi) { struct file *f = get_fobj(fi); size_t len, copied = 0; struct record *rec; int chunk, nr, rc; off_t addr; (void) path; len = f->session_size; if ((size_t) offset >= len) return 0; if (offset + size > len) size = len - offset; while (size > 0) { rec = find_record(f, offset, &nr); if (rec == NULL) { copied = -EINVAL; DEBUG("%s: invalid addr, size: %lu copied: %lu len: %lu\n", __func__, size, copied, len); goto out; } /* write linefeed directly to buffer and go to next record */ if (rec == LINEFEED_OFFSET) { BUG(!f->linefeed); if (f->translate) *buf = LINEFEED_ASCII; else *buf = LINEFEED_EBCDIC; buf++; copied++; offset++; size--; continue; } /* get addr and block size from record */ get_block_data_from_record(rec, offset, &addr, &chunk); if (chunk <= 0 || addr < 0) DIE("Invalid record data\n"); /* copy less if there is not enough space in the fuse buffer */ if (size < (size_t) chunk) chunk = size; /* read one record */ if (addr == NULL_BLOCK) memset(buf, 0, chunk); else { rc = _read(buf, chunk, addr); if (rc < 0) return rc; } if (f->translate) { rc = convert_text(cmsfs.iconv_from, buf, chunk); if (rc < 0) return rc; } copied += chunk; size -= chunk; buf += chunk; offset += chunk; } out: DEBUG("%s: copied: %lu\n", __func__, copied); return copied; } static int cmsfs_statfs(const char *path, struct statvfs *buf) { unsigned int inode_size = cmsfs.blksize + sizeof(struct fst_entry); unsigned int free_blocks = cmsfs.total_blocks - cmsfs.used_blocks; unsigned long long tmp; (void) path; buf->f_bsize = buf->f_frsize = cmsfs.blksize; buf->f_blocks = cmsfs.total_blocks; buf->f_bfree = buf->f_bavail = free_blocks; /* number of possible inodes */ tmp = (unsigned long long) cmsfs.total_blocks * cmsfs.blksize / inode_size; buf->f_files = (long) tmp; tmp = (unsigned long long) free_blocks * cmsfs.blksize / inode_size; buf->f_ffree = (long) tmp; buf->f_namemax = MAX_FNAME - 1; return 0; } static int cmsfs_utimens(const char *path, const struct timespec ts[2]) { struct fst_entry fst; off_t fst_addr; struct tm tm; int rc; if (cmsfs.readonly) return -EACCES; fst_addr = lookup_file(path + 1, &fst, HIDE_UNLINKED); if (!fst_addr) return -ENOENT; /* convert timespec to tm */ memset(&tm, 0, sizeof(struct tm)); if (localtime_r(&ts[0].tv_sec, &tm) == NULL) return -EINVAL; update_fst_date(&fst, &tm); rc = _write(&fst, sizeof(fst), fst_addr); BUG(rc < 0); return 0; } /* * Get the address of the last directory entry. */ static off_t find_last_fdir_entry(off_t addr, int level) { struct fst_entry fst; int left, rc; off_t ptr; if (level > 0) { level--; left = PTRS_PER_BLOCK; while (left--) { ptr = get_fixed_pointer(addr + left * PTR_SIZE); BUG(ptr < 0); if (ptr) return find_last_fdir_entry(ptr, level); } DIE("Directory entry not found\n"); return 0; } left = cmsfs.blksize / sizeof(struct fst_entry); while (left--) { rc = _read(&fst, sizeof(fst), addr + left * sizeof(struct fst_entry)); BUG(rc < 0); if (is_file(fst.name, fst.type)) return addr + left * sizeof(struct fst_entry); } DIE("Directory entry not found\n"); } static int delete_file(const char *path) { off_t fst_kill, fst_last; struct walk_file walk; struct file *f_moved; char file[MAX_FNAME]; struct fst_entry fst; struct file *f; int rc = 0, i; if (cmsfs.readonly) return -EROFS; fst_kill = lookup_file(path + 1, &fst, SHOW_UNLINKED); if (!fst_kill) return -ENOENT; f = create_file_object(&fst, &rc); if (f == NULL) return rc; /* delete all data blocks */ for (i = 0; i < f->fst->nr_blocks; i++) free_block(f->blist[i].disk_addr); if (f->fst->fop) { rc = f->fops->delete_pointers(f, f->fst->levels, ABS(f->fst->fop)); if (rc < 0) goto error; } if (cmsfs.dir_levels) fst_last = find_last_fdir_entry(get_fop(cmsfs.fdir), cmsfs.dir_levels); else fst_last = find_last_fdir_entry(cmsfs.fdir, cmsfs.dir_levels); /* remove unlinked file from fcache */ strncpy(file, path + 1, MAX_FNAME); str_toupper(file); invalidate_htab_entry(file); if (fst_last == fst_kill) goto skip_copy; /* copy last entry over unlinked entry */ rc = _read(&fst, sizeof(struct fst_entry), fst_last); BUG(rc < 0); rc = _write(&fst, sizeof(struct fst_entry), fst_kill); BUG(rc < 0); /* update moved fcache entry */ memset(file, 0, sizeof(file)); decode_edf_name(file, fst.name, fst.type); update_htab_entry(fst_kill, file); /* update cached address of moved FST */ f_moved = file_open(file); if (f_moved != NULL) f->fst_addr = fst_kill; skip_copy: /* delete last entry */ rc = _zero(fst_last, sizeof(struct fst_entry)); BUG(rc < 0); /* if the deleted entry was the first of a block, free the block */ if (fst_last % cmsfs.blksize == 0) { cache_dblocks(&walk); /* delete the last block from dlist */ walk.dlist_used--; free_block(fst_last); purge_dblock_ptrs(cmsfs.dir_levels, get_fop(cmsfs.fdir)); rewrite_dblock_ptrs(&walk); free_dblocks(&walk); } destroy_file_object(f); decrease_file_count(); update_block_count(); return 0; error: destroy_file_object(f); return rc; } static int cmsfs_rename(const char *path, const char *new_path) { struct fst_entry fst, fst_new; off_t fst_addr, fst_addr_new; char uc_old_name[MAX_FNAME]; char fname[8], ftype[8]; char *uc_new_name; struct file *f; int rc; if (cmsfs.readonly) return -EACCES; fst_addr = lookup_file(path + 1, &fst, HIDE_UNLINKED); if (!fst_addr) return -ENOENT; /* if new file already exists it must be overwritten so delete it */ fst_addr_new = lookup_file(new_path + 1, &fst_new, HIDE_UNLINKED); if (fst_addr_new) { delete_file(new_path); /* fst_addr may have changed due to copy-up */ fst_addr = lookup_file(path + 1, &fst, HIDE_UNLINKED); } rc = edf_name_valid(new_path + 1); if (rc) return rc; /* force uppercase */ uc_new_name = strdup(new_path + 1); if (uc_new_name == NULL) return -ENOMEM; str_toupper(uc_new_name); rc = encode_edf_name(uc_new_name, fname, ftype); if (rc) return rc; memcpy(&fst.name[0], fname, 8); memcpy(&fst.type[0], ftype, 8); strncpy(uc_old_name, path + 1, MAX_FNAME); str_toupper(uc_old_name); invalidate_htab_entry(uc_old_name); /* update name in file object if the file is opened */ f = file_open(uc_old_name); if (f != NULL) { strncpy(f->path, new_path, MAX_FNAME + 1); str_toupper(f->path); memcpy(f->fst->name, fname, 8); memcpy(f->fst->type, ftype, 8); } rc = _write(&fst, sizeof(fst), fst_addr); BUG(rc < 0); return 0; } static int cmsfs_fsync(const char *path, int datasync, struct fuse_file_info *fi) { (void) path; (void) datasync; (void) fi; if (cmsfs.readonly) return -EROFS; return msync(cmsfs.map, cmsfs.size, MS_SYNC); } /* * Detect whether the whole block can be freed. */ static int block_started(struct file *f, struct record *rec) { if (rec->disk_start == NULL_BLOCK) { if (rec->null_block_started) return 1; else return 0; } if (f->fst->record_format == RECORD_LEN_FIXED) { if (rec->disk_start % cmsfs.blksize == 0) return 1; else return 0; } if (f->fst->record_format == RECORD_LEN_VARIABLE) { if ((rec->disk_start % cmsfs.blksize == 0) || (rec->disk_start % cmsfs.blksize == 1) || (rec->disk_start % cmsfs.blksize == 2)) return 1; else return 0; } return 0; } /* * Note: only called for the very last record of a file. That means if the * data starts on a block offset the block can be freed. It does not free * header bytes for variable record files if they are on the previous block! */ static void free_record(struct file *f, struct record *rec) { struct record_ext *rext = rec->ext; f->fst->nr_records--; if (block_started(f, rec)) { free_block(rec->disk_start); f->fst->nr_blocks--; if (!rec->disk_start && f->fst->record_format == RECORD_LEN_FIXED) f->nr_null_blocks--; } while (rext != NULL) { /* extensions always start on a new block */ free_block(rext->disk_start); f->fst->nr_blocks--; if (!rext->disk_start && f->fst->record_format == RECORD_LEN_FIXED) f->nr_null_blocks--; rext = rext->next; } } static int update_var_header_len(struct file *f, u16 *header, struct record *rec) { off_t prev_block_end; int rc, split = 0; if (rec->disk_start % cmsfs.blksize == 0) split = 2; if (rec->disk_start % cmsfs.blksize == 1) split = 1; /* header is completely in this block */ if (!split) { rc = _write(header, sizeof(*header), rec->disk_start - sizeof(*header)); if (rc < 0) return rc; return 0; } BUG(!rec->block_nr); prev_block_end = f->blist[rec->block_nr - 1].disk_addr | DATA_BLOCK_MASK; if (split == 1) { rc = _write((char *) header + 1, 1, rec->disk_start - 1); if (rc < 0) return rc; rc = _write((char *) header, 1, prev_block_end); if (rc < 0) return rc; return 0; } if (split == 2) { rc = _write(header, sizeof(*header), prev_block_end - 1); if (rc < 0) return rc; return 0; } return 0; } /* * Update the displacement of the last block if the block was spanned and * the new end is inside the previously spanned block. The displacement * must point after the last record to a null length header. * If the block wasn't spanned the displacement of the trimmed record needs * no update. */ static void adjust_displacement(struct file *f, int bnr, unsigned int disp) { if (f->blist[bnr].disp == VAR_RECORD_SPANNED) f->blist[bnr].disp = disp; } /* * Split the last record if needed and wipe until block end. * offset points to the last byte of the trimmed record that is * not a line feed. */ static int trim_record(struct file *f, off_t offset, struct record *rec) { int rc, wipe_off, wipe_len, free = 0; off_t file_start = rec->file_start; struct record_ext *rext; u16 header; BUG(!offset_in_record(offset, rec)); if (offset >= rec->file_start && offset < rec->file_start + rec->first_block_len) { wipe_off = offset + 1 - rec->file_start; wipe_len = cmsfs.blksize - ((rec->disk_start & DATA_BLOCK_MASK) + wipe_off); if (!wipe_len) goto ext; if (rec->disk_start) { rc = _zero(rec->disk_start + wipe_off, wipe_len); BUG(rc < 0); } if (f->fst->record_format == RECORD_LEN_VARIABLE) adjust_displacement(f, rec->block_nr, (rec->disk_start + wipe_off) & DATA_BLOCK_MASK); free = 1; } ext: if (rec->ext == NULL) goto header; file_start += rec->first_block_len; rext = rec->ext; do { if (free) { free_block(rext->disk_start); f->fst->nr_blocks--; if (!rext->disk_start && f->fst->record_format == RECORD_LEN_FIXED) f->nr_null_blocks--; } else { if (offset >= file_start && offset < file_start + rext->len) { wipe_off = offset + 1 - file_start; wipe_len = cmsfs.blksize - ((rec->disk_start & DATA_BLOCK_MASK) + wipe_off); if (!wipe_len) continue; if (rext->disk_start) { rc = _zero(rext->disk_start + wipe_off, wipe_len); BUG(rc < 0); } if (f->fst->record_format == RECORD_LEN_VARIABLE) adjust_displacement(f, rext->block_nr, (rext->disk_start + wipe_off) & DATA_BLOCK_MASK); free = 1; } } file_start += rext->len; rext = rext->next; } while (rext != NULL); header: /* update variable record header with new record length */ if (f->fst->record_format == RECORD_LEN_VARIABLE) { header = offset + 1 - rec->file_start; rc = update_var_header_len(f, &header, rec); if (rc < 0) return rc; /* update total_len in rlist, needed to recalculate lrecl */ rec->total_len = header; } return 0; } /* * Update levels count. */ static void update_levels(struct file *f) { int per_block = f->ptr_per_block; int levels = 1, blocks = f->fst->nr_blocks; if (blocks < 2) { f->fst->levels = 0; return; } while (blocks / (per_block + 1)) { levels++; blocks /= per_block; } f->fst->levels = levels; } /* * Called by write only using the cached value. */ static void update_lrecl_fast(struct file *f, int rlen) { if (rlen > (int) f->fst->record_len) f->fst->record_len = rlen; } /* * Update longest record length for variable files. */ static void update_lrecl(struct file *f) { unsigned int lrecl = 0; struct record *rec; int i; if (f->fst->record_format == RECORD_LEN_FIXED) return; if (!f->fst->nr_records) { f->fst->record_len = 0; return; } for (i = 0; i < f->fst->nr_records; i++) { rec = get_record(f, i); if (rec->total_len > lrecl) lrecl = rec->total_len; } f->fst->record_len = lrecl; } static int shrink_file(struct file *f, off_t size) { struct record *new_end_rec, *end_rec; int rlen = f->fst->record_len; off_t offset = size; int new_end_nr, rc; /* truncate MUST be aligned to record length for fixed files */ if (f->fst->record_format == RECORD_LEN_FIXED) { if (f->linefeed) rlen++; if (size % rlen) return -EINVAL; } if (size == 0) { new_end_nr = -1; new_end_rec = NULL; goto free; } /* * offset may point to the linefeed after a record, let it point to the * last byte of the new last record instead. The linefeed is virtual * and will be generated automatically. */ if (f->linefeed) { new_end_rec = find_record(f, offset - 1, &new_end_nr); if (new_end_rec == LINEFEED_OFFSET) offset--; } /* get the new last record of the file */ new_end_rec = find_record(f, offset - 1, &new_end_nr); BUG(new_end_rec == NULL || new_end_rec == LINEFEED_OFFSET); free: /* free from the end until new_end_rec */ while (f->fst->nr_records - 1 > new_end_nr) { /* get the currently last record of the file */ end_rec = get_record(f, f->fst->nr_records - 1); free_record(f, end_rec); } if (new_end_rec != NULL) { rc = trim_record(f, offset - 1, new_end_rec); if (rc < 0) return rc; } f->session_size = size; if (f->fst->fop) f->fops->delete_pointers(f, f->fst->levels, ABS(f->fst->fop)); update_levels(f); update_lrecl(f); rc = rewrite_pointers(f); if (rc < 0) return rc; update_block_count(); return 0; } static void hide_null_blocks(struct file *f) { if (f->fst->record_format == RECORD_LEN_VARIABLE) return; f->fst->nr_blocks -= f->nr_null_blocks; } static void unhide_null_blocks(struct file *f) { if (f->fst->record_format == RECORD_LEN_VARIABLE) return; f->fst->nr_blocks += f->nr_null_blocks; } static void update_fst(struct file *f, off_t addr) { int rc; hide_null_blocks(f); rc = _write(f->fst, sizeof(*f->fst), addr); BUG(rc < 0); unhide_null_blocks(f); } static int cmsfs_truncate(const char *path, off_t size) { struct fst_entry fst; off_t fst_addr, len; struct file *f; int rc = 0; if (cmsfs.readonly) return -EROFS; /* * If file is opened and modified disk content may be obsolete. * Must use the file object to get the current version of the file. */ f = file_open(path + 1); if (f != NULL) { fst_addr = f->fst_addr; len = f->session_size; } else { fst_addr = lookup_file(path + 1, &fst, HIDE_UNLINKED); if (!fst_addr) return -ENOENT; len = get_file_size_logical(&fst); if (len < 0) return -EIO; f = create_file_object(&fst, &rc); if (f == NULL) return rc; } if (len == size) return 0; if (size < len) { rc = shrink_file(f, size); if (rc != 0) return rc; } else return -EINVAL; rc = set_fst_date_current(f->fst); if (rc != 0) return rc; update_fst(f, fst_addr); if (!f->use_count) destroy_file_object(f); return rc; } #ifdef HAVE_SETXATTR static int cmsfs_setxattr(const char *path, const char *name, const char *value, size_t size, int flags) { struct fst_entry fst; off_t fst_addr; int mode_n, rc; long int rlen; char mode_l; char *in; /* meaningless since our xattrs are virtual and not stored on disk */ (void) flags; if (cmsfs.readonly) return -EROFS; fst_addr = lookup_file(path + 1, &fst, HIDE_UNLINKED); if (!fst_addr) return -ENOENT; if (strcmp(name, xattr_format.name) == 0) { /* only allowed for empty files */ if (fst.nr_records != 0) return -EINVAL; if (size != xattr_format.size) return -ERANGE; if (*value == 'F') { fst.record_format = RECORD_LEN_FIXED; fst.ptr_size = 0x4; } else if (*value == 'V') { fst.record_format = RECORD_LEN_VARIABLE; fst.ptr_size = 0xc; } else return -EINVAL; goto write; } if (strcmp(name, xattr_lrecl.name) == 0) { /* done by fs for variable files */ if (fst.record_format == RECORD_LEN_VARIABLE) return -EINVAL; /* only allowed for empty files */ if (fst.nr_records != 0) return -EINVAL; if (size > xattr_lrecl.size) return -ERANGE; in = calloc(size + 1, 1); if (in == NULL) return -ENOMEM; memcpy(in, value, size); errno = 0; rlen = strtol(in, (char **) NULL, 10); free(in); if (errno != 0 || rlen == 0 || rlen > MAX_RECORD_LEN) return -EINVAL; fst.record_len = rlen; goto write; } if (strcmp(name, xattr_mode.name) == 0) { if (size != xattr_mode.size) return -ERANGE; mode_l = value[0]; if (mode_l < 'A' || mode_l > 'Z') return -EINVAL; mode_n = atoi(&value[1]); if (!isdigit(value[1]) || mode_n < 0 || mode_n > 6) return -EINVAL; ebcdic_enc((char *) &fst.mode, value, sizeof(fst.mode)); goto write; } return -ENODATA; write: rc = _write(&fst, sizeof(fst), fst_addr); BUG(rc < 0); return 0; } static int cmsfs_getxattr(const char *path, const char *name, char *value, size_t size) { char buf[xattr_lrecl.size + 1]; struct fst_entry fst; /* nothing for root directory but clear error code needed */ if (strcmp(path, "/") == 0) return -ENODATA; if (!lookup_file(path + 1, &fst, HIDE_UNLINKED)) return -ENOENT; /* null terminate strings */ memset(value, 0, size); /* format */ if (strcmp(name, xattr_format.name) == 0) { if (size == 0) return xattr_format.size; if (size < xattr_format.size) return -ERANGE; if (fst.record_format == RECORD_LEN_FIXED) *value = 'F'; else if (fst.record_format == RECORD_LEN_VARIABLE) *value = 'V'; return xattr_format.size; } /* lrecl */ if (strcmp(name, xattr_lrecl.name) == 0) { if (size == 0) return xattr_lrecl.size; if (size < xattr_lrecl.size) return -ERANGE; memset(buf, 0, sizeof(buf)); snprintf(buf, sizeof(buf), "%d", fst.record_len); memcpy(value, buf, strlen(buf)); return strlen(buf); } /* mode */ if (strcmp(name, xattr_mode.name) == 0) { if (size == 0) return xattr_mode.size; if (size < xattr_mode.size) return -ERANGE; ebcdic_dec(value, (char *) &fst.mode, 2); return xattr_mode.size; } return -ENODATA; } static int cmsfs_listxattr(const char *path, char *list, size_t size) { struct fst_entry fst; size_t list_len; int pos = 0; if (!lookup_file(path + 1, &fst, HIDE_UNLINKED)) return -ENOENT; list_len = strlen(xattr_format.name) + 1 + strlen(xattr_lrecl.name) + 1 + strlen(xattr_mode.name); if (!size) return list_len; if (size < list_len) return -ERANGE; strcpy(list, xattr_format.name); pos += strlen(xattr_format.name) + 1; strcpy(&list[pos], xattr_lrecl.name); pos += strlen(xattr_lrecl.name) + 1; strcpy(&list[pos], xattr_mode.name); pos += strlen(xattr_mode.name) + 1; return pos; } #endif /* HAVE_SETXATTR */ /* * Return the number of unused bytes in the last block. */ static int examine_last_block(struct file *f) { struct record_ext *rext; struct record *rec; int last_bnr, len; off_t start; if (!f->fst->nr_blocks || !f->fst->nr_records) return 0; /* * For subsequent writes we know how much is left on the current block. * */ if (f->wstate->block_state != BWS_BLOCK_NOT_INIT) return f->wstate->block_free; last_bnr = f->fst->nr_blocks - 1; rec = &f->rlist[f->fst->nr_records - 1]; /* last block may be an extension */ if (rec->block_nr == last_bnr) { start = rec->disk_start; len = rec->first_block_len; goto out; } /* if write is split and exactly the extension not yet started */ if (rec->ext == NULL) return 0; rext = rec->ext; do { if (rext->block_nr == last_bnr) { start = rext->disk_start; len = rext->len; goto out; } rext = rext->next; } while (rext != NULL); return 0; out: if (start == NULL_BLOCK) start = f->null_ctr; return ((start | DATA_BLOCK_MASK) + 1) - (start + len); } static int get_record_len(struct file *f, size_t size) { int chunk = 0; if (f->fst->record_format == RECORD_LEN_FIXED) { if (!f->fst->record_len) { chunk = (size > MAX_RECORD_LEN) ? MAX_RECORD_LEN : size; f->fst->record_len = chunk; } else chunk = f->fst->record_len; if (chunk > MAX_RECORD_LEN) return -EINVAL; if (size < (size_t) chunk) chunk = size; } else if (f->fst->record_format == RECORD_LEN_VARIABLE) { chunk = size; if (chunk > MAX_RECORD_LEN) chunk = MAX_RECORD_LEN; } return chunk; } /* * Get the disk address of the first byte after the last record. */ static off_t disk_end(struct file *f) { struct record_ext *rext; struct record *rec; if (!f->fst->nr_records) return 0; /* * Only the first write on a newly opened file should set the write_ptr * according to what is on the disk. Subsequent writes should use the * actual write_ptr. This avoids the problem for fixed records that * for a record that is not yet completely written the calculated * write_ptr would be wrong. */ if (f->wstate->block_state != BWS_BLOCK_NOT_INIT) return f->write_ptr; rec = &f->rlist[f->fst->nr_records - 1]; if (rec->ext == NULL) { if (rec->disk_start == NULL_BLOCK) return 0; else return rec->disk_start + rec->first_block_len; } rext = rec->ext; while (rext->next != NULL) rext = rext->next; if (rext->disk_start == NULL_BLOCK) return 0; else return rext->disk_start + rext->len; } /* * Store the displacement for the first write to a new block. * nr_blocks already contains the new block. If the block is completely filled * the displacement is spanned, also if one header byte is used * since the header belongs to the record regarding the * displacement but not if both header bytes are on the block. */ void store_displacement(struct file *f, int disp) { f->blist[f->fst->nr_blocks - 1].disp = disp; f->wstate->block_state = BWS_BLOCK_USED; } /* * Allocate a new block if needed, set write pointer and return the number * of bytes available on the block. */ static int write_prepare_block(struct file *f, int null_block, ssize_t size) { int len, new_block = 0; if (null_block) { /* * TODO: need to write header before killing write_ptr for * sparse files. */ BUG(f->fst->record_format == RECORD_LEN_VARIABLE); f->write_ptr = 0; /* new null block started ? */ if (f->null_ctr % cmsfs.blksize == 0) { new_block = 1; len = cmsfs.blksize; /* * Prevent allocating a null block if the block would * not be complete. Use a normal block instead. */ if (size < cmsfs.blksize) { f->write_ptr = get_zero_block(); if (f->write_ptr < 0) return f->write_ptr; } } else len = ((f->null_ctr | DATA_BLOCK_MASK) + 1) - f->null_ctr; } else { if (f->write_ptr % cmsfs.blksize == 0) { /* * For fixed files use a different padding in text * mode to pad records with spaces. */ if (f->fst->record_format == RECORD_LEN_FIXED && f->translate) f->write_ptr = get_filled_block(); else f->write_ptr = get_zero_block(); if (f->write_ptr < 0) { /* reset to catch subsequent writes */ f->write_ptr = 0; return -ENOSPC; } new_block = 1; len = cmsfs.blksize; } else len = ((f->write_ptr | DATA_BLOCK_MASK) + 1) - f->write_ptr; } if (new_block) { f->wstate->block_state = BWS_BLOCK_NEW; f->blist[f->fst->nr_blocks].disk_addr = f->write_ptr; if (!f->write_ptr && f->fst->record_format == RECORD_LEN_FIXED) f->nr_null_blocks++; f->wstate->block_free = cmsfs.blksize; f->fst->nr_blocks++; } return len; } /* * Write variable record length header and return number of written bytes. */ static int write_var_header(struct file *f, int len, u16 vheader) { u8 half_vheader; int rc; if (f->wstate->var_record_state == RWS_HEADER_COMPLETE || f->wstate->var_record_state == RWS_RECORD_INCOMPLETE) return 0; if (f->wstate->var_record_state == RWS_HEADER_STARTED) { /* write secord header byte */ half_vheader = vheader & 0xff; rc = _write(&half_vheader, 1, f->write_ptr); if (rc < 0) return rc; f->write_ptr++; f->wstate->block_free--; f->wstate->var_record_state = RWS_HEADER_COMPLETE; f->wstate->var_records_written++; return 1; } /* block cannot be spanned if a header starts on it */ if (f->wstate->block_state == BWS_BLOCK_NEW) store_displacement(f, f->write_ptr & DATA_BLOCK_MASK); if (len >= 2) { rc = _write(&vheader, 2, f->write_ptr); if (rc < 0) return rc; f->write_ptr += 2; f->wstate->block_free -= 2; f->wstate->var_record_len = vheader; f->wstate->var_record_state = RWS_HEADER_COMPLETE; f->wstate->var_records_written++; return 2; } else { /* len = 1, write first header byte */ half_vheader = vheader >> 8; rc = _write(&half_vheader, 1, f->write_ptr); if (rc < 0) return rc; f->write_ptr++; f->wstate->block_free--; f->wstate->var_record_len = vheader; f->wstate->var_record_state = RWS_HEADER_STARTED; return 1; } } static int extend_block_fixed(struct file *f, const char *buf, int len, size_t size, int rlen) { int rc; (void) rlen; if (size < (size_t) len) len = size; if (f->write_ptr) { rc = _write(buf, len, f->write_ptr); if (rc < 0) return rc; f->write_ptr += len; f->wstate->block_free -= len; } return len; } static int extend_block_variable(struct file *f, const char *buf, int len, size_t size, int rlen) { int rc, copied = 0, vh_len = 0, max = cmsfs.blksize; if (!f->write_ptr) return len; while (len > 0) { /* record may be party written already */ if (size < (size_t) rlen) rlen = size; vh_len = write_var_header(f, len, rlen); if (vh_len < 0) return vh_len; len -= vh_len; if (!len) return copied; /* record does not fit on block */ if (len < rlen) rlen = len; /* remaining record data less than block len */ if (f->wstate->var_record_len < rlen) rlen = f->wstate->var_record_len; rc = _write(buf, rlen, f->write_ptr); if (rc < 0) return rc; f->write_ptr += rlen; f->wstate->block_free -= rlen; if (f->wstate->block_state == BWS_BLOCK_NEW) { /* * If the second byte of a split header was written * (blocksize - 1) is enough to make the block spanned. */ if (vh_len == 1) max--; if (rlen >= max) store_displacement(f, VAR_RECORD_SPANNED); else store_displacement(f, f->write_ptr & DATA_BLOCK_MASK); } copied += rlen; size -= rlen; len -= rlen; f->wstate->var_record_len -= rlen; BUG(f->wstate->var_record_len < 0); if (!f->wstate->var_record_len) { f->wstate->var_record_state = RWS_RECORD_COMPLETE; /* reset rlen for the next record */ rlen = get_record_len(f, size); } DEBUG("%s: wrote %d record bytes\n", __func__, rlen); if (size <= 0) return copied; } /* record is not yet finished */ f->wstate->var_record_state = RWS_RECORD_INCOMPLETE; return copied; } /* * Extend an existing block or write data on a new block. * * size: requestes bytes to write to disk * rlen: projected record len * len: bytes left on the block */ static int extend_block(struct file *f, const char *buf, size_t size, int rlen) { int len = write_prepare_block(f, (buf == NULL) ? 1 : 0, size); if (len < 0) return -ENOSPC; BUG(!len); return f->fops->write_data(f, buf, len, size, rlen); } /* * Delete the record data from rlist and free extensions. */ static void delete_record(struct file *f, int nr) { struct record *rec = &f->rlist[nr]; struct record_ext *tmp, *rext = rec->ext; memset(rec, 0, sizeof(struct record)); while (rext != NULL) { tmp = rext->next; free(rext); rext = tmp; } } /* * Update records from a start record to the end. The start record is one less * than the previous last record since the previous last record may be * incomplete. */ static int update_records(struct file *f, int nrecords) { int i, rc, rnr, bnr = 0, skip = 0; off_t total = 0; rnr = f->fst->nr_records - 1; if (rnr >= 0) { total = f->rlist[rnr].file_start; if (f->linefeed && total) total--; bnr = f->rlist[rnr].block_nr; skip = f->rlist[rnr].disk_start & DATA_BLOCK_MASK; /* skip must point before a variable header */ if (f->fst->record_format == RECORD_LEN_VARIABLE) { if (skip >= VAR_RECORD_HEADER_SIZE) skip -= VAR_RECORD_HEADER_SIZE; else if (skip == 1) { bnr--; skip = cmsfs.blksize - 1; } else if (skip == 0 && f->rlist[rnr].disk_start) { bnr--; skip = cmsfs.blksize - 2; } } delete_record(f, rnr); rnr--; } if (rnr < -1) { rnr = -1; skip = 0; total = 0; bnr = 0; } f->fst->nr_records += nrecords; f->record_scan_state = RSS_DATA_BLOCK_STARTED; for (i = bnr; i < f->fst->nr_blocks; i++) { if (f->fst->record_format == RECORD_LEN_FIXED) cache_fixed_data_block(f, f->blist[i].disk_addr + skip, &bnr, &rnr, &total, skip); else { rc = cache_variable_data_block(f, f->blist[i].disk_addr + skip, &bnr, &rnr, f->blist[i].disp, &total, skip); if (rc < 0) return rc; } skip = 0; } return 0; } /* * Calculate the number of new records. */ static int new_records(struct file *f, size_t size, int rlen) { double tmp = size; int len; if (f->fst->record_format == RECORD_LEN_FIXED) { len = f->fst->record_len; if (f->linefeed) len++; /* need to fill a previously started record first */ if (f->session_size && f->session_size % len) tmp = tmp - (len - (f->session_size % len)); } if (tmp <= 0) return 0; tmp = ceil(tmp / rlen); return (int) tmp; } /* * Calculate number of new blocks. */ static int new_blocks(struct file *f, size_t size, int last, int nrecords) { int last_usable = last; double tmp = size; /* subtract header bytes */ if (last_usable && f->fst->record_format == RECORD_LEN_VARIABLE) { if (last_usable == 1) last_usable = 0; else last_usable -= VAR_RECORD_HEADER_SIZE; } if ((int) size <= last_usable) return 0; if (f->fst->record_format == RECORD_LEN_VARIABLE) tmp += (double) nrecords * VAR_RECORD_HEADER_SIZE; tmp -= last; if (tmp <= 0) return 0; tmp = ceil(tmp / cmsfs.blksize); return (int) tmp; } /* * Increase record list count. */ static void resize_rlist(struct file *f, int new) { if (!new) return; f->rlist = realloc(f->rlist, (f->fst->nr_records + new) * sizeof(struct record)); if (f->rlist == NULL) DIE_PERROR("realloc failed"); memset(&f->rlist[f->fst->nr_records], 0, new * sizeof(struct record)); } /* * Increase block list count. */ static void resize_blist(struct file *f, int new) { if (!new) return; f->blist = realloc(f->blist, (f->fst->nr_blocks + new) * sizeof(struct block)); if (f->blist == NULL) DIE_PERROR("realloc failed"); memset(&f->blist[f->fst->nr_blocks], 0, new * sizeof(struct block)); } /* * Reserve blocks for meta data (pointer blocks) of a file so that * blocks for meta-data are available if the disk runs full. */ static void reserve_meta_blocks(struct file *f, int old, int new, int level) { double nentries, oentries; int newp; if (!new) return; newp = pointers_per_level(f, level, old + new); oentries = pointers_per_level(f, level, old); oentries = ceil(oentries / f->ptr_per_block); nentries = newp; nentries = ceil(nentries / f->ptr_per_block); cmsfs.reserved_blocks += nentries - oentries; if (newp > f->ptr_per_block) reserve_meta_blocks(f, oentries, nentries, ++level); } /* * Update the max record number in the last pointer block per level. */ static int update_last_block_vptr(struct file *f, off_t addr, int level, struct var_ptr *vptr) { int last, rc; if (!level) return 0; level--; /* read offset pointer from the end of the var pointer block */ rc = _read(&last, sizeof(last), addr + cmsfs.blksize - sizeof(last)); if (rc < 0) return rc; if (last % sizeof(*vptr) || last > cmsfs.blksize) return -EIO; rc = _read(vptr, sizeof(*vptr), addr + last); if (rc < 0) return rc; /* update max record number */ vptr->hi_record_nr = f->fst->nr_records; rc = _write(vptr, sizeof(*vptr), addr + last); if (rc < 0) return rc; if (!level) return 0; if (vptr->next == NULL_BLOCK) return 0; return update_last_block_vptr(f, ABS(vptr->next), level, vptr); } static void reset_write_state(struct file *f) { f->wstate->var_record_state = RWS_RECORD_COMPLETE; f->wstate->var_record_len = 0; f->wstate->var_records_written = 0; } /* * Append records at current file end. If buf is NULL write zero bytes. */ static int write_append(struct file *f, const char *buf, size_t size) { int rc, i, nrecords, nblocks, last, len, copied = 0; int rlen = get_record_len(f, size); if (rlen < 0) return rlen; nrecords = new_records(f, size, rlen); /* get last block unused bytes for block count */ last = examine_last_block(f); /* initialize write_ptr once */ f->write_ptr = disk_end(f); nblocks = new_blocks(f, size, last, nrecords); if (nblocks > 0) f->ptr_dirty = 1; resize_rlist(f, nrecords); resize_blist(f, nblocks); if (f->fst->nr_blocks + nblocks > 1) reserve_meta_blocks(f, f->fst->nr_blocks, nblocks, 1); reset_write_state(f); /* first use existing last block */ if (last) { len = extend_block(f, buf, size, rlen); if (len < 0) return len; copied += len; size -= len; if (buf != NULL) buf += len; } for (i = 0; i < nblocks; i++) { len = extend_block(f, buf, size, rlen); if (len < 0) { if (copied > 0) { /* * Not all records may be written, need to * update nrecords to store a correct * hi_record_nr in the last vptr. */ if (f->fst->record_format == RECORD_LEN_VARIABLE) nrecords = f->wstate->var_records_written; goto out; } else return len; } copied += len; size -= len; if (buf != NULL) buf += len; DEBUG("%s: wrote: %d bytes\n", __func__, copied); } out: rc = update_records(f, nrecords); if (rc < 0) return rc; if (f->fst->record_format == RECORD_LEN_VARIABLE) update_lrecl_fast(f, rlen); return copied; } static int do_write(struct file *f, const char *buf, size_t size, off_t offset) { off_t len, copied = 0; struct var_ptr vptr; int rc; if (!size) return 0; len = f->session_size; if (f->linefeed) len -= f->fst->nr_records; BUG(len < 0); if (offset < len) return -EINVAL; /* * Writes with null blocks (sparse files) may be prevented by tools * which call lseek instead. Since we don't implement lseek fuse may * call us with an offset after file. We don't support sparse file * writes currently. */ if (offset > len) return -EINVAL; copied = write_append(f, buf, size); if (copied <= 0) return copied; f->session_size += copied; /* add linefeed byte */ if (f->linefeed) f->session_size++; if (f->ptr_dirty) { f->old_levels = f->fst->levels; update_levels(f); if (f->fst->fop) f->fops->delete_pointers(f, f->old_levels, ABS(f->fst->fop)); rc = rewrite_pointers(f); if (rc < 0) return rc; f->ptr_dirty = 0; } else if (f->fst->levels > 0 && f->fst->record_format == RECORD_LEN_VARIABLE) { rc = update_last_block_vptr(f, ABS(f->fst->fop), f->fst->levels, &vptr); if (rc < 0) return rc; } rc = set_fst_date_current(f->fst); if (rc != 0) return rc; update_fst(f, f->fst_addr); set_fdir_date_current(); update_block_count(); return copied; } static void cache_write_data(struct file *f, const char *buf, int len) { if (f->wcache_used + len > WCACHE_MAX) len = WCACHE_MAX - f->wcache_used; if (buf == NULL) memset(&f->wcache[f->wcache_used], FILLER_ASCII, len); else memcpy(&f->wcache[f->wcache_used], buf, len); f->wcache_used += len; } static void purge_wcache(struct file *f) { f->wcache_used = 0; f->wcache_commited = 0; } /* * Scan for the next newline character and return the number of bytes in * this record. */ static ssize_t find_newline(const char *buf, int len) { char *pos; pos = memchr(buf, LINEFEED_ASCII, len); if (pos == NULL) return LINEFEED_NOT_FOUND; else return pos - buf; } static int cmsfs_write(const char *path, const char *buf, size_t size, off_t offset, struct fuse_file_info *fi) { int scan_len = MIN(size, (size_t)MAX_RECORD_LEN + 1); int rc, nl_byte = 1, null_record = 0, pad = 0; struct file *f = get_fobj(fi); ssize_t rsize; (void) path; if (cmsfs.readonly) return -EROFS; if (!f->linefeed) return do_write(f, buf, size, offset); /* remove already committed bytes */ offset -= f->wcache_commited; /* write offset must be at the end of the file */ if (offset + f->null_records + f->pad_bytes != f->session_size) return -EINVAL; if (f->fst->record_format == RECORD_LEN_FIXED && f->fst->record_len) scan_len = MIN(scan_len, f->fst->record_len + 1); rsize = find_newline(buf, scan_len); BUG(rsize < LINEFEED_NOT_FOUND); if (rsize == LINEFEED_NOT_FOUND) { if (f->wcache_used + scan_len >= WCACHE_MAX) { purge_wcache(f); return -EINVAL; } else { if (f->fst->record_format == RECORD_LEN_FIXED && f->wcache_commited + scan_len > f->fst->record_len) { purge_wcache(f); return -EINVAL; } cache_write_data(f, buf, scan_len); f->wcache_commited += scan_len; return scan_len; } } cache_write_data(f, buf, rsize); if (f->fst->record_format == RECORD_LEN_FIXED && f->wcache_used < f->fst->record_len) { pad = f->fst->record_len - f->wcache_used; cache_write_data(f, NULL, pad); } /* translate */ rc = convert_text(cmsfs.iconv_to, f->wcache, f->wcache_used); if (rc < 0) return rc; /* * Note: empty records are forbidden by design. CMS converts * an empty record to a single space. Follow that convention. */ if (!f->wcache_used) { *f->wcache = FILLER_EBCDIC; f->wcache_used = 1; nl_byte = 0; null_record = 1; } /* correct file offset by removing the virtual linefeeds */ offset -= (f->fst->nr_records - f->null_records); offset += f->pad_bytes; BUG(offset < 0); rc = do_write(f, f->wcache, f->wcache_used, offset); if (rc < 0) return rc; rc += nl_byte; if (null_record) f->null_records++; rc -= f->wcache_commited; rc -= pad; BUG(rc < 0); purge_wcache(f); f->pad_bytes += pad; return rc; } static int cmsfs_unlink(const char *path) { struct fst_entry fst; off_t fst_addr; struct file *f; if (cmsfs.readonly) return -EROFS; fst_addr = lookup_file(path + 1, &fst, HIDE_UNLINKED); if (!fst_addr) return -ENOENT; f = file_open(path + 1); if (f != NULL) { f->unlinked = 1; return 0; } return delete_file(path); } static int flush_wcache(struct file *f) { off_t offset = f->session_size; int rc; /* translate */ rc = convert_text(cmsfs.iconv_to, f->wcache, f->wcache_used); if (rc < 0) return rc; /* correct file offset by removing the virtual linefeeds */ offset -= (f->fst->nr_records - f->null_records); BUG(offset < 0); rc = do_write(f, f->wcache, f->wcache_used, offset); purge_wcache(f); f->null_records = 0; if (rc < 0) return rc; return 0; } static int cmsfs_release(const char *path, struct fuse_file_info *fi) { struct file *f = get_fobj(fi); int rc = 0; (void) path; if (f == NULL) { DEBUG("release internal error\n"); return -EINVAL; } if (fi->flags & O_RDWR || fi->flags & O_WRONLY) { f->write_count--; if (f->wcache_used) rc = flush_wcache(f); } if (f->use_count == 1) { if (f->unlinked) delete_file(f->path); util_list_remove(&open_file_list, f); destroy_file_object(f); } else f->use_count--; fi->fh = 0; return rc; } static void init_fops(struct file *f) { if (f->fst->record_format == RECORD_LEN_FIXED) f->fops = &fops_fixed; else f->fops = &fops_variable; } /* * Create a file object to cache all needed file data. * Note: the caller must ensure that the file exists. */ static struct file *create_file_object(struct fst_entry *fst, int *rc) { struct file *f; f = malloc(sizeof(*f)); if (f == NULL) goto oom; memset(f, 0, sizeof(*f)); f->fst = malloc(sizeof(struct fst_entry)); if (f->fst == NULL) goto oom_f; memcpy(f->fst, fst, sizeof(*fst)); workaround_nr_blocks(f); init_fops(f); f->linefeed = linefeed_mode_enabled(f->fst); f->translate = f->linefeed; f->record_scan_state = RSS_DATA_BLOCK_STARTED; if (f->fst->record_format == RECORD_LEN_FIXED) f->ptr_per_block = cmsfs.fixed_ptrs_per_block; else f->ptr_per_block = cmsfs.var_ptrs_per_block; f->wstate = malloc(sizeof(*f->wstate)); if (f->wstate == NULL) goto oom_fst; memset(f->wstate, 0, sizeof(*f->wstate)); /* * Prevent calloc for zero records since it returns a pointer != NULL * which causes trouble at free. Also don't call cache_file. */ if (f->fst->nr_records == 0) return f; f->rlist = calloc(f->fst->nr_records, sizeof(struct record)); if (f->rlist == NULL) goto oom_wstate; f->blist = calloc(f->fst->nr_blocks, sizeof(struct block)); if (f->blist == NULL) goto oom_rlist; *rc = cache_file(f); if (*rc < 0) goto error; return f; error: if (*rc == 0) *rc = -ENOMEM; oom_rlist: free(f->rlist); oom_wstate: free(f->wstate); oom_fst: free(f->fst); oom_f: free(f); oom: return NULL; } static void destroy_file_object(struct file *f) { struct record_ext *rext, *tmp; struct record *rec; int i; free(f->wcache); free(f->wstate); for (i = 0; i < f->fst->nr_records; i++) { rec = &f->rlist[i]; rext = rec->ext; while (rext != NULL) { tmp = rext->next; free(rext); rext = tmp; } } free(f->rlist); free(f->blist); free(f->fst); free(f); } static struct file_operations fops_fixed = { .cache_data = cache_file_fixed, .write_data = extend_block_fixed, .delete_pointers = purge_pointer_block_fixed, .write_pointers = rewrite_pointer_block_fixed, }; static struct file_operations fops_variable = { .cache_data = cache_file_variable, .write_data = extend_block_variable, .delete_pointers = purge_pointer_block_variable, .write_pointers = rewrite_pointer_block_variable, }; static struct fuse_operations cmsfs_oper = { .getattr = cmsfs_getattr, .statfs = cmsfs_statfs, .readdir = cmsfs_readdir, .open = cmsfs_open, .release = cmsfs_release, .read = cmsfs_read, .utimens = cmsfs_utimens, .rename = cmsfs_rename, .fsync = cmsfs_fsync, .truncate = cmsfs_truncate, .create = cmsfs_create, .write = cmsfs_write, .unlink = cmsfs_unlink, #ifdef HAVE_SETXATTR .listxattr = cmsfs_listxattr, .getxattr = cmsfs_getxattr, .setxattr = cmsfs_setxattr, /* no removexattr since our xattrs are virtual */ #endif }; static int cmsfs_fuse_main(struct fuse_args *args, struct fuse_operations *cmsfs_oper) { #if FUSE_VERSION >= 26 return fuse_main(args->argc, args->argv, cmsfs_oper, NULL); #else return fuse_main(args->argc, args->argv, cmsfs_oper); #endif } static int cmsfs_process_args(void *data, const char *arg, int key, struct fuse_args *outargs) { (void) data; switch (key) { case FUSE_OPT_KEY_OPT: if (strcmp(arg, "allow_other") == 0) cmsfs.allow_other = 1; return 1; case FUSE_OPT_KEY_NONOPT: if (cmsfs.device == NULL) { cmsfs.device = strdup(arg); return 0; } return 1; case KEY_HELP: usage(outargs->argv[0]); fuse_opt_add_arg(outargs, "-ho"); cmsfs_fuse_main(outargs, &cmsfs_oper); exit(0); case KEY_VERSION: fprintf(stderr, COMP "FUSE file system for CMS disks " "program version %s\n", RELEASE_STRING); fprintf(stderr, "Copyright IBM Corp. 2010, 2017\n"); fuse_opt_add_arg(outargs, "--version"); exit(0); default: DIE("Process arguments error\n"); } } static void init_io(int fd) { int prot; DEBUG("read-only: %d", cmsfs.readonly); cmsfs.fd = fd; cmsfs.size = (off_t) cmsfs.total_blocks * cmsfs.blksize; DEBUG(" mmap size: %lu", cmsfs.size); /* try to map the whole block device for speeding-up access */ if (cmsfs.readonly) prot = PROT_READ; else prot = PROT_READ | PROT_WRITE; cmsfs.map = mmap(NULL, cmsfs.size, prot, MAP_SHARED, fd, 0); if (cmsfs.map == MAP_FAILED) { DEBUG("\nmmap failed, using pread/write for disk I/O.\n"); io_ops.read = &read_syscall; io_ops.write = &write_syscall; } else { DEBUG(" addr: %p\n", cmsfs.map); io_ops.read = &read_memory; io_ops.write = &write_memory; } } static void cmsfs_init(int fd) { init_io(fd); /* calculate blocksize dependent values */ cmsfs.data_block_mask = cmsfs.blksize - 1; cmsfs.nr_blocks_512 = cmsfs.blksize / 512; cmsfs.fixed_ptrs_per_block = cmsfs.blksize / sizeof(struct fixed_ptr); cmsfs.var_ptrs_per_block = cmsfs.blksize / sizeof(struct var_ptr); cmsfs.bits_per_data_block = get_order(cmsfs.blksize); /* store directory information */ cmsfs.dir_levels = get_levels(cmsfs.fdir); cmsfs.files = get_files_count(cmsfs.fdir); /* alloc cache entries for all files */ cmsfs.fcache_max = max_cache_entries(); cmsfs.fcache = calloc(cmsfs.fcache_max, sizeof(struct fcache_entry)); cmsfs.amap = get_fop(cmsfs.fdir + sizeof(struct fst_entry)); cmsfs.amap_levels = get_levels(cmsfs.fdir + sizeof(struct fst_entry)); cmsfs.amap_bytes_per_block = cmsfs.blksize * 8 * cmsfs.blksize; if (!hcreate_r(cmsfs.fcache_max, &cmsfs.htab)) DIE("hcreate failed\n"); util_list_init(&open_file_list, struct file, list); util_list_init(&text_type_list, struct filetype, list); scan_conf_file(&text_type_list); } int main(int argc, char *argv[]) { struct fuse_args args = FUSE_ARGS_INIT(argc, argv); char *fsname; int rc, fd; #ifdef DEBUG_ENABLED logfile = fopen(DEBUG_LOGFILE, "w"); if (logfile == NULL) DIE_PERROR("Cannot open file " DEBUG_LOGFILE " for writing"); #endif if (fuse_opt_parse(&args, &cmsfs, cmsfs_opts, cmsfs_process_args) == -1) DIE("Failed to parse option\n"); if (!cmsfs.device) DIE("Missing device\n" "Try '%s --help' for more information\n", argv[0]); DEBUG("using device: %s", cmsfs.device); fd = get_device_info(&cmsfs); DEBUG(" blocksize: %d\n", cmsfs.blksize); fsname = malloc(FSNAME_MAX_LEN); if (fsname == NULL) DIE_PERROR("malloc failed"); #if FUSE_VERSION >= 27 snprintf(fsname, FSNAME_MAX_LEN, "-osubtype=cmsfs,fsname=%s", cmsfs.device); #else snprintf(fsname, FSNAME_MAX_LEN, "-ofsname=%s", cmsfs.device); #endif fuse_opt_add_arg(&args, fsname); free(fsname); cmsfs_init(fd); if (cmsfs.readonly) fuse_opt_add_arg(&args, "-oro"); /* force single threaded mode which requires no locking */ fuse_opt_add_arg(&args, "-s"); /* force immediate file removal */ fuse_opt_add_arg(&args, "-ohard_remove"); if (cmsfs.mode == BINARY_MODE && (cmsfs.codepage_from != NULL || cmsfs.codepage_to != NULL)) DIE("Incompatible options, select -a or -t if using --from or --to\n"); if (cmsfs.mode != BINARY_MODE) { if (cmsfs.codepage_from == NULL) cmsfs.codepage_from = CODEPAGE_EDF; if (cmsfs.codepage_to == NULL) cmsfs.codepage_to = CODEPAGE_LINUX; setup_iconv(&cmsfs.iconv_from, cmsfs.codepage_from, cmsfs.codepage_to); setup_iconv(&cmsfs.iconv_to, cmsfs.codepage_to, cmsfs.codepage_from); } rc = cmsfs_fuse_main(&args, &cmsfs_oper); fuse_opt_free_args(&args); #ifdef DEBUG_ENABLED fclose(logfile); #endif hdestroy_r(&cmsfs.htab); return rc; } s390-tools-2.3.0/cmsfs-fuse/cmsfs-fuse.h000066400000000000000000000056371323410431100176520ustar00rootroot00000000000000/* * cmsfs-fuse - CMS EDF filesystem support for Linux * * Data structures * * Copyright IBM Corp. 2010, 2017 * * s390-tools is free software; you can redistribute it and/or modify * it under the terms of the MIT license. See LICENSE for details. */ #ifndef _CMSFS_H #define _CMSFS_H #include #include #include "lib/util_list.h" #define COMP "cmsfs-fuse: " extern struct cmsfs cmsfs; /* conversion between absolute and relative addresses */ #define ABS(x) ((off_t) (x - 1) * cmsfs.blksize) #define REL(x) ((x / cmsfs.blksize) + 1) struct fcache_entry { /* filename used as hash key */ char name[18]; /* location of fst entry */ off_t fst_addr; /* filename string address */ char *str; }; enum cmsfs_mode { BINARY_MODE, TEXT_MODE, TYPE_MODE, }; /* the per device global struture */ struct cmsfs { /* name of the block device, e.g. /dev/dasde */ const char *device; /* global file descriptor of the underlying block device */ int fd; /* start of mmap of the whole block device */ char *map; /* size of the disk */ off_t size; /* formatted blocksize */ int blksize; /* number of 512 byte blocks per block */ int nr_blocks_512; /* device is read only */ int readonly; /* access permission for other users */ int allow_other; /* offset to label */ off_t label; /* offset to file directory root FST */ off_t fdir; /* offset to allocation map */ off_t amap; /* depth of directories */ int dir_levels; /* depth of allocation maps */ int amap_levels; /* files count on the device */ int files; /* conversion mode */ enum cmsfs_mode mode; /* iconv codepage options */ const char *codepage_from; const char *codepage_to; iconv_t iconv_from; iconv_t iconv_to; /* disk stats */ int total_blocks; int used_blocks; /* blocks reserved for outstanding meta data */ int reserved_blocks; /* constants */ int fixed_ptrs_per_block; int var_ptrs_per_block; int bits_per_data_block; int bits_per_ptr_block; int data_block_mask; off_t amap_bytes_per_block; /* file cache */ struct fcache_entry *fcache; int fcache_used; int fcache_max; struct hsearch_data htab; }; #define MAX_TYPE_LEN 9 struct filetype { char name[MAX_TYPE_LEN]; struct util_list_node list; }; #define NULL_BLOCK 0 #define VAR_FILE_END 1 #define PTRS_PER_BLOCK (cmsfs.fixed_ptrs_per_block) #define VPTRS_PER_BLOCK (cmsfs.var_ptrs_per_block) #define DATA_BLOCK_MASK (cmsfs.data_block_mask) #define BITS_PER_DATA_BLOCK (cmsfs.bits_per_data_block) #define BYTES_PER_BLOCK (cmsfs.amap_bytes_per_block) extern int get_device_info(struct cmsfs *cmsfs); extern int scan_conf_file(struct util_list *list); extern int is_edf_char(int c); #ifndef _CMSFS_FSCK int _read(void *, size_t, off_t); int _write(const void *, size_t, off_t); int _zero(off_t, size_t); off_t get_fixed_pointer(off_t); off_t get_free_block(void); off_t get_zero_block(void); void free_block(off_t); #endif #endif s390-tools-2.3.0/cmsfs-fuse/config.c000066400000000000000000000046611323410431100170330ustar00rootroot00000000000000/* * cmsfs-fuse - CMS EDF filesystem support for Linux * * Config option parsing * * Copyright IBM Corp. 2010, 2017 * * 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 #include #include #include #include #include #include #include #include #include "lib/zt_common.h" #include "cmsfs-fuse.h" #include "helper.h" #define MAX_LINE_LEN 80 static char *conf_file; static int open_conf_file(FILE **fh) { const char *home_env; conf_file = malloc(4096); if (conf_file == NULL) DIE_PERROR("malloc failed"); home_env = getenv("HOME"); if (home_env == NULL) goto no_home; sprintf(conf_file, "%s/.cmsfs-fuse/filetypes.conf", home_env); *fh = fopen(conf_file, "r"); if (*fh != NULL) goto out; no_home: sprintf(conf_file, "%s/%s", TOOLS_SYSCONFDIR, "/cmsfs-fuse/filetypes.conf"); *fh = fopen(conf_file, "r"); if (*fh == NULL) { free(conf_file); return -ENOENT; } out: DEBUG("using config file: %s\n", conf_file); return 0; } static void add_filetype(char *name, struct util_list *list) { struct filetype *entry; entry = malloc(sizeof(*entry)); if (entry == NULL) DIE_PERROR("malloc failed"); strncpy(entry->name, name, MAX_TYPE_LEN); util_list_add_head(list, entry); } static int filetype_valid(const char *type, int line) { unsigned int i; if (strlen(type) > 8) { WARN("entry too long in line: %d in config file: %s\n", line, conf_file); return 0; } for (i = 0; i < strlen(type); i++) if (!is_edf_char(*(type + i))) { WARN("invalid character in line: %d in config file: %s\n", line, conf_file); return 0; } return 1; } int scan_conf_file(struct util_list *list) { char buf[MAX_LINE_LEN], *tmp; int line = 0; FILE *fh; if (open_conf_file(&fh) < 0) return -ENOENT; while (fgets(buf, MAX_LINE_LEN, fh) != NULL) { line++; tmp = buf; while (isblank(*tmp)) tmp++; if (*tmp == '\n') continue; /* * Skip comments, comment must be "# " because # is a valid * EDF character. */ if (strlen(tmp) > 1 && *tmp == '#' && *(tmp + 1) == ' ') continue; /* remove trailing \n */ if (strlen(tmp) && *(tmp + strlen(tmp) - 1) == '\n') *(tmp + strlen(tmp) - 1) = '\0'; if (filetype_valid(tmp, line)) add_filetype(tmp, list); } fclose(fh); free(conf_file); return 0; } s390-tools-2.3.0/cmsfs-fuse/dasd.c000066400000000000000000000077571323410431100165120ustar00rootroot00000000000000/* * cmsfs-fuse - CMS EDF filesystem support for Linux * * DASD specific functions * * Copyright IBM Corp. 2010, 2017 * * 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 #include #include #include #include #include #include #include #include #include "cmsfs-fuse.h" #include "edf.h" #include "helper.h" #define BLKSSZGET _IO(0x12, 104) /* CMS disk label starts with ASCII string "CMS1" */ #define VOL_LABEL_EBCDIC 0xc3d4e2f1 static int disk_supported(int fd, struct cmsfs *cmsfs) { unsigned int cms_id = VOL_LABEL_EBCDIC; struct cms_label label; int rc; rc = lseek(fd, cmsfs->label, SEEK_SET); if (rc < 0) { perror(COMP "lseek failed"); return 0; } rc = read(fd, &label, sizeof(label)); if (rc < 0) { perror(COMP "read failed"); return 0; } /* check that the label contains the CMS1 string */ if (memcmp(label.id, &cms_id, sizeof(cms_id)) != 0) return 0; /* label sanity checks */ if (label.blocksize != 4096 && label.blocksize != 2048 && label.blocksize != 1024 && label.blocksize != 512) { fprintf(stderr, COMP "Invalid disk block size!\n"); return 0; } if (label.dop != 4 && label.dop != 5) { fprintf(stderr, COMP "Invalid disk origin pointer!\n"); return 0; } if (label.fst_entry_size != sizeof(struct fst_entry)) { fprintf(stderr, COMP "Invalid FST entry size!\n"); return 0; } if (label.fst_per_block != label.blocksize / label.fst_entry_size) { fprintf(stderr, COMP "Invalid FST per block value!\n"); return 0; } /* set the blocksize to the formatted one */ cmsfs->blksize = label.blocksize; DEBUG(" DOP: %d", label.dop); /* block number 5 means 0x4000... */ cmsfs->fdir = (label.dop - 1) * cmsfs->blksize; DEBUG(" fdir: %lx", cmsfs->fdir); /* get disk usage for statfs */ cmsfs->total_blocks = label.total_blocks; cmsfs->used_blocks = label.used_blocks; DEBUG(" Total blocks: %d Used blocks: %d", cmsfs->total_blocks, cmsfs->used_blocks); return 1; } static void get_device_info_ioctl(int fd, struct cmsfs *cmsfs) { if (ioctl(fd, BLKSSZGET, &cmsfs->blksize) != 0) DIE("ioctl error get blocksize\n"); } static int label_offsets[] = { 4096, 512, 2048, 1024, 8192 }; static void get_device_info_file(int fd, struct cmsfs *cmsfs) { unsigned int cms_id = VOL_LABEL_EBCDIC; unsigned int i; char label[4]; off_t offset; int rc; cmsfs->label = 0; /* * Read the blocksize from label. Unfortunately the blocksize * position depends on the blocksize... time for some heuristics. */ for (i = 0; i < ARRAY_SIZE(label_offsets); i++) { offset = label_offsets[i]; rc = lseek(fd, offset, SEEK_SET); if (rc < 0) DIE_PERROR("lseek failed"); rc = read(fd, &label, 4); if (rc < 0) DIE_PERROR("read failed"); /* check if the label contains the CMS1 string */ if (memcmp(label, &cms_id, sizeof(cms_id)) == 0) { cmsfs->label = offset; break; } } if (!cmsfs->label) DIE("Error CMS1 label not found!\n"); } int get_device_info(struct cmsfs *cmsfs) { struct stat stat; int fd; /* * Open writable, if write access is not granted fall back to * read only. */ fd = open(cmsfs->device, O_RDWR); if (fd < 0) { if (errno == EROFS || errno == EACCES) { cmsfs->readonly = 1; fd = open(cmsfs->device, O_RDONLY); if (fd < 0) DIE_PERROR("open failed"); } else DIE_PERROR("open failed"); } if (fstat(fd, &stat) < 0) DIE_PERROR("fstat failed"); if (S_ISBLK(stat.st_mode)) { get_device_info_ioctl(fd, cmsfs); cmsfs->label = 2 * cmsfs->blksize; /* FBA disks have a different label location */ if (!disk_supported(fd, cmsfs)) { cmsfs->label = cmsfs->blksize; if (!disk_supported(fd, cmsfs)) goto error; } } else if (S_ISREG(stat.st_mode)) get_device_info_file(fd, cmsfs); else goto error; if (!disk_supported(fd, cmsfs)) goto error; return fd; error: DIE("Unsupported disk\n"); } s390-tools-2.3.0/cmsfs-fuse/ebcdic.h000066400000000000000000000110241323410431100167730ustar00rootroot00000000000000/* * cmsfs-fuse - CMS EDF filesystem support for Linux * * EBCDIC to ASCII conversion: * EDF uses an EBCDIC codepage based on 037 with some modifications. * * Copyright IBM Corp. 2010, 2017 * * s390-tools is free software; you can redistribute it and/or modify * it under the terms of the MIT license. See LICENSE for details. */ #ifndef _EBCDIC_H #define _EBCDIC_H #include #include /* * EBCDIC 037 -> ISO8859-1 * changes: * 0x5f: 0xaa -> 0x5e ^ * 0xad: 0x07 -> 0x5b [ * 0xbd: 0x07 -> 0x5d ] */ static char ebc2asc[256] = { /* 0x00 */ 0x00, 0x01, 0x02, 0x03, 0x07, 0x09, 0x07, 0x7F, 0x07, 0x07, 0x07, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, /* 0x10 */ 0x10, 0x11, 0x12, 0x13, 0x07, 0x0A, 0x08, 0x07, 0x18, 0x19, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, /* 0x20 */ 0x07, 0x07, 0x1C, 0x07, 0x07, 0x0A, 0x17, 0x1B, 0x07, 0x07, 0x07, 0x07, 0x07, 0x05, 0x06, 0x07, /* 0x30 */ 0x07, 0x07, 0x16, 0x07, 0x07, 0x07, 0x07, 0x04, 0x07, 0x07, 0x07, 0x07, 0x14, 0x15, 0x07, 0x1A, /* 0x40 */ 0x20, 0xFF, 0x83, 0x84, 0x85, 0xA0, 0x07, 0x86, 0x87, 0xA4, 0x9B, 0x2E, 0x3C, 0x28, 0x2B, 0x7C, /* 0x50 */ 0x26, 0x82, 0x88, 0x89, 0x8A, 0xA1, 0x8C, 0x07, 0x8D, 0xE1, 0x21, 0x24, 0x2A, 0x29, 0x3B, 0x5E, /* 0x60 */ 0x2D, 0x2F, 0x07, 0x8E, 0x07, 0x07, 0x07, 0x8F, 0x80, 0xA5, 0x07, 0x2C, 0x25, 0x5F, 0x3E, 0x3F, /* 0x70 */ 0x07, 0x90, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x70, 0x60, 0x3A, 0x23, 0x40, 0x27, 0x3D, 0x22, /* 0x80 */ 0x07, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0xAE, 0xAF, 0x07, 0x07, 0x07, 0xF1, /* 0x90 */ 0xF8, 0x6A, 0x6B, 0x6C, 0x6D, 0x6E, 0x6F, 0x70, 0x71, 0x72, 0xA6, 0xA7, 0x91, 0x07, 0x92, 0x07, /* 0xa0 */ 0xE6, 0x7E, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7A, 0xAD, 0xAB, 0x07, 0x5B, 0x07, 0x07, /* 0xb0 */ 0x5E, 0x9C, 0x9D, 0xFA, 0x07, 0x07, 0x07, 0xAC, 0xAB, 0x07, 0x5B, 0x5D, 0x07, 0x5D, 0x07, 0x07, /* 0xc0 */ 0x7B, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x07, 0x93, 0x94, 0x95, 0xA2, 0x07, /* 0xd0 */ 0x7D, 0x4A, 0x4B, 0x4C, 0x4D, 0x4E, 0x4F, 0x50, 0x51, 0x52, 0x07, 0x96, 0x81, 0x97, 0xA3, 0x98, /* 0xe0 */ 0x5C, 0xF6, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, 0x5A, 0xFD, 0x07, 0x99, 0x07, 0x07, 0x07, /* 0xf0 */ 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x07, 0x07, 0x9A, 0x07, 0x07, 0x07 }; /* ISO8859-1 -> EBCDIC 037 */ static char asc2ebc[256] = { /* 0x00 */ 0x00, 0x01, 0x02, 0x03, 0x37, 0x2D, 0x2E, 0x2F, 0x16, 0x05, 0x15, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, /* 0x10 */ 0x10, 0x11, 0x12, 0x13, 0x3C, 0x3D, 0x32, 0x26, 0x18, 0x19, 0x3F, 0x27, 0x22, 0x1D, 0x1E, 0x1F, /* 0x20 */ 0x40, 0x5A, 0x7F, 0x7B, 0x5B, 0x6C, 0x50, 0x7D, 0x4D, 0x5D, 0x5C, 0x4E, 0x6B, 0x60, 0x4B, 0x61, /* 0x30 */ 0xF0, 0xF1, 0xF2, 0xF3, 0xF4, 0xF5, 0xF6, 0xF7, 0xF8, 0xF9, 0x7A, 0x5E, 0x4C, 0x7E, 0x6E, 0x6F, /* 0x40 */ 0x7C, 0xC1, 0xC2, 0xC3, 0xC4, 0xC5, 0xC6, 0xC7, 0xC8, 0xC9, 0xD1, 0xD2, 0xD3, 0xD4, 0xD5, 0xD6, /* 0x50 */ 0xD7, 0xD8, 0xD9, 0xE2, 0xE3, 0xE4, 0xE5, 0xE6, 0xE7, 0xE8, 0xE9, 0xBA, 0xE0, 0xBB, 0xB0, 0x6D, /* 0x60 */ 0x79, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, /* 0x70 */ 0x97, 0x98, 0x99, 0xA2, 0xA3, 0xA4, 0xA5, 0xA6, 0xA7, 0xA8, 0xA9, 0xC0, 0x4F, 0xD0, 0xA1, 0x07, /* 0x80 */ 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, /* 0x90 */ 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, /* 0xa0 */ 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, /* 0xb0 */ 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, /* 0xc0 */ 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, /* 0xd0 */ 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, /* 0xe0 */ 0x3F, 0x59, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, /* 0xf0 */ 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x90, 0x3F, 0x3F, 0x3F, 0x3F, 0xEA, 0x3F, 0xFF }; #define EBCDIC_ENCODE 0x0 #define EBCDIC_DECODE 0x1 static inline void a2e(char *dst, const char *src, int len, int to) { char *conv; int i; if (to == EBCDIC_ENCODE) conv = asc2ebc; else conv = ebc2asc; for (i = 0; i < len; i++) dst[i] = conv[(unsigned int)src[i]]; } static inline void ebcdic_enc(char *dst, const char *src, int len) { a2e(dst, src, len, EBCDIC_ENCODE); } static inline void ebcdic_dec(char *dst, const char *src, int len) { a2e(dst, src, len, EBCDIC_DECODE); } #endif s390-tools-2.3.0/cmsfs-fuse/edf.h000066400000000000000000000047341323410431100163320ustar00rootroot00000000000000/* * cmsfs-fuse - CMS EDF filesystem support for Linux * * EDF and label structures * * Copyright IBM Corp. 2010, 2017 * * s390-tools is free software; you can redistribute it and/or modify * it under the terms of the MIT license. See LICENSE for details. */ #ifndef _EDF_H #define _EDF_H #include "helper.h" /* * File status table entry */ struct fst_entry { char name[8]; char type[8]; char res1[8]; short int mode; char res2[4]; char record_format; char flag; int record_len; char res3[4]; unsigned int fop; /* number of data blocks (not incl. pointer blocks) */ int nr_blocks; int nr_records; char levels; char ptr_size; char date[6]; char res4[4]; }; struct cms_label { char id[6]; char user_id[6]; unsigned int blocksize; unsigned int dop; unsigned int f_cylinders; unsigned int max_cylinders; unsigned int total_blocks; unsigned int used_blocks; unsigned int fst_entry_size; unsigned int fst_per_block; char date[6]; unsigned int res1[3]; char res2[8]; }; #define RECORD_LEN_VARIABLE 0xe5 #define RECORD_LEN_FIXED 0xc6 /* TODO: correct for fixed? */ #define MAX_RECORD_LEN 0xffff #define FST_ENTRY_SIZE sizeof(struct fst_entry) #define FST_ENTRY_DIR_NAME 0x0000000100000000ULL #define FST_ENTRY_DIR_TYPE 0xc4c9d9c5c3e3d6d9ULL /* 'DIRECTOR' */ #define FST_ENTRY_ALLOC_NAME 0x0000000200000000ULL #define FST_ENTRY_ALLOC_TYPE 0xc1d3d3d6c3d4c1d7ULL /* 'ALLOCMAP' */ #define FST_FLAG_CENTURY 0x0008 #define FST_FOP_OFFSET 0x28 #define FST_LEVEL_OFFSET 0x34 #define VAR_RECORD_HEADER_SIZE 0x2 #define VAR_RECORD_SPANNED 0xffffffff #define PTR_SIZE (sizeof(struct fixed_ptr)) #define VPTR_SIZE (sizeof(struct var_ptr)) struct fixed_ptr { unsigned int next; }; struct var_ptr { unsigned int next; int hi_record_nr; unsigned int disp; }; static inline int is_directory(const char *name, const char *type) { if ((*(unsigned long long *) name) != FST_ENTRY_DIR_NAME) return 0; if ((*(unsigned long long *) type) != FST_ENTRY_DIR_TYPE) return 0; return 1; } static inline int is_allocmap(const char *name, const char *type) { if ((*(unsigned long long *) name) != FST_ENTRY_ALLOC_NAME) return 0; if ((*(unsigned long long *) type) != FST_ENTRY_ALLOC_TYPE) return 0; return 1; } static inline int is_file(void *name, void *type) { if ((*(unsigned long long *) name) == 0ULL) return 0; /* Assumption: type = 0 is not legal */ if ((*(unsigned long long *) type) == 0ULL) return 0; return 1; } #endif s390-tools-2.3.0/cmsfs-fuse/etc/000077500000000000000000000000001323410431100161665ustar00rootroot00000000000000s390-tools-2.3.0/cmsfs-fuse/etc/filetypes.conf000066400000000000000000000014411323410431100210410ustar00rootroot00000000000000# # Filetypes that are interpreted as text files. If you want an EBCDIC # file translated to ASCII, add the extension here. # # Comments must include a space after the # # Add your extensions here: PRM CONF # The following types were taken from the z/VM TCPIP.DATA file: $EXEC $REXX $XEDIT AMS AMSERV ANN ANNOUNCE APP APPEND ASC ASCII ASM ASM3705 ASSEMBLE AVL AVAIL A37 BASDATA BASIC BKS BKSHELF C C++ CAT CATALOG CNTRL COB COBOL COPY CPP DIRECT DLCS DOCUMENT ESERV EXC EXEC FFT FOR FORM FORTRAN FREEFORT GCS GROUP H HPP HTM HTML H++ JOB LISTING LOG LST MAC MACLIB MACRO MAK MAKE ME MEMBER MEMO MODULE NAM NAMES NETLOG NONE NOT NOTE NOTEBOOK OFS OPT OPTIONS PACKAGE PASCAL PKG PLAS PLI PLIOPT PLS PVT REXX RPG SCR SCRIPT STY STYLE TEXT TEXTXXXX TXT TXTXXXX UPDATE UPDT VMT VSBASIC VSBDATA XED XEDIT s390-tools-2.3.0/cmsfs-fuse/helper.h000066400000000000000000000021201323410431100170360ustar00rootroot00000000000000/* * cmsfs-fuse - CMS EDF filesystem support for Linux * * Common helper functions * * Copyright IBM Corp. 2010, 2017 * * s390-tools is free software; you can redistribute it and/or modify * it under the terms of the MIT license. See LICENSE for details. */ #ifndef _HELPER_H #define _HELPER_H extern FILE *logfile; #define DEBUG_LOGFILE "/tmp/cmsfs-fuse.log" #ifdef DEBUG_ENABLED #define DEBUG(...) \ do { \ fprintf(logfile, __VA_ARGS__); \ fflush(logfile); \ } while (0) #else #define DEBUG(...) #endif #define DIE(...) \ do { \ fprintf(stderr, COMP __VA_ARGS__); \ exit(1); \ } while (0) #define DIE_PERROR(...) \ do { \ perror(COMP __VA_ARGS__); \ exit(1); \ } while (0) #define BUG(x) \ if (x) { \ fprintf(stderr, COMP " assert failed at " \ __FILE__ ":%d in %s()\n", __LINE__, __func__); \ exit(1); \ } #define WARN(...) \ do { \ fprintf(stderr, COMP "Warning, " __VA_ARGS__); \ } while (0) #define ARRAY_SIZE(arr) (sizeof(arr) / sizeof((arr)[0])) #endif s390-tools-2.3.0/common.mak000066400000000000000000000273151323410431100153320ustar00rootroot00000000000000ifndef $(COMMON_INCLUDED) COMMON_INCLUDED = true # Global definitions # The variable "DISTRELEASE" should be overwritten in rpm spec files with: # "make DISTRELEASE=%{release}" and "make install DISTRELEASE=%{release}" VERSION = 2 RELEASE = 3 PATCHLEVEL = 0 DISTRELEASE = build-$(shell date +%Y%m%d) S390_TOOLS_RELEASE = $(VERSION).$(RELEASE).$(PATCHLEVEL)-$(DISTRELEASE) export S390_TOOLS_RELEASE reldir = $(subst $(shell cd -P $(dir $(filter %common.mak,$(MAKEFILE_LIST))); \ pwd)/,,$(CURDIR)) rootdir= $(dir $(filter %common.mak,$(MAKEFILE_LIST))) export S390_TEST_LIB_PATH=$(rootdir)/s390-tools-testsuite/lib # # For cross compiles specify CROSS_COMPILE= on the commandline: # # $ make CROSS_COMPILE="s390x-5.1.0-" # CROSS_COMPILE = # # Commands can be overwritten on the command line with "make =": # # $ make CC=gcc-4.8 # # The "cmd_define" macro wraps the command definition so that the commands # can be user supplied and are still pretty-printed for the build process. # # The macro is called with the following parameters: # # $(1) - Used command variable in the Makefiles # $(2) - Pretty Print output for the command # $(3) - Default command if not user-specified # # The Example below... # # $(eval $(call cmd_define, CC," CC ",$(CROSS_COMPILE)gcc)) # # ... produces the following code: # # CC = $(CROSS_COMPILE)gcc # CC_SILENT := $(CC) # override CC = $(call echocmd," CC ",/$@)$(CC_SILENT) # # The "strip" make function is used for the first parameter to allow blanks, # which improves readability. # define cmd_define $(strip $(1)) = $(3) $(strip $(1))_SILENT := $$($(strip $(1))) override $(strip $(1)) = $$(call echocmd,$(2),/$$@)$$($(strip $(1))_SILENT) endef $(eval $(call cmd_define, AS," AS ",$(CROSS_COMPILE)as)) $(eval $(call cmd_define, LINK," LINK ",$(CROSS_COMPILE)gcc)) $(eval $(call cmd_define, LD," LD ",$(CROSS_COMPILE)ld)) $(eval $(call cmd_define, CC," CC ",$(CROSS_COMPILE)gcc)) $(eval $(call cmd_define, HOSTCC," HOSTCC ",gcc)) $(eval $(call cmd_define, LINKXX," LINKXX ",$(CROSS_COMPILE)g++)) $(eval $(call cmd_define, CXX," CXX ",$(CROSS_COMPILE)g++)) $(eval $(call cmd_define, CPP," CPP ",$(CROSS_COMPILE)gcc -E)) $(eval $(call cmd_define, AR," AR ",$(CROSS_COMPILE)ar)) $(eval $(call cmd_define, NM," NM ",$(CROSS_COMPILE)nm)) $(eval $(call cmd_define, STRIP," STRIP ",$(CROSS_COMPILE)strip)) $(eval $(call cmd_define,OBJCOPY," OBJCOPY ",$(CROSS_COMPILE)objcopy)) $(eval $(call cmd_define,OBJDUMP," OBJDUMP ",$(CROSS_COMPILE)objdump)) $(eval $(call cmd_define,RUNTEST," RUNTEST ",$(S390_TEST_LIB_PATH)/s390_runtest)) $(eval $(call cmd_define, CAT," CAT ",cat)) $(eval $(call cmd_define, SED," SED ",sed)) $(eval $(call cmd_define, GZIP," GZIP ",gzip)) $(eval $(call cmd_define, MV," MV ",mv)) CHECK = sparse CHECK_SILENT := $(CHECK) CHECKTOOL = $(call echocmd," CHECK ",/$@)$(CHECK_SILENT) SKIP = echo " SKIP $(call reldir) due to" INSTALL = install CP = cp ifneq ("${V}","1") MAKEFLAGS += --quiet echocmd=echo $1$(call reldir)$2; RUNTEST += > /dev/null 2>&1 else echocmd= endif ifeq ("${W}","1") DEFAULT_CFLAGS = -g -rdynamic -fstack-protector-all -W -Wall -Wformat-security -Wextra else DEFAULT_CFLAGS = -g -rdynamic -fstack-protector-all -W -Wall -Wformat-security endif ifeq ("${D}","1") DEFAULT_CFLAGS += -Og else DEFAULT_CFLAGS += -O3 endif DEFAULT_CPPFLAGS = -D_GNU_SOURCE DEFAULT_LDFLAGS = -rdynamic # # Check for build dependency # # $1: Name of tool or feature that requires dependency # $2: Name of include file to check # $3: Name of required devel package # $4: Option to skip build (e.g. HAVE_FUSE=0) # $5: Additional compiler & linker options (optional) # check_dep=\ printf "\#include <%s>\n int main(void) {return 0;}" $2 | ( $(CC) $(filter-out --coverage, $(ALL_CFLAGS)) $(ALL_CPPFLAGS) $5 -o /dev/null -xc - ) > /dev/null 2>&1; \ if [ $$? != 0 ]; \ then \ printf " REQCHK %s (%s)\n" $1 $2; \ printf "********************************************************************************\n" >&2; \ printf "* Missing build requirement for: %-45s *\n" $1 >&2; \ printf "* Install package..............: %-45s *\n" $3 >&2; \ printf "* You can skip build with......: make %-40s *\n" $4 >&2; \ printf "********************************************************************************\n" >&2; \ exit 1; \ fi # # Support alternate install root # # INSTALLDIR: Finally install s390-tools to INSTALLDIR. This can be used # for testing locally installed tools. # DESTDIR: Temporary install s390-tools to this directory. This can be # used for building s390-tools e.g. with rpmbuild. # # The difference between INSTALLDIR and DESTDIR is that for INSTALLDIR # internally used directories (e.g. for config files) are adjusted. # # Example: # # $ cd cpumf # $ INSTALLDIR=/tmp make install # $ cat /tmp/lib/s390-tools/cpumf_helper | grep DATA_DIR # my $CPUMF_DATA_DIR = '/tmp/usr/share/s390-tools/cpumf'; # # $ make clean # $ DESTDIR=/tmp make install # $ cat /tmp/lib/s390-tools/cpumf_helper | grep DATA_DIR # my $CPUMF_DATA_DIR = '/usr/share/s390-tools/cpumf'; # ifdef INSTROOT $(error INSTROOT is no longer available, use DESTDIR instead) endif INSTALLDIR ?= DESTDIR ?= USRSBINDIR = $(INSTALLDIR)/usr/sbin USRBINDIR = $(INSTALLDIR)/usr/bin BINDIR = $(INSTALLDIR)/sbin LIBDIR = $(INSTALLDIR)/lib SYSCONFDIR = $(INSTALLDIR)/etc MANDIR = $(INSTALLDIR)/usr/share/man VARDIR = $(INSTALLDIR)/var TOOLS_DATADIR = $(INSTALLDIR)/usr/share/s390-tools TOOLS_LIBDIR = $(INSTALLDIR)/lib/s390-tools ZFCPDUMP_DIR = $(TOOLS_LIBDIR)/zfcpdump # Systemd support files are installed only if a directory is specified # for SYSTEMDSYSTEMUNITDIR (e.g. /lib/systemd/system) SYSTEMDSYSTEMUNITDIR = INSTDIRS = $(USRSBINDIR) $(USRBINDIR) $(BINDIR) $(LIBDIR) $(MANDIR) \ $(SYSCONFDIR) $(SYSCONFDIR)/sysconfig \ $(TOOLS_LIBDIR) $(TOOLS_DATADIR) \ $(ZFCPDUMP_DIR) $(SYSTEMDSYSTEMUNITDIR) OWNER = $(shell id -un) GROUP = $(shell id -gn) export INSTALLDIR BINDIR LIBDIR MANDIR OWNER GROUP # Special defines for zfcpdump ZFCPDUMP_PART_IMAGE = zfcpdump_part.image ZFCPDUMP_PART_RD = zfcpdump_part.rd export ZFCPDUMP_DIR ZFCPDUMP_PART_IMAGE ZFCPDUMP_PART_RD CFLAGS ?= $(DEFAULT_CFLAGS) $(OPT_FLAGS) HOSTCFLAGS ?= $(DEFAULT_CFLAGS) $(OPT_FLAGS) CPPFLAGS ?= $(DEFAULT_CPPFLAGS) LDFLAGS ?= $(DEFAULT_LDFLAGS) ALL_CFLAGS = -DS390_TOOLS_RELEASE=$(S390_TOOLS_RELEASE) \ -DS390_TOOLS_LIBDIR=$(TOOLS_LIBDIR) \ -DS390_TOOLS_DATADIR=$(TOOLS_DATADIR) \ -DS390_TOOLS_SYSCONFDIR=$(SYSCONFDIR) \ -DS390_TOOLS_BINDIR=$(BINDIR) \ $(CFLAGS) CXXFLAGS ?= $(DEFAULT_CFLAGS) $(OPT_FLAGS) ALL_CXXFLAGS = -DS390_TOOLS_RELEASE=$(S390_TOOLS_RELEASE) \ -DS390_TOOLS_LIBDIR=$(TOOLS_LIBDIR) \ -DS390_TOOLS_DATADIR=$(TOOLS_DATADIR) \ -DS390_TOOLS_SYSCONFDIR=$(SYSCONFDIR) \ -DS390_TOOLS_BINDIR=$(BINDIR) \ $(CXXFLAGS) ALL_CPPFLAGS = -I $(rootdir)include $(CPPFLAGS) ALL_LDFLAGS = $(LDFLAGS) # make G=1 # Compile tools so that gcov can be used to collect code coverage data. # See the gcov man page for details. ifeq ("${G}","1") ALL_CFLAGS := $(filter-out -O%,$(ALL_CFLAGS)) --coverage ALL_CXXFLAGS := $(filter-out -O%,$(ALL_CXXFLAGS)) --coverage ALL_LDFLAGS += --coverage endif export AS LD CC CPP AR NM STRIP OBJCOPY OBJDUMP INSTALL CFLAGS CXXFLAGS \ LDFLAGS CPPFLAGS ALL_CFLAGS ALL_CXXFLAGS ALL_LDFLAGS ALL_CPPFLAGS ifneq ($(shell $(CC_SILENT) -dumpspecs 2>/dev/null | grep -e '[^f]no-pie'),) NO_PIE_CFLAGS := -fno-pie NO_PIE_LINKFLAGS := -no-pie NO_PIE_LDFLAGS := -no-pie else NO_PIE_CFLAGS := NO_PIE_LINKFLAGS := NO_PIE_LDFLAGS := endif # Overwrite implicite makefile rules for having nice compile output %.o: %.c ifeq ("${C}","1") $(CHECKTOOL) $(ALL_CPPFLAGS) $(ALL_CFLAGS) -c $< -o $@ endif $(CC) $(ALL_CPPFLAGS) $(ALL_CFLAGS) -c $< -o $@ %.o: %.cpp $(CXX) $(ALL_CPPFLAGS) $(ALL_CXXFLAGS) -c $< -o $@ %: %.o $(LINK) $(ALL_LDFLAGS) $^ $(LOADLIBES) $(LDLIBS) -o $@ %.a: $(AR) rcs $@ $^ all: help: @echo 'Usage: make [TARGETS] [OPTIONS]' @echo '' @echo 'TARGETS' @echo ' all Build all tools (default target)' @echo ' install Install tools' @echo ' clean Delete all generated files' @echo '' @echo 'OPTIONS' @echo ' D=1 Build with debugging option "-Og"' @echo ' C=1 Build with check tool defined with "CHECK=" (default=sparse)' @echo ' G=1 Build with gcov to collect code coverage data' @echo ' V=1 Generate verbose build output' @echo ' W=1 Build with higher warning level' @echo '' @echo 'EXAMPLES' @echo ' # make clean all D=1 W=1 -j' @echo ' # make C=1 CHECK=smatch' .PHONY: help # Automatic dependency generation # # Create ".o.d" dependency files with the -MM compile option for all ".c" and # ".cpp" files in the directory of the Makefile that includes common.mak: # # $ gcc -MM vmcp.c # vmcp.o: vmcp.c vmcp.h ../include/zt_common.h # # Use -MM instead of -M to *not* mention system header files. We expect # "make clean all" in case of system header updates. # We consider header files in three possible directories sources_h = \ $(wildcard *.h) \ $(wildcard ../include/*.h) \ $(wildcard $(rootdir)/include/lib/*.h) # Rules to create ".o.d" files out of ".c" or ".cpp" files: .%.o.d: %.c $(sources_h) $(CC_SILENT) -MM $(ALL_CPPFLAGS) $(ALL_CFLAGS) $< > $@ .%.o.d: %.cpp $(sources_h) $(CXX_SILENT) -MM $(ALL_CPPFLAGS) $(ALL_CXXFLAGS) $< > $@ # The sources_c/cpp variable contains a list of all ".c" or ".cpp" files in # in the current directory. sources_c = $(wildcard *.c) sources_cpp = $(wildcard *.cpp) # The dependencies_c/cpp variable contains a list of all ".o.d" files, # one for each ".c" or ".cpp" file. dependencies_c = $(sources_c:%.c=.%.o.d) dependencies_cpp = $(sources_cpp:%.cpp=.%.o.d) # Include all ".o.d" dependency files for all make targets except for "clean" ifneq ($(MAKECMDGOALS),clean) -include $(dependencies_c) -include $(dependencies_cpp) endif # Rules for internal libraries needed to ensure that these files are build # with their own build flags even if they are build from external directories. # # Because of the PHONY directory dependency all tools that use libraries # check the library directory via "make -C" when the tools Makefile is # processed. $(rootdir)/libutil/libutil.a: $(rootdir)/libutil $(MAKE) -C $(rootdir)/libutil/ libutil.a .PHONY: $(rootdir)/libutil $(rootdir)/libccw/libccw.a: $(rootdir)/libccw $(MAKE) -C $(rootdir)/libccw/ libccw.a .PHONY: $(rootdir)/libccw $(rootdir)/libvtoc/libvtoc.a: $(rootdir)/libvtoc $(MAKE) -C $(rootdir)/libvtoc/ libvtoc.a .PHONY: $(rootdir)/libvtoc $(rootdir)/libdasd/libdasd.a: $(rootdir)/libdasd $(MAKE) -C $(rootdir)/libdasd/ libdasd.a .PHONY: $(rootdir)/libdasd $(rootdir)/libzds/libzds.a: $(rootdir)/libzds $(MAKE) -C $(rootdir)/libzds/ libzds.a .PHONY: $(rootdir)/libzds $(rootdir)/libu2s/libu2s.a: $(rootdir)/libu2s $(MAKE) -C $(rootdir)/libu2s/ libu2s.a .PHONY: $(rootdir)/libu2s $(rootdir)/libvmdump/libvmdump.a: $(rootdir)/libvmdump $(MAKE) -C $(rootdir)/libvmdump/ libvmdump.a .PHONY: $(rootdir)/libvmdump $(rootdir)/zipl/boot/data.o: $(MAKE) -C $(rootdir)/zipl/boot/ data.o install_dirs: for dir in $(INSTDIRS); do \ test -d $(DESTDIR)$$dir || $(INSTALL) -g $(GROUP) -o $(OWNER) -d $(DESTDIR)$$dir; \ done for i in 1 2 3 4 5 6 7 8; do \ test -d $(DESTDIR)$(MANDIR)/man$$i || $(INSTALL) -g $(GROUP) -o $(OWNER) \ -d $(DESTDIR)$(MANDIR)/man$$i; \ done install_echo: $(call echocmd," INSTALL ") install: install_echo install_dirs clean_echo: $(call echocmd," CLEAN ") clean_gcov: rm -f *.gcda *.gcno *.gcov clean_dep: rm -f .*.o.d clean: clean_echo clean_gcov clean_dep endif s390-tools-2.3.0/cpacfstats/000077500000000000000000000000001323410431100154735ustar00rootroot00000000000000s390-tools-2.3.0/cpacfstats/Makefile000066400000000000000000000015101323410431100171300ustar00rootroot00000000000000include ../common.mak ALL_CPPFLAGS += -DVERSION=$(VERSION) ifeq (${HAVE_PFM},0) all: $(SKIP) HAVE_PFM=0 install: $(SKIP) HAVE_PFM=0 else check_dep: $(call check_dep, \ "cpacfstats", \ "perfmon/pfmlib.h", \ "libpfm-devel or libpfm4-dev", \ "HAVE_PFM=0") all: check_dep cpacfstats cpacfstatsd cpacfstatsd: cpacfstatsd.o stats_sock.o perf_crypto.o $(LINK) $(ALL_LDFLAGS) $^ $(LDLIBS) -lpfm -o $@ cpacfstats: cpacfstats.o stats_sock.o $(LINK) $(ALL_LDFLAGS) $^ $(LDLIBS) -o $@ install: all $(INSTALL) -m 755 cpacfstatsd $(DESTDIR)$(USRSBINDIR) $(INSTALL) -m 755 cpacfstats $(DESTDIR)$(USRBINDIR) $(INSTALL) -m 644 cpacfstatsd.8 $(DESTDIR)$(MANDIR)/man8 $(INSTALL) -m 644 cpacfstats.1 $(DESTDIR)$(MANDIR)/man1 endif clean: rm -f *.o *~ cpacfstatsd cpacfstats .PHONY: all clean install check_dep s390-tools-2.3.0/cpacfstats/cpacfstats.1000066400000000000000000000101341323410431100177070ustar00rootroot00000000000000.\" cpacfstats.1 .\" .\" Copyright IBM Corp. 2015, 2017 .\" s390-tools is free software; you can redistribute it and/or modify .\" it under the terms of the MIT license. See LICENSE for details. .\" .\" use .\" groff -man -Tutf8 cpacfstats.1 .\" or .\" nroff -man cpacfstats.1 .\" to process this source .\" .TH cpacfstats "1" "January 2015" "s390-tools" . .ds c \fcpacfstats\fP . .SH NAME cpacfstats \- enable, disable and display CPACF statistical data . .SH SYNOPSIS .B cpacfstats .RB [ \-h | \-\-help ] .RB [ \-v | \-\-version ] .RB [ \-e | \-\-enable .I counter .RB ] .RB [ \-d | \-\-disable .I counter .RB ] .RB [ \-r | \-\-reset .I counter .RB ] .RB [ \-p | \-\-print .I counter .RB ] . .SH DESCRIPTION The cpacfstats client application interacts with the cpacfstatsd daemon and triggers actions. The application enables, disables, resets, and fetches one or all of the mainframe CPACF performance counters with the help of the daemon process. 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. Note: All cryptographic counters are bound to physical CPUs. If you use dedicated CPUs for your LPAR, you obtain only your CPACF activities. If your CPUs are shared with other LPARs, their cryptographic activities might distort the results. CPACF performance counters are available on LPARs only. 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 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 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 or \fBall\fR. If the counter argument is omitted, all performance counters are reset to 0. .TP \fB\-p\fR or \fB\-\-print\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 or \fBall\fR. If the counter argument is omitted or if there is no argument, all performance counters are displayed. .TP The default command is --print all. . .SH FILES .nf /var/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 SEE ALSO cpacfstatsd (8) s390-tools-2.3.0/cpacfstats/cpacfstats.c000066400000000000000000000121601323410431100177720ustar00rootroot00000000000000/* * cpacfstats - display and maintain CPACF perf counters * * cpacfstats client implementation * * Copyright IBM Corp. 2015, 2017 * * 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 #include #include #define __STDC_FORMAT_MACROS #include #include #include #include #include #include #include #include "lib/zt_common.h" #include "cpacfstats.h" static const char *const name = "cpacfstats"; static const char *const usage = "Usage: %s [OPTIONS [COUNTER]]\n" "\n" "Enable, disable, reset and read CPACF Crypto Activity Counters\n" "Use OPTIONS described below:\n" "\n" "\t-h, --help Print this help, then exit\n" "\t-v, --version Print version information, then exit\n" "\t-e, --enable [counter] Enable one or all counters\n" "\t-d, --disable [counter] Disable one or all counters\n" "\t-r, --reset [counter] Reset one or all counter values\n" "\t-p, --print [counter] Print one or all counter values\n" "\tcounter can be: 'aes' 'des' 'rng' 'sha' or 'all'\n"; static const char *const counter_str[] = { [DES_FUNCTIONS] = "des", [AES_FUNCTIONS] = "aes", [SHA_FUNCTIONS] = "sha", [PRNG_FUNCTIONS] = "rng", [ALL_COUNTER] = "all" }; static int send_query(int s, enum cmd_e cmd, enum ctr_e ctr) { struct msg m; memset(&m, 0, sizeof(m)); m.head.m_ver = VERSION; m.head.m_type = QUERY; m.query.m_ctr = ctr; m.query.m_cmd = cmd; return send_msg(s, &m); } static int recv_answer(int s, int *ctr, int *state, uint64_t *value) { struct msg m; int rc; rc = recv_msg(s, &m); if (rc == 0) { if (m.head.m_ver != VERSION) { eprint("Received msg with wrong version %d != %d\n", m.head.m_ver, VERSION); return -1; } if (m.head.m_type != ANSWER) { eprint("Received msg with wrong type %d != %d\n", m.head.m_type, ANSWER); return -1; } *ctr = m.answer.m_ctr; *state = m.answer.m_state; *value = m.answer.m_value; } return rc; } static void print_answer(int ctr, int state, uint64_t value) { if (state < 0) printf(" %s counter: error state %d\n", counter_str[ctr], state); else if (state == DISABLED) printf(" %s counter: disabled\n", counter_str[ctr]); else printf(" %s counter: %"PRIu64"\n", counter_str[ctr], value); } int eprint(const char *format, ...) { char buf[1024]; va_list vargs; int i, n; i = snprintf(buf, sizeof(buf), "%s: ", name); va_start(vargs, format); n = vsnprintf(buf+i, sizeof(buf)-i, format, vargs); va_end(vargs); if (n > 0) fputs(buf, stderr); return n; } int main(int argc, char *argv[]) { enum ctr_e ctr = ALL_COUNTER; enum cmd_e cmd = PRINT; int i, j, s, state; uint64_t value; if (argc > 1) { int opt, idx = 0; const struct option long_opts[] = { { "help", 0, NULL, 'h' }, { "version", 0, NULL, 'v' }, { "enable", 0, NULL, 'e' }, { "disable", 0, NULL, 'd' }, { "reset", 0, NULL, 'r' }, { "print", 0, NULL, 'p' }, { NULL, 0, NULL, 0 } }; while (1) { opt = getopt_long(argc, argv, "hvedrp", long_opts, &idx); if (opt == -1) break; /* no more arguments */ switch (opt) { case 'h': printf(usage, name); exit(0); break; case 'v': printf("%s: Linux on System z CPACF Crypto Activity Counters Client\n" "Version %s\n%s\n", name, RELEASE_STRING, COPYRIGHT); exit(0); break; case 'e': cmd = ENABLE; break; case 'd': cmd = DISABLE; break; case 'r': cmd = RESET; break; case 'p': cmd = PRINT; break; default: eprint("Invalid argument, try -h or --help for more information\n"); exit(1); break; } } /* there may be an optional counter argument */ if (optind > 0 && optind < argc) { for (i = 0; i <= ALL_COUNTER; i++) if (strcmp(argv[optind], counter_str[i]) == 0) break; if (i > ALL_COUNTER) { eprint("Unknown counter '%s'\n", argv[optind]); exit(1); } ctr = (enum ctr_e) i; } } /* try to open and connect socket to the cpacfstatsd daemon */ s = open_socket(CLIENT); if (s < 0) { eprint("Can't connect to daemon\n"); exit(1); } /* send query */ if (send_query(s, cmd, ctr) != 0) { eprint("Error on sending query message to daemon\n"); close(s); exit(1); } if (ctr == ALL_COUNTER) { for (i = 0; i < ALL_COUNTER; i++) { /* receive answer */ if (recv_answer(s, &j, &state, &value) != 0) { eprint("Error on receiving answer message from daemon\n"); exit(1); } if (state < 0) { eprint("Received bad status code %d from daemon\n", state); close(s); exit(1); } print_answer(j, state, value); } } else { /* receive answer */ if (recv_answer(s, &j, &state, &value) != 0) { eprint("Error on receiving answer message from daemon\n"); close(s); exit(1); } if (state < 0) { eprint("Received bad status code %d from daemon\n", state); close(s); exit(1); } print_answer(j, state, value); } /* close connection */ close(s); return 0; } s390-tools-2.3.0/cpacfstats/cpacfstats.h000066400000000000000000000035531323410431100200050ustar00rootroot00000000000000/* * cpacfstats - display and maintain CPACF perf counters * * common function prototypes and definitions * * Copyright IBM Corp. 2015, 2017 * * s390-tools is free software; you can redistribute it and/or modify * it under the terms of the MIT license. See LICENSE for details. */ #ifndef CPACFSTATS_H #define CPACFSTATS_H #include "lib/zt_common.h" #define COPYRIGHT "Copyright IBM Corp. 2015, 2017" int eprint(const char *format, ...); /* * Counter names * ALL_COUNTER must always be the last member of the enum */ enum ctr_e { DES_FUNCTIONS = 0, AES_FUNCTIONS, SHA_FUNCTIONS, PRNG_FUNCTIONS, ALL_COUNTER }; enum type_e { QUERY = 0, ANSWER }; enum cmd_e { PRINT = 0, ENABLE, DISABLE, RESET }; enum state_e { DISABLED = 0, ENABLED }; /* * query send from clent to daemon * Consist of: * enum counter * enum command */ struct msg_query { uint32_t m_ctr; uint32_t m_cmd; } __packed; /* * answer send from daemon to client * Consist of: * enum counter * status code: < 0 error, 0 disabled, > 0 enabled * counter value */ struct msg_answer { uint32_t m_ctr; int32_t m_state; uint64_t m_value; } __packed; /* stats_sock.c */ #define SERVER 1 #define CLIENT 2 #define BACKLOG 10 #define SOCKET_FILE "/var/run/cpacfstatsd_socket" #define PID_FILE "/var/run/cpacfstatsd.pid" #define CPACFSTATS_GROUP "cpacfstats" struct msg_header { uint32_t m_ver; uint32_t m_type; } __packed; struct msg { struct msg_header head; union { struct msg_query query; struct msg_answer answer; }; } __packed; int open_socket(int mode); int send_msg(int sfd, struct msg *m); int recv_msg(int sfd, struct msg *m); /* perf_crypto.c */ int perf_init(void); void perf_close(void); int perf_enable_ctr(enum ctr_e ctr); int perf_disable_ctr(enum ctr_e ctr); int perf_reset_ctr(enum ctr_e ctr); int perf_read_ctr(enum ctr_e ctr, uint64_t *value); #endif s390-tools-2.3.0/cpacfstats/cpacfstatsd.8000066400000000000000000000062641323410431100200730ustar00rootroot00000000000000.\" cpacfstatsd.8 .\" .\" Copyright IBM Corp. 2015, 2017 .\" s390-tools is free software; you can redistribute it and/or modify .\" it under the terms of the MIT license. See LICENSE for details. .\" .\" use .\" groff -man -Tutf8 cpacfstatsd.8 .\" or .\" nroff -man cpacfstatsd.8 .\" to process this source .\" .TH cpacfstatsd "8" "January 2015" "s390-tools" . .ds c \fcpacfstatsd\fP . .SH NAME cpacfstatsd \- CPACF statistics collection daemon process . .SH SYNOPSIS .B cpacfstatsd .RB [ \-h | \-\-help ] .RB [ \-v | \-\-version ] .RB [ \-f | \-\-foreground ] . .SH DESCRIPTION The cpacfstatsd controlling daemon enables, disables, resets, and fetches the mainframe CPACF performance counter registers. The daemon receives commands from the user application cpacfstats through the UNIX Domain Socket, processes them and returns the requested information. For all available commands, see the cpacfstats man page. Prerequisites .P - The running Linux kernel must have the the CONFIG_PERF_EVENTS config option enabled. .P - Libpfm version 4 or higher is needed to successfully run the daemon. .P - Your LPAR must be configured to enable the "Counter Facility Security Options". .P - The daemon requires root privileges to interact with the performance ioctls of the kernel. CPU hotplug is not recognized by the daemon. When adding or removing a CPU, restart the daemon to ensure correct summing of the per-CPU performance counters. The starting daemon first checks for any stale pid file /var/run/cpacfstatsd.pid. If this file exists, and the process ID in the file belongs to an active process, an error message is printed to the console and the program terminates. The daemon and the client cpacfstats communicate through a Unix Domain Socket. This socket is created by the daemon at startup with the associated socket file /run/cpacfstatsd_socket. For security reasons only members of the group \fIcpacfstats\fR are allowed to communicate with the daemon. A system administrator should create this group and add all users which are allowed to run the cpacfstats client to the group. After startup, the daemon runs in the background and detaches from any terminal. Errors and warnings are posted to the syslog subsystem. Check the process list and the system syslog messages for confirmation of successful startup. On regular termination the pid file, the communication socket and the associated file is removed gracefully. .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\-f\fR or \fB\-\-foreground\fR Run the daemon in foreground mode, thus printing errors to stderr instead of posting them through syslog. This option might be useful when debugging daemon startup and initialization failures. .SH FILES .nf /var/run/cpacfstatsd_socket /var/run/cpacfstatsd.pid .fi .SH RETURN VALUE .IP 0 The daemon was successfully set to run in the background. This does not imply that the daemon startup was successful, as the main initialization is done in the re-spawned process. Check the syslog for success or failure. .IP 1 The daemon could not be set to run in the background. .SH SEE ALSO cpacfstats (1) s390-tools-2.3.0/cpacfstats/cpacfstatsd.c000066400000000000000000000220401323410431100201340ustar00rootroot00000000000000/* * cpacfstats - display and maintain CPACF perf counters * * cpacfstatsd daemon implementation * * Copyright IBM Corp. 2015, 2017 * * 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 #include #include #define __STDC_FORMAT_MACROS #include #include #include #include #include #include #include #include #include #include #include #include "lib/zt_common.h" #include "cpacfstats.h" static const char *const name = "cpacfstatsd"; static const char *const usage = "Usage: %s [OPTIONS]\n" "\n" "Daemon to provide access to CPACF perf counters\n" "Use OPTIONS described below:\n" "\n" "\t-h, --help Print this help, then exit\n" "\t-v, --version Print version information, then exit\n" "\t-f, --foreground Run in foreground, do not detach\n"; static int daemonized; static int ctr_state[ALL_COUNTER]; static int recv_query(int s, enum ctr_e *ctr, enum cmd_e *cmd) { struct msg m; int rc; rc = recv_msg(s, &m); if (rc == 0) { if (m.head.m_ver != VERSION) { eprint("Received msg with wrong version %d != %d\n", m.head.m_ver, VERSION); return -1; } if (m.head.m_type != QUERY) { eprint("Received msg with wrong type %d != %d\n", m.head.m_type, QUERY); return -1; } *ctr = m.query.m_ctr; *cmd = m.query.m_cmd; } return rc; } static int send_answer(int s, int ctr, int state, uint64_t value) { struct msg m; memset(&m, 0, sizeof(m)); m.head.m_ver = VERSION; m.head.m_type = ANSWER; m.answer.m_ctr = ctr; m.answer.m_state = state; m.answer.m_value = value; return send_msg(s, &m); } static int do_enable(int s, enum ctr_e ctr) { uint64_t value; int i, rc = 0; for (i = 0; i < ALL_COUNTER; i++) { if (i == (int) ctr || ctr == ALL_COUNTER) { if (!ctr_state[i]) { rc = perf_enable_ctr(i); if (rc != 0) { send_answer(s, i, rc, 0); break; } ctr_state[i] = 1; } rc = perf_read_ctr(i, &value); if (rc != 0) { send_answer(s, i, rc, 0); break; } send_answer(s, i, ENABLED, value); } } return rc; } static int do_disable(int s, enum ctr_e ctr) { int i, rc = 0; for (i = 0; i < ALL_COUNTER; i++) { if (i == (int) ctr || ctr == ALL_COUNTER) { if (ctr_state[i]) { rc = perf_disable_ctr(i); if (rc != 0) { send_answer(s, i, rc, 0); break; } ctr_state[i] = 0; } send_answer(s, i, DISABLED, 0); } } return rc; } static int do_reset(int s, enum ctr_e ctr) { int i, rc = 0; for (i = 0; i < ALL_COUNTER; i++) { if (i == (int) ctr || ctr == ALL_COUNTER) { if (ctr_state[i]) { rc = perf_reset_ctr(i); if (rc != 0) { send_answer(s, i, rc, 0); break; } send_answer(s, i, ENABLED, 0); } else { send_answer(s, i, DISABLED, 0); } } } return rc; } static int do_print(int s, enum ctr_e ctr) { int i, rc = 0; uint64_t value; for (i = 0; i < ALL_COUNTER; i++) { if (i == (int) ctr || ctr == ALL_COUNTER) { if (ctr_state[i]) { rc = perf_read_ctr(i, &value); if (rc != 0) { send_answer(s, i, rc, 0); break; } send_answer(s, i, ENABLED, value); } else { send_answer(s, i, DISABLED, 0); } } } return rc; } static int become_daemon(void) { FILE *f; int fd; /* syslog */ openlog("cpacfstatsd", 0, LOG_DAEMON); /* * fork and terminate parent * Reasons: * - opens new command line prompt * - the child process is guaranteed not to be the process group leader * nessecarry for setsid. */ switch (fork()) { case -1: /* error */ eprint("Fork() failed, errno=%d [%s]\n", errno, strerror(errno)); return -1; case 0: /* child */ break; default: /* parent */ _exit(0); } if (chdir("/") != 0) { eprint("Chdir('/') failed, errno=%d [%s]\n", errno, strerror(errno)); return -1; } /* start new session */ if (setsid() == -1) { eprint("Setsid() failed, errno=%d [%s]\n", errno, strerror(errno)); return -1; } /* clear umask so that socket has right default permission */ umask(0007); /* make stdin, stdout and stderr use /dev/null */ fd = open("/dev/null", O_RDWR); if (fd < 0) { eprint("Could not open /dev/null, errno=%d [%s]\n", errno, strerror(errno)); return -1; } dup2(fd, STDIN_FILENO); dup2(fd, STDOUT_FILENO); dup2(fd, STDERR_FILENO); close(fd); daemonized = 1; /* make pid file, fails if the file exists */ f = fopen(PID_FILE, "w+x"); if (!f) { eprint("Couldn't create pid file '%s', errno=%d [%s]\n", PID_FILE, errno, strerror(errno)); return -1; } fprintf(f, "%lu", (unsigned long)getpid()); fflush(f); fclose(f); chmod(PID_FILE, 0644); return 0; } static void remove_sock(void) { remove(SOCKET_FILE); } static int check_pidfile(void) { unsigned long pid; FILE *f; f = fopen(PID_FILE, "r"); if (!f) { if (errno == ENOENT) { /* pid file does not exit, pid file check is ok */ return 0; } /* unknown errno, pid file check is not ok */ eprint("Unknown error on pid file check '%s', errno=%d [%s]\n", PID_FILE, errno, strerror(errno)); return -1; } /* pid file could be opened, scan pid in there */ if (fscanf(f, "%lu", &pid) != 1) { /* * invalid, maybe a leftover from a previous run * remove and return pid file check ok */ fclose(f); remove(PID_FILE); return 0; } fclose(f); /* check if this process is still running */ if (kill(pid, 0) != 0) { /* * failure, assume this means there is no such pid running * remove pid file and return pid file check ok */ remove(PID_FILE); return 0; } /* * looks like there is another cpacfstatsd running * return with pid file check failure */ eprint("Looks like there is another cpacfstatsd (pid=%lu) running\n", pid); eprint("Please check and maybe remove stale pid file '%s'\n", PID_FILE); return -1; } static void remove_pidfile(void) { remove(PID_FILE); } void signalhandler(int sig) { if (sig == SIGTERM) eprint("Caught signal SIGTERM, terminating...\n"); else if (sig == SIGINT) eprint("Caught signal SIGINT, terminating...\n"); else eprint("Caught signal %d, terminating...\n", sig); remove_sock(); perf_close(); remove_pidfile(); exit(0); } int eprint(const char *format, ...) { char buf[512]; va_list vargs; int i, n; i = snprintf(buf, sizeof(buf), "%s: ", name); va_start(vargs, format); n = vsnprintf(buf+i, sizeof(buf)-i, format, vargs); va_end(vargs); if (n > 0) { if (daemonized) syslog(LOG_WARNING, "%s", buf); else fputs(buf, stderr); } return n; } int main(int argc, char *argv[]) { int rc, sfd, foreground = 0; struct sigaction act; if (argc > 1) { int opt, idx = 0; const struct option long_opts[] = { { "help", 0, NULL, 'h' }, { "foreground", 0, NULL, 'f' }, { "version", 0, NULL, 'v' }, { NULL, 0, NULL, 0 } }; while (1) { opt = getopt_long(argc, argv, "hfv", long_opts, &idx); if (opt == -1) break; /* no more arguments */ switch (opt) { case 'h': printf(usage, name); exit(0); case 'f': foreground = 1; break; case 'v': printf("%s: Linux on System z CPACF Crypto Activity Counters Daemon\n" "Version %s\n%s\n", name, RELEASE_STRING, COPYRIGHT); exit(0); default: printf("%s: Invalid argument, try -h or --help for more information\n", name); exit(1); } } } if (check_pidfile() != 0) { eprint("Stalled pid file or daemon allready running, terminating\n"); exit(1); } if (!foreground) { if (become_daemon() != 0) { eprint("Couldn't daemonize\n"); exit(1); } } if (perf_init() != 0) { eprint("Couldn't initialize perf lib\n"); exit(1); } atexit(perf_close); sfd = open_socket(SERVER); if (sfd < 0) { eprint("Couldn't initialize server socket\n"); exit(1); } atexit(remove_sock); memset(&act, 0, sizeof(act)); act.sa_handler = signalhandler; act.sa_flags = 0; if (sigaction(SIGINT, &act, 0) != 0) { eprint("Couldn't establish signal handler for SIGINT, errno=%d [%s]\n", errno, strerror(errno)); exit(1); } if (sigaction(SIGTERM, &act, 0) != 0) { eprint("Couldn't establish signal handler for SIGTERM, errno=%d [%s]\n", errno, strerror(errno)); exit(1); } eprint("Running\n"); while (1) { enum ctr_e ctr; enum cmd_e cmd; int s; s = accept(sfd, NULL, NULL); if (s < 0) { if (errno == EINTR) continue; eprint("Accept() failure, errno=%d [%s]\n", errno, strerror(errno)); exit(1); } rc = recv_query(s, &ctr, &cmd); if (rc != 0) { eprint("Recv_query() failed, ignoring\n"); goto cleanup; } if (cmd == ENABLE) rc = do_enable(s, ctr); else if (cmd == DISABLE) rc = do_disable(s, ctr); else if (cmd == RESET) rc = do_reset(s, ctr); else if (cmd == PRINT) rc = do_print(s, ctr); else { eprint("Received unknown command %d, ignoring\n", (int) cmd); goto cleanup; } cleanup: close(s); } return 0; } s390-tools-2.3.0/cpacfstats/perf_crypto.c000066400000000000000000000133531323410431100202000ustar00rootroot00000000000000/* * cpacfstats - display and maintain CPACF perf counters * * low level perf functions * * Copyright IBM Corp. 2015, 2017 * * 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 #include #define __STDC_FORMAT_MACROS #include #include #include #include #include #include #include #include #include #include "cpacfstats.h" /* correlation between counter and perf counter string */ static const struct { char pfm_name[80]; enum ctr_e ctr; } pmf_counter_name[ALL_COUNTER] = { {"cpum_cf::DEA_FUNCTIONS", DES_FUNCTIONS}, {"cpum_cf::AES_FUNCTIONS", AES_FUNCTIONS}, {"cpum_cf::SHA_FUNCTIONS", SHA_FUNCTIONS}, {"cpum_cf::PRNG_FUNCTIONS", PRNG_FUNCTIONS} }; /* * We need one filedescriptor per CPU per counter. * So perf_init builds this: * * ctr_fds - is an array of pointers to file descriptor arrays. * Each file descriptor array has space for number of logical CPUs + 1 * filedescriptors (int values). The last element of each file descriptor * array is always 0, assuming there will never appear a filedescriptor * with value 0: * * ctr_fds: * ctr_fds[0] -> [file descriptor 0] [fd1] ... [fd cpus-1][0] * ctr_fds[1] -> [file descriptor 0] [fd1] ... [fd cpus-1][0] * ... * ctr_fds[ALL_COUNTER-1] -> [file descriptor 0] [fd1] ... [fd cpus-1][0] */ static int *ctr_fds[ALL_COUNTER]; int perf_init(void) { int i, cpus, ctr, cpu, ec, *fds; memset(ctr_fds, 0, sizeof(ctr_fds)); /* initialize performance monitoring library */ ec = pfm_initialize(); if (ec != PFM_SUCCESS) { eprint("Pfm_initialize() returned with failure (%d:%s)\n", ec, pfm_strerror(ec)); return -1; } /* get number of logical processors */ cpus = sysconf(_SC_NPROCESSORS_ONLN); /* for each counter */ for (ctr = 0; ctr < ALL_COUNTER; ctr++) { /* * allocate an array of ints to store for each CPU * one filedescriptor + a terminating 0 */ fds = (int *) calloc(sizeof(int), cpus+1); if (!fds) { eprint("Malloc() of %d byte failed, errno=%d [%s]\n", (int)(sizeof(int) * (cpus+1)), errno, strerror(errno)); return -1; } ctr_fds[ctr] = fds; for (cpu = 0; cpu < cpus; cpu++) { pfm_perf_encode_arg_t pfm_arg; struct perf_event_attr pfm_event; int fd; memset(&pfm_arg, 0, sizeof(pfm_arg)); memset(&pfm_event, 0, sizeof(pfm_event)); pfm_arg.attr = &pfm_event; pfm_arg.size = sizeof(pfm_arg); pfm_event.size = sizeof(pfm_event); /* search for the counter's corresponding pfm name */ for (i = ALL_COUNTER-1; i >= 0; i--) if ((int) pmf_counter_name[i].ctr == ctr) break; if (i < 0) { eprint("Pfm ctr name not found for counter %d, please adjust pmf_counter_name[] in %s\n", ctr, __FILE__); return -1; } /* encode the counters perf event into pfm_arg.attr */ ec = pfm_get_os_event_encoding( pmf_counter_name[i].pfm_name, PFM_PLM0, PFM_OS_PERF_EVENT, &pfm_arg); if (ec != PFM_SUCCESS) { eprint("Pfm_initialize() for %s failed (%d:%s)\n", pmf_counter_name[i].pfm_name, ec, pfm_strerror(ec)); return -1; } /* fetch file descriptor for this perf event * the counter event should start disabled */ pfm_event.disabled = 1; fd = perf_event_open( &pfm_event, -1, /* pid -1 means all processes */ cpu, -1, /* group filedescriptor */ 0); /* flags */ if (fd < 0) { eprint("Perf_event_open() failed with errno=%d [%s]\n", errno, strerror(errno)); return -1; } fds[cpu] = fd; } } return 0; } void perf_close(void) { int ctr, *fds; for (ctr = 0; ctr < ALL_COUNTER; ctr++) { for (fds = ctr_fds[ctr]; fds && *fds; fds++) { close(*fds); *fds = 0; } free(ctr_fds[ctr]); ctr_fds[ctr] = NULL; } } int perf_enable_ctr(enum ctr_e ctr) { int *fds, ec, rc = 0; if (ctr == ALL_COUNTER) { for (ctr = 0; ctr < ALL_COUNTER; ctr++) { rc = perf_enable_ctr(ctr); if (rc != 0) return rc; } } else { for (fds = ctr_fds[ctr]; fds && *fds; fds++) { ec = ioctl(*fds, PERF_EVENT_IOC_ENABLE, 0); if (ec < 0) { eprint("Ioctl(PERF_EVENT_IOC_ENABLE) failed with errno=%d [%s]\n", errno, strerror(errno)); rc = -1; } } } return rc; } int perf_disable_ctr(enum ctr_e ctr) { int *fds, ec, rc = 0; if (ctr == ALL_COUNTER) { for (ctr = 0; ctr < ALL_COUNTER; ctr++) { rc = perf_disable_ctr(ctr); if (rc != 0) return rc; } } else { for (fds = ctr_fds[ctr]; fds && *fds; fds++) { ec = ioctl(*fds, PERF_EVENT_IOC_DISABLE, 0); if (ec < 0) { eprint("Ioctl(PERF_EVENT_IOC_DISABLE) failed with errno=%d [%s]\n", errno, strerror(errno)); rc = -1; } } } return rc; } int perf_reset_ctr(enum ctr_e ctr) { int *fds, ec, rc = 0; if (ctr == ALL_COUNTER) { for (ctr = 0; ctr < ALL_COUNTER; ctr++) { rc = perf_reset_ctr(ctr); if (rc != 0) return rc; } } else { for (fds = ctr_fds[ctr]; fds && *fds; fds++) { ec = ioctl(*fds, PERF_EVENT_IOC_RESET, 0); if (ec < 0) { eprint("Ioctl(PERF_EVENT_IOC_RESET) failed with errno=%d [%s]\n", errno, strerror(errno)); rc = -1; } } } return rc; } int perf_read_ctr(enum ctr_e ctr, uint64_t *value) { int *fds, ec, rc = -1; uint64_t val; if (!value) return -1; *value = 0; for (fds = ctr_fds[ctr]; fds && *fds; fds++) { ec = read(*fds, &val, sizeof(val)); if (ec != sizeof(val)) { eprint("Read() on perf file descriptor failed with errno=%d [%s]\n", errno, strerror(errno)); } else { *value += val; rc = 0; } } return rc; } s390-tools-2.3.0/cpacfstats/stats_sock.c000066400000000000000000000103751323410431100200220ustar00rootroot00000000000000/* * cpacfstats - display and maintain CPACF perf counters * * basic socket and receive/send functions * * Copyright IBM Corp. 2015, 2017 * * 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 #include #include #include #include #include #include #include #include #include #include #include "cpacfstats.h" int open_socket(int mode) { struct sockaddr_un sock_addr; struct group *grp; mode_t m; int s; /* group handling */ grp = getgrnam(CPACFSTATS_GROUP); if (!grp) { eprint("Getgrnam() failed, group '%s' may not exist on this system ?\n", CPACFSTATS_GROUP); return -1; } /* the client checks for the unix domain socket file */ if (mode != SERVER) { if (access(SOCKET_FILE, F_OK) != 0) { eprint("Can't access domain socket file '%s', errno=%d [%s]\n", SOCKET_FILE, errno, strerror(errno)); if (errno == ENOENT) eprint("Maybe cpacfstatsd daemon is not running ???\n"); return -1; } } /* create socket */ s = socket(AF_UNIX, SOCK_STREAM, 0); if (s == -1) { eprint("Socket(AF_UNIX,SOCK_STREAM) failed, errno=%d [%s]\n", errno, strerror(errno)); return -1; } if (mode == SERVER) remove(SOCKET_FILE); memset(&sock_addr, 0, sizeof(sock_addr)); sock_addr.sun_family = AF_UNIX; strncpy(sock_addr.sun_path, SOCKET_FILE, sizeof(sock_addr.sun_path)); sock_addr.sun_path[sizeof(sock_addr.sun_path)-1] = '\0'; if (mode == SERVER) { if (bind(s, (struct sockaddr *) &sock_addr, sizeof(struct sockaddr_un)) < 0) { eprint("Bind('%s') failed, errno=%d [%s]\n", SOCKET_FILE, errno, strerror(errno)); return -1; } /* change group ownership of the socket file */ if (chown(SOCKET_FILE, 0, grp->gr_gid)) { eprint("Chown('%s',...) failed, errno=%d [%s]\n", SOCKET_FILE, errno, strerror(errno)); return -1; } /* adapt permissions */ m = S_IRUSR|S_IWUSR|S_IXUSR | S_IRGRP|S_IWGRP|S_IXGRP; if (chmod(SOCKET_FILE, m)) { eprint("Chmod('%s',...) failed, errno=%d [%s]\n", SOCKET_FILE, errno, strerror(errno)); return -1; } /* now put the socket into listen state */ if (listen(s, BACKLOG) < 0) { eprint("Listen() failed, errno=%d [%s]\n", errno, strerror(errno)); return -1; } } else { if (connect(s, (struct sockaddr *) &sock_addr, sizeof(sock_addr)) < 0) { eprint("Connect() failed, errno=%d [%s]\n", errno, strerror(errno)); return -1; } } return s; } static int __write(int fd, const void *buf, int buflen) { const unsigned char *p = buf; int n, i = 0; while (i < buflen) { n = write(fd, p+i, buflen-i); if (n < 0) { if (errno == EINTR) continue; else return n; } i += n; } return i; } static int __read(int fd, void *buf, int buflen) { unsigned char *p = buf; int n, i = 0; while (i < buflen) { n = read(fd, p+i, buflen-i); if (n < 0) { if (errno == EINTR) continue; else return n; } else if (n == 0) { return i; } else { i += n; } } return i; } int send_msg(int sfd, struct msg *m) { int n, len; len = sizeof(m->head); switch (m->head.m_type) { case QUERY: len += sizeof(m->query); break; case ANSWER: len += sizeof(m->answer); break; default: eprint("Unknown type %d\n", m->head.m_type); return -1; } n = __write(sfd, m, len); if (n != len) { eprint("Write() error: write()=%d expected %d, errno=%d [%s]\n", n, len, errno, strerror(errno)); return -1; } return 0; } int recv_msg(int sfd, struct msg *m) { int n, len; len = sizeof(m->head); n = __read(sfd, m, len); if (n != len) { eprint("Recv() error: read()=%d expected %d, errno=%d [%s]\n", n, len, errno, strerror(errno)); return -1; } switch (m->head.m_type) { case QUERY: len = sizeof(m->query); break; case ANSWER: len = sizeof(m->answer); break; default: eprint("Unknown type %d\n", m->head.m_type); return -1; } n = __read(sfd, ((char *)m) + sizeof(m->head), len); if (n != len) { eprint("Recv() error: recv()=%d expected %d, errno=%d [%s]\n", n, len, errno, strerror(errno)); return -1; } return 0; } s390-tools-2.3.0/cpumf/000077500000000000000000000000001323410431100144525ustar00rootroot00000000000000s390-tools-2.3.0/cpumf/Makefile000066400000000000000000000037771323410431100161300ustar00rootroot00000000000000#!/usr/bin/make -f include ../common.mak CPUMF_DATADIR = $(TOOLS_DATADIR)/cpumf DATA_FILES = cpum-cf-hw-counter.map \ cpum-cf-cfvn-1.ctr cpum-cf-cfvn-3.ctr \ cpum-cf-csvn-generic.ctr \ cpum-cf-extended-z10.ctr cpum-cf-extended-z196.ctr \ cpum-cf-extended-zEC12.ctr cpum-sf-modes.ctr \ cpum-cf-extended-z13.ctr cpum-cf-extended-z14.ctr LIB_FILES = bin/cpumf_helper USRBIN_SCRIPTS = bin/lscpumf USRSBIN_SCRIPTS = bin/chcpumf MAN_FILES = lscpumf.1 chcpumf.8 all: scripts: $(USRBIN_SCRIPTS) $(USRSBIN_SCRIPTS) $(LIB_FILES) chmod +x $(USRBIN_SCRIPTS) $(USRSBIN_SCRIPTS) $(LIB_FILES) check: install: scripts install-man for prg in $(USRBIN_SCRIPTS); do \ $(INSTALL) -g $(GROUP) -o $(OWNER) -m 755 $$prg $(DESTDIR)$(USRBINDIR) ; \ done for prg in $(USRSBIN_SCRIPTS); do \ $(INSTALL) -g $(GROUP) -o $(OWNER) -m 755 $$prg $(DESTDIR)$(USRSBINDIR) ; \ done test -d $(DESTDIR)$(CPUMF_DATADIR) || mkdir -p $(DESTDIR)$(CPUMF_DATADIR) for lib in $(LIB_FILES); do \ $(INSTALL) -g $(GROUP) -o $(OWNER) -m 755 $$lib $(DESTDIR)$(TOOLS_LIBDIR) ; \ done for data in $(DATA_FILES); do \ $(INSTALL) -g $(GROUP) -o $(OWNER) -m 644 data/$$data $(DESTDIR)$(CPUMF_DATADIR) ; \ done install-man: for man in $(MAN_FILES); do \ msection=`echo $$man |sed 's/.*\.\([1-9]\)$$/man\1/'` ; \ $(INSTALL) -g $(GROUP) -o $(OWNER) -m 644 -D man/$$man $(DESTDIR)$(MANDIR)/$$msection/$$man ; \ done man2pdf: for man in $(MAN_FILES); do \ man -t man/$$man |ps2pdf -sPAPERSIZE=a4 - man/$${man}.pdf ; \ done man2text: for man in $(MAN_FILES); do \ MANWIDTH=80 LANG=C man man/$$man |col -b |expand > man/$${man}.txt ; \ done clean: rm -f $(LIB_FILES) $(USRBIN_SCRIPTS) $(USRSBIN_SCRIPTS) %: %.in real_libdir=$(TOOLS_LIBDIR); \ real_cpumfdatadir=$(CPUMF_DATADIR); \ $(SED) -e "s#@lib_path@#$$real_libdir#g" \ -e "s#@cpumfdata_path@#$$real_cpumfdatadir#g" \ -e 's#@S390_TOOLS_RELEASE@#$(S390_TOOLS_RELEASE)#g' \ < $< > $@ .PHONY: all scripts install install-man man2pdf man2text clean s390-tools-2.3.0/cpumf/bin/000077500000000000000000000000001323410431100152225ustar00rootroot00000000000000s390-tools-2.3.0/cpumf/bin/chcpumf.in000066400000000000000000000107251323410431100172040ustar00rootroot00000000000000#!/usr/bin/perl -W # # chcpumf - Control CPU-measurement facilities # # Copyright IBM Corp. 2014, 2017 # # s390-tools is free software; you can redistribute it and/or modify # it under the terms of the MIT license. See LICENSE for details. # use strict; use warnings; use File::Basename qw/fileparse/; use Data::Dumper; use Getopt::Long qw/:config no_ignore_case/; # Global constants my $CPUMF_HELPER = '@lib_path@/cpumf_helper'; my $CPUM_SFB_SIZE = '/sys/module/kernel/parameters/cpum_sfb_size'; # Prototypes sub main(); sub show_help(); sub show_version(); sub do_set_sfb_size($); sub cpumf_set_sfb_size($); sub invoke_cpumf_helper($); sub main() { my $config = { # Internal data cpumf => {}, # CPU-MF information hash }; unless (GetOptions( # General options for help, version, verbose,... "h|help" => \&show_help, "v|version" => \&show_version, "V|verbose+" => \$config->{verbose}, # Change specific options "m|min=i" => \$config->{min}, "x|max=i" => \$config->{max}, )) { print STDERR "One or more options are not valid\n"; print STDERR "Try '" . fileparse($0) . " --help' for more information\n"; exit 1; } # Collect CPU-MF information $config->{cpumf} = invoke_cpumf_helper("-i"); die "Failed to collect CPU-MF information: $!\n" unless $config->{cpumf}; # Process parameters my $exitval = 5; if (defined($config->{min}) || defined($config->{max})) { $exitval = do_set_sfb_size($config); } else { print STDERR "You must specify a valid option\n"; exit 1; } exit($exitval); } sub show_help() { my $prog = fileparse($0); print <<"EoHelp"; Usage: chcpumf -h|-v chcpumf -m chcpumf -x Options: -m Specifies the initial size of the sampling buffer. A sample-data-block (SDB) consumes about 4 kilobytes. -x Specifies the maximum size of the sampling buffer. A sample-data-block (SDB) consumes about 4 kilobytes. -h Displays help information, then exits. -v Displays version information, then exits. For more help information, issue 'man $prog'. EoHelp exit 0; } sub show_version() { print <<'EoVersion'; CPU-measurement facility utilities, version @S390_TOOLS_RELEASE@ Copyright IBM Corp. 2014, 2017 EoVersion exit 0; } sub cpumf_set_sfb_size($) { my @size = @{shift()}; my $val = join ',', @size[0,1]; my ($SFBSIZE, $rc); return undef unless open($SFBSIZE, '>', $CPUM_SFB_SIZE); # Check the return code of print and close to detect error conditions # reported by the device driver. Because perl might buffer data, print # might be successful, but close might then report the error condition. # So check the return code of both functions and always close the file # handle. $rc = print { $SFBSIZE } "$val\n"; unless ($rc) { close($SFBSIZE); return $rc; } return close($SFBSIZE); } sub do_set_sfb_size($) { my $c = shift(); my $size; # Check if sampling facility is available unless (exists $c->{cpumf}->{sf}) { print STDERR "No CPU-measurement sampling facility detected\n"; return 2; } # Check if perf support is available unless (exists $c->{cpumf}->{sf}->{perf}) { print STDERR "No perf support for the CPU-measurement" . " sampling facility availalble\n"; return 2; } # Optionally, change the sampling buffer sizes if (defined($c->{min}) || defined($c->{max})) { $size = invoke_cpumf_helper("--sfb-size"); # Use current size value for zero min/max specifications $size->[0] = $c->{min} if defined($c->{min}); $size->[1] = $c->{max} if defined($c->{max}); # Validate new settings if ($size->[0] < 1 || $size->[1] < 1) { die "The specified number(s) are not valid\n"; } if ($size->[0] >= $size->[1]) { die "The specified maximum must be greater " . "than the minimum\n"; } # Set new sampling buffer sizes unless (cpumf_set_sfb_size($size)) { die "Failed to change sampling buffer size: $!\n"; } } # Finally, show sampling buffer sizes if ($c->{verbose}) { $size = invoke_cpumf_helper("--sfb-size"); print "Sampling buffer sizes:\n"; printf " Minimum: %6u sample-data-blocks\n", $size->[0]; printf " Maximum: %6u sample-data-blocks\n", $size->[1]; } return 0; } sub invoke_cpumf_helper($) { my $parms = shift(); my $result; # Call helper module my $output = qx"$CPUMF_HELPER $parms"; die "Failed to run helper module for '$parms'\n" if $? >> 8; $result = eval "$output"; die "Failed to parse helper module data\n" if $@; return $result; } &main(); __DATA__ __END__ s390-tools-2.3.0/cpumf/bin/cpumf_helper.in000066400000000000000000000317141323410431100202310ustar00rootroot00000000000000#!/usr/bin/perl -W # # cpumf_helper - Helper module for managing CPU-measurement facilities (CPU-MF) # # Copyright IBM Corp. 2014, 2017 # # s390-tools is free software; you can redistribute it and/or modify # it under the terms of the MIT license. See LICENSE for details. # use strict; use warnings; use Carp qw/croak/; use Data::Dumper; use Getopt::Long qw/:config no_ignore_case/; use Pod::Usage; # Global constants my $SERVICE_LEVELS = '/proc/service_levels'; my $CPUMF_DATA_DIR = '@cpumfdata_path@'; my $CPUM_SFB_SIZE = '/sys/module/kernel/parameters/cpum_sfb_size'; my $CPUM_SF_DBF='/sys/kernel/debug/s390dbf/cpum_sf'; # Counter set bits (according to QUERY COUNTER INFORMATION) my $BASIC_SET = 0x0002; my $PROBLEM_STATE_SET = 0x0004; my $CRYPTO_SET = 0x0008; my $EXTENTED_SET = 0x0001; my $MT_DIAG_SET = 0x0020; my $COPROC_GRP_SET = 0x8000; # Public prototypes sub cpumf_collect_data(); sub cpumf_get_sfb_size(); sub cpumf_set_sfb_size($); sub cpumf_parse_ctrdef($;$); sub cpumf_load_ctrdef($;$); sub cpumf_get_counter_set($); sub cpumf_counter_set_names(); sub cpumf_counter_set_ids(); sub cpumf_hardware_counter_map(); # Internal prototypes sub cpumf_parse_cf($$); sub cpumf_parse_sf($$); sub cpumf_get_sfb_size() { my $val = "0,0"; my $SFBSIZE; return $val unless open($SFBSIZE, '<', $CPUM_SFB_SIZE); $val = <$SFBSIZE>; chomp($val); close($SFBSIZE); return [split /,/, $val]; } sub cpumf_set_sfb_size($) { my @size = @{shift()}; my $val = join ',', @size[0,1]; my ($SFBSIZE, $rc); return undef unless open($SFBSIZE, '>', $CPUM_SFB_SIZE); # Check the return code of print and close to detect error conditions # reported by the device driver. Because perl might buffer data, print # might be successful, but close might then report the error condition. # So check the return code of both functions and always close the file # handle. $rc = print { $SFBSIZE } "$val\n"; unless ($rc) { close($SFBSIZE); return $rc; } return close($SFBSIZE); } sub cpumf_parse_cf($$) { my ($ent, $data) = @_; if ($ent =~ /version=([0-9.]+) authorization=([[:xdigit:]]+)/) { $data->{cf} = { version => $1, auth => hex($2) }; } } sub cpumf_parse_sf($$) { my ($ent, $data) = @_; # Parse common sampling facility entry if ($ent =~ /min_rate=(\d+) max_rate=(\d+) cpu_speed=(\d+)/) { $data->{sf} = { min_sampl_interval => $1, max_sampl_interval => $2, cpu_speed => $3, # This contains a list of authorized sampling modes, for # example, basic modes => {}, }; } # Parse sampling facility mode entries if ($ent =~ /mode=(\w+) sample_size=(\d+)/) { $data->{sf}->{modes}->{$1}->{sample_size} = $2; } } sub cpumf_collect_data() { my $SL; # Collect CPU-MF information from /proc/service_levels return undef unless open($SL, '<', $SERVICE_LEVELS); my @sl = <$SL>; chomp(@sl); close($SL); # Process CPU-MF information and build data hash my $data = {}; foreach my $ent (@sl) { $ent =~ s/^CPU-MF: // or next; cpumf_parse_cf($ent, $data) if $ent =~ s/Counter facility: //; cpumf_parse_sf($ent, $data) if $ent =~ s/Sampling facility: //; } # Collect perf support for available facilities if (-e '/sys/bus/event_source/devices/cpum_cf') { $data->{cf}->{perf} = "cpum_cf" if exists $data->{cf}; } if (-e '/sys/bus/event_source/devices/cpum_sf') { $data->{sf}->{perf} = "cpum_sf" if exists $data->{sf}; } return $data; } # Parse the specified counter definition file and returns a hash containing # the parsed counter definition. The optional argument specifies a hash # reference to which the new definitions are added. This reference is returned. sub cpumf_parse_ctrdef($;$) { my $ctrdef = shift(); my $h = @_ ? shift() : {}; my $CTRDEF; return undef unless open($CTRDEF, '<', "$CPUMF_DATA_DIR/$ctrdef"); my ($ctr, $name); while (my $line = <$CTRDEF>) { next if $line =~ /^#/; chomp($line); # Parse start of counter definition entry if ($line =~ m/^Counter:\s*(0x[[:xdigit:]]+|\d+) \s+Name:\s*([[:alnum:]_]+)$/x) { ($ctr, $name) = ($1, $2); $ctr = hex($ctr) if $ctr =~ /^0x/; unless (length($ctr)) { print STDERR "Found invalid entry in counter " . "definition: line $.\n"; } $h->{$ctr} = { name => $name || "" }; } # At this point, a counter must be defined next unless defined($ctr); # Parse short description (optional) if ($line =~ m/^Short-Description:\s*(\S.*)?$/) { $h->{ctr}->{shortdesc} = $1 || ""; # Parse start of counter description } elsif ($line =~ m/^Description:\s*(\S.*)?$/) { $h->{$ctr}->{desc} = $1 || ""; # Parse end of counter description } elsif ($line =~ m/^\.$/) { # Complete the counter definition $h->{$ctr}->{set} = cpumf_get_counter_set($ctr); # Trim whitespaces $h->{$ctr}->{shortdesc} =~ s/^\s+|\s+$//g if $h->{$ctr}->{shortdesc}; $h->{$ctr}->{desc} =~ s/^\s+|\s+$//g if $h->{$ctr}->{desc}; # Finally, reset counter for next entry $ctr = undef; # Line is part of counter description (if $ctr_num is set) } else { $h->{$ctr}->{desc} .= " $line"; } } close($CTRDEF); return $h; } # IBM System z hardware with CPU-M counter facility support my $system_z_hwtype_map = { # Machine type Description '' => 'Unknown hardware model', 2097 => 'IBM System z10 EC', 2098 => 'IBM System z10 BC', 2817 => 'IBM zEnterprise 196', 2818 => 'IBM zEnterprise 114', 2827 => 'IBM zEnterprise EC12', 2828 => 'IBM zEnterprise BC12', 2964 => 'IBM z13', 2965 => 'IBM z13s', 3906 => 'IBM z14', }; sub get_hardware_type() { my $type = ""; my $SYSINFO; return undef unless open($SYSINFO, '<', '/proc/sysinfo'); while (my $line = <$SYSINFO>) { if ($line =~ m/^Type:\s*(\d+)\s*$/) { $type = $1; last; } } close($SYSINFO); return $type; } sub get_cpum_cf_version() { my $SL; my $v = { cfvn => 0, csvn => 0, }; return $v unless open($SL, '<', $SERVICE_LEVELS); while (my $line = <$SL>) { # CPU-MF: Counter facility: version=3.5 if ($line =~ m/^CPU-MF: Counter facility: version=(\d+)\.(\d+)/) { $v->{cfvn} = $1; # Counter First Version Number $v->{csvn} = $2; # Counter Second Version Number last; } } close($SL); return $v } sub cpumf_load_ctrdef($;$) { my $hw_type = shift(); my $authorized = @_ ? shift() : 0xffff; # Counter Set authorization my $ctrmap = cpumf_hardware_counter_map(); return unless $ctrmap; # Obtain CPU-MF counter facility versions my $version = get_cpum_cf_version(); # List of "generic" counter sets my @def = (); push @def, "cfvn-" . $version->{cfvn}; push @def, "csvn-generic"; my $h = {}; # Load counter set definition foreach my $ent (@def) { cpumf_parse_ctrdef($ctrmap->{$ent}, $h) or croak "Failed to read counter definition for $ent: $!\n"; } # Load hardware model specific counter set(s) if ($hw_type && $ctrmap->{$hw_type}) { # Hardware-model specific counter sets are: # - Extended Counter Set # - MT-diagnostic Counter Set cpumf_parse_ctrdef($ctrmap->{$hw_type}, $h) or croak "Failed to read hardware-model counter definition: $!\n"; } # Remove counter sets that miss authorizations my @no_auth_list = (); foreach my $ctr (sort keys %$h) { push @no_auth_list, $ctr unless $h->{$ctr}->{set} & $authorized; } delete $h->{$_} foreach (@no_auth_list); return $h; } sub cpumf_get_counter_set($) { my $ctr = shift(); return $BASIC_SET if $ctr < 32; return $PROBLEM_STATE_SET if $ctr < 64; return $CRYPTO_SET if $ctr < 128; # # The extended counter set ranges from # 128 to # 159 for csvn == 1 # 175 for csvn == 2 # 255 for csvn > 2 # Tolerate any future counters up to # the MT-diagnostic counter set. return $EXTENTED_SET if $ctr < 448; # # The MT-diagnostic counter set ranges from # 448 to # 495 for cvsn > 3 return $MT_DIAG_SET if $ctr <= 495; return 0; } sub cpumf_counter_set_names() { return { # Identifier Name $BASIC_SET => 'Basic Counter Set', $PROBLEM_STATE_SET => 'Problem-State Counter Set', $CRYPTO_SET => 'Crypto-Activity Counter Set', $EXTENTED_SET => 'Extended Counter Set', $MT_DIAG_SET => 'MT-diagnostic Counter Set', $COPROC_GRP_SET => 'Coprocessor Group Counter Set', }; } sub cpumf_counter_set_ids() { return [$BASIC_SET, $PROBLEM_STATE_SET, $CRYPTO_SET, $EXTENTED_SET, $MT_DIAG_SET, $COPROC_GRP_SET]; } sub cpumf_hardware_counter_map() { my $map = do "$CPUMF_DATA_DIR/cpum-cf-hw-counter.map"; croak "Failed to parse mapfile: $@" if $@; croak "Failed to read mapfile: $!" unless defined $map; return $map; } sub cpumf_helper_main() { # Configuration settings and options my $conf = { }; # Parse command line option GetOptions( "i|info" => \$conf->{opt_info}, "c|counter=i" => \$conf->{opt_ctr}, "ctr-def=s" => \$conf->{opt_ctrdef}, "hardware-type" => \$conf->{opt_hwtype}, "ctr-set-names" => \$conf->{opt_ctrset_names}, "ctr-set-ids" => \$conf->{opt_ctrset_ids}, "sfb-size" => \$conf->{opt_sfb_size}, "ctr-sf" => \$conf->{opt_sf_ctr}, ) or pod2usage(-message =>"One or more options are not valid", -exitval => 1); # Setting up Data::Dumper to create parseable Perl output local $Data::Dumper::Purity = 1; local $Data::Dumper::Sortkeys = 1; local $Data::Dumper::Terse = 1; ###print STDERR "CONF: " . Dumper($conf) . "\n"; # Process command line options my $exitval = 0; my $result; if (defined($conf->{opt_info})) { $result = cpumf_collect_data(); # Display System z hardware type } elsif (defined($conf->{opt_hwtype})) { my $type = get_hardware_type(); $result = [$type, $system_z_hwtype_map->{$type} || ""]; # Display counters for current System z hardware } elsif (defined($conf->{opt_ctr})) { my $type = get_hardware_type(); $type = 0 unless $type; $result = cpumf_load_ctrdef($type, $conf->{opt_ctr}); # Display counters for a particular System z hardware type } elsif (defined($conf->{opt_ctrdef})) { my $m = cpumf_hardware_counter_map(); if (exists $m->{$conf->{opt_ctrdef}}) { $result = cpumf_parse_ctrdef($m->{$conf->{opt_ctrdef}}); } else { printf STDERR "Invalid counter definition\n"; $exitval = 2; } # Display the size of the sampling facility buffer (sfb) } elsif (defined($conf->{opt_sfb_size})) { $result = cpumf_get_sfb_size(); # Display counter definitions for the sampling facility support (perf) } elsif (defined($conf->{opt_sf_ctr})) { $result = cpumf_parse_ctrdef('cpum-sf-modes.ctr'); # Display mapping of counter set IDs to counter set names } elsif (defined($conf->{opt_ctrset_names})) { $result = cpumf_counter_set_names(); # Display counter set IDs } elsif (defined($conf->{opt_ctrset_ids})) { $result = cpumf_counter_set_ids(); } else { pod2usage(-message => "No option specified", -exitval => 1); } # Display result print Dumper($result) if $result; exit $exitval; } &cpumf_helper_main(); __DATA__ __END__ =head1 NAME B - Helper module for managing CPU-Measurement Facilities (CPU-MF) =head1 SYNOPSIS =head1 DESCRIPTION The B program is not intended for ordinary use. =head1 OPTIONS =over 8 =item B<-i>, B<--info> Displays detailed information about installed and available CPU-measurement facilities and the related Linux support. =item B<-c>, B<--counter> I Displays counter information for the current System z hardware. The authorization value provides information about the authorized counter sets. The value must be specified in decimal format. To display all supported counters, specify C<65535> (FFFF hex). To display supported counters for a particular System z hardware, use the B<--ctr-def> option and specify the System z hardware type. =item B<--hardware-type> Displays the System z hardware type. =item B<--ctr-def> I Displays detailed information about the specified counter definition. Valid counter definitions start with C or followed by the counter first/second version number of the CPU-Measurement Counter Facility. To display counter information of model-specific counter sets, specify the System z hardware type for I. =item B<--ctr-set-names> Displays the mapping of counter set IDs to counter set names. The counter set IDs are numbers that are used in counter definitions. =item B<--ctr-set-ids> Displays the counter set IDs. The counter set IDs are numbers that match the authorization bits (see QUERY COUNTER INFORMATION). The output format is a list. To get the ID for a particular counter set, use an index number as follows: =over 16 =item 0: Basic Counter Set =item 1: Problem-State Counter Set =item 2: Crypto-Activity Counter Set =item 3: Extended Counter Set =item 4: MT-diagnostic Counter Set =item 5: Coprocessor Group Counter Set =back =item B<--ctr-sf> Displays the counter definitions for the sampling facility support. These counter definitions are specific to Linux perf infrastructure. =item B<--sfb-size> Displays the size of the sampling facility buffer (SFB). The minimum and maximum numbers are measured in units of sample-data-blocks. A sample-data-block uses about 4 kilobytes. =back =head1 FILES =head1 SEE ALSO L, L =cut s390-tools-2.3.0/cpumf/bin/lscpumf.in000066400000000000000000000233751323410431100172350ustar00rootroot00000000000000#!/usr/bin/perl -W # # lscpumf - Display information about CPU-measurement facilities # # Copyright IBM Corp. 2014, 2017 # # s390-tools is free software; you can redistribute it and/or modify # it under the terms of the MIT license. See LICENSE for details. # use strict; use warnings; use Data::Dumper; use File::Basename qw/fileparse/; use Getopt::Long qw/:config no_ignore_case/; # Global constants my $CPUMF_HELPER = '@lib_path@/cpumf_helper'; # Prototypes sub main(); sub show_help(); sub show_version(); sub do_show_info($); sub do_show_cf($); sub do_show_sf($); sub do_show_ctr($;$); sub do_show_sf_events($); sub invoke_cpumf_helper($); sub main() { my $config = { # Internal data cpumf => {}, # CPU-MF information hash }; unless (GetOptions( # General options for help, version,... "h|help" => \&show_help, "v|version" => \&show_version, # Display options "i|info" => \$config->{opt_info}, "c|list-counters" => \$config->{opt_ctr}, "C|list-all-counters" => \$config->{opt_ctr_all}, "s|list-sampling-events" => \$config->{opt_ctr_sf}, )) { print STDERR "One or more options are not valid\n"; print STDERR "Try '" . fileparse($0) . " --help' for more information\n"; exit 1; } # Collect CPU-MF information $config->{cpumf} = invoke_cpumf_helper("-i"); die "Failed to collect CPU-MF information: $!\n" unless $config->{cpumf}; # Process parameters my $exitval = 5; if (defined($config->{opt_info})) { do_show_cf($config); do_show_sf($config); $exitval = 0; } elsif (defined($config->{opt_ctr})) { $exitval = do_show_ctr($config); } elsif (defined($config->{opt_ctr_all})) { $exitval = do_show_ctr($config, "all") } elsif (defined($config->{opt_ctr_sf})) { $exitval = do_show_sf_events($config); } else { $exitval = do_show_info($config); } exit($exitval); } sub show_help() { my $prog = fileparse($0); print <<"EoHelp"; Usage: lscpumf -h|-v lscpumf [-i] lscpumf -c|-C Options: -i Displays detailed information. -c Lists counters for which the LPAR is authorized. -C Lists counters regardless of LPAR authorization. -s Lists perf raw events that activate the sampling facility. -h Displays help information, then exits. -v Displays version information, then exits. For more help information, issue 'man $prog'. EoHelp exit 0; } sub show_version() { print <<'EoVersion'; CPU-measurement facility utilities, version @S390_TOOLS_RELEASE@ Copyright IBM Corp. 2014, 2017 EoVersion exit 0; } sub do_show_info($) { my $c = shift(); my $cpumf = $c->{cpumf}; my @f = (); push @f, "CPU-measurement Counter Facility" if exists $cpumf->{cf}; push @f, "CPU-measurement Sampling Facility" if exists $cpumf->{sf}; if (@f) { print((join "\n", @f) . "\n"); } else { print STDERR "No CPU-measurement facilities detected\n"; return 2; } return 0; } sub do_show_cf($) { my $c = shift(); # Check if counter facility is available unless (exists $c->{cpumf}->{cf}) { print STDERR "No CPU-measurement counter facility detected\n"; return 2; } # Retrieve counter facility information my $cf = $c->{cpumf}->{cf}; # Create list of authorized counter sets my @sets = (); push @sets, "None" unless $cf->{auth}; push @sets, "Crypto-Activity counter set" if $cf->{auth} & 0x8; push @sets, "Problem-State counter set" if $cf->{auth} & 0x4; push @sets, "Basic counter set" if $cf->{auth} & 0x2; push @sets, "Extented counter set" if $cf->{auth} & 0x1; push @sets, "MT-diagnostic counter set" if $cf->{auth} & 0x20; print "CPU-measurement counter facility\n"; print "-" x 74 . "\n"; # TODO Display additional information about available conters depending # on the version information print "Version: " . $cf->{version} . "\n"; print "\n"; print "Authorized counter sets:\n"; print " $_\n" foreach (sort @sets); printf "\nLinux perf event support: %s\n\n", exists $cf->{perf} ? "Yes (PMU: $cf->{perf})" : "No"; return 0; } sub div_ceil($$) { my ($a, $b) = @_; return int(($a + $b - 1) / $b); } sub humanize_bytes($;$) { my $bytes = shift(); my @units = split //, " KMGTPEZY"; my $u = @_ ? shift() : 0; while ($bytes >= 1024 && $u <= $#units) { $bytes /= 1024; $u++; } return sprintf "%.f%sB", $bytes, $units[$u]; } sub get_sfb_details($) { my $n_sdb = shift(); # Calculate sampling buffer structure my $n_sdbt = div_ceil($n_sdb, 511); my $n_pages = $n_sdb + $n_sdbt; return [ $n_sdb, # number of sample-data-blocks $n_sdbt, # number of sample-data-block-tables $n_pages, # number of 4K pages $n_pages * 4096, # size in bytes ]; } sub do_show_sf($) { my $c = shift(); # Check if sampling facility is available unless (exists $c->{cpumf}->{sf}) { print STDERR "No CPU-measurement sampling facility detected\n"; return 2; } my $sf = $c->{cpumf}->{sf}; my $size = invoke_cpumf_helper("--sfb-size"); # Sampling facility information print "CPU-measurement sampling facility\n"; print "-" x 74 . "\n"; print "Sampling Interval:\n"; printf " Minimum: %10u cycles (approx. %8u Hz)\n", $sf->{min_sampl_interval}, 1000000 * $sf->{cpu_speed} / $sf->{min_sampl_interval}; printf " Maximum: %10u cycles (approx. %8u Hz)\n", $sf->{max_sampl_interval}, 1000000 * $sf->{cpu_speed} / $sf->{max_sampl_interval}; print "\n"; print "Authorized sampling modes:\n"; foreach my $m (sort keys %{$sf->{modes}}) { printf " %-10s (sample size: %3u bytes)\n", $m, $sf->{modes}->{$m}->{sample_size}; } print "\n"; printf "\nLinux perf event support: %s\n\n", exists $sf->{perf} ? "Yes (PMU: $sf->{perf})" : "No"; # Sampling buffer settings for cpum_sf goto out unless exists $sf->{perf}; print "Current sampling buffer settings for $sf->{perf}:\n"; printf " Basic-sampling mode\n"; my $s = get_sfb_details($size->[0]); printf " Minimum: %6u sample-data-blocks (%6s)\n", $s->[0], humanize_bytes($s->[3]); $s = get_sfb_details($size->[1]); printf " Maximum: %6u sample-data-blocks (%6s)\n", $s->[0], humanize_bytes($s->[3]); unless (exists $sf->{modes}->{diagnostic}) { goto out; } # Sampling buffer setting specific to diagnostic-sampling mode my $f_diag = div_ceil($sf->{modes}->{diagnostic}->{sample_size}, $sf->{modes}->{basic}->{sample_size}); print "\n"; printf " Diagnostic-sampling mode (including basic-sampling)\n"; $s = get_sfb_details($size->[0] * $f_diag); printf " Minimum: %6u sample-data-blocks (%6s)\n", $s->[0], humanize_bytes($s->[3]); $s = get_sfb_details($size->[1] * $f_diag); printf " Maximum: %6u sample-data-blocks (%6s)\n", $s->[0], humanize_bytes($s->[3]); printf " Size factor: %2u\n", $f_diag; out: return 0; } sub print_counters($$) { my ($ctrdef, $header) = @_; my $set_name_map = invoke_cpumf_helper('--ctr-set-names'); my $out = []; my ($ctr_perf, $ctr_num, $set, $name, $desc); format PERF_CTR_FORM = r@<<<< @<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< $ctr_perf, $name ^<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< $desc ^<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< ~~ $desc @<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< $set . print $header; $~ = "PERF_CTR_FORM"; foreach my $ctr (sort { $a <=> $b } keys %$ctrdef) { $ctr_perf = sprintf "%x", $ctr; $ctr_num = $ctr < (1 << 16) ? $ctr : ""; $name = $ctrdef->{$ctr}->{name}; $desc = $ctrdef->{$ctr}->{shortdesc} ? $ctrdef->{$ctr}->{shortdesc} : $ctrdef->{$ctr}->{desc}; $desc .= "."; $set = $set_name_map->{$ctrdef->{$ctr}->{set}}; if ($set) { $set = "Counter $ctr_num / $set."; } else { $set = "This event is not associated with a counter set."; } write; } } sub do_show_ctr($;$) { my $c = shift(); # Check if counter facility is available unless (exists $c->{cpumf}->{cf}) { print STDERR "No CPU-measurement counter facility detected\n"; return 2; } # Retrieve counter authorization ("all" or authorized counters only) my $auth = @_ ? hex("0xFFFF") : $c->{cpumf}->{cf}->{auth}; # Retrieve counter information my $ctrs = invoke_cpumf_helper("-c $auth"); unless ($ctrs) { print STDERR "No counters are available or authorized\n"; return 3; } # Retrieve hardware type my $hwtype = invoke_cpumf_helper("--hardware-type"); my $model = length $hwtype->[1] ? "for $hwtype->[1]" : ""; my $header = <<"EoHeader"; Perf event counter list $model ============================================================================== Raw event Name Description ------------------------------------------------------------------------------ EoHeader print_counters($ctrs, $header); return 0; } sub do_show_sf_events($) { my $c = shift(); # Check if sampling facility is available unless (exists $c->{cpumf}->{sf}) { print STDERR "No CPU-measurement sampling facility detected\n"; return 2; } my $sf = $c->{cpumf}->{sf}; my $events = invoke_cpumf_helper("--ctr-sf"); # Remove events with missing authorization delete $events->{0xB0000} unless exists $sf->{modes}->{basic}; delete $events->{0xBD000} unless exists $sf->{modes}->{diagnostic}; unless ($events) { print STDERR "Sampling facility is not authorized\n"; return 3; } # Display sampling facility events (aka. counters) my $header = <<"EoHeader"; Perf events for activating the sampling facility ============================================================================== Raw event Name Description ------------------------------------------------------------------------------ EoHeader print_counters($events, $header); return 0; } sub invoke_cpumf_helper($) { my $parms = shift(); my $result; # Call helper module my $output = qx"$CPUMF_HELPER $parms"; die "Failed to run helper module for '$parms'\n" if $? >> 8; $result = eval "$output"; die "Failed to parse helper module data\n" if $@; return $result; } &main(); __DATA__ __END__ s390-tools-2.3.0/cpumf/data/000077500000000000000000000000001323410431100153635ustar00rootroot00000000000000s390-tools-2.3.0/cpumf/data/cpum-cf-cfvn-1.ctr000066400000000000000000000021201323410431100205120ustar00rootroot00000000000000Counter: 0 Name:CPU_CYCLES Description: Cycle Count . Counter: 1 Name:INSTRUCTIONS Description: Instruction Count . Counter: 2 Name:L1I_DIR_WRITES Description: Level-1 I-Cache Directory Write Count . Counter: 3 Name:L1I_PENALTY_CYCLES Description: Level-1 I-Cache Penalty Cycle Count . Counter: 4 Name:L1D_DIR_WRITES Description: Level-1 D-Cache Directory Write Count . Counter: 5 Name:L1D_PENALTY_CYCLES Description: Level-1 D-Cache Penalty Cycle Count . Counter: 32 Name:PROBLEM_STATE_CPU_CYCLES Description: Problem-State Cycle Count . Counter: 33 Name:PROBLEM_STATE_INSTRUCTIONS Description: Problem-State Instruction Count . Counter: 34 Name:PROBLEM_STATE_L1I_DIR_WRITES Description: Problem-State Level-1 I-Cache Directory Write Count . Counter: 35 Name:PROBLEM_STATE_L1I_PENALTY_CYCLES Description: Problem-State Level-1 I-Cache Penalty Cycle Count . Counter: 36 Name:PROBLEM_STATE_L1D_DIR_WRITES Description: Problem-State Level-1 D-Cache Directory Write Count . Counter: 37 Name:PROBLEM_STATE_L1D_PENALTY_CYCLES Description: Problem-State Level-1 D-Cache Penalty Cycle Count . s390-tools-2.3.0/cpumf/data/cpum-cf-cfvn-3.ctr000066400000000000000000000012041323410431100205160ustar00rootroot00000000000000Counter: 0 Name:CPU_CYCLES Description: Cycle Count . Counter: 1 Name:INSTRUCTIONS Description: Instruction Count . Counter: 2 Name:L1I_DIR_WRITES Description: Level-1 I-Cache Directory Write Count . Counter: 3 Name:L1I_PENALTY_CYCLES Description: Level-1 I-Cache Penalty Cycle Count . Counter: 4 Name:L1D_DIR_WRITES Description: Level-1 D-Cache Directory Write Count . Counter: 5 Name:L1D_PENALTY_CYCLES Description: Level-1 D-Cache Penalty Cycle Count . Counter: 32 Name:PROBLEM_STATE_CPU_CYCLES Description: Problem-State Cycle Count . Counter: 33 Name:PROBLEM_STATE_INSTRUCTIONS Description: Problem-State Instruction Count . s390-tools-2.3.0/cpumf/data/cpum-cf-csvn-generic.ctr000066400000000000000000000052201323410431100220070ustar00rootroot00000000000000Counter: 64 Name:PRNG_FUNCTIONS Description: Total number of the PRNG functions issued by the CPU . Counter: 65 Name:PRNG_CYCLES Description: Total number of CPU cycles when the DEA/AES coprocessor is busy performing PRNG functions issued by the CPU . Counter: 66 Name:PRNG_BLOCKED_FUNCTIONS Description: Total number of the PRNG functions that are issued by the CPU and are blocked because the DEA/AES coprocessor is busy performing a function issued by another CPU . Counter: 67 Name:PRNG_BLOCKED_CYCLES Description: Total number of CPU cycles blocked for the PRNG functions issued by the CPU because the DEA/AES coprocessor is busy performing a function issued by another CPU . Counter: 68 Name:SHA_FUNCTIONS Description: Total number of SHA functions issued by the CPU . Counter: 69 Name:SHA_CYCLES Description: Total number of CPU cycles when the SHA coprocessor is busy performing the SHA functions issued by the CPU . Counter: 70 Name:SHA_BLOCKED_FUNCTIONS Description: Total number of the SHA functions that are issued by the CPU and are blocked because the SHA coprocessor is busy performing a function issued by another CPU . Counter: 71 Name:SHA_BLOCKED_CYCLES Description: Total number of CPU cycles blocked for the SHA functions issued by the CPU because the SHA coprocessor is busy performing a function issued by another CPU . Counter: 72 Name:DEA_FUNCTIONS Description: Total number of the DEA functions issued by the CPU . Counter: 73 Name:DEA_CYCLES Description: Total number of CPU cycles when the DEA/AES coprocessor is busy performing the DEA functions issued by the CPU . Counter: 74 Name:DEA_BLOCKED_FUNCTIONS Description: Total number of the DEA functions that are issued by the CPU and are blocked because the DEA/AES coprocessor is busy performing a function issued by another CPU . Counter: 75 Name:DEA_BLOCKED_CYCLES Description: Total number of CPU cycles blocked for the DEA functions issued by the CPU because the DEA/AES coprocessor is busy performing a function issued by another CPU . Counter: 76 Name:AES_FUNCTIONS Description: Total number of AES functions issued by the CPU . Counter: 77 Name:AES_CYCLES Description: Total number of CPU cycles when the DEA/AES coprocessor is busy performing the AES functions issued by the CPU . Counter: 78 Name:AES_BLOCKED_FUNCTIONS Description: Total number of AES functions that are issued by the CPU and are blocked because the DEA/AES coprocessor is busy performing a function issued by another CPU . Counter: 79 Name:AES_BLOCKED_CYCLES Description: Total number of CPU cycles blocked for the AES functions issued by the CPU because the DEA/AES coprocessor is busy performing a function issued by another CPU . s390-tools-2.3.0/cpumf/data/cpum-cf-extended-z10.ctr000066400000000000000000000063731323410431100216460ustar00rootroot00000000000000Counter: 128 Name:L1I_L2_SOURCED_WRITES Description: A directory write to the Level-1 I-Cache directory where the returned cache line was sourced from the Level-2 (L1.5) cache . Counter: 129 Name:L1D_L2_SOURCED_WRITES Description: A directory write to the Level-1 D-Cache directory where the installed cache line was sourced from the Level-2 (L1.5) cache . Counter: 130 Name:L1I_L3_LOCAL_WRITES Description: A directory write to the Level-1 I-Cache directory where the installed cache line was sourced from the Level-3 cache that is on the same book as the Instruction cache (Local L2 cache) . Counter: 131 Name:L1D_L3_LOCAL_WRITES Description: A directory write to the Level-1 D-Cache directory where the installtion cache line was source from the Level-3 cache that is on the same book as the Data cache (Local L2 cache) . Counter: 132 Name:L1I_L3_REMOTE_WRITES Description: A directory write to the Level-1 I-Cache directory where the installed cache line was sourced from a Level-3 cache that is not on the same book as the Instruction cache (Remote L2 cache) . Counter: 133 Name:L1D_L3_REMOTE_WRITES Description: A directory write to the Level-1 D-Cache directory where the installed cache line was sourced from a Level-3 cache that is not on the same book as the Data cache (Remote L2 cache) . Counter: 134 Name:L1D_LMEM_SOURCED_WRITES Description: A directory write to the Level-1 D-Cache directory where the installed cache line was sourced from memory that is attached to the same book as the Data cache (Local Memory) . Counter: 135 Name:L1I_LMEM_SOURCED_WRITES Description: A directory write to the Level-1 I-Cache where the installed cache line was sourced from memory that is attached to the s ame book as the Instruction cahe (local Memory) . Counter: 136 Name:L1D_RO_EXCL_WRITES Description: A directory write to the Level-1 D-Cache where the line was originally in a Read-Only state in the cache but has been updated to be in the Exclusive state that allows stores to the cache line . Counter: 137 Name:L1I_CACHELINE_INVALIDATES Description: A cache line in the Level-1 I-Cache has been invalidated by a store on the same CPU as the Level-1 I-Cache . Counter: 138 Name:ITLB1_WRITES Description: A translation entry has been written into the Level-1 Instruction Translation Lookaside Buffer . Counter: 139 Name:DTLB1_WRITES Description: A translation entry has been written to the Level-1 Data Translation Lookaside Buffer . Counter: 140 Name:TLB2_PTE_WRITES Description: A translation entry has been written to the Level-2 TLB Page Table Entry arrays . Counter: 141 Name:TLB2_CRSTE_WRITES Description: A translation entry has been written to the Level-2 TLB Common Region Segment Table Entry arrays . Counter: 142 Name:TLB2_CRSTE_HPAGE_WRITES Description: A translation entry has been written to the Level-2 TLB Common Region Segment Table Entry arrays for a one-megabyte large page translation . Counter: 145 Name:ITLB1_MISSES Description: Level-1 Instruction TLB miss in progress. Incremented by one for every cycle an ITLB1 miss is in progress . Counter: 146 Name:DTLB1_MISSES Description: Level-1 Data TLB miss in progress. Incremented by one for every cycle an DTLB1 miss is in progress . Counter: 147 Name:L2C_STORES_SENT Description: Incremented by one for every store sent to Level-2 (L1.5) cache . s390-tools-2.3.0/cpumf/data/cpum-cf-extended-z13.ctr000066400000000000000000000256371323410431100216550ustar00rootroot00000000000000# Counter decriptions for the # IBM z13 extended counter and MT-diagnostic counter set # # Notes for transactional-execution mode symbolic names: # TX .. transactional-execution mode # NC .. nonconstrained # C .. constrained # # Undefined counters in the extended counter set: # 142 # 180-217 # 221-225 # Undefined counters in the MT-diagnostic counter set: # 450-495 # # # Extended Counter Set # --------------------------------------------------------------------- Counter:128 Name:L1D_WRITES_RO_EXCL A directory write to the Level-1 Data cache where the line was originally in a Read-Only state in the cache but has been updated to be in the Exclusive state that allows stores to the cache line. . Counter:129 Name:DTLB1_WRITES Description: A translation entry has been written to the Level-1 Data Translation Lookaside Buffer . Counter:130 Name:DTLB1_MISSES Description: Level-1 Data TLB miss in progress. Incremented by one for every cycle a DTLB1 miss is in progress. . Counter:131 Name:DTLB1_HPAGE_WRITES Description: A translation entry has been written to the Level-1 Data Translation Lookaside Buffer for a one-megabyte page . Counter:132 Name:DTLB1_GPAGE_WRITES A translation entry has been written to the Level-1 Data Translation Lookaside Buffer for a two-gigabyte page. . Counter:133 Name:L1D_L2D_SOURCED_WRITES Description: A directory write to the Level-1 Data cache directory where the returned cache line was sourced from the Level-2 Data cache . Counter:134 Name:ITLB1_WRITES Description: A translation entry has been written to the Level-1 Instruction Translation Lookaside Buffer . Counter:135 Name:ITLB1_MISSES Description: Level-1 Instruction TLB miss in progress. Incremented by one for every cycle an ITLB1 miss is in progress . Counter:136 Name:L1I_L2I_SOURCED_WRITES Description: A directory write to the Level-1 Instruction cache directory where the returned cache line was sourced from the Level-2 Instruction cache . Counter:137 Name:TLB2_PTE_WRITES Description: A translation entry has been written to the Level-2 TLB Page Table Entry arrays . Counter:138 Name:TLB2_CRSTE_HPAGE_WRITES Description: A translation entry has been written to the Level-2 TLB Combined Region Segment Table Entry arrays for a one-megabyte large page translation . Counter:139 Name:TLB2_CRSTE_WRITES Description: A translation entry has been written to the Level-2 TLB Combined Region Segment Table Entry arrays . Counter:140 Name:TX_C_TEND Description: A TEND instruction has completed in a constrained transactional-execution mode . Counter:141 Name:TX_NC_TEND Description: A TEND instruction has completed in a non-constrained transactional-execution mode . Counter:143 Name:L1C_TLB1_MISSES Description: Increments by one for any cycle where a Level-1 cache or Level-1 TLB miss is in progress. . Counter:144 Name:L1D_ONCHIP_L3_SOURCED_WRITES Description: A directory write to the Level-1 Data cache directory where the returned cache line was sourced from an On-Chip Level-3 cache without intervention . Counter:145 Name:L1D_ONCHIP_L3_SOURCED_WRITES_IV Description: A directory write to the Level-1 Data cache directory where the returned cache line was sourced from an On-Chip Level-3 cache with intervention . Counter:146 Name:L1D_ONNODE_L4_SOURCED_WRITES Description: A directory write to the Level-1 Data cache directory where the returned cache line was sourced from an On-Node Level-4 cache . Counter:147 Name:L1D_ONNODE_L3_SOURCED_WRITES_IV Description: A directory write to the Level-1 Data cache directory where the returned cache line was sourced from an On-Node Level-3 cache with intervention . Counter:148 Name:L1D_ONNODE_L3_SOURCED_WRITES Description: A directory write to the Level-1 Data cache directory where the returned cache line was sourced from an On-Node Level-3 cache without intervention . Counter:149 Name:L1D_ONDRAWER_L4_SOURCED_WRITES Description: A directory write to the Level-1 Data cache directory where the returned cache line was sourced from an On-Drawer Level-4 cache . Counter:150 Name:L1D_ONDRAWER_L3_SOURCED_WRITES_IV Description: A directory write to the Level-1 Data cache directory where the returned cache line was sourced from an On-Drawer Level-3 cache with intervention . Counter:151 Name:L1D_ONDRAWER_L3_SOURCED_WRITES Description: A directory write to the Level-1 Data cache directory where the returned cache line was sourced from an On-Drawer Level-3 cache without intervention . Counter:152 Name:L1D_OFFDRAWER_SCOL_L4_SOURCED_WRITES Description: A directory write to the Level-1 Data cache directory where the returned cache line was sourced from an Off-Drawer Same-Column Level-4 cache . Counter:153 Name:L1D_OFFDRAWER_SCOL_L3_SOURCED_WRITES_IV Description: A directory write to the Level-1 Data cache directory where the returned cache line was sourced from an Off-Drawer Same-Column Level-3 cache with intervention . Counter:154 Name:L1D_OFFDRAWER_SCOL_L3_SOURCED_WRITES Description: A directory write to the Level-1 Data cache directory where the returned cache line was sourced from an Off-Drawer Same-Column Level-3 cache without intervention . Counter:155 Name:L1D_OFFDRAWER_FCOL_L4_SOURCED_WRITES Description: A directory write to the Level-1 Data cache directory where the returned cache line was sourced from an Off-Drawer Far-Column Level-4 cache . Counter:156 Name:L1D_OFFDRAWER_FCOL_L3_SOURCED_WRITES_IV Description: A directory write to the Level-1 Data cache directory where the returned cache line was sourced from an Off-Drawer Far-Column Level-3 cache with intervention . Counter:157 Name:L1D_OFFDRAWER_FCOL_L3_SOURCED_WRITES Description: A directory write to the Level-1 Data cache directory where the returned cache line was sourced from an Off-Drawer Far-Column Level-3 cache without intervention . Counter:158 Name:L1D_ONNODE_MEM_SOURCED_WRITES Description: A directory write to the Level-1 Data cache directory where the returned cache line was sourced from On-Node memory . Counter:159 Name:L1D_ONDRAWER_MEM_SOURCED_WRITES Description: A directory write to the Level-1 Data cache directory where the returned cache line was sourced from On-Drawer memory . Counter:160 Name:L1D_OFFDRAWER_MEM_SOURCED_WRITES Description: A directory write to the Level-1 Data cache directory where the returned cache line was sourced from On-Drawer memory . Counter:161 Name:L1D_ONCHIP_MEM_SOURCED_WRITES Description: A directory write to the Level-1 Data cache directory where the returned cache line was sourced from On-Chip memory . Counter:162 Name:L1I_ONCHIP_L3_SOURCED_WRITES Description: A directory write to the Level-1 Instruction cache directory where the returned cache line was sourced from an On-Chip Level-3 cache without intervention . Counter:163 Name:L1I_ONCHIP_L3_SOURCED_WRITES_IV Description: A directory write to the Level-1 Instruction cache directory where the returned cache line was sourced from an On Chip Level-3 cache with intervention . Counter:164 Name:L1I_ONNODE_L4_SOURCED_WRITES Description: A directory write to the Level-1 Instruction cache directory where the returned cache line was sourced from an On-Node Level-4 cache . Counter:165 Name:L1I_ONNODE_L3_SOURCED_WRITES_IV Description: A directory write to the Level-1 Instruction cache directory where the returned cache line was sourced from an On-Node Level-3 cache with intervention . Counter:166 Name:L1I_ONNODE_L3_SOURCED_WRITES Description: A directory write to the Level-1 Instruction cache directory where the returned cache line was sourced from an On-Node Level-3 cache without intervention . Counter:167 Name:L1I_ONDRAWER_L4_SOURCED_WRITES Description: A directory write to the Level-1 Instruction cache directory where the returned cache line was sourced from an On-Drawer Level-4 cache . Counter:168 Name:L1I_ONDRAWER_L3_SOURCED_WRITES_IV Description: A directory write to the Level-1 Instruction cache directory where the returned cache line was sourced from an On-Drawer Level-3 cache with intervention . Counter:169 Name:L1I_ONDRAWER_L3_SOURCED_WRITES Description: A directory write to the Level-1 Instruction cache directory where the returned cache line was sourced from an On-Drawer Level-3 cache without intervention . Counter:170 Name:L1I_OFFDRAWER_SCOL_L4_SOURCED_WRITES Description: A directory write to the Level-1 Instruction cache directory where the returned cache line was sourced from an Off-Drawer Same-Column Level-4 cache . Counter:171 Name:L1I_OFFDRAWER_SCOL_L3_SOURCED_WRITES_IV Description: A directory write to the Level-1 Instruction cache directory where the returned cache line was sourced from an Off-Drawer Same-Column Level-3 cache with intervention . Counter:172 Name:L1I_OFFDRAWER_SCOL_L3_SOURCED_WRITES Description: A directory write to the Level-1 Instruction cache directory where the returned cache line was sourced from an Off-Drawer Same-Column Level-3 cache without intervention . Counter:173 Name:L1I_OFFDRAWER_FCOL_L4_SOURCED_WRITES Description: A directory write to the Level-1 Instruction cache directory where the returned cache line was sourced from an Off-Drawer Far-Column Level-4 cache . Counter:174 Name:L1I_OFFDRAWER_FCOL_L3_SOURCED_WRITES_IV Description: A directory write to the Level-1 Instruction cache directory where the returned cache line was sourced from an Off-Drawer Far-Column Level-3 cache with intervention . Counter:175 Name:L1I_OFFDRAWER_FCOL_L3_SOURCED_WRITES Description: A directory write to the Level-1 Instruction cache directory where the returned cache line was sourced from an Off-Drawer Far-Column Level-3 cache without intervention . Counter:176 Name:L1I_ONNODE_MEM_SOURCED_WRITES Description: A directory write to the Level-1 Instruction cache directory where the returned cache line was sourced from On-Node memory . Counter:177 Name:L1I_ONDRAWER_MEM_SOURCED_WRITES Description: A directory write to the Level-1 Instruction cache directory where the returned cache line was sourced from On-Drawer memory . Counter:178 Name:L1I_OFFDRAWER_MEM_SOURCED_WRITES Description: A directory write to the Level-1 Instruction cache directory where the returned cache line was sourced from On-Drawer memory . Counter:179 Name:L1I_ONCHIP_MEM_SOURCED_WRITES Description: A directory write to the Level-1 Instruction cache directory where the returned cache line was sourced from On-Chip memory . Counter:218 Name:TX_NC_TABORT Description: A transaction abort has occurred in a non-constrained transactional-execution mode . Counter:219 Name:TX_C_TABORT_NO_SPECIAL Description: A transaction abort has occurred in a constrained transactional-execution mode and the CPU is not using any special logic to allow the transaction to complete . Counter:220 Name:TX_C_TABORT_SPECIAL Description: A transaction abort has occurred in a constrained transactional-execution mode and the CPU is using special logic to allow the transaction to complete . # # MT-diagnostic counter set # --------------------------------------------------------------------- Counter:448 Name:MT_DIAG_CYCLES_ONE_THR_ACTIVE Description: Cycle count with one thread active . Counter:449 Name:MT_DIAG_CYCLES_TWO_THR_ACTIVE Description: Cycle count with two threads active . s390-tools-2.3.0/cpumf/data/cpum-cf-extended-z14.ctr000066400000000000000000000245261323410431100216520ustar00rootroot00000000000000# Counter decriptions for the # IBM z14 extended counter and MT-diagnostic counter set # # Notes for transactional-execution mode symbolic names: # TX .. transactional-execution mode # NC .. nonconstrained # C .. constrained # # Undefined counters in the extended counter set: # 142 # 158-161 # 176-223 # 227-231 # 233-242 # 246-255 # Undefined counters in the MT-diagnostic counter set: # 450-495 # # # Extended Counter Set # --------------------------------------------------------------------- Counter:128 Name:L1D_WRITES_RO_EXCL A directory write to the Level-1 Data cache where the line was originally in a Read-Only state in the cache but has been updated to be in the Exclusive state that allows stores to the cache line . Counter:129 Name:DTLB2_WRITES Description: A translation has been written into The Translation Lookaside Buffer 2 (TLB2) and the request was made by the data cache . Counter:130 Name:DTLB2_MISSES Description: A TLB2 miss is in progress for a request made by the data cache. Incremented by one for every TLB2 miss in progress for the Level-1 Data cache on this cycle . Counter:131 Name:DTLB2_HPAGE_WRITES Description: A translation entry was written into the Combined Region and Segment Table Entry array in the Level-2 TLB for a one-megabyte page or a Last Host Translation was done . Counter:132 Name:DTLB2_GPAGE_WRITES Description: A translation entry for a two-gigabyte page was written into the Level-2 TLB . Counter:133 Name:L1D_L2D_SOURCED_WRITES Description: A directory write to the Level-1 Data cache directory where the returned cache line was sourced from the Level-2 Data cache . Counter:134 Name:ITLB2_WRITES Description: A translation entry has been written into the Translation Lookaside Buffer 2 (TLB2) and the request was made by the instruction cache . Counter:135 Name:ITLB2_MISSES Description: A TLB2 miss is in progress for a request made by the instruction cache. Incremented by one for every TLB2 miss in progress for the Level-1 Instruction cache in a cycle . Counter:136 Name:L1I_L2I_SOURCED_WRITES Description: A directory write to the Level-1 Instruction cache directory where the returned cache line was sourced from the Level-2 Instruction cache . Counter:137 Name:TLB2_PTE_WRITES Description: A translation entry was written into the Page Table Entry array in the Level-2 TLB . Counter:138 Name:TLB2_CRSTE_WRITES Description: Translation entries were written into the Combined Region and Segment Table Entry array and the Page Table Entry array in the Level-2 TLB . Counter:139 Name:TLB2_ENGINES_BUSY Description: The number of Level-2 TLB translation engines busy in a cycle . Counter:140 Name:TX_C_TEND Description: A TEND instruction has completed in a constrained transactional-execution mode . Counter:141 Name:TX_NC_TEND Description: A TEND instruction has completed in a non-constrained transactional-execution mode . Counter:143 Name:L1C_TLB2_MISSES Description: Increments by one for any cycle where a level-1 cache or level-2 TLB miss is in progress . Counter:144 Name:L1D_ONCHIP_L3_SOURCED_WRITES Description: A directory write to the Level-1 Data cache directory where the returned cache line was sourced from an On-Chip Level-3 cache without intervention . Counter:145 Name:L1D_ONCHIP_MEMORY_SOURCED_WRITES Description: A directory write to the Level-1 Data cache directory where the returned cache line was sourced from On-Chip memory . Counter:146 Name:L1D_ONCHIP_L3_SOURCED_WRITES_IV Description: A directory write to the Level-1 Data cache directory where the returned cache line was sourced from an On-Chip Level-3 cache with intervention . Counter:147 Name:L1D_ONCLUSTER_L3_SOURCED_WRITES Description: A directory write to the Level-1 Data cache directory where the returned cache line was sourced from On-Cluster Level-3 cache withountervention . Counter:148 Name:L1D_ONCLUSTER_MEMORY_SOURCED_WRITES Description: A directory write to the Level-1 Data cache directory where the returned cache line was sourced from an On-Cluster memory . Counter:149 Name:L1D_ONCLUSTER_L3_SOURCED_WRITES_IV Description: A directory write to the Level-1 Data cache directory where the returned cache line was sourced from an On-Cluster Level-3 cache with intervention . Counter:150 Name:L1D_OFFCLUSTER_L3_SOURCED_WRITES Description: A directory write to the Level-1 Data cache directory where the returned cache line was sourced from an Off-Cluster Level-3 cache without intervention . Counter:151 Name:L1D_OFFCLUSTER_MEMORY_SOURCED_WRITES Description: A directory write to the Level-1 Data cache directory where the returned cache line was sourced from Off-Cluster memory . Counter:152 Name:L1D_OFFCLUSTER_L3_SOURCED_WRITES_IV Description: A directory write to the Level-1 Data cache directory where the returned cache line was sourced from an Off-Cluster Level-3 cache with intervention . Counter:153 Name:L1D_OFFDRAWER_L3_SOURCED_WRITES Description: A directory write to the Level-1 Data cache directory where the returned cache line was sourced from an Off-Drawer Level-3 cache without intervention . Counter:154 Name:L1D_OFFDRAWER_MEMORY_SOURCED_WRITES Description: A directory write to the Level-1 Data cache directory where the returned cache line was sourced from Off-Drawer memory . Counter:155 Name:L1D_OFFDRAWER_L3_SOURCED_WRITES_IV Description: A directory write to the Level-1 Data cache directory where the returned cache line was sourced from an Off-Drawer Level-3 cache with intervention . Counter:156 Name:L1D_ONDRAWER_L4_SOURCED_WRITES Description: A directory write to the Level-1 Data cache directory where the returned cache line was sourced from On-Drawer Level-4 cache . Counter:157 Name:L1D_OFFDRAWER_L4_SOURCED_WRITES Description: A directory write to the Level-1 Data cache directory where the returned cache line was sourced from Off-Drawer Level-4 cache . Counter:158 Name:L1D_ONCHIP_L3_SOURCED_WRITES_RO Description: A directory write to the Level-1 Data cache directory where the returned cache line was sourced from On-Chip L3 but a read-only invalidate was done to remove other copies of the cache line . Counter:162 Name:L1I_ONCHIP_L3_SOURCED_WRITES Description: A directory write to the Level-1 Instruction cache directory where the returned cache ine was sourced from an On-Chip Level-3 cache without intervention . Counter:163 Name:L1I_ONCHIP_MEMORY_SOURCED_WRITES Description: A directory write to the Level-1 Instruction cache directory where the returned cache ine was sourced from On-Chip memory . Counter:164 Name:L1I_ONCHIP_L3_SOURCED_WRITES_IV Description: A directory write to the Level-1 Instruction cache directory where the returned cache ine was sourced from an On-Chip Level-3 cache with intervention . Counter:165 Name:L1I_ONCLUSTER_L3_SOURCED_WRITES Description: A directory write to the Level-1 Instruction cache directory where the returned cache line was sourced from an On-Cluster Level-3 cache without intervention . Counter:166 Name:L1I_ONCLUSTER_MEMORY_SOURCED_WRITES Description: A directory write to the Level-1 Instruction cache directory where the returned cache line was sourced from an On-Cluster memory . Counter:167 Name:L1I_ONCLUSTER_L3_SOURCED_WRITES_IV Description: A directory write to the Level-1 Instruction cache directory where the returned cache line was sourced from On-Cluster Level-3 cache with intervention . Counter:168 Name:L1I_OFFCLUSTER_L3_SOURCED_WRITES Description: A directory write to the Level-1 Instruction cache directory where the returned cache line was sourced from an Off-Cluster Level-3 cache without intervention . Counter:169 Name:L1I_OFFCLUSTER_MEMORY_SOURCED_WRITES Description: A directory write to the Level-1 Instruction cache directory where the returned cache line was sourced from Off-Cluster memory . Counter:170 Name:L1I_OFFCLUSTER_L3_SOURCED_WRITES_IV Description: A directory write to the Level-1 Instruction cache directory where the returned cache line was sourced from an Off-Cluster Level-3 cache with intervention . Counter:171 Name:L1I_OFFDRAWER_L3_SOURCED_WRITES Description: A directory write to the Level-1 Instruction cache directory where the returned cache line was sourced from an Off-Drawer Level-3 cache without intervention . Counter:172 Name:L1I_OFFDRAWER_MEMORY_SOURCED_WRITES Description: A directory write to the Level-1 Instruction cache directory where the returned cache line was sourced from Off-Drawer memory . Counter:173 Name:L1I_OFFDRAWER_L3_SOURCED_WRITES_IV Description: A directory write to the Level-1 Instruction cache directory where the returned cache line was sourced from an Off-Drawer Level-3 cache with intervention . Counter:174 Name:L1I_ONDRAWER_L4_SOURCED_WRITES Description: A directory write to the Level-1 Instruction cache directory where the returned cache line was sourced from On-Drawer Level-4 cache . Counter:175 Name:L1I_OFFDRAWER_L4_SOURCED_WRITES Description: A directory write to the Level-1 Instruction cache directory where the returned cache line was sourced from Off-Drawer Level-4 cache . Counter:224 Name:BCD_DFP_EXECUTION_SLOTS Description: Count of floating point execution slots used for finished Binary Coded Decimal to Decimal Floating Point conversions. Instructions: CDZT, CXZT, CZDT, CZXT . Counter:225 Name:VX_BCD_EXECUTION_SLOTS Description: Count of floating point execution slots used for finished vector arithmetic Binary Coded Decimal instructions. Instructions: VAP, VSP, VMPVMSP, VDP, VSDP, VRP, VLIP, VSRP, VPSOPVCP, VTP, VPKZ, VUPKZ, VCVB, VCVBG, VCVDVCVDG . Counter:226 Name:DECIMAL_INSTRUCTIONS Description: Decimal instructions dispatched. Instructions: CVB, CVD, AP, CP, DP, ED, EDMK, MP, SRP, SP, ZAP . Counter:232 Name:LAST_HOST_TRANSLATIONS Description: Last Host Translation done . Counter:243 Name:TX_NC_TABORT Description: A transaction abort has occurred in a non-constrained transactional-execution mode . Counter:244 Name:TX_C_TABORT_NO_SPECIAL Description: A transaction abort has occurred in a constrained transactional-execution mode and the CPU is not using any special logic to allow the transaction to complete . Counter:245 Name:TX_C_TABORT_SPECIAL Description: A transaction abort has occurred in a constrained transactional-execution mode and the CPU is using special logic to allow the transaction to complete . # # MT-diagnostic counter set # --------------------------------------------------------------------- Counter:448 Name:MT_DIAG_CYCLES_ONE_THR_ACTIVE Description: Cycle count with one thread active . Counter:449 Name:MT_DIAG_CYCLES_TWO_THR_ACTIVE Description: Cycle count with two threads active . s390-tools-2.3.0/cpumf/data/cpum-cf-extended-z196.ctr000066400000000000000000000101741323410431100217370ustar00rootroot00000000000000Counter: 128 Name:L1D_L2_SOURCED_WRITES Description: A directory write to the Level-1 D-Cache directory where the returned cache line was sourced from the Level-2 cache . Counter: 129 Name:L1I_L2_SOURCED_WRITES Description: A directory write to the Level-1 I-Cache directory where the returned cache line was sourced from the Level-2 cache . Counter: 130 Name:DTLB1_MISSES Description: Level-1 Data TLB miss in progress. Incremented by one for every cycle a DTLB1 miss is in progress. . Counter: 131 Name:ITLB1_MISSES Description: Level-1 Instruction TLB miss in progress. Incremented by one for every cycle a ITLB1 miss is in progress. . Counter: 133 Name:L2C_STORES_SENT Description: Incremented by one for every store sent to Level-2 cache . Counter: 134 Name:L1D_OFFBOOK_L3_SOURCED_WRITES Description: A directory write to the Level-1 D-Cache directory where the returned cache line was sourced from an Off Book Level-3 cache . Counter: 135 Name:L1D_ONBOOK_L4_SOURCED_WRITES Description: A directory write to the Level-1 D-Cache directory where the returned cache line was sourced from an On Book Level-4 cache . Counter: 136 Name:L1I_ONBOOK_L4_SOURCED_WRITES Description: A directory write to the Level-1 I-Cache directory where the returned cache line was sourced from an On Book Level-4 cache . Counter: 137 Name:L1D_RO_EXCL_WRITES Description: A directory write to the Level-1 D-Cache where the line was originally in a Read-Only state in the cache but has been updated to be in the Exclusive state that allows stores to the cache line . Counter: 138 Name:L1D_OFFBOOK_L4_SOURCED_WRITES Description: A directory write to the Level-1 D-Cache directory where the returned cache line was sourced from an Off Book Level-4 cache . Counter: 139 Name:L1I_OFFBOOK_L4_SOURCED_WRITES Description: A directory write to the Level-1 I-Cache directory where the returned cache line was sourced from an Off Book Level-4 cache . Counter: 140 Name:DTLB1_HPAGE_WRITES Description: A translation entry has been written to the Level-1 Data Translation Lookaside Buffer for a one-megabyte page . Counter: 141 Name:L1D_LMEM_SOURCED_WRITES Description: A directory write to the Level-1 D-Cache where the installed cache line was sourced from memory that is attached to the same book as the Data cache (Local Memory) . Counter: 142 Name:L1I_LMEM_SOURCED_WRITES Description: A directory write to the Level-1 I-Cache where the installed cache line was sourced from memory that is attached to the same book as the Instruction cache (Local Memory) . Counter: 143 Name:L1I_OFFBOOK_L3_SOURCED_WRITES Description: A directory write to the Level-1 I-Cache directory where the returned cache line was sourced from an Off Book Level-3 cache . Counter: 144 Name:DTLB1_WRITES Description: A translation entry has been written to the Level-1 Data Translation Lookaside Buffer . Counter: 145 Name:ITLB1_WRITES Description: A translation entry has been written to the Level-1 Instruction Translation Lookaside Buffer . Counter: 146 Name:TLB2_PTE_WRITES Description: A translation entry has been written to the Level-2 TLB Page Table Entry arrays . Counter: 147 Name:TLB2_CRSTE_HPAGE_WRITES Description: A translation entry has been written to the Level-2 TLB Common Region Segment Table Entry arrays for a one-megabyte large page translation . Counter: 148 Name:TLB2_CRSTE_WRITES Description: A translation entry has been written to the Level-2 TLB Common Region Segment Table Entry arrays . Counter: 150 Name:L1D_ONCHIP_L3_SOURCED_WRITES Description: A directory write to the Level-1 D-Cache directory where the returned cache line was sourced from an On Chip Level-3 cache . Counter: 152 Name:L1D_OFFCHIP_L3_SOURCED_WRITES Description: A directory write to the Level-1 D-Cache directory where the returned cache line was sourced from an Off Chip/On Book Level-3 cache . Counter: 153 Name:L1I_ONCHIP_L3_SOURCED_WRITES Description: A directory write to the Level-1 I-Cache directory where the returned cache line was sourced from an On Chip Level-3 cache . Counter: 155 Name:L1I_OFFCHIP_L3_SOURCED_WRITES Description: A directory write to the Level-1 I-Cache directory where the returned cache line was sourced from an Off Chip/On Book Level-3 cache . s390-tools-2.3.0/cpumf/data/cpum-cf-extended-zEC12.ctr000066400000000000000000000152211323410431100220500ustar00rootroot00000000000000Counter: 128 Name:DTLB1_MISSES Description: Level-1 Data TLB miss in progress. Incremented by one for every cycle a DTLB1 miss is in progress. . Counter:129 Name:ITLB1_MISSES Description: Level-1 Instruction TLB miss in progress. Incremented by one for every cycle a ITLB1 miss is in progress. . Counter:130 Name:L1D_L2I_SOURCED_WRITES Description: A directory write to the Level-1 Data cache directory where the returned cache line was sourced from the Level-2 Instruction cache . Counter:131 Name:L1I_L2I_SOURCED_WRITES Description: A directory write to the Level-1 Instruction cache directory where the returned cache line was sourced from the Level-2 Instruction cache . Counter:132 Name:L1D_L2D_SOURCED_WRITES Description: A directory write to the Level-1 Data cache directory where the returned cache line was sourced from the Level-2 Data cache . Counter:133 Name:DTLB1_WRITES Description: A translation entry has been written to the Level-1 Data Translation Lookaside Buffer . Counter:135 Name:L1D_LMEM_SOURCED_WRITES Description: A directory write to the Level-1 Data cache where the installed cache line was sourced from memory that is attached to the same book as the Data cache (Local Memory) . Counter:137 Name:L1I_LMEM_SOURCED_WRITES Description: A directory write to the Level-1 Instruction cache where the installed cache line was sourced from memory that is attached to the same book as the Instruction cache (Local Memory) . Counter:138 Name:L1D_RO_EXCL_WRITES Description: A directory write to the Level-1 D-Cache where the line was originally in a Read-Only state in the cache but has been updated to be in the Exclusive state that allows stores to the cache line . Counter:139 Name:DTLB1_HPAGE_WRITES Description: A translation entry has been written to the Level-1 Data Translation Lookaside Buffer for a one-megabyte page . Counter:140 Name:ITLB1_WRITES Description: A translation entry has been written to the Level-1 Instruction Translation Lookaside Buffer . Counter:141 Name:TLB2_PTE_WRITES Description: A translation entry has been written to the Level-2 TLB Page Table Entry arrays . Counter:142 Name:TLB2_CRSTE_HPAGE_WRITES Description: A translation entry has been written to the Level-2 TLB Common Region Segment Table Entry arrays for a one-megabyte large page translation . Counter:143 Name:TLB2_CRSTE_WRITES Description: A translation entry has been written to the Level-2 TLB Common Region Segment Table Entry arrays . Counter:144 Name:L1D_ONCHIP_L3_SOURCED_WRITES Description: A directory write to the Level-1 Data cache directory where the returned cache line was sourced from an On Chip Level-3 cache without intervention . Counter:145 Name:L1D_OFFCHIP_L3_SOURCED_WRITES Description: A directory write to the Level-1 Data cache directory where the returned cache line was sourced from an Off Chip/On Book Level-3 cache without intervention . Counter:146 Name:L1D_OFFBOOK_L3_SOURCED_WRITES Description: A directory write to the Level-1 Data cache directory where the returned cache line was sourced from an Off Book Level-3 cache without intervention . Counter:147 Name:L1D_ONBOOK_L4_SOURCED_WRITES Description: A directory write to the Level-1 Data cache directory where the returned cache line was sourced from an On Book Level-4 cache . Counter:148 Name:L1D_OFFBOOK_L4_SOURCED_WRITES Description: A directory write to the Level-1 Data cache directory where the returned cache line was sourced from an Off Book Level-4 cache . # # Notes for the transactional-execution mode notations: # TX .. transactional-execution mode # NC .. nonconstrained # C .. constrained # Counter:149 Name:TX_NC_TEND Description: A TEND instruction has completed in a nonconstrained transactional-execution mode . Counter:150 Name:L1D_ONCHIP_L3_SOURCED_WRITES_IV Description: A directory write to the Level-1 Data cache directory where the returned cache line was sourced from a On Chip Level-3 cache with intervention . Counter:151 Name:L1D_OFFCHIP_L3_SOURCED_WRITES_IV Description: A directory write to the Level-1 Data cache directory where the returned cache line was sourced from an Off Chip/On Book Level-3 cache with intervention . # XXX Remove SOURCED from L1D_OFFBOOK_L3_SOURCED_WRITES... Counter:152 Name:L1D_OFFBOOK_L3_SOURCED_WRITES_IV Description: A directory write to the Level-1 Data cache directory where the returned cache line was sourced from an Off Book Level-3 cache with intervention . Counter:153 Name:L1I_ONCHIP_L3_SOURCED_WRITES Description: A directory write to the Level-1 Instruction cache directory where the returned cache line was sourced from an On Chip Level-3 cache without intervention . Counter:154 Name:L1I_OFFCHIP_L3_SOURCED_WRITES Description: A directory write to the Level-1 Instruction cache directory where the returned cache line was sourced from an Off Chip/On Book Level-3 cache without intervention . Counter:155 Name:L1I_OFFBOOK_L3_SOURCED_WRITES Description: A directory write to the Level-1 Instruction cache directory where the returned cache line was sourced from an Off Book Level-3 cache without intervention . Counter:156 Name:L1I_ONBOOK_L4_SOURCED_WRITES Description: A directory write to the Level-1 Instruction cache directory where the returned cache line was sourced from an On Book Level-4 cache . Counter:157 Name:L1I_OFFBOOK_L4_SOURCED_WRITES Description: A directory write to the Level-1 Instruction cache directory where the returned cache line was sourced from an Off Book Level-4 cache . Counter:158 Name:TX_C_TEND Description: A TEND instruction has completed in a constrained transactional-execution mode . Counter:159 Name:L1I_ONCHIP_L3_SOURCED_WRITES_IV Description: A directory write to the Level-1 Instruction cache directory where the returned cache line was sourced from an On Chip Level-3 cache with intervention . Counter:160 Name:L1I_OFFCHIP_L3_SOURCED_WRITES_IV Description: A directory write to the Level-1 Instruction cache directory where the returned cache line was sourced from an Off Chip/On Book Level-3 cache with intervention . Counter:161 Name:L1I_OFFBOOK_L3_SOURCED_WRITES_IV Description: A directory write to the Level-1 Instruction cache directory where the returned cache line was sourced from an Off Book Level-3 cache with intervention . Counter:177 Name:TX_NC_TABORT Description: A transaction abort has occurred in a nonconstrained transactional-execution mode . Counter:178 Name:TX_C_TABORT_NO_SPECIAL Description: A transaction abort has occurred in a constrained transactional-execution mode and the CPU is not using any special logic to allow the transaction to complete . Counter:179 Name:TX_C_TABORT_SPECIAL Description: A transaction abort has occurred in a constrained transactional-execution mode and the CPU is using special logic to allow the transaction to complete . s390-tools-2.3.0/cpumf/data/cpum-cf-hw-counter.map000066400000000000000000000013721323410431100215100ustar00rootroot00000000000000# CPU-measurement facilities # # Mapping of: # 1. CPU-MF counter first/second version numbers to "generic" counter # definitions # 2. IBM z Systems hardware to respective extended counter set definitions # # { # Definition # File name # CFVN 'cfvn-1' => 'cpum-cf-cfvn-1.ctr', 'cfvn-3' => 'cpum-cf-cfvn-3.ctr', # CSVN 'csvn-generic' => 'cpum-cf-csvn-generic.ctr', # Extended counters 2097 => 'cpum-cf-extended-z10.ctr', 2098 => 'cpum-cf-extended-z10.ctr', 2817 => 'cpum-cf-extended-z196.ctr', 2818 => 'cpum-cf-extended-z196.ctr', 2827 => 'cpum-cf-extended-zEC12.ctr', 2828 => 'cpum-cf-extended-zEC12.ctr', 2964 => 'cpum-cf-extended-z13.ctr', 2965 => 'cpum-cf-extended-z13.ctr', 3906 => 'cpum-cf-extended-z14.ctr', }; s390-tools-2.3.0/cpumf/data/cpum-sf-modes.ctr000066400000000000000000000006031323410431100205530ustar00rootroot00000000000000# Perf raw event counter definitions for the # CPU-measurment sampling facility # # Basic-sampling mode Counter: 0xB0000 Name:SF_CYCLES_BASIC Description: Sample CPU cycles using basic-sampling mode . # Diagnostic-sampling mode (includes basic-sampling) Counter: 0xBD000 Name:SF_CYCLES_BASIC_DIAG Description: Sample CPU cycle using diagnostic-sampling mode (not for ordinary use) . s390-tools-2.3.0/cpumf/man/000077500000000000000000000000001323410431100152255ustar00rootroot00000000000000s390-tools-2.3.0/cpumf/man/chcpumf.8000066400000000000000000000027141323410431100167470ustar00rootroot00000000000000.\" chcpumf.8 .\" .\" .\" Copyright IBM Corp. 2014, 2017 .\" s390-tools is free software; you can redistribute it and/or modify .\" it under the terms of the MIT license. See LICENSE for details. .\" ---------------------------------------------------------------------- .TH chcpumf "8" "February 2014" "s390-tools" "CPU-MF management programs" . .ds c \fBchcpumf\fP . . .SH NAME chcpumf \- manage the CPU-measurement facilities support . . .SH SYNOPSIS .B chcpumf .RB [ \-m | \-\-min .IR num_sdb ] .RB [ \-x | \-\-max .IR num_sdb ] .br .B chcpumf .BR \-h | \-\-help .br .B chcpumf .BR \-v | \-\-version . . .SH DESCRIPTION The chcpumf program helps you to manage settings for the CPU-measurement facilities for Linux on System z. . . .SH OPTIONS .TP .BR \-m ", " \-\-min " \fInum_sdb\fP" Specifies the minimum sampling facility buffer size in sample-data-blocks (SDB). A sample-data-block consumes about 4 kilobytes. This is the initial buffer size when you start the sampling facility. . .TP .BR \-x ", " \-\-max " \fInum_sdb\fP" Specifies the maximum sampling facility buffer size in sample-data-blocks (SDB). A sample-data-block consumes about 4 kilobytes. This is the maximum size to which the buffer is dynamically adjusted during sampling. . .TP .BR \-V ", " \-\-verbose Displays verbose messages. . .TP .BR \-h ", " \-\-help Displays help information, then exits. . .TP .BR \-v ", " \-\-version Displays version information, then exits. . . .SH "SEE ALSO" .BR lscpumf (1) s390-tools-2.3.0/cpumf/man/lscpumf.1000066400000000000000000000032651323410431100167660ustar00rootroot00000000000000\" lscpumf.1 .\" .\" .\" Copyright IBM Corp. 2014, 2017 .\" s390-tools is free software; you can redistribute it and/or modify .\" it under the terms of the MIT license. See LICENSE for details. .\" ---------------------------------------------------------------------- .TH lscpumf "1" "February 2014" "s390-tools" "CPU-MF management programs" . .ds c \fBlscpumf\fP . . .SH NAME lscpumf \- display information about CPU-measurement facilities . . .SH SYNOPSIS .B lscpumf .RB [ \-i | \-\-info ] .br .B lscpumf .RB [ \-c | \-\-list\-counters ] .RB [ \-C | \-\-list\-all\-counters ] .RB [ \-s | \-\-list\-sampling\-events ] .br .B lscpumf .BR \-h | \-\-help .br .B lscpumf .BR \-v | \-\-version . . .SH DESCRIPTION The lscpumf program lists information about CPU-measurement facilities that are supported by Linux on System z. . . .SH OPTIONS .TP .BR \-i ", " \-\-info Displays detailed information about available and supported CPU-measurement facilities. . .TP .BR \-c ", " \-\-list\-counters Lists counters that are provided by the CPU-measurement facility, omitting counters for which the LPAR is not authorized. For counter measurements with the perf program, the raw event identifier is displayed. . .TP .BR \-C ", " \-\-list\-all\-counters Lists all counters that are provided by the CPU-measurement counter facility, regardless of LPAR authorization. To list only those counters for which the LPAR is authorized, use the -c option. . .TP .BR \-s ", " \-\-list\-sampling\-events Lists perf raw events that activate the sampling facility. . .TP .BR \-h ", " \-\-help Displays help information, then exits. . .TP .BR \-v ", " \-\-version Displays version information, then exits. . . .SH "SEE ALSO" .BR chcpumf (8) s390-tools-2.3.0/cpuplugd/000077500000000000000000000000001323410431100151635ustar00rootroot00000000000000s390-tools-2.3.0/cpuplugd/Makefile000066400000000000000000000010311323410431100166160ustar00rootroot00000000000000include ../common.mak all: cpuplugd LDLIBS += -lm OBJECTS = daemon.o cpu.o info.o terms.o config.o main.o getopt.o mem.o cpuplugd: $(OBJECTS) $(LINK) $(ALL_LDFLAGS) $^ $(LOADLIBES) $(LDLIBS) -o $@ clean: rm -f cpuplugd $(OBJECTS) install: all $(INSTALL) -g $(GROUP) -o $(OWNER) -m 755 cpuplugd \ $(DESTDIR)$(USRSBINDIR) $(INSTALL) -g $(GROUP) -o $(OWNER) -m 644 man/cpuplugd.8 \ $(DESTDIR)$(MANDIR)/man8 $(INSTALL) -g $(GROUP) -o $(OWNER) -m 644 man/cpuplugd.conf.5 \ $(DESTDIR)$(MANDIR)/man5 .PHONY: all install clean s390-tools-2.3.0/cpuplugd/config.c000066400000000000000000000226171323410431100166040ustar00rootroot00000000000000/* * cpuplugd - Linux for System z Hotplug Daemon * * Config file parsing * * Copyright IBM Corp. 2007, 2017 * * 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 "cpuplugd.h" /* * Return the value of a variable which parse_config() found within the * configuration file. Use only for values valid >= 0, because -1 is returned * in error case. */ static long parse_positive_value(char *ptr) { long value = 0; unsigned int i; if (ptr == NULL) return -1; for (i = 0; i < strlen(ptr); i++) { if (isdigit(ptr[i]) == 0) return -1; } sscanf(ptr, "%ld", &value); return value; } char *get_var_rvalue(char *var_name) { char tmp_name[MAX_VARNAME + 3]; /* +3 for '\0', '=' and '\n' */ unsigned int tmp_length; char *rvalue; tmp_name[0] = '\n'; strncpy(&tmp_name[1], var_name, MAX_VARNAME + 1); /* +1 for '\0' */ tmp_length = strlen(tmp_name); tmp_name[tmp_length] = '='; tmp_name[tmp_length + 1] = '\0'; rvalue = strstr(varinfo, tmp_name); if (rvalue == NULL) return NULL; rvalue += strlen(tmp_name); return rvalue; } static void add_var(char *name, char *rvalue) { size_t offset, size; unsigned int i; if (get_var_rvalue(name)) cpuplugd_exit("Variable defined twice: %s\n", name); for (i = 0; i < sym_names_count; i++) { if (strncmp(name, sym_names[i].name, MAX(strlen(sym_names[i].name), strlen(name))) != 0) continue; cpuplugd_exit("Cannot use (pre-defined) variable name: %s\n", name); } offset = strlen(varinfo); /* +3 because of extra '=', '\n' and '\0' */ size = offset + strlen(name) + strlen(rvalue) + 3; if (size > varinfo_size) //TODO realloc? cpuplugd_exit("buffer for variables too small: need %ld, " "have %ld (bytes)\n", size, varinfo_size); size -= offset; snprintf(&varinfo[offset], size, "%s=%s\n", name, rvalue); return; } static int check_term(char *symbol, char *name, char *rvalue, struct term **term) { if (!strncasecmp(name, symbol, strlen(symbol))) { cpuplugd_debug("found the following rule: %s = %s\n", name, rvalue); *term = parse_term(&rvalue, OP_PRIO_NONE); if (rvalue[0] == '\0') return 1; cpuplugd_exit("parsing error at %s, position: %s\n", symbol, rvalue); } return 0; } static int check_value(char *symbol, char *name, char *rvalue, long *value) { if (!strncasecmp(name, symbol, strlen(symbol))) { *value = parse_positive_value(rvalue); cpuplugd_debug("found %s value: %ld\n", symbol, *value); if (*value >= 0) return 1; cpuplugd_exit("parsing error at update\n"); } return 0; } /* * Parse a single line of the configuration file */ static void parse_configline(char *line) { char *match, *name, *rvalue, *start, *stop; int i, j; size_t len; char temp[strlen(line) + 1]; if (line[0] == '#') return; for (i = j = 0; line[i] != 0; i++) /* Remove whitespace. */ if (!isblank(line[i]) && !isspace(line[i])) temp[j++] = line[i]; temp[j] = '\0'; match = strchr(temp, '='); if (match == NULL) return; *match = '\0'; /* Separate name and right hand value */ name = temp; /* left side of = */ rvalue = match + 1; /* right side of = */ /* * remove the double quotes * example: CPU_MIN="2" */ start = strchr(rvalue, '"'); /* points to first " */ stop = strrchr(rvalue, '"'); /* points to last " */ len = stop - start; if (start != NULL && stop != NULL && len > 0) { rvalue[len] = '\0'; rvalue = rvalue + 1; } else cpuplugd_exit("the configuration file has syntax " "errors at %s, position: %s\n", name, rvalue); if (check_term("hotplug", name, rvalue, &cfg.hotplug)) return; if (check_term("hotunplug", name, rvalue, &cfg.hotunplug)) return; if (check_term("memplug", name, rvalue, &cfg.memplug)) return; if (check_term("memunplug", name, rvalue, &cfg.memunplug)) return; if (check_term("cmm_inc", name, rvalue, &cfg.cmm_inc)) return; if (check_term("cmm_dec", name, rvalue, &cfg.cmm_dec)) return; if (check_value("update", name, rvalue, &cfg.update)) { if (cfg.update > 0) return; cpuplugd_exit("update must be > 0\n"); } if (check_value("cpu_min", name, rvalue, &cfg.cpu_min)) { if (cfg.cpu_min > 0) return; cpuplugd_exit("cpu_min must be > 0\n"); } if (check_value("cpu_max", name, rvalue, &cfg.cpu_max)) { if (cfg.cpu_max == 0) /* if cpu_max is 0, we use the overall number of cpus */ cfg.cpu_max = get_numcpus(); return; } if (check_value("cmm_min", name, rvalue, &cfg.cmm_min)) return; if (check_value("cmm_max", name, rvalue, &cfg.cmm_max)) return; cpuplugd_debug("found the following variable: %s = %s\n", name, rvalue); if (strlen(name) > MAX_VARNAME) cpuplugd_exit("Variable name too long (max. length is " "%i chars): %s\n", MAX_VARNAME, name); add_var(name, rvalue); } /* * Function used to parse the min and max values at the beginning of the * configuration file as well as the hotplug and hotunplug rules. */ void parse_configfile(char *file) { char linebuffer[MAX_LINESIZE + 2]; /* current line incl. \n and \0 */ char *linep_offset; FILE *filp; filp = fopen(file, "r"); if (!filp) cpuplugd_exit("Opening configuration file failed: %s\n", strerror(errno)); while (fgets(linebuffer, sizeof(linebuffer), filp) != NULL) { if (!(linep_offset = strchr(linebuffer, '\n'))) cpuplugd_exit("Line is too long (max. length is %i " "characters): %s\n", MAX_LINESIZE, linebuffer); parse_configline(linebuffer); } } /* * Check if the required settings are found in the configuration file. * "Autodetect" if cpu and/or memory hotplug configuration entries * where specified */ void check_config() { int cpuid; int lpar_status; lpar_status = check_lpar(); if (cfg.update < 0) cpuplugd_exit("No valid update interval specified.\n"); if (cfg.cpu_max < cfg.cpu_min && cfg.cpu_max != 0) cpuplugd_exit("cpu_max below cpu_min, aborting.\n"); if (cfg.cpu_max < 0 || cfg.cpu_min < 0 || cfg.hotplug == NULL || cfg.hotunplug == NULL) { cpuplugd_error("No valid CPU hotplug configuration " "detected.\n"); cpu = 0; } else { cpu = 1; cpuplugd_debug("Valid CPU hotplug configuration detected.\n"); } if (cfg.cmm_max < 0 || cfg.cmm_min < 0 || cfg.memplug == NULL || cfg.memunplug == NULL || cfg.cmm_inc == NULL || cfg.cmm_max < cfg.cmm_min) { cpuplugd_error("No valid memory hotplug configuration " "detected.\n"); memory = 0; } else { memory = 1; /* * check if all the necessary files exit below /proc */ if (check_cmmfiles() != 0 && lpar_status == 0) { cpuplugd_info("Can not open /proc/sys/vm/cmm_pages. " "The memory hotplug function will be " "disabled.\n"); memory = 0; } if (memory == 1 && lpar_status == 0) cpuplugd_debug("Valid memory hotplug configuration " "detected.\n"); if (memory == 1 && lpar_status == 1) { cpuplugd_debug("Valid memory hotplug configuration " "detected inside LPAR. " "The memory hotplug function will be " "disabled. \n"); memory = 0; } } if (memory == 0 && cpu == 0) cpuplugd_exit("Exiting, because neither a valid cpu nor a val" "id memory hotplug configuration was found.\n"); /* * Save the number of online cpus and the cmm_pagesize at startup, * so that we can enable exactly the same amount when the daemon ends */ if (cpu) { num_cpu_start = get_num_online_cpus(); cpuplugd_debug("Daemon started with %d active cpus.\n", num_cpu_start); /* * Check that the initial number of cpus is not below the * minimum */ if (num_cpu_start < cfg.cpu_min && get_numcpus() >= cfg.cpu_min) { cpuplugd_debug("The number of online cpus is below " "the minimum and will be increased.\n"); cpuid = 0; while (get_num_online_cpus() < cfg.cpu_min && cpuid < get_numcpus()) { if (is_online(cpuid) == 1) { cpuid++; continue; } cpuplugd_debug("cpu with id %d is currently offline " "and will be enabled\n", cpuid); hotplug(cpuid); cpuid++; } } if (get_num_online_cpus() > cfg.cpu_max) { cpuplugd_debug("The number of online cpus is above the maximum" " and will be decreased.\n"); cpuid = 0; while (get_num_online_cpus() > cfg.cpu_max && cpuid < get_numcpus()) { if (is_online(cpuid) != 1) { cpuid++; continue; } cpuplugd_debug("cpu with id %d is currently online " "and will be disabled\n", cpuid); hotunplug(cpuid); cpuid++; } } if (cfg.cpu_min > get_numcpus()) /* * This check only works if nobody used the * additional_cpus in the boot parameter section */ cpuplugd_exit("The minimum amount of cpus is above " "the number of available cpus.\n" "Detected %d available cpus\n", get_numcpus()); if (get_num_online_cpus() < cfg.cpu_min) cpuplugd_exit("Failed to set the number of online " "cpus to the minimum. Aborting.\n"); } if (memory == 1) { /* * Check that the initial value of cmm_pages is not below * cmm_min or above cmm_max */ cmm_pagesize_start = get_cmmpages_size(); if (cmm_pagesize_start < cfg.cmm_min) { cpuplugd_debug("cmm_pages is below minimum and will " "be increased.\n"); set_cmm_pages(cfg.cmm_min); } if (cmm_pagesize_start > cfg.cmm_max) { cpuplugd_debug("cmm_pages is above the maximum and will" " be decreased.\n"); set_cmm_pages(cfg.cmm_max); } } } s390-tools-2.3.0/cpuplugd/cpu.c000066400000000000000000000112111323410431100161120ustar00rootroot00000000000000/* * cpuplugd - Linux for System z Hotplug Daemon * * CPU hotplug functions * * Copyright IBM Corp. 2007, 2017 * * 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 #include "cpuplugd.h" /* * Return overall number of available cpus. This does not necessarily * mean that those are currently online */ int get_numcpus() { int i; char path[PATH_MAX]; int number = 0; for (i = 0; ; i++) { /* check whether file exists and is readable */ sprintf(path, "/sys/devices/system/cpu/cpu%d/online", i); if (access(path, R_OK) == 0) number++; else break; } return number; } /* * Return number of online cpus */ int get_num_online_cpus() { FILE *filp; int i; char path[PATH_MAX]; int status = 0; int value_of_onlinefile, rc; for (i = 0; i <= get_numcpus(); i++) { /* check wether file exists and is readable */ sprintf(path, "/sys/devices/system/cpu/cpu%d/online", i); if (access(path, R_OK) != 0) continue; filp = fopen(path, "r"); if (!filp) cpuplugd_exit("Cannot open cpu online file: " "%s\n", strerror(errno)); else { rc = fscanf(filp, "%d", &value_of_onlinefile); if (rc != 1) cpuplugd_exit("Cannot read cpu online file: " "%s\n", strerror(errno)); if (value_of_onlinefile == 1) status++; } fclose(filp); } return status; } /* * Enable a certain cpu */ int hotplug(int cpuid) { FILE *filp; char path[PATH_MAX]; int status, rc; sprintf(path, "/sys/devices/system/cpu/cpu%d/online", cpuid); if (access(path, W_OK) == 0) { filp = fopen(path, "w"); if (!filp) cpuplugd_exit("Cannot open cpu online file: %s\n", strerror(errno)); fprintf(filp, "1"); fclose(filp); /* * check if the attempt to enable the cpus really worked */ filp = fopen(path, "r"); rc = fscanf(filp, "%d", &status); if (rc != 1) cpuplugd_exit("Cannot open cpu online file: %s\n", strerror(errno)); fclose(filp); if (status == 1) { cpuplugd_debug("cpu with id %d enabled\n", cpuid); return 1; } else { cpuplugd_debug("failed to enable cpu with id %d\n", cpuid); return -1; } } else { cpuplugd_error("hotplugging cpu with id %d failed\n", cpuid); return -1; } return -1; } /* * Disable a certain cpu */ int hotunplug(int cpuid) { FILE *filp; int state, rc; int retval = -1; char path[PATH_MAX]; state = -1; sprintf(path, "/sys/devices/system/cpu/cpu%d/online", cpuid); if (access(path, W_OK) == 0) { filp = fopen(path, "w"); fprintf(filp, "0"); fclose(filp); /* * Check if the attempt to enable the cpus really worked */ filp = fopen(path, "r"); rc = fscanf(filp, "%d", &state); if (rc != 1) cpuplugd_error("Failed to disable cpu with id %d\n", cpuid); fclose(filp); if (state == 0) return 1; } else { cpuplugd_error("unplugging cpu with id %d failed\n", cpuid); } return retval; } /* * Check if a certain cpu is currently online */ int is_online(int cpuid) { FILE *filp; int state; int retval, rc; char path[PATH_MAX]; retval = -1; sprintf(path, "/sys/devices/system/cpu/cpu%d/online", cpuid); if (access(path, R_OK) == 0) { filp = fopen(path, "r"); rc = fscanf(filp, "%d", &state); if (rc == 1) { if (state == 1) retval = 1; if (state == 0) retval = 0; fclose(filp); } } return retval; } /* * Cleanup method. If the daemon is stopped, we (re)activate all cpus */ void reactivate_cpus() { /* * Only enable the number of cpus which where * available at daemon startup time */ int cpuid, nc; cpuid = 0; /* suppress verbose messages on exit */ debug = 0; /* * We check for num_cpu_start != 0 because we might want to * clean up, before we queried for the number on cpus at * startup */ if (num_cpu_start == 0) return; while (get_num_online_cpus() != num_cpu_start && cpuid < get_numcpus()) { nc = get_num_online_cpus(); if (nc == num_cpu_start) return; if (nc > num_cpu_start && is_online(cpuid) == 1) hotunplug(cpuid); if (nc < num_cpu_start && is_online(cpuid) == 0) hotplug(cpuid); cpuid++; } } /* * In kernels > 2.6.24 cpus can be deconfigured. The following functions is used * to check if a certain cpus is in a deconfigured state. */ int cpu_is_configured(int cpuid) { FILE *filp; int retval, state, rc; char path[4096]; retval = -1; sprintf(path, "/sys/devices/system/cpu/cpu%d/configure", cpuid); if (access(path, R_OK) == 0) { filp = fopen(path, "r"); rc = fscanf(filp, "%d", &state); if (rc == 1) { if (state == 1) retval = 1; if (state == 0) retval = 0; fclose(filp); } } return retval; } s390-tools-2.3.0/cpuplugd/cpuplugd.h000066400000000000000000000117241323410431100171640ustar00rootroot00000000000000/* * cpuplugd - Linux for System z Hotplug Daemon * * Header file * * Copyright IBM Corp. 2007, 2017 * * s390-tools is free software; you can redistribute it and/or modify * it under the terms of the MIT license. See LICENSE for details. */ #ifndef __USE_ISOC99 #define __USE_ISOC99 #endif #include #include #include #include #include #include #include #include #include #include "lib/util_base.h" #include "lib/zt_common.h" #define NAME "cpuplugd" #define MAX_HISTORY 100 #define PIDFILE "/var/run/cpuplugd.pid" #define LOCKFILE "/var/lock/cpuplugd.lock" #define PROCINFO_LINE 512 #define CPUSTAT_SIZE 1024 #define VARINFO_SIZE 4096 #define MAX_VARNAME 128 #define MAX_LINESIZE 2048 #define CPUSTATS 10 /* * Precedence of C operators * full list: * http://www.imada.sdu.dk/~svalle/ * courses/dm14-2005/mirror/c/_7193_tabular246.gif * * () * +- * * / * < > * & * | */ enum op_prio { OP_PRIO_NONE, OP_PRIO_OR, OP_PRIO_AND, /* greater and lower */ OP_PRIO_CMP, OP_PRIO_ADD, OP_PRIO_MULT }; enum operation { /* Leaf operators */ OP_SYMBOL_LOADAVG, OP_SYMBOL_RUNABLE, OP_SYMBOL_CPUS, OP_SYMBOL_USER, OP_SYMBOL_NICE, OP_SYMBOL_SYSTEM, OP_SYMBOL_IDLE, OP_SYMBOL_IOWAIT, OP_SYMBOL_IRQ, OP_SYMBOL_SOFTIRQ, OP_SYMBOL_STEAL, OP_SYMBOL_GUEST, OP_SYMBOL_GUEST_NICE, OP_SYMBOL_APCR, OP_SYMBOL_SWAPRATE, OP_SYMBOL_FREEMEM, OP_SYMBOL_MEMINFO, OP_SYMBOL_VMSTAT, OP_SYMBOL_CPUSTAT, OP_SYMBOL_TIME, OP_CONST, /* Unary operators */ OP_NEG, OP_NOT, /* Binary operators */ OP_AND, OP_OR, OP_GREATER, OP_LESSER, OP_PLUS, OP_MINUS, OP_MULT, OP_DIV, /* ... */ /*Variables which are eligible within rules*/ VAR_LOAD, /* loadaverage */ VAR_RUN, /* number of runnable processes */ VAR_ONLINE /* number of online cpus */ }; struct symbols { double loadavg; double runnable_proc; double onumcpus; double idle; double freemem; double apcr; double swaprate; double user; double nice; double system; double iowait; double irq; double softirq; double steal; double guest; double guest_nice; }; struct term { enum operation op; double value; struct term *left, *right; char *proc_name; unsigned int index; }; /* * List of argurments taken fromt the configuration file * */ struct config { long cpu_max; long cpu_min; long update; long cmm_max; long cmm_min; struct term *cmm_inc; struct term *cmm_dec; struct term *hotplug; struct term *hotunplug; struct term *memplug; struct term *memunplug; }; struct symbol_names { char *name; enum operation symop; }; extern int foreground; extern int debug; extern char *configfile; extern int debug; /* is verbose specified? */ extern int memory; extern int cpu; extern int num_cpu_start; /* # of online cpus at the time of the startup */ extern long cmm_pagesize_start; /* cmm_pageize at the time of daemon startup */ extern struct config cfg; extern int reload_pending; extern unsigned long meminfo_size; extern unsigned long vmstat_size; extern unsigned long cpustat_size; extern unsigned long varinfo_size; extern char *meminfo; extern char *vmstat; extern char *cpustat; extern char *varinfo; extern double *timestamps; extern unsigned int history_max; extern unsigned int history_current; extern struct symbol_names sym_names[]; extern unsigned int sym_names_count; int get_numcpus(); int get_num_online_cpus(); void get_loadavg_runnable(double *loadavg, double *runnable); void clean_up(); void reactivate_cpus(); void parse_configfile(char *file); void print_term(struct term *fn); struct term *parse_term(char **p, enum op_prio prio); int eval_term(struct term *fn, struct symbols *symbols); double eval_double(struct term *fn, struct symbols *symbols); double get_proc_value(char *procinfo, char *name, char separator); void proc_read(char *procinfo, char *path, unsigned long size); void proc_cpu_read(char *procinfo); unsigned long proc_read_size(char *path); char *get_var_rvalue(char *var_name); void cleanup_cmm(void); int hotplug(int cpuid); int hotunplug(int cpuid); int is_online(int cpuid); long get_cmmpages_size(); void parse_options(int argc, char **argv); void check_if_started_twice(); void handle_signals(void); void handle_sighup(void); void reload_daemon(void); int daemonize(void); int check_cmmfiles(void); void check_config(); void set_cmm_pages(long size); int check_lpar(); int cpu_is_configured(int cpuid); void setup_history(void); #define cpuplugd_info(fmt, ...) ({ \ if (foreground == 1) \ printf(fmt, ##__VA_ARGS__); \ if (foreground == 0) \ syslog(LOG_INFO, fmt, ##__VA_ARGS__); \ }) #define cpuplugd_error(fmt, ...) ({ \ if (foreground == 1) \ fprintf(stderr, fmt, ##__VA_ARGS__); \ if (foreground == 0) \ syslog(LOG_ERR, fmt, ##__VA_ARGS__); \ }) #define cpuplugd_debug(fmt, ...) ({ \ if (debug) \ cpuplugd_info(fmt, ##__VA_ARGS__); \ }) #define cpuplugd_exit(fmt, ...) ({ \ cpuplugd_error(fmt, ##__VA_ARGS__); \ clean_up(); \ }) s390-tools-2.3.0/cpuplugd/daemon.c000066400000000000000000000145541323410431100166030ustar00rootroot00000000000000/* * cpuplugd - Linux for System z Hotplug Daemon * * Daemon functions * * Copyright IBM Corp. 2007, 2017 * * 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 #include #include #include "cpuplugd.h" const char *name = NAME; static const char *pid_file = PIDFILE; const char *const usage = "Usage: %s [OPTIONS]\n" "\n" "Daemon to dynamically hotplug cpus and memory based on a set of rules\n" "Use OPTIONS described below.\n" "\n" "\t-c, --config CONFIGFILE Path to the configuration file\n" "\t-f, --foreground Run in foreground, do not detach\n" "\t-h, --help Print this help, then exit\n" "\t-v, --version Print version information, then exit\n" "\t-V, --verbose Provide more verbose output\n"; /* * Print command usage */ void print_usage(int is_error, char program_name[]) { fprintf(is_error ? stderr : stdout, usage, program_name); exit(is_error ? 1 : 0); } /* * Print command version */ void print_version() { printf("%s: Linux on System z CPU hotplug daemon version %s\n", name, RELEASE_STRING); printf("Copyright IBM Corp. 2007, 2017\n"); exit(0); } /* * Store daemon's pid so it can be stopped */ static int store_pid(void) { FILE *filp; filp = fopen(pid_file, "w"); if (!filp) { cpuplugd_error("cannot open pid file %s: %s\n", pid_file, strerror(errno)); return -1; } fprintf(filp, "%d\n", getpid()); fclose(filp); return 0; } /* * Run daemon in background and write pid file */ int daemonize(void) { int fd, pipe_fds[2], startup_rc = 1; pid_t pid; if (pipe(pipe_fds) == -1) { cpuplugd_error("cannot create pipe\n"); return -1; } pid = fork(); if (pid < 0) goto close_pipe; if (pid != 0) { /* Wait for startup return code from daemon */ if (read(pipe_fds[0], &startup_rc, sizeof(startup_rc)) == -1) cpuplugd_error("cannot read from pipe\n"); /* On success daemon has written pid file at this point */ exit(startup_rc); } /* Create new session */ if (setsid() < 0) goto notify_parent; /* Redirect stdin/out/err to /dev/null */ fd = open("/dev/null", O_RDWR, 0); if (fd == -1) goto notify_parent; if (dup2(fd, STDIN_FILENO) < 0) goto notify_parent; if (dup2(fd, STDOUT_FILENO) < 0) goto notify_parent; if (dup2(fd, STDERR_FILENO) < 0) goto notify_parent; /* Create pid file */ if (store_pid() < 0) goto notify_parent; startup_rc = 0; notify_parent: /* Inform waiting parent about startup return code */ if (write(pipe_fds[1], &startup_rc, sizeof(startup_rc)) == -1) { cpuplugd_error("cannot write to pipe\n"); startup_rc = 1; } close_pipe: close(pipe_fds[0]); close(pipe_fds[1]); return startup_rc ? -1 : 0; } /* * Check that we don't try to start this daemon twice */ void check_if_started_twice() { FILE *filp; int pid, rc; filp = fopen(pid_file, "r"); if (filp) { rc = fscanf(filp, "%d", &pid); if (rc != 1) { cpuplugd_error("Reading pid file failed. Aborting!\n"); exit(1); } cpuplugd_error("pid file %s still exists.\nThis might indicate " "that an instance of this daemon is already " "running.\n", pid_file); exit(1); } } /* * Clean up method */ void clean_up() { cpuplugd_info("terminated\n"); remove(pid_file); remove(LOCKFILE); reactivate_cpus(); if (memory) cleanup_cmm(); exit(1); } /* * End the deamon */ void kill_daemon(int UNUSED(a)) { cpuplugd_info("shutting down\n"); remove(pid_file); remove(LOCKFILE); reactivate_cpus(); if (memory) cleanup_cmm(); exit(0); } /* * Reload the daemon (for lsb compliance) */ void reload_handler(int UNUSED(a)) { reload_pending = 1; } void reload_daemon() { unsigned int temp_history; long temp_mem; int temp_cpu; cpuplugd_info("cpuplugd restarted\n"); /* * Before we parse the configuration file again we have to save * the original values prior to startup. If we don't do this cpuplugd * will no longer know how many cpus the system had before the daemon * was started and therefor can't restore theres in case it is stopped */ temp_cpu = num_cpu_start; temp_mem = cmm_pagesize_start; temp_history = history_max; /* clear varinfo before re-reading variables from config file */ memset(varinfo, 0, varinfo_size); history_max = 1; parse_configfile(configfile); if (history_max > MAX_HISTORY) cpuplugd_exit("History depth %i exceeded maximum (%i)\n", history_max, MAX_HISTORY); if (history_max != temp_history) { free(meminfo); free(vmstat); free(cpustat); free(timestamps); setup_history(); } check_config(); num_cpu_start = temp_cpu; cmm_pagesize_start = temp_mem; } /* * Set up for handling SIGTERM or SIGINT */ void handle_signals(void) { struct sigaction act; act.sa_flags = 0; sigemptyset(&act.sa_mask); act.sa_handler = kill_daemon; if (sigaction(SIGTERM, &act, NULL) < 0) { cpuplugd_error("sigaction( SIGTERM, ... ) failed - reason %s\n", strerror(errno)); exit(1); } if (sigaction(SIGINT, &act, NULL) < 0) { cpuplugd_error("sigaction( SIGINT, ... ) failed - reason %s\n", strerror(errno)); exit(1); } } /* * Signal handler for sighup. This is used to force the deamon to reload its * configuration file. * This feature is also required by a lsb compliant init script */ void handle_sighup(void) { struct sigaction act; act.sa_flags = 0; sigemptyset(&act.sa_mask); act.sa_handler = reload_handler; if (sigaction(SIGHUP, &act, NULL) < 0) { cpuplugd_error("sigaction( SIGHUP, ... ) failed - reason %s\n", strerror(errno)); exit(1); } } /* Check if we are running in an LPAR environment. * This functions return 1 if we run inside an lpar and 0 otherwise */ int check_lpar() { int rc; FILE *filp; size_t bytes_read; char buffer[2048]; char *contains_vm; rc = 0; filp = fopen("/proc/cpuinfo", "r"); if (!filp) cpuplugd_exit("cannot open /proc/cpuinfo: %s\n", strerror(errno)); bytes_read = fread(buffer, 1, sizeof(buffer) - 1, filp); if (bytes_read == 0) cpuplugd_exit("Reading /proc/cpuinfo failed: %s\n", strerror(errno)); /* NUL-terminate the text */ buffer[bytes_read] = '\0'; contains_vm = strstr(buffer, "version = FF"); if (contains_vm == NULL) { rc = 1; cpuplugd_debug("Detected System running in LPAR mode\n"); } else cpuplugd_debug("Detected System running in z/VM mode\n"); fclose(filp); return rc; } s390-tools-2.3.0/cpuplugd/getopt.c000066400000000000000000000043551323410431100166400ustar00rootroot00000000000000/* * cpuplugd - Linux for System z Hotplug Daemon * * Command line parsing * * Copyright IBM Corp. 2007, 2017 * * 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 #include "cpuplugd.h" void print_usage(int is_error, char program_name[]); void print_version(); int foreground; int debug; char *configfile; int cpu_idle_limit; void parse_options(int argc, char **argv) { int config_file_specified = -1; const struct option long_options[] = { { "help", no_argument, NULL, 'h'}, { "foreground", no_argument, NULL, 'f' }, { "config", required_argument, NULL, 'c' }, { "version", no_argument, NULL, 'v' }, { "verbose", no_argument, NULL, 'V' }, { NULL, 0, NULL, 0} }; /* dont run without any argument */ if (argc == 0 || argc == 1) print_usage(0, argv[0]); while (optind < argc) { int index = -1; struct option *opt = 0; int result = getopt_long(argc, argv, "hfc:vVm", long_options, &index); if (result == -1) break; /* end of list */ switch (result) { case 'h': print_usage(0, argv[0]); break; case 'f': foreground = 1; break; case 'c': /* * This prevents -cbla and enforces the * user to specify -c bla */ if (strcmp(argv[optind-1], optarg) == 0) { configfile = optarg; config_file_specified = 1; } else { cpuplugd_error("Unrecognized option: %s\n", optarg); exit(1); } break; case 'v': print_version(); break; case 'V': debug = 1; break; case 0: /* all parameter that do not appear in the optstring */ opt = (struct option *)&(long_options[index]); printf("'%s' was specified.", opt->name); if (opt->has_arg == required_argument) printf("Arg: <%s>", optarg); printf("\n"); break; case '?': printf("Try '%s' --help' for more information.\n", argv[0]); exit(1); break; case -1: /* * We also run in this case if no argument was * specified */ break; default: print_usage(0, argv[0]); } } if (config_file_specified == -1) { printf("You have to specify a configuration file!\n"); printf("Try '%s' --help' for more information.\n", argv[0]); exit(1); } } s390-tools-2.3.0/cpuplugd/info.c000066400000000000000000000102621323410431100162630ustar00rootroot00000000000000/* * cpuplugd - Linux for System z Hotplug Daemon * * /proc info functions * * Copyright IBM Corp. 2007, 2017 * * 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 #include #include "cpuplugd.h" /* * Return current load average and runnable processes based on /proc/loadavg * * Example: 0.20 0.18 0.12 1/80 11206 * * The first three columns measure CPU utilization of the last 1, 5, * and 15 minute periods. * The fourth column shows the number of currently running processes * and the total number of processes. * The last column displays the last process ID used. */ void get_loadavg_runnable(double *loadavg, double *runnable) { FILE *filp; double dummy; int rc; filp = fopen("/proc/loadavg", "r"); if (!filp) cpuplugd_exit("cannot open kernel loadaverage " "statistics: %s\n", strerror(errno)); rc = fscanf(filp, "%lf %lf %lf %lf/", loadavg, &dummy, &dummy, runnable); if (rc != 4) cpuplugd_exit("cannot parse kernel loadaverage " "statistics: %s\n", strerror(errno)); fclose(filp); return; } void proc_cpu_read(char *procinfo) { FILE *filp; unsigned int rc, onumcpus; unsigned long user, nice, system, idle, iowait, irq, softirq, steal, guest, guest_nice, total_ticks; double loadavg, runnable; guest = guest_nice = 0; /* set to 0 if not present in kernel */ filp = fopen("/proc/stat", "r"); if (!filp) cpuplugd_exit("/proc/stat open failed: %s\n", strerror(errno)); rc = fscanf(filp, "cpu %ld %ld %ld %ld %ld %ld %ld %ld %ld %ld", &user, &nice, &system, &idle, &iowait, &irq, &softirq, &steal, &guest, &guest_nice); get_loadavg_runnable(&loadavg, &runnable); onumcpus = get_num_online_cpus(); total_ticks = user + nice + system + idle + iowait + irq + softirq + steal + guest + guest_nice; rc = snprintf(procinfo, cpustat_size, "onumcpus %d\nloadavg %f\n" "runnable_proc %f\nuser %ld\nnice %ld\nsystem %ld\n" "idle %ld\niowait %ld\nirq %ld\nsoftirq %ld\nsteal %ld\n" "guest %ld\nguest_nice %ld\ntotal_ticks %ld\n", onumcpus, loadavg, runnable, user, nice, system, idle, iowait, irq, softirq, steal, guest, guest_nice, total_ticks); if (rc >= cpustat_size) cpuplugd_exit("cpustat buffer too small: need %d, have %ld " "(bytes)\n", rc, cpustat_size); fclose(filp); return; } void proc_read(char *procinfo, char *path, unsigned long size) { size_t bytes_read; FILE *filp; filp = fopen(path, "r"); if (!filp) cpuplugd_exit("%s open failed: %s\n", path, strerror(errno)); bytes_read = fread(procinfo, 1, size, filp); if (bytes_read == 0) cpuplugd_exit("%s read failed\n", path); if (bytes_read == size) cpuplugd_exit("procinfo buffer too small for %s\n", path); procinfo[bytes_read] = '\0'; fclose(filp); return; } unsigned long proc_read_size(char *path) { FILE *filp; char buf[PROCINFO_LINE]; char *linep, *linep_offset; unsigned long size; filp = fopen(path, "r"); if (!filp) cpuplugd_exit("%s open failed: %s\n", path, strerror(errno)); size = 0; while ((linep = fgets(buf, sizeof(buf), filp))) { if (!(linep_offset = strchr(linep, '\n'))) cpuplugd_exit("buf too small for line\n"); size = size + linep_offset - linep + 1; } fclose(filp); return size; } double get_proc_value(char *procinfo, char *name, char separator) { char buf[PROCINFO_LINE]; char *proc_offset; unsigned long proc_length, name_length; double value; int found; value = -1; found = 0; name_length = strlen(name); while ((proc_offset = strchr(procinfo, separator))) { proc_length = proc_offset - procinfo; /* * proc_read_size() made sure that proc_length < PROCINFO_LINE */ memcpy(buf, procinfo, proc_length); buf[proc_length] = '\0'; procinfo = proc_offset + 1; if (strncmp(buf, name, MAX(proc_length, name_length)) == 0) { errno = 0; value = strtod(procinfo, NULL); if (errno) cpuplugd_exit("strtod failed\n"); found = 1; break; } proc_offset = strchr(procinfo, '\n'); procinfo = proc_offset + 1; } if (!found) cpuplugd_exit("Symbol %s not found, check your config file\n", name); return value; } s390-tools-2.3.0/cpuplugd/main.c000066400000000000000000000355501323410431100162630ustar00rootroot00000000000000/* * cpuplugd - Linux for System z Hotplug Daemon * * Main functions * * Copyright IBM Corp. 2007, 2017 * * 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 #include #include #include #include #include "cpuplugd.h" struct symbol_names sym_names[] = { { "loadavg", OP_SYMBOL_LOADAVG }, { "runnable_proc", OP_SYMBOL_RUNABLE }, { "onumcpus", OP_SYMBOL_CPUS }, { "user", OP_SYMBOL_USER }, { "nice", OP_SYMBOL_NICE }, { "system", OP_SYMBOL_SYSTEM }, { "idle", OP_SYMBOL_IDLE }, { "iowait", OP_SYMBOL_IOWAIT }, { "irq", OP_SYMBOL_IRQ }, { "softirq", OP_SYMBOL_SOFTIRQ }, { "steal", OP_SYMBOL_STEAL }, { "guest_nice", OP_SYMBOL_GUEST_NICE }, { "guest", OP_SYMBOL_GUEST }, { "swaprate", OP_SYMBOL_SWAPRATE }, { "apcr", OP_SYMBOL_APCR }, { "freemem", OP_SYMBOL_FREEMEM }, { "meminfo.", OP_SYMBOL_MEMINFO }, { "vmstat.", OP_SYMBOL_VMSTAT }, { "cpustat.", OP_SYMBOL_CPUSTAT }, { "time", OP_SYMBOL_TIME }, }; struct config cfg = { .cpu_max = -1, .cpu_min = -1, .update = -1, .cmm_min = -1, .cmm_max = -1, .cmm_inc = NULL, .cmm_dec = NULL, .memplug = NULL, .memunplug = NULL, .hotplug = NULL, .hotunplug = NULL, }; int num_cpu_start, memory, cpu, reload_pending; long cmm_pagesize_start; unsigned long meminfo_size, vmstat_size, cpustat_size, varinfo_size; char *meminfo, *vmstat, *cpustat, *varinfo; double *timestamps; unsigned int history_max, history_current, history_prev, sym_names_count; static struct symbols symbols; static jmp_buf jmpenv; static struct sigaction act; /* * Handle the sigfpe signal which we might catch during rule evaluating */ static void sigfpe_handler(int UNUSED(sig)) { longjmp(jmpenv, 1); } static void eval_cpu_rules(void) { double diffs[CPUSTATS], diffs_total, percent_factor; char *procinfo_current, *procinfo_prev; int cpu, nr_cpus, on_off; nr_cpus = get_numcpus(); procinfo_current = cpustat + history_current * cpustat_size; procinfo_prev = cpustat + history_prev * cpustat_size; diffs[0] = get_proc_value(procinfo_current, "user", ' ') - get_proc_value(procinfo_prev, "user", ' '); diffs[1] = get_proc_value(procinfo_current, "nice", ' ') - get_proc_value(procinfo_prev, "nice", ' '); diffs[2] = get_proc_value(procinfo_current, "system", ' ') - get_proc_value(procinfo_prev, "system", ' '); diffs[3] = get_proc_value(procinfo_current, "idle", ' ') - get_proc_value(procinfo_prev, "idle", ' '); diffs[4] = get_proc_value(procinfo_current, "iowait", ' ') - get_proc_value(procinfo_prev, "iowait", ' '); diffs[5] = get_proc_value(procinfo_current, "irq", ' ') - get_proc_value(procinfo_prev, "irq", ' '); diffs[6] = get_proc_value(procinfo_current, "softirq", ' ') - get_proc_value(procinfo_prev, "softirq", ' '); diffs[7] = get_proc_value(procinfo_current, "steal", ' ') - get_proc_value(procinfo_prev, "steal", ' '); diffs[8] = get_proc_value(procinfo_current, "guest", ' ') - get_proc_value(procinfo_prev, "guest", ' '); diffs[9] = get_proc_value(procinfo_current, "guest_nice", ' ') - get_proc_value(procinfo_prev, "guest_nice", ' '); diffs_total = get_proc_value(procinfo_current, "total_ticks", ' ') - get_proc_value(procinfo_prev, "total_ticks", ' '); if (diffs_total == 0) diffs_total = 1; symbols.loadavg = get_proc_value(procinfo_current, "loadavg", ' '); symbols.runnable_proc = get_proc_value(procinfo_current, "runnable_proc", ' '); symbols.onumcpus = get_proc_value(procinfo_current, "onumcpus", ' '); percent_factor = 100 * symbols.onumcpus; symbols.user = (diffs[0] / diffs_total) * percent_factor; symbols.nice = (diffs[1] / diffs_total) * percent_factor; symbols.system = (diffs[2] / diffs_total) * percent_factor; symbols.idle = (diffs[3] / diffs_total) * percent_factor; symbols.iowait = (diffs[4] / diffs_total) * percent_factor; symbols.irq = (diffs[5] / diffs_total) * percent_factor; symbols.softirq = (diffs[6] / diffs_total) * percent_factor; symbols.steal = (diffs[7] / diffs_total) * percent_factor; symbols.guest = (diffs[8] / diffs_total) * percent_factor; symbols.guest_nice = (diffs[9] / diffs_total) * percent_factor; /* only use this for development and testing */ cpuplugd_debug("cpustat values:\n%s", cpustat + history_current * cpustat_size); if (debug && foreground == 1) { printf("-------------------- CPU --------------------\n"); printf("cpu_min: %ld\n", cfg.cpu_min); printf("cpu_max: %ld\n", cfg.cpu_max); printf("loadavg: %f \n", symbols.loadavg); printf("user percent = %f\n", symbols.user); printf("nice percent = %f\n", symbols.nice); printf("system percent = %f\n", symbols.system); printf("idle percent = %f\n", symbols.idle); printf("iowait percent = %f\n", symbols.iowait); printf("irq percent = %f\n", symbols.irq); printf("softirq percent = %f\n", symbols.softirq); printf("steal percent = %f\n", symbols.steal); printf("guest percent = %f\n", symbols.guest); printf("guest_nice percent = %f\n", symbols.guest_nice); printf("numcpus %d\n", nr_cpus); printf("runnable_proc: %d\n", (int) symbols.runnable_proc); printf("---------------------------------------------\n"); printf("onumcpus: %d\n", (int) symbols.onumcpus); printf("---------------------------------------------\n"); printf("hotplug: "); print_term(cfg.hotplug); printf("\n"); printf("hotunplug: "); print_term(cfg.hotunplug); printf("\n"); printf("---------------------------------------------\n"); } on_off = 0; /* Evaluate the hotplug rule */ if (eval_term(cfg.hotplug, &symbols)) on_off++; /* Evaluate the hotunplug rule only if hotplug did not match */ else if (eval_term(cfg.hotunplug, &symbols)) on_off--; if (on_off > 0) { /* check the cpu nr limit */ if (symbols.onumcpus + 1 > cfg.cpu_max) { /* cpu limit reached */ cpuplugd_debug("maximum cpu limit is reached\n"); return; } /* try to find a offline cpu */ for (cpu = 0; cpu < nr_cpus; cpu++) if (is_online(cpu) == 0 && cpu_is_configured(cpu) != 0) break; if (cpu < nr_cpus) { cpuplugd_debug("cpu with id %d is currently offline " "and will be enabled\n", cpu); if (hotplug(cpu) == -1) cpuplugd_debug("unable to find a cpu which " "can be enabled\n"); } else { /* * In case we tried to enable a cpu but this failed. * This is the case if a cpu is deconfigured */ cpuplugd_debug("unable to find a cpu which can " "be enabled\n"); } } else if (on_off < 0) { /* check cpu nr limit */ if (symbols.onumcpus <= cfg.cpu_min) { cpuplugd_debug("minimum cpu limit is reached\n"); return; } /* try to find a online cpu */ for (cpu = get_numcpus() - 1; cpu >= 0; cpu--) { if (is_online(cpu) != 0) break; } if (cpu > 0) { cpuplugd_debug("cpu with id %d is currently online " "and will be disabled\n", cpu); hotunplug(cpu); } } } static void eval_mem_rules(double interval) { long cmmpages_size, cmm_inc, cmm_dec, cmm_new; double free_memory, swaprate, apcr; char *procinfo_current, *procinfo_prev; procinfo_current = meminfo + history_current * meminfo_size; free_memory = get_proc_value(procinfo_current, "MemFree", ':'); procinfo_current = vmstat + history_current * vmstat_size; procinfo_prev = vmstat + history_prev * vmstat_size; swaprate = (get_proc_value(procinfo_current, "pswpin", ' ') + get_proc_value(procinfo_current, "pswpout", ' ') - get_proc_value(procinfo_prev, "pswpin", ' ') - get_proc_value(procinfo_prev, "pswpout", ' ')) / interval; apcr = (get_proc_value(procinfo_current, "pgpgin", ' ') + get_proc_value(procinfo_current, "pgpgout", ' ') - get_proc_value(procinfo_prev, "pgpgin", ' ') - get_proc_value(procinfo_prev, "pgpgout", ' ')) / interval; cmmpages_size = get_cmmpages_size(); symbols.apcr = apcr; // apcr in 512 byte blocks / sec symbols.swaprate = swaprate; // swaprate in 4K pages / sec symbols.freemem = free_memory / 1024; // freemem in MB cmm_inc = eval_double(cfg.cmm_inc, &symbols); /* cmm_dec is optional */ if (cfg.cmm_dec) cmm_dec = eval_double(cfg.cmm_dec, &symbols); else cmm_dec = cmm_inc; /* only use this for development and testing */ if (debug && foreground == 1) { printf("------------------- Memory ------------------\n"); printf("cmm_min: %ld\n", cfg.cmm_min); printf("cmm_max: %ld\n", cfg.cmm_max); printf("swaprate: %f\n", symbols.swaprate); printf("apcr: %f\n", symbols.apcr); printf("cmm_inc: %ld = ", cmm_inc); print_term(cfg.cmm_inc); printf("\n"); printf("cmm_dec: %ld = ", cmm_dec); if (cfg.cmm_dec) print_term(cfg.cmm_dec); else print_term(cfg.cmm_inc); printf("\n"); printf("free memory: %f MB\n", symbols.freemem); printf("---------------------------------------------\n"); printf("cmm_pages: %ld\n", cmmpages_size); printf("---------------------------------------------\n"); printf("memplug: "); print_term(cfg.memplug); printf("\n"); printf("memunplug: "); print_term(cfg.memunplug); printf("\n"); printf("---------------------------------------------\n"); } cmm_new = cmmpages_size; /* Evaluate the memplug rule */ if (eval_term(cfg.memplug, &symbols)) { if (cmm_dec < 0) { cpuplugd_error("cmm_dec went negative (%ld), set it " "to 0.\n", cmm_dec); cmm_dec = 0; } cmm_new -= cmm_dec; /* Evaluate the memunplug rule only if memplug did not match */ } else if (eval_term(cfg.memunplug, &symbols)) { if (cmm_inc < 0) { cpuplugd_error("cmm_inc went negative (%ld), set it " "to 0.\n", cmm_inc); cmm_inc = 0; } cmm_new += cmm_inc; } if (cmm_new < cfg.cmm_min) { cpuplugd_debug("minimum memory limit is reached\n"); cmm_new = cfg.cmm_min; } if (cmm_new > cfg.cmm_max) { cpuplugd_debug("maximum memory limit is reached\n"); cmm_new = cfg.cmm_max; } if (cmm_new != cmmpages_size) set_cmm_pages(cmm_new); } static void time_read(double *timestamps) { struct timeval tv; int rc; cpuplugd_debug("\n==================== New interval " "====================\n"); rc = gettimeofday(&tv, NULL); if (!rc) { *timestamps = tv.tv_sec + (double) tv.tv_usec / 1000000; cpuplugd_debug("Timestamp: %s (%f seconds since " "the Epoch)\n", ctime(&tv.tv_sec), *timestamps); } else cpuplugd_exit("gettimeofday failed: %s\n", strerror(errno)); return; } void setup_history() { /* * The /proc file size will vary during intervals, use double of current * size to have enough buffer for growing values. */ meminfo_size = proc_read_size("/proc/meminfo") * 2; vmstat_size = proc_read_size("/proc/vmstat") * 2; cpustat_size = CPUSTAT_SIZE; meminfo = malloc(meminfo_size * (history_max + 1)); if (!meminfo) cpuplugd_exit("Out of memory: meminfo\n"); vmstat = malloc(vmstat_size * (history_max + 1)); if (!vmstat) cpuplugd_exit("Out of memory: vmstat\n"); cpustat = malloc(cpustat_size * (history_max + 1)); if (!cpustat) cpuplugd_exit("Out of memory: cpustat\n"); timestamps = malloc(sizeof(double) * (history_max + 1)); if (!timestamps) cpuplugd_exit("Out of memory: timestamps\n"); /* * Read history data, at least 1 interval for swaprate, apcr, idle, etc. */ history_current = 0; cpuplugd_info("Waiting %i intervals to accumulate history.\n", history_max); do { time_read(×tamps[history_current]); proc_read(meminfo + history_current * meminfo_size, "/proc/meminfo", meminfo_size); proc_read(vmstat + history_current * vmstat_size, "/proc/vmstat", vmstat_size); proc_cpu_read(cpustat + history_current * cpustat_size); sleep(cfg.update); history_current++; } while (history_current < history_max); history_current--; } int main(int argc, char *argv[]) { double interval; int fd, rc; reload_pending = 0; sym_names_count = sizeof(sym_names) / sizeof(struct symbol_names); varinfo_size = VARINFO_SIZE; varinfo = calloc(varinfo_size, 1); if (!varinfo) { cpuplugd_error("Out of memory: varinfo\n"); exit(1); } /* * varinfo must start with '\n' for correct string matching * in get_var_rvalue(). */ varinfo[0] = '\n'; /* Parse the command line options */ parse_options(argc, argv); /* flock() lock file to prevent multiple instances of cpuplugd */ fd = open(LOCKFILE, O_CREAT | O_RDONLY, S_IRUSR); if (fd == -1) { cpuplugd_error("Cannot open lock file %s: %s\n", LOCKFILE, strerror(errno)); exit(1); } rc = flock(fd, LOCK_EX | LOCK_NB); if (rc) { cpuplugd_error("flock() failed on lock file %s: %s\nThis might " "indicate that an instance of this daemon is " "already running.\n", LOCKFILE, strerror(errno)); exit(1); } /* Make sure that the daemon is not started multiple times */ check_if_started_twice(); /* Store daemon pid also in foreground mode */ handle_signals(); handle_sighup(); /* Need 1 history level minimum for internal symbols */ history_max = 1; /* * Parse arguments from the configuration file, also calculate * history_max */ parse_configfile(configfile); if (history_max > MAX_HISTORY) cpuplugd_exit("History depth %i exceeded maximum (%i)\n", history_max, MAX_HISTORY); /* Check the settings in the configuration file */ check_config(); if (!foreground) { rc = daemonize(); if (rc < 0) cpuplugd_exit("Detach from terminal failed: %s\n", strerror(errno)); } /* Unlock lock file */ flock(fd, LOCK_UN); close(fd); /* Install signal handler for floating point exceptions */ rc = feenableexcept(FE_DIVBYZERO | FE_OVERFLOW | FE_UNDERFLOW | FE_INVALID); act.sa_flags = SA_NODEFER; sigemptyset(&act.sa_mask); act.sa_handler = sigfpe_handler; if (sigaction(SIGFPE, &act, NULL) < 0) cpuplugd_exit("sigaction( SIGFPE, ... ) failed - reason %s\n", strerror(errno)); setup_history(); /* Main loop */ while (1) { if (reload_pending) { // check for daemon reload reload_daemon(); reload_pending = 0; } history_prev = history_current; history_current = (history_current + 1) % (history_max + 1); time_read(×tamps[history_current]); proc_read(meminfo + history_current * meminfo_size, "/proc/meminfo", meminfo_size); proc_read(vmstat + history_current * vmstat_size, "/proc/vmstat", vmstat_size); proc_cpu_read(cpustat + history_current * cpustat_size); interval = timestamps[history_current] - timestamps[history_prev]; cpuplugd_debug("config update interval: %ld seconds\n", cfg.update); cpuplugd_debug("real update interval: %f seconds\n", interval); /* Run code that may signal failure via longjmp. */ if (cpu == 1) { if (setjmp(jmpenv) == 0) eval_cpu_rules(); else cpuplugd_error("Floating point exception, " "skipping cpu rule " "evaluation.\n"); } if (memory == 1) { if (setjmp(jmpenv) == 0) eval_mem_rules(interval); else cpuplugd_error("Floating point exception, " "skipping memory rule " "evaluation.\n"); } sleep(cfg.update); } return 0; } s390-tools-2.3.0/cpuplugd/man/000077500000000000000000000000001323410431100157365ustar00rootroot00000000000000s390-tools-2.3.0/cpuplugd/man/cpuplugd.8000066400000000000000000000034061323410431100176550ustar00rootroot00000000000000.\" Copyright 2017 IBM Corp. .\" s390-tools is free software; you can redistribute it and/or modify .\" it under the terms of the MIT license. See LICENSE for details. .\" .TH CPUPLUGD 8 "May 2011" "s390-tools" . .SH NAME cpuplugd \- Linux on System z CPU and memory hotplug daemon . .SH SYNOPSIS .B cpuplugd .RI [ OPTIONS ] . .SH DESCRIPTION The cpuplugd daemon dynamically enables and disables CPUs and increases or decreases the cooperative memory management (CMM) page pool based on a set of rules. When the daemon is stopped, the size of the CMM page pool and the number of active CPUs are reset to the values they had before the cpuplugd was started. This program can be used to control the number of CPUs for Linux on z/VM and for Linux in LPAR mode. The memory hotplug feature (CMM page pool) applies to Linux on z/VM only. . .SH OPTIONS .TP \fB\-c\fP or \fB\-\-config\fP \fI\fP Specify the absolute path to the configuration file. This option is mandatory. The default configuration file can be found in /etc/cpuplugd.conf. . .TP \fB\-f\fP or \fB\-\-foreground\fP Run in the foreground and not as daemon. If this option is omitted, the program runs in the background. . .TP \fB\-h\fP or \fB\-\-help\fP Print usage message and exit. . .TP \fB\-v\fP or \fB\-\-version\fP Print Version information and exit. . .TP \fB\-V\fP or \fB\-\-verbose\fP Print verbose messages to stdout (when running in foreground) or to syslog otherwise. This options is mainly used for debugging purposes. . .SH EXAMPLES To test a setup start cpuplugd in foreground mode using verbose output: .br .RS 4 cpuplugd \-V \-f \-c /etc/cpuplugd.conf .RE For daemon mode, start cpuplugd from an init script as follows: .br .RS 4 cpuplugd \-c /etc/cpuplugd.conf .RE .SH SEE ALSO .BR cpuplugd.conf (5) s390-tools-2.3.0/cpuplugd/man/cpuplugd.conf.5000066400000000000000000000252201323410431100205740ustar00rootroot00000000000000.\" Copyright 2017 IBM Corp. .\" s390-tools is free software; you can redistribute it and/or modify .\" it under the terms of the MIT license. See LICENSE for details. .\" .TH CPUPLUGD.CONF 5 "May 2011" "s390-tools" . .SH NAME cpuplugd.conf \- Configuration file for the Linux on System z CPU and memory hotplug daemon . .SH DESCRIPTION The cpuplugd.conf configuration file contains the configuration information for the Linux for System z CPU and memory hotplug daemon. Use this file to specify rules for enabling or disabling CPUs and for adding or removing memory. . .SS "CPU hotplug" CPUs can be enabled and disabled through a sysfs interface. The status file for a CPU, here CPU number 16 (counting starts at 0), is /sys/devices/system/cpu/cpu15/online. Writing a 0 to this file disables the CPU. Writing a 1 enables the CPU. . .SS "Memory hotplug" The rules that add or remove memory use the cooperative memory management (CMM) feature. CMM is a mechanism to reduce the memory available to Linux instances that run as guests of z/VM. CMM allocates pages to a dynamic page pool not available to Linux. A diagnose code indicates to z/VM that the pages in the page pool are out of use. z/VM can then immediately reuse these pages for other guests. . .SS "Layout of the configuration file" The configuration file contains variables specifying static numbers or expressions. They are of the format \fB=""\fP and they need to be specified within one line. Expressions can be specified to calculate algebraic values or to define boolean rules, which determine when a hotplug/hotunplug action should be taken. The maximum valid line length is 2048 characters. There are case-insensitive pre-defined and case-sensitive user-defined variables. The configuration file must include specifications for all pre-defined variables. If a variable is not set, the hotplug function it applies to, CPU or memory, is disabled. The only exception to this rule is CMM_DEC, which defaults to the setting for CMM_INC if omitted. If a pre-defined variable is set more than once, only the last occurrence is used. User-defined variables must not be set more than once. . .SS "Hotplug rules" Set these pre-defined variables to an expression that resolves to a boolean value (true or false). These variables trigger hotplug actions. Setting a variable to "0" disables the action. . .RS 2 .IP "-" 2 \fBHOTPLUG\fP - used to enable CPUs .IP "-" 2 \fBHOTUNPLUG\fP - used to disable CPUs .IP "-" 2 \fBMEMPLUG\fP - used to increase the available memory .IP "-" 2 \fBMEMUNPLUG\fP - used to decrease the amount of memory .RE .PP The following operators can be used in a hotplug rule expression: .br .RS 2 .B + * ( ) / - < > .RE .br Furthermore, the boolean operators \fB & \fP (and) \fB|\fP (or) and \fB!\fP (not) can be used. If both HOTPLUG and HOTUNPLUG evaluate to true, only the HOTPLUG action is triggered. If both MEMPLUG and MEMUNPLUG evaluate to true, only the MEMPLUG action is triggered. . .SS "Pre-defined static variables" The following pre-defined variables can be set only to a static, positive, numeric value: . .RS 2 .IP "-" 2 \fBCPU_MIN\fP - the minimum number of CPUs to keep online (> 0) .IP "-" 2 \fBCPU_MAX\fP - the maximum number of CPUs to enable (>= 0) .IP "-" 2 \fBUPDATE\fP - the interval at which cpuplugd evaluates the rules (in seconds, > 0) .IP "-" 2 \fBCMM_MIN\fP - the minimum size of the CMM page pool (>= 0) .IP "-" 2 \fBCMM_MAX\fP - the maximum size of the CMM page pool (>= 0) .RE .PP If the value of CPU_MAX is 0, the overall number of CPUs found in this system is used as the maximum. . .SS "Pre-defined dynamic variables" The following pre-defined variables can either be set to a static value or to an algebraic expression: . .RS 2 .IP "-" 2 \fBCMM_INC\fP - the amount of pages by which the CMM page pool is increased if the MEMUNPLUG rule is matched (available system memory is decreased). .IP "-" 2 \fBCMM_DEC\fP - the amount of pages by which the CMM page pool is decreased if the MEMPLUG rule is matched (available system memory is increased). .RE .PP The following operators can be used in a dynamic variable expression: .br .RS 2 .B + * ( ) / - < > .RE .br . .SS "User-defined variables" You can specify complex calculations as user-defined variables, which can then be used in expressions. User-defined variables are case-sensitive and must not match a pre-defined variable or keyword. In the configuration file, definitions for user-defined variables must precede their use in expressions. Variable names consist of alphanumeric characters (a-z,A-Z,0-9) and the "_" character, see section \fB"EXAMPLES"\fP for an example (pgscanrate). The maximum name length for a variable is 128 characters, and the maximum total size for all user-defined variables (names + values) is 4096 characters. . .SS "Keywords for CPU hotplug rules" The \fBHOTPLUG\fP and \fBHOTUNPLUG\fP rules can contain the following pre-defined keywords: . .RS 2 .IP "-" 2 \fBloadavg\fP - the current load average .IP "-" 2 \fBonumcpus\fP - the current number of CPUs which are online .IP "-" 2 \fBrunnable_proc\fP - the current amount of runnable processes .IP "-" 2 \fBuser\fP - the current CPU user percentage .IP "-" 2 \fBnice\fP - the current CPU nice percentage .IP "-" 2 \fBsystem\fP - the current CPU system percentage .IP "-" 2 \fBidle\fP - the current CPU idle percentage .IP "-" 2 \fBiowait\fP - the current CPU iowait percentage .IP "-" 2 \fBirq\fP - the current CPU irq percentage .IP "-" 2 \fBsoftirq\fP - the current CPU softirq percentage .IP "-" 2 \fBsteal\fP - the current CPU steal percentage .IP "-" 2 \fBguest\fP - the current CPU guest percentage (depends on kernel version: if not reported in /proc/stat, this is set to 0) .IP "-" 2 \fBguest_nice\fP - the current CPU guest_nice percentage (depends on kernel version: if not reported in /proc/stat, this is set to 0) .IP "-" 2 \fBcpustat.\fP - data from /proc/stat and /proc/loadavg .IP "-" 2 \fBtime\fP - floating point timestamp in "seconds.microseconds" since the Unix Epoch (1970-01-01 00:00:00 +0000 (UTC)) .RE .PP The percentage values are accumulated over all online CPUs, so they can vary between 0 and (100 * \fBonumcpus\fP). CPU usage data from /proc/stat and /proc/loadavg is accessible by specifying \fBcpustat.\fP, where \fB\fP can be any of the keywords described above, plus \fBtotal_ticks\fP. In this case, \fBloadavg\fP, \fBonumcpus\fP and \fBrunnable_proc\fP provide the same values as the pre-defined keywords, while the others refer to the raw timer ticks as reported by /proc/stat, not the percentage. For example, \fBcpustat.idle\fP reports the timer ticks spent in idle since system start, and \fBcpustat.total_ticks\fP indicates the sum of all reported timer ticks, which can be useful for user-defined percentage calculations. . .SS "Keywords for memory hotplug rules" The \fBMEMPLUG\fP and \fBMEMUNPLUG\fP rules can contain the following pre-defined keywords: . .RS 2 .IP "-" 2 \fBapcr\fP - the amount of page cache operations, i.e. pgpin + pgpout from /proc/vmstat (in 512 byte blocks / second) .IP "-" 2 \fBfreemem\fP - the amount of free memory (in megabytes) .IP "-" 2 \fBswaprate\fP - the number of swap operations, i.e. pswpin + pswpout from /proc/vmstat (in pages / second) .IP "-" 2 \fBmeminfo.\fP - any value from /proc/meminfo .IP "-" 2 \fBvmstat.\fP - any value from /proc/vmstat .IP "-" 2 \fBtime\fP - floating point timestamp in "seconds.microseconds" since the Unix Epoch (1970-01-01 00:00:00 +0000 (UTC)) .RE .PP All values from /proc/meminfo and /proc/vmstat can be used in an expression by specifying \fBmeminfo.\fP or \fBvmstat.\fP, where \fB\fP matches a symbol name reported by /proc/meminfo or /proc/vmstat (case sensitive), e.g. \fBmeminfo.MemTotal\fP. . .SS "History function" There is a history function for the following keywords: . .RS 2 .IP "-" 2 \fBcpustat.\fP - data from /proc/stat and /proc/loadavg .IP "-" 2 \fBmeminfo.\fP - any value from /proc/meminfo .IP "-" 2 \fBvmstat.\fP - any value from /proc/vmstat .IP "-" 2 \fBtime\fP - floating point timestamp in "seconds.microseconds" since the Unix Epoch (1970-01-01 00:00:00 +0000 (UTC)) .RE .PP The history levels can be accessed by appending \fB[]\fP to the name, where \fB\fP indicates the amount of past intervals where the value was gathered. [0] means the current interval (the [0] can be omitted in this case), [1] means the previous interval, [2] means two intervals ago, and so on. The history limit is 100. For example, \fBcpustat.system[1]\fP would indicate the system value from /proc/stat at the previous interval, while \fBvmstat.pgpgin\fP and \fBvmstat.pgpgin[0]\fP would both mean the current pgpgin value from /proc/vmstat. The \fBtime\fP keyword and its history values can be used to calculate values dependent on time intervals, see section \fB"EXAMPLES"\fP for an example (pgscanrate). . .SH EXAMPLES A complete configuration file could look like this: .nf ------------------------------ config file start ------------------------------ UPDATE="5" CPU_MIN="2" CPU_MAX="5" CMM_MIN="0" CMM_MAX="131072" # 512 MB pgscan_k="vmstat.pgscan_kswapd_dma + vmstat.pgscan_kswapd_normal + vmstat.pgscan_kswapd_movable" pgscan_d="vmstat.pgscan_direct_dma + vmstat.pgscan_direct_normal + vmstat.pgscan_direct_movable" pgscan_k1="vmstat.pgscan_kswapd_dma[1] + vmstat.pgscan_kswapd_normal[1] + vmstat.pgscan_kswapd_movable[1]" pgscan_d1="vmstat.pgscan_direct_dma[1] + vmstat.pgscan_direct_normal[1] + vmstat.pgscan_direct_movable[1]" pgscanrate="(pgscan_k + pgscan_d - pgscan_k1 - pgscan_d1) / (time - time[1])" cache="meminfo.Cached + meminfo.Buffers" # CMM_INC: 10% of free memory + cache, in 4K pages CMM_INC="(meminfo.MemFree + cache) / 40" # CMM_DEC: 10% of total memory in 4K pages CMM_DEC="meminfo.MemTotal / 40" HOTPLUG = "(loadavg > onumcpus + 0.75) & (idle < 10.0)" HOTUNPLUG = "(loadavg < onumcpus - 0.25) | (idle > 50)" # Plug memory if page scan rate is above 20 pages / sec MEMPLUG = "pgscanrate > 20" # Unplug memory while free memory is above 10% of total memory, or cache uses # more than 50% of total memory MEMUNPLUG = "(meminfo.MemFree > meminfo.MemTotal / 10) | (cache > meminfo.MemTotal / 2)" ------------------------------ config file end ------------------------------ .fi The example includes multiple user-defined variables to calculate the page scan rate with values from /proc/vmstat, as well as the cache size. \fBAttention:\fP Do not use these example rules on production systems. The rules have been designed to illustrate the configuration file syntax and are not suitable for actually governing hotplug actions. Useful rules differ considerably depending on the workload, resources, and requirements of the system they are designed for. . .SH SEE ALSO .BR cpuplugd (8) s390-tools-2.3.0/cpuplugd/mem.c000066400000000000000000000032461323410431100161120ustar00rootroot00000000000000/* * cpuplugd - Linux for System z Hotplug Daemon * * cmm functions * * Copyright IBM Corp. 2007, 2017 * * 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 "cpuplugd.h" /* * The cmm_pages value defines the size of the balloon of blocked memory. * Increasing the value is removing memory from Linux, which is an memunplug. * Decreasing the value is adding memory back to Linux, which is memplug. */ /* * Set the value of cmm_pages */ void set_cmm_pages(long pages) { FILE *filp; filp = fopen("/proc/sys/vm/cmm_pages", "w"); if (!filp) cpuplugd_exit("Cannot open /proc/sys/vm/cmmpages: %s\n", strerror(errno)); cpuplugd_debug("changing number of pages permanently reserved to %ld\n", pages); fprintf(filp, "%ld\n", pages); fclose(filp); return; } /* * Read number of pages permanently reserved */ long get_cmmpages_size() { FILE *filp; long size; int rc; filp = fopen("/proc/sys/vm/cmm_pages", "r"); if (!filp) cpuplugd_exit("Cannot open /proc/sys/vm/cmm_pages: %s\n", strerror(errno)); rc = fscanf(filp, "%ld", &size); if (rc == 0) cpuplugd_exit("Can not read /proc/sys/vm/cmm_pages: %s\n", strerror(errno)); fclose(filp); return size; } /* * Reset cmm pagesize to value we found prior to daemon startup */ void cleanup_cmm() { set_cmm_pages(cmm_pagesize_start); return; } /* * Function to check if the cmm kernel module is loaded and the required * files below /proc exit */ int check_cmmfiles(void) { FILE *filp; filp = fopen("/proc/sys/vm/cmm_pages", "r"); if (!filp) return -1; fclose(filp); return 0; } s390-tools-2.3.0/cpuplugd/terms.c000066400000000000000000000264551323410431100164750ustar00rootroot00000000000000/* * cpuplugd - Linux for System z Hotplug Daemon * * Term parsing * * Copyright IBM Corp. 2007, 2017 * * 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 "cpuplugd.h" static enum op_prio op_prio_table[] = { [OP_NEG] = OP_PRIO_ADD, [OP_GREATER] = OP_PRIO_CMP, [OP_LESSER] = OP_PRIO_CMP, [OP_PLUS] = OP_PRIO_ADD, [OP_MINUS] = OP_PRIO_ADD, [OP_MULT] = OP_PRIO_MULT, [OP_DIV] = OP_PRIO_MULT, [OP_AND] = OP_PRIO_AND, [OP_OR] = OP_PRIO_OR, }; static void free_term(struct term *fn) { if (!fn) return; switch (fn->op) { case OP_SYMBOL_LOADAVG: case OP_SYMBOL_RUNABLE: case OP_SYMBOL_CPUS: case OP_SYMBOL_USER: case OP_SYMBOL_NICE: case OP_SYMBOL_SYSTEM: case OP_SYMBOL_IDLE: case OP_SYMBOL_IOWAIT: case OP_SYMBOL_IRQ: case OP_SYMBOL_SOFTIRQ: case OP_SYMBOL_STEAL: case OP_SYMBOL_GUEST: case OP_SYMBOL_GUEST_NICE: case OP_CONST: free(fn); break; case OP_NEG: case OP_NOT: free_term(fn->left); free(fn); break; case OP_GREATER: case OP_LESSER: case OP_PLUS: case OP_MINUS: case OP_MULT: case OP_DIV: case OP_AND: free_term(fn->left); free_term(fn->right); free(fn); break; case OP_OR: free_term(fn->left); free_term(fn->right); free(fn); break; default: break; } } void print_term(struct term *fn) { switch (fn->op) { case OP_SYMBOL_LOADAVG: printf("loadavg"); break; case OP_SYMBOL_RUNABLE: printf("runnable_proc"); break; case OP_SYMBOL_CPUS: printf("onumcpus"); break; case OP_SYMBOL_USER: printf("user"); break; case OP_SYMBOL_NICE: printf("nice"); break; case OP_SYMBOL_SYSTEM: printf("system"); break; case OP_SYMBOL_IDLE: printf("idle"); break; case OP_SYMBOL_IOWAIT: printf("iowait"); break; case OP_SYMBOL_IRQ: printf("irq"); break; case OP_SYMBOL_SOFTIRQ: printf("softirq"); break; case OP_SYMBOL_STEAL: printf("steal"); break; case OP_SYMBOL_GUEST: printf("guest"); break; case OP_SYMBOL_GUEST_NICE: printf("guest_nice"); break; case OP_SYMBOL_SWAPRATE: printf("swaprate"); break; case OP_SYMBOL_FREEMEM: printf("freemem"); break; case OP_SYMBOL_APCR: printf("apcr"); break; case OP_SYMBOL_MEMINFO: printf("meminfo.%s[%u]", fn->proc_name, fn->index); break; case OP_SYMBOL_VMSTAT: printf("vmstat.%s[%u]", fn->proc_name, fn->index); break; case OP_SYMBOL_CPUSTAT: printf("cpustat.%s[%u]", fn->proc_name, fn->index); break; case OP_SYMBOL_TIME: printf("time[%u]", fn->index); break; case OP_CONST: printf("%f", fn->value); break; case OP_NEG: printf("-("); print_term(fn->left); printf(")"); break; case OP_NOT: printf("!("); print_term(fn->left); printf(")"); break; case OP_PLUS: case OP_MINUS: case OP_MULT: case OP_DIV: case OP_AND: case OP_OR: case OP_GREATER: case OP_LESSER: printf("("); print_term(fn->left); switch (fn->op) { case OP_AND: printf(") & ("); break; case OP_OR: printf(") | ("); break; case OP_GREATER: printf(") > ("); break; case OP_LESSER: printf(") < ("); break; case OP_PLUS: printf(") + ("); break; case OP_MINUS: printf(") - ("); break; case OP_MULT: printf(") * ("); break; case OP_DIV: printf(") / ("); break; // TODO OP_CONST, OP_SYMBOL_LOADAVG, ... possible here??? case OP_CONST: printf("%f", fn->value); break; case OP_SYMBOL_LOADAVG: case OP_SYMBOL_RUNABLE: case OP_SYMBOL_CPUS: case OP_SYMBOL_USER: case OP_SYMBOL_NICE: case OP_SYMBOL_SYSTEM: case OP_SYMBOL_IDLE: case OP_SYMBOL_IOWAIT: case OP_SYMBOL_IRQ: case OP_SYMBOL_SOFTIRQ: case OP_SYMBOL_STEAL: case OP_SYMBOL_GUEST: case OP_SYMBOL_GUEST_NICE: case OP_SYMBOL_APCR: case OP_SYMBOL_SWAPRATE: case OP_SYMBOL_FREEMEM: case OP_SYMBOL_MEMINFO: // TODO use default: ??? case OP_SYMBOL_VMSTAT: // TODO use default: ??? case OP_SYMBOL_CPUSTAT: // TODO use default: ??? case OP_SYMBOL_TIME: // TODO use default: ??? case OP_NEG: case OP_NOT: case VAR_LOAD: case VAR_RUN: case VAR_ONLINE: break; } print_term(fn->right); printf(")"); break; case VAR_LOAD: case VAR_RUN: case VAR_ONLINE: break; } } static struct term *parse_var_term(char **p) { char *s, *var_rvalue; struct term *fn; unsigned int length; char var_name[MAX_VARNAME + 1]; s = *p; length = 0; fn = NULL; while (isalnum(*s) || *s == '_') { var_name[length] = *s; length++; s++; if (length > MAX_VARNAME) cpuplugd_exit("Variable name too long (max. length is " "%i chars): %s\n", MAX_VARNAME, *p); } var_name[length] = '\0'; var_rvalue = get_var_rvalue(var_name); if (var_rvalue) { fn = parse_term(&var_rvalue, OP_PRIO_NONE); if (var_rvalue[0] != '\n') cpuplugd_exit("parsing error at %s, position: %s\n", var_name, var_rvalue); *p = s; } return fn; } struct term *parse_term(char **p, enum op_prio prio) { struct term *fn, *new; enum operation op; char *s, *endptr; double value; unsigned int length, i, index; s = *p; fn = NULL; if (*s == '-') { s++; fn = malloc(sizeof(struct term)); if (fn == NULL) goto out_error; if (isdigit(*s)) { value = 0; length = 0; sscanf(s, "%lf%n", &value, &length); fn->op = OP_CONST; fn->value = -value; s += length; } else { fn->op = OP_NEG; fn->left = parse_term(&s, prio); if (fn->left == NULL) goto out_error; } } else if (*s == '!') { s++; fn = malloc(sizeof(struct term)); if (fn == NULL) goto out_error; fn->op = OP_NOT; fn->left = parse_term(&s, prio); if (fn->left == NULL) goto out_error; } else if (isdigit(*s)) { value = 0; length = 0; sscanf(s, "%lf%n", &value, &length); for (i = 0; i < length; i++) s++; fn = malloc(sizeof(struct term)); if (fn == NULL) goto out_error; fn->op = OP_CONST; fn->value = value; } else if (*s == '(') { s++; fn = parse_term(&s, OP_PRIO_NONE); if (fn == NULL || *s != ')') goto out_error; s++; } else { /* Check for variable name */ fn = parse_var_term(&s); if (fn == NULL) { for (i = 0; i < sym_names_count; i++) if (strncmp(s, sym_names[i].name, strlen(sym_names[i].name)) == 0) break; if (i >= sym_names_count) /* Term doesn't make sense. */ goto out_error; /* * Parse meminfo/vmstat/cpustat with optional history * index [x] */ fn = malloc(sizeof(struct term)); if (fn == NULL) goto out_error; fn->op = sym_names[i].symop; s += strlen(sym_names[i].name); length = 0; if (fn->op == OP_SYMBOL_MEMINFO || fn->op == OP_SYMBOL_VMSTAT || fn->op == OP_SYMBOL_CPUSTAT) { while (isalpha(s[length]) || s[length] == '_') length++; fn->proc_name = malloc(length + 1); if (fn->proc_name == NULL) goto out_error; strncpy(fn->proc_name, s, length); fn->proc_name[length] = '\0'; } if (fn->op == OP_SYMBOL_MEMINFO || fn->op == OP_SYMBOL_VMSTAT || fn->op == OP_SYMBOL_CPUSTAT || fn->op == OP_SYMBOL_TIME) { if (s[length] == '[') { length++; if (!isdigit(s[length])) goto out_error; index = strtol(s + length, &endptr, 10); length = endptr - s; if (s[length] != ']') goto out_error; fn->index = index; if (history_max < index) history_max = index; length++; } s += length; } } } while (1) { switch (*s) { case '>': op = OP_GREATER; break; case '<': op = OP_LESSER; break; case '+': op = OP_PLUS; break; case '-': op = OP_MINUS; break; case '*': op = OP_MULT; break; case '/': op = OP_DIV; break; case '|': op = OP_OR; break; case '&': op = OP_AND; break; default: goto out; } if (prio >= op_prio_table[op]) break; s++; new = malloc(sizeof(struct term)); new->op = op; new->left = fn; if (new == NULL) goto out_error; new->right = parse_term(&s, op_prio_table[op]); if (new->right == NULL) { free(new); goto out_error; } fn = new; } out: *p = s; return fn; out_error: if (fn) free_term(fn); return NULL; } static double get_value(struct term *fn) { double value = 0; char *procinfo; unsigned int history_index; if (fn->index <= history_current) history_index = history_current - fn->index; else history_index = history_max + 1 - (fn->index - history_current); switch (fn->op) { case OP_SYMBOL_MEMINFO: procinfo = meminfo + history_index * meminfo_size; value = get_proc_value(procinfo, fn->proc_name, ':'); break; case OP_SYMBOL_VMSTAT: procinfo = vmstat + history_index * vmstat_size; value = get_proc_value(procinfo, fn->proc_name, ' '); break; case OP_SYMBOL_CPUSTAT: procinfo = cpustat + history_index * cpustat_size; value = get_proc_value(procinfo, fn->proc_name, ' '); break; case OP_SYMBOL_TIME: value = timestamps[history_index]; break; default: cpuplugd_exit("Invalid term specified: %i\n", fn->op); } return value; } double eval_double(struct term *fn, struct symbols *symbols) { double a, b, sum; switch (fn->op) { case OP_SYMBOL_LOADAVG: return symbols->loadavg; case OP_SYMBOL_RUNABLE: return symbols->runnable_proc; case OP_SYMBOL_CPUS: return symbols->onumcpus; case OP_SYMBOL_USER: return symbols->user; case OP_SYMBOL_NICE: return symbols->nice; case OP_SYMBOL_SYSTEM: return symbols->system; case OP_SYMBOL_IDLE: return symbols->idle; case OP_SYMBOL_IOWAIT: return symbols->iowait; case OP_SYMBOL_IRQ: return symbols->irq; case OP_SYMBOL_SOFTIRQ: return symbols->softirq; case OP_SYMBOL_STEAL: return symbols->steal; case OP_SYMBOL_GUEST: return symbols->guest; case OP_SYMBOL_GUEST_NICE: return symbols->guest_nice; case OP_SYMBOL_FREEMEM: return symbols->freemem; case OP_SYMBOL_APCR: return symbols->apcr; case OP_SYMBOL_SWAPRATE: return symbols->swaprate; case OP_SYMBOL_MEMINFO: case OP_SYMBOL_VMSTAT: case OP_SYMBOL_CPUSTAT: case OP_SYMBOL_TIME: return get_value(fn); case OP_CONST: return fn->value; case OP_NEG: return -eval_double(fn->left, symbols); case OP_PLUS: return eval_double(fn->left, symbols) + eval_double(fn->right, symbols); case OP_MINUS: return eval_double(fn->left, symbols) - eval_double(fn->right, symbols); case OP_MULT: a = eval_double(fn->left, symbols); b = eval_double(fn->right, symbols); sum = a*b; return sum; /*return eval_double(fn->left, symbols) * eval_double(fn->right, symbols);*/ case OP_DIV: a = eval_double(fn->left, symbols); b = eval_double(fn->right, symbols); sum = a/b; return sum; /*return eval_double(fn->left, symbols) / eval_double(fn->right, symbols); */ case OP_NOT: case OP_AND: case OP_OR: case OP_GREATER: case OP_LESSER: case VAR_LOAD: case VAR_RUN: case VAR_ONLINE: cpuplugd_exit("Invalid term specified: %i\n", fn->op); } return 0; } int eval_term(struct term *fn, struct symbols *symbols) { if (fn == NULL || symbols == NULL) return 0.0; switch (fn->op) { case OP_NOT: return !eval_term(fn->left, symbols); case OP_OR: return eval_term(fn->left, symbols) == 1 || eval_term(fn->right, symbols) == 1; case OP_AND: return eval_term(fn->left, symbols) == 1 && eval_term(fn->right, symbols) == 1; case OP_GREATER: return eval_double(fn->left, symbols) > eval_double(fn->right, symbols); case OP_LESSER: return eval_double(fn->left, symbols) < eval_double(fn->right, symbols); default: return eval_double(fn, symbols) != 0.0; } } s390-tools-2.3.0/dasdfmt/000077500000000000000000000000001323410431100147625ustar00rootroot00000000000000s390-tools-2.3.0/dasdfmt/Makefile000066400000000000000000000007531323410431100164270ustar00rootroot00000000000000include ../common.mak all: dasdfmt libs = $(rootdir)/libdasd/libdasd.a \ $(rootdir)/libvtoc/libvtoc.a \ $(rootdir)/libu2s/libu2s.a \ $(rootdir)/libutil/libutil.a dasdfmt: dasdfmt.o $(libs) install: all $(INSTALL) -d -m 755 $(DESTDIR)$(BINDIR) $(DESTDIR)$(MANDIR)/man8 $(INSTALL) -g $(GROUP) -o $(OWNER) -m 755 dasdfmt $(DESTDIR)$(BINDIR) $(INSTALL) -g $(GROUP) -o $(OWNER) -m 644 dasdfmt.8 \ $(DESTDIR)$(MANDIR)/man8 clean: rm -f *.o *~ dasdfmt core .PHONY: all install clean s390-tools-2.3.0/dasdfmt/dasdfmt.8000066400000000000000000000144751323410431100165100ustar00rootroot00000000000000.\" Copyright 2017 IBM Corp. .\" s390-tools is free software; you can redistribute it and/or modify .\" it under the terms of the MIT license. See LICENSE for details. .\" .TH DASDFMT 8 "Apr 2006" "s390-tools" .SH NAME dasdfmt \- formatting of DASD (ECKD) disk drives. .SH SYNOPSIS \fBdasdfmt\fR [-h] [-t] [-v] [-y] [-p] [-P] [-m \fIstep\fR] .br [-r \fIcylinder\fR] [-b \fIblksize\fR] [-l \fIvolser\fR] [-d \fIlayout\fR] .br [-L] [-V] [-F] [-k] [-C] [-M \fImode\fR] \fIdevice\fR .SH DESCRIPTION \fBdasdfmt\fR formats a DASD (ECKD) disk drive to prepare it for usage with Linux for S/390. The \fIdevice\fR is the node of the device (e.g. '/dev/dasda'). Any device node created by udev for kernel 2.6 can be used (e.g. '/dev/dasd/0.0.b100/disc'). .br \fBWARNING\fR: Careless usage of \fBdasdfmt\fR can result in \fBLOSS OF DATA\fR. .SH OPTIONS .TP \fB-h\fR or \fB--help\fR Print usage and exit. .TP \fB-t\fR or \fB--test\fR Disables any modification of the disk drive. .br \fBdasdfmt\fR just prints out, what it \fBwould\fR do. .TP \fB-v\fR Increases verbosity. .TP \fB-y\fR Start formatting without further user-confirmation. .TP \fB--norecordzero\fR Remove permission for subsystem to format write record zero. .br This is an expert option: Per default in recent dasd drivers, subsystems are granted the permission to format write record zero. This option is used to remove this permission. .br .TP \fB-L\fR or \fB--no_label\fR Omit the writing of a disk label after formatting. .br This makes only sense for the 'ldl' disk layout. .br The '-L' option has to be specified after the '-d ldl' option. .br e.g. dasdfmt -d ldl -L /dev/... .TP \fB-V\fR or \fB--version\fR Print version number and exit. .TP \fB-F\fR or \fB--force\fR Formats the device without performing sanity checking. .TP \fB-C\fR or \fB--check_host_count\fR Force dasdfmt to check the host access open count to ensure the device is not online on another operating system instance .TP \fB-d\fR \fIlayout\fR or \fB--disk_layout\fR=\fIlayout\fR Formats the device with compatible disk layout or linux disk layout. \fIlayout\fR is either \fIcdl\fR for the compatible disk layout (default) or \fIldl\fR for the linux disk layout. .br Compatible disk layout means a special handling of the first two tracks of the volume. This enables other S/390 or zSeries operating systems to access this device (e.g. for backup purposes). .TP \fB-p\fR or \fB--progressbar\fR Print a progress bar while formatting. Do not use this option if you are using a 3270 console, running in background or redirecting the output to a file. .TP \fB-P\fR or \fB--percentage\fR Print one line for each formatted cylinder showing the number of the cylinder and percentage of formatting process. Intended to be used by higher level interfaces. .TP \fB-m\fR \fIstep\fR or \fB--hashmarks\fR=\fIstep\fR Print a hashmark every \fIstep\fR cylinders. The value \fIstep\fR has to be within range [1,1000], otherwise it will be set to the default, which is 10. .br You can use this option to see the progress of formatting in case you are not able to use the progress bar option -p, e.g. with a 3270 terminal. .br The value will be at least as big as the -r or --requestsize value. .br .TP \fB-M\fR \fImode\fR or \fB--mode\fR=\fImode\fR Specify the \fImode\fR to be used to format the device. Valid modes are: .RS .IP full Format the entire disk with the specified blocksize. (default) .IP quick Format the first two tracks and write label and partition information. Only use this option if you are sure that the target DASD already contains a regular format with the specified blocksize. A blocksize can optionally be specified using \fB-b\fR (\fB--blocksize\fR). .IP expand Format all unformatted tracks at the end of the target DASD. This mode assumes that tracks at the beginning of the DASD volume have already been correctly formatted, while a consecutive set of tracks at the end are unformatted. You can use this mode to make added space available for Linux use after dynamically increasing the size of a DASD volume. A blocksize can optionally be specified using \fB-b\fR (\fB--blocksize\fR). .RE .TP \fB--check\fR Perform a complete format check on a DASD volume. A blocksize can be specified with \fB-b\fR (\fB--blocksize\fR). .TP \fB-r\fR \fIcylindercount\fR or \fB--requestsize\fR=\fIcylindercount\fR Number of cylinders to be processed in one formatting step. The value must be an integer in the range 1 - 255. .br Use this parameter to exploit any available PAV devices. The number of cylinders optimally matches the number of associated devices, counting the base device and all alias devices. .br .TP \fB-b\fR \fIblksize\fR or \fB--blocksize\fR=\fIblksize\fR Specify blocksize to be used. \fIblksize\fR must be a positive integer and always be a power of two. The recommended blocksize is 4096 bytes. .TP \fB-l\fR \fIvolser\fR or \fB--label\fR=\fIvolser\fR Specify the volume serial number or volume identifier to be written to disk after formatting. If no label is specified, a sensible default is used. \fIvolser\fR is interpreted as ASCII string and is automatically converted to uppercase and then to EBCDIC. .br e.g. -l LNX001 or --label=DASD01 .br The \fIvolser\fR identifies by serial number the volume. A volume serial number is 1 through 6 alphanumeric or one of the following special characters: $, #, @, %. Enclose a serial number that contains special characters in apostrophes. If the number is shorter than six characters, it is padded with trailing blanks. .br Do not code a volume serial number as SCRTCH, PRIVAT, or Lnnnnn (L with five numbers); these are used in OS/390 messages to ask the operator to mount a volume. Do not code a volume serial number as MIGRAT, which is used by the OS/390 Hierarchical Storage Manager DFSMShsm for migrated data sets. .br NOTE: Try to avoid using special characters in the volume serial. This may cause problems accessing a disk by volser. .br In case you really have to use special characters, make sure you are using quotes. In addition there is a special handling for the '$' sign. Please specify it using '\\$' if necessary. .br e.g. -l 'a@b\\$c#' to get A@B$C# .br .TP \fB-k\fR or \fB--keep_volser\fR Keeps the Volume Serial Number, when writing the Volume Label. This is useful, if the Serial Number has been written with a VM Tool and should not be overwritten. .br .SH SEE ALSO .BR fdasd (8) s390-tools-2.3.0/dasdfmt/dasdfmt.c000066400000000000000000001275411323410431100165620ustar00rootroot00000000000000/* * dasdfmt - Format DASD ECKD devices for use by Linux * * Copyright IBM Corp. 1999, 2017 * Copyright Red Hat Inc. 2017 * * 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 #include #include #include #include "lib/dasd_base.h" #include "lib/dasd_sys.h" #include "lib/util_opt.h" #include "lib/util_prg.h" #include "lib/util_proc.h" #include "lib/vtoc.h" #include "lib/zt_common.h" #include "dasdfmt.h" #define BUSIDSIZE 8 #define SEC_PER_DAY (60 * 60 * 24) #define SEC_PER_HOUR (60 * 60) static int filedes; static char dev_filename[PATH_MAX]; static int disk_disabled; static format_data_t format_params; static format_mode_t mode; static char *prog_name; static volatile sig_atomic_t program_interrupt_in_progress; static int reqsize; static const struct util_prg prg = { .desc = "Use dasdfmt to format a DASD ECKD device for use by Linux.\n" "DEVICE is the node of the device (e.g. '/dev/dasda').", .args = "DEVICE", .copyright_vec = { { .owner = "IBM Corp.", .pub_first = 1999, .pub_last = 2017, }, UTIL_PRG_COPYRIGHT_END } }; /* Defines for options with no short command */ #define OPT_CHECK 128 #define OPT_NOZERO 129 static struct util_opt opt_vec[] = { UTIL_OPT_SECTION("FORMAT ACTIONS"), { .option = { "mode", required_argument, NULL, 'M' }, .argument = "MODE", .desc = "Specify scope of operation using MODE:\n" " full: Full device (default)\n" " quick: Only the first two tracks\n" " expand: Unformatted tracks at device end", }, { .option = { "check", no_argument, NULL, OPT_CHECK }, .desc = "Perform complete format check on device", .flags = UTIL_OPT_FLAG_NOSHORT, }, UTIL_OPT_SECTION("FORMAT OPTIONS"), { .option = { "blocksize", required_argument, NULL, 'b' }, .argument = "SIZE", .desc = "Format blocks to SIZE bytes (default 4096)", }, { .option = { "disk_layout", required_argument, NULL, 'd' }, .argument = "LAYOUT", .desc = "Specify the disk layout:\n" " cdl: Compatible Disk Layout (default)\n" " ldl: Linux Disk Layout", }, { .option = { "keep_volser", no_argument, NULL, 'k' }, .desc = "Do not change the current volume serial", }, { .option = { "label", required_argument, NULL, 'l' }, .argument = "VOLSER", .desc = "Specify volume serial number", }, { .option = { "no_label", no_argument, NULL, 'L' }, .desc = "Don't write a disk label", }, { .option = { "requestsize", required_argument, NULL, 'r' }, .argument = "NUM", .desc = "Process NUM cylinders in one formatting step", }, { .option = { "norecordzero", no_argument, NULL, OPT_NOZERO }, .desc = "Prevent storage server from modifying record 0", .flags = UTIL_OPT_FLAG_NOSHORT, }, { .option = { NULL, no_argument, NULL, 'y' }, .desc = "Start formatting without further user-confirmation", .flags = UTIL_OPT_FLAG_NOLONG, }, UTIL_OPT_SECTION("DISPLAY PROGRESS"), { .option = { "hashmarks", required_argument, NULL, 'm' }, .argument = "NUM", .desc = "Show a hashmark every NUM cylinders", }, { .option = { "progressbar", no_argument, NULL, 'p' }, .desc = "Show a progressbar", }, { .option = { "percentage", no_argument, NULL, 'P' }, .desc = "Show progress in percent", }, UTIL_OPT_SECTION("MISC"), { .option = { "check_host_count", no_argument, NULL, 'C' }, .desc = "Check if device is in use by other hosts", }, { .option = { "force", no_argument, NULL, 'F' }, .desc = "Format without performing sanity checking", }, { .option = { "test", no_argument, NULL, 't' }, .desc = "Run in dry-run mode without modifying the DASD", }, { .option = { NULL, no_argument, NULL, 'v' }, .desc = "Print verbose messages when executing", .flags = UTIL_OPT_FLAG_NOLONG, }, UTIL_OPT_HELP, { .option = { "version", no_argument, NULL, 'V' }, .desc = "Print version information, then exit", }, UTIL_OPT_END }; /* * Helper function to calculate the days, hours, minutes, and seconds * for a given timestamp in seconds */ static void calc_time(time_t time, int *d, int *h, int *m, int *s) { *d = time / SEC_PER_DAY; time %= SEC_PER_DAY; *h = time / SEC_PER_HOUR; time %= SEC_PER_HOUR; *m = time / 60; *s = time % 60; } /* * This function calculates and prints the estimated time of accomplishment. */ static void print_eta(int p_new, int started) { static struct timeval start; struct timeval now; time_t time_elapsed; time_t time_end; int d, h, m, s; static int p_init; int p; if (!started) { gettimeofday(&start, NULL); p_init = p_new; } gettimeofday(&now, NULL); time_elapsed = now.tv_sec - start.tv_sec; /* * We might start somewhere in the middle with an initial percentage * value of i.e. 60%. Therefore we need to calculate the relative * percentage of p_new within that remaining range (100 - 60) in order * to correctly estimate the remaining time. */ if (p_init == 100) p = p_init; else p = 100 * (p_new - p_init) / (100 - p_init); if (p == 0) time_end = time_elapsed; else time_end = time_elapsed * (100 - p) / p; /* Calculate days, hours, minutes, and seconds */ calc_time(time_end, &d, &h, &m, &s); if (p_new == 100) calc_time(time_elapsed, &d, &h, &m, &s); /* Avoid printing leading zeros */ if (d > 0) printf(" [%dd %dh %dm %ds%-4s", d, h, m, s, "]"); else if (h > 0) printf(" [%dh %dm %ds%-7s", h, m, s, "]"); else if (m > 0) printf(" [%dm %ds%-6s", m, s, "]"); else if (s > 0 || p > 50) printf(" [%ds%-5s", s, "]"); else printf(" [--%-1s", "]"); } /* * Draw the progress indicator depending on what command line argument is set. * This can either be a progressbar, hashmarks, or percentage. */ static void draw_progress(dasdfmt_info_t *info, int cyl, unsigned int cylinders, int aborted) { static int hashcount; static int started; static int p_old; int p_new = 0; int barlength; int i; if (info->print_progressbar) { printf("cyl %7d of %7d |", cyl, cylinders); p_new = cyl * 100 / cylinders; if (p_new != p_old || !started || aborted) { /* percent value has changed */ p_old = p_new; barlength = cyl * 33 / cylinders; for (i = 1; i <= barlength; i++) printf("#"); for (i = barlength + 1; i <= 33; i++) printf("-"); printf("|%3d%%", p_new); if (aborted) p_new = 100; print_eta(p_new, started); started = 1; } printf("\r"); fflush(stdout); } if (info->print_hashmarks && (cyl / info->hashstep - hashcount) != 0) { printf("#"); fflush(stdout); hashcount++; } if (info->print_percentage) { printf("cyl %7d of %7d |%3d%%\n", cyl, cylinders, cyl * 100 / cylinders); fflush(stdout); } } /* * Helper function for recs_per_track. */ static inline unsigned int ceil_quot(unsigned int d1, unsigned int d2) { return (d1 + (d2 - 1)) / d2; } /* * Calculate records per track depending on the device characteristics. */ static unsigned int recs_per_track(struct dasd_eckd_characteristics *rdc, unsigned int kl, unsigned int dl) { int dn, kn; switch (rdc->dev_type) { case 0x3380: if (kl) return 1499 / (15 + 7 + ceil_quot(kl + 12, 32) + ceil_quot(dl + 12, 32)); else return 1499 / (15 + ceil_quot(dl + 12, 32)); case 0x3390: dn = ceil_quot(dl + 6, 232) + 1; if (kl) { kn = ceil_quot(kl + 6, 232) + 1; return 1729 / (10 + 9 + ceil_quot(kl + 6 * kn, 34) + 9 + ceil_quot(dl + 6 * dn, 34)); } else { return 1729 / (10 + 9 + ceil_quot(dl + 6 * dn, 34)); } case 0x9345: dn = ceil_quot(dl + 6, 232) + 1; if (kl) { kn = ceil_quot(kl + 6, 232) + 1; return 1420 / (18 + 7 + ceil_quot(kl + 6 * kn, 34) + ceil_quot(dl + 6 * dn, 34)); } else { return 1420 / (18 + 7 + ceil_quot(dl + 6 * dn, 34)); } } return 0; } /* * Evaluate errors recognized by format checks and print appropriate error * messages depending on the content of cdata. */ static void evaluate_format_error(dasdfmt_info_t *info, format_check_t *cdata, unsigned int heads) { struct dasd_eckd_characteristics *rdc; /* Special blocksize values of the first 3 records of trk 0 of cyl 0 */ const int blksizes_trk0[] = { 24, 144, 80 }; /* Special blocksize value of trk 1 cyl 0 */ const int blksize_trk1 = 96; unsigned int cyl; unsigned int head; unsigned int rpt; unsigned int kl = 0; int blksize = cdata->expect.blksize; if (info->print_progressbar || info->print_hashmarks) printf("\n"); /* * If mode is not QUICK and a device format couldn't be determined, the * device is considered not formatted. * Also, reading record zero will never happen. If the record in error * is 0 nonetheless, the device is not formatted at all as well! */ if ((info->dasd_info.format == DASD_FORMAT_NONE && mode != QUICK) || cdata->rec == 0) { ERRMSG("WARNING: The specified device is not " "formatted at all.\n"); return; } cyl = cdata->unit / heads; head = cyl >> 16; head <<= 4; head |= cdata->unit % heads; /* * Set special expected values for the first 3 records of trk 0 of cyl 0 * and trk 1 of cyl 0 when checking a CDL formatted device. */ if ((cdata->expect.intensity & DASD_FMT_INT_COMPAT) && cyl == 0 && head == 0 && cdata->rec < 4) { kl = 4; blksize = blksizes_trk0[cdata->rec - 1]; } if ((cdata->expect.intensity & DASD_FMT_INT_COMPAT) && cyl == 0 && head == 1) { kl = 44; blksize = blksize_trk1; } rdc = (struct dasd_eckd_characteristics *) &info->dasd_info.characteristics; rpt = recs_per_track(rdc, kl, cdata->expect.blksize); ERRMSG("WARNING: The specified device is not formatted as expected.\n"); switch (cdata->result) { case DASD_FMT_ERR_TOO_FEW_RECORDS: ERRMSG("Too few records (found %d, expected %d) " "at cyl: %d trk: %d rec: %d.\n", cdata->num_records, rpt, cyl, head, cdata->rec); break; case DASD_FMT_ERR_TOO_MANY_RECORDS: ERRMSG("Too many records (found %d, expected %d) " "at cyl: %d trk: %d rec: %d.\n", cdata->num_records, rpt, cyl, head, cdata->rec); break; case DASD_FMT_ERR_BLKSIZE: ERRMSG("Invalid blocksize (found %d, expected %d) " "at cyl: %d trk: %d rec: %d.\n", cdata->blksize, blksize, cyl, head, cdata->rec); break; case DASD_FMT_ERR_RECORD_ID: ERRMSG("Invalid record ID at cyl: %d trk: %d rec: %d.\n", cyl, head, cdata->rec); break; case DASD_FMT_ERR_KEY_LENGTH: ERRMSG("Invalid key length (found %d, expected %d) " "at cyl: %d trk: %d rec: %d.\n", cdata->key_length, kl, cyl, head, cdata->rec); break; } } static void disk_enable(void) { int err; err = dasd_disk_enable(filedes); if (err != 0) ERRMSG_EXIT(EXIT_FAILURE, "%s: (prepare device) IOCTL " "BIODASDENABLE failed (%s)\n", prog_name, strerror(err)); disk_disabled = 0; } static void disk_disable(const char *device) { int err; err = dasd_disk_disable(device, &filedes); if (err != 0) ERRMSG_EXIT(EXIT_FAILURE, "%s: (prepare device) IOCTL " "BIODASDDISABLE failed. (%s)\n", prog_name, strerror(err)); disk_disabled = 1; } /* * signal handler: * enables the disk again in case of SIGTERM, SIGINT and SIGQUIT */ static void program_interrupt_signal(int sig) { int rc; if (program_interrupt_in_progress) raise(sig); program_interrupt_in_progress = 1; if (disk_disabled) { printf("Re-accessing the device...\n"); disk_enable(); } printf("Rereading the partition table...\n"); rc = dasd_reread_partition_table(dev_filename, 5); if (rc) { ERRMSG("%s: (signal handler) Re-reading partition table " "failed. (%s)\n", prog_name, strerror(rc)); } else { printf("Exiting...\n"); } signal(sig, SIG_DFL); raise(sig); } /* * check given device name for blanks and some special characters */ static void get_device_name(char *devname, int optind, int argc, char *argv[]) { struct util_proc_dev_entry dev_entry; struct stat dev_stat; if (optind + 1 < argc) ERRMSG_EXIT(EXIT_MISUSE, "%s: More than one device specified!\n", prog_name); if (optind >= argc) ERRMSG_EXIT(EXIT_MISUSE, "%s: No device specified!\n", prog_name); if (strlen(argv[optind]) >= PATH_MAX) ERRMSG_EXIT(EXIT_MISUSE, "%s: device name too long!\n", prog_name); strcpy(devname, argv[optind]); if (stat(devname, &dev_stat) != 0) ERRMSG_EXIT(EXIT_MISUSE, "%s: Could not get information for " "device node %s: %s\n", prog_name, devname, strerror(errno)); if (minor(dev_stat.st_rdev) & PARTN_MASK) { ERRMSG_EXIT(EXIT_MISUSE, "%s: Unable to format partition %s. " "Please specify a device.\n", prog_name, devname); } if (util_proc_dev_get_entry(dev_stat.st_rdev, 1, &dev_entry) == 0) { if (strncmp(dev_entry.name, "dasd", 4) != 0) ERRMSG_EXIT(EXIT_MISUSE, "%s: Unsupported device type '%s'.\n", prog_name, dev_entry.name); } else { printf("%s WARNING: Unable to get driver name for device node %s", prog_name, devname); } } static void get_blocksize(const char *device, unsigned int *blksize) { int err; err = dasd_get_blocksize(device, blksize); if (err != 0) ERRMSG_EXIT(EXIT_FAILURE, "%s: the ioctl to get the blocksize " "of the device failed (%s).\n", prog_name, strerror(err)); } /* * Check whether a specified blocksize matches the blocksize of the device */ static void check_blocksize(dasdfmt_info_t *info, unsigned int blksize) { unsigned int dev_blksize; if (!info->blksize_specified || info->dasd_info.format == DASD_FORMAT_NONE) return; get_blocksize(dev_filename, &dev_blksize); if (dev_blksize != blksize) { ERRMSG_EXIT(EXIT_FAILURE, "WARNING: Device is formatted with a " "different blocksize (%d).\nUse --mode=full to " "perform a clean format.\n", dev_blksize); } } /* * Check whether a specified layout matches the layout * a device is formatted with. */ static void check_layout(dasdfmt_info_t *info, unsigned int intensity) { char layout[4]; if (!info->layout_specified || info->dasd_info.format == DASD_FORMAT_NONE) return; if ((intensity & DASD_FMT_INT_COMPAT) && info->dasd_info.format == DASD_FORMAT_CDL) return; if (!(intensity & DASD_FMT_INT_COMPAT) && info->dasd_info.format == DASD_FORMAT_LDL) return; if (info->dasd_info.format == DASD_FORMAT_CDL) sprintf(layout, "CDL"); if (info->dasd_info.format == DASD_FORMAT_LDL) sprintf(layout, "LDL"); ERRMSG_EXIT(EXIT_FAILURE, "WARNING: Device is formatted with a " "different layout (%s).\n", layout); } /* * check for disk type and set some variables (e.g. usage count) */ static void check_disk(dasdfmt_info_t *info, char *devname) { int err; bool ro; err = dasd_is_ro(devname, &ro); if (err != 0) ERRMSG_EXIT(EXIT_FAILURE, "%s: the ioctl call to retrieve read/write " "status information failed (%s)\n", prog_name, strerror(err)); if (ro) ERRMSG_EXIT(EXIT_FAILURE, "Disk is read only!\n"); if (!info->force) if (info->dasd_info.open_count > 1) ERRMSG_EXIT(EXIT_BUSY, "Disk in use!\n"); if (strncmp(info->dasd_info.type, "ECKD", 4) != 0) { ERRMSG_EXIT(EXIT_FAILURE, "%s: Unsupported disk type\n%s is not an " "ECKD disk!\n", prog_name, devname); } if (dasd_sys_raw_track_access(devname)) { ERRMSG_EXIT(EXIT_FAILURE, "%s: Device '%s' is in raw-track access mode\n", prog_name, devname); } } /* * check the volume serial for special * characters and move blanks to the end */ static int check_volser(char *s, int devno) { int i, j; for (i = 0; i < 6; i++) { if ((s[i] < 0x20) || (s[i] > 0x7a) || ((s[i] >= 0x21) && (s[i] <= 0x22)) || /* !" */ ((s[i] >= 0x26) && (s[i] <= 0x2f)) || /* &'()*+,-./ */ ((s[i] >= 0x3a) && (s[i] <= 0x3f)) || /* :;<=>? */ ((s[i] >= 0x5b) && (s[i] <= 0x60))) /* \]^_` */ s[i] = ' '; s[i] = toupper(s[i]); } s[6] = 0x00; for (i = 0; i < 6; i++) { if (s[i] == ' ') for (j = i; j < 6; j++) if (s[j] != ' ') { s[i] = s[j]; s[j] = ' '; break; } } if (s[0] == ' ') { printf("Usage error, switching to default.\n"); sprintf(s, "0X%04x", devno); for (i = 0; i < 6; i++) s[i] = toupper(s[i]); return -1; } return 0; } /* * do some blocksize checks */ static int check_param(char *s, size_t buffsize, format_data_t *data) { int tmp = data->blksize; if ((tmp < 512) || (tmp > 4096)) { strncpy(s, "Blocksize must be one of the following positive " "integers:\n512, 1024, 2048, 4096.", buffsize); if (buffsize > 0) s[buffsize - 1] = '\0'; return -1; } while (tmp > 0) { if ((tmp % 2) && (tmp != 1)) { strncpy(s, "Blocksize must be a power of 2.", buffsize); if (buffsize > 0) s[buffsize - 1] = '\0'; return -1; } tmp /= 2; } return 0; } /* * Retrieve disk information and set cylinders and heads accordingly. */ static void set_geo(dasdfmt_info_t *info, unsigned int *cylinders, unsigned int *heads) { struct dasd_eckd_characteristics *characteristics; if (info->verbosity > 0) printf("Retrieving disk geometry...\n"); characteristics = (struct dasd_eckd_characteristics *) &info->dasd_info.characteristics; if (characteristics->no_cyl == LV_COMPAT_CYL && characteristics->long_no_cyl) *cylinders = characteristics->long_no_cyl; else *cylinders = characteristics->no_cyl; *heads = characteristics->trk_per_cyl; } /* * Set VTOC label information */ static void set_label(dasdfmt_info_t *info, volume_label_t *vlabel, format_data_t *p, unsigned int cylinders) { char inp_buffer[5]; if (info->writenolabel) { if (cylinders > LV_COMPAT_CYL && !info->withoutprompt) { printf("\n--->> ATTENTION! <<---\n"); printf("You specified to write no labels to a" " volume with more then %u cylinders.\n" "Cylinders above this limit will not be" " accessible as a linux partition!\n" "Type \"yes\" to continue, no will leave" " the disk untouched: ", LV_COMPAT_CYL); if (fgets(inp_buffer, sizeof(inp_buffer), stdin) == NULL) return; if (strcasecmp(inp_buffer, "yes") && strcasecmp(inp_buffer, "yes\n")) { printf("Omitting ioctl call (disk will " "NOT be formatted).\n"); return; } } } else { if (!info->labelspec && !info->keep_volser) { char buf[7]; sprintf(buf, "0X%04x", info->dasd_info.devno); check_volser(buf, info->dasd_info.devno); vtoc_volume_label_set_volser(vlabel, buf); } if (p->intensity & DASD_FMT_INT_COMPAT) { info->cdl_format = 1; vtoc_volume_label_set_label(vlabel, "VOL1"); vtoc_volume_label_set_key(vlabel, "VOL1"); vtoc_set_cchhb(&vlabel->vtoc, 0x0000, 0x0001, 0x01); } else { vtoc_volume_label_set_label(vlabel, "LNX1"); } } } /* * Check whether hashsteps are within the correct interval. */ static void check_hashmarks(dasdfmt_info_t *info) { if (info->print_hashmarks) { if (info->hashstep < reqsize) info->hashstep = reqsize; if ((info->hashstep < 1) || (info->hashstep > 1000)) { printf("Hashmark increment is not in range <1,1000>, " "using the default.\n"); info->hashstep = 10; } printf("Printing hashmark every %d cylinders.\n", info->hashstep); } } /* * This function checks whether a range of tracks is in regular format * with the specified block size. */ static format_check_t check_track_format(dasdfmt_info_t *info, format_data_t *p) { format_check_t cdata = { .expect = { .blksize = p->blksize, .intensity = p->intensity, .start_unit = p->start_unit, .stop_unit = p->stop_unit }, 0 }; int err; err = dasd_check_format(dev_filename, &cdata); if (err != 0) { if (err == ENOTTY) { ERRMSG("%s: Missing kernel support for format checking", prog_name); if (mode == EXPAND) { ERRMSG(". Mode 'expand' cannot be used"); } else if (!info->check) { ERRMSG(" (--force to override)"); } ERRMSG_EXIT(EXIT_FAILURE, ".\n"); } ERRMSG_EXIT(EXIT_FAILURE, "%s: Could no check format: %s\n", prog_name, strerror(err)); } return cdata; } /* * Either do the actual format or check depending on the check-value. */ static int process_tracks(dasdfmt_info_t *info, unsigned int cylinders, unsigned int heads, format_data_t *format_params) { format_check_t cdata = { .expect = {0}, 0}; format_data_t step = *format_params; unsigned long step_value; unsigned long cur_trk; int cyl = 0, err; check_hashmarks(info); cur_trk = format_params->start_unit; while (cur_trk < format_params->stop_unit) { step_value = reqsize * heads - (cur_trk % heads); step.start_unit = cur_trk; if (cur_trk + heads * reqsize >= format_params->stop_unit) step.stop_unit = format_params->stop_unit; else step.stop_unit = cur_trk + step_value - 1; if (info->check) { cdata = check_track_format(info, &step); if (cdata.result) { cyl = cur_trk / heads + 1; draw_progress(info, cyl, cylinders, 1); evaluate_format_error(info, &cdata, heads); break; } } else { err = dasd_format_disk(filedes, &step); if (err != 0) ERRMSG_EXIT(EXIT_FAILURE, "%s: the ioctl call " "to format tracks failed. (%s)\n", prog_name, strerror(err)); } cyl = cur_trk / heads + 1; draw_progress(info, cyl, cylinders, 0); cur_trk += step_value; } /* We're done, draw the 100% mark */ if (!cdata.result) { cyl = step.stop_unit / heads + 1; draw_progress(info, cyl, cylinders, 0); printf("\n"); } return cdata.result; } /* * This function checks the format of the entire disk. */ static void check_disk_format(dasdfmt_info_t *info, unsigned int cylinders, unsigned int heads, format_data_t *check_params) { check_params->start_unit = 0; check_params->stop_unit = (cylinders * heads) - 1; printf("Checking format of the entire disk...\n"); if (info->testmode) { printf("Test mode active, omitting ioctl.\n"); return; } check_blocksize(info, check_params->blksize); check_layout(info, check_params->intensity); /* * If no layout was specified, set the intensity * according to what the layout seems to be. */ if (!info->layout_specified) { if (info->dasd_info.format == DASD_FORMAT_CDL) check_params->intensity |= DASD_FMT_INT_COMPAT; else if (info->dasd_info.format == DASD_FORMAT_LDL) check_params->intensity &= ~DASD_FMT_INT_COMPAT; } if (process_tracks(info, cylinders, heads, check_params)) { ERRMSG_EXIT(EXIT_FAILURE, "Use --mode=full to perform a " "clean format.\n"); } printf("Done. Disk is fine.\n"); } /* * ask the user to specify a blocksize */ static format_data_t ask_user_for_blksize(format_data_t params) { char c, str[ERR_LENGTH], buffer[20]; int i, rc; i = params.blksize; do { params.blksize = i; printf("Please enter the blocksize of the formatting [%d]: ", i); if (fgets(buffer, sizeof(buffer), stdin) == NULL) break; rc = sscanf(buffer, "%d%c", ¶ms.blksize, &c); if ((rc == 2) && (c == '\n')) rc = 1; if (rc == -1) rc = 1; /* this happens, if enter is pressed */ if (rc != 1) printf(" -- wrong input, try again.\n"); if (check_param(str, ERR_LENGTH, ¶ms) < 0) { printf(" -- %s\n", str); rc = 0; } } while (rc != 1); return params; } /* * print all information needed to format the device */ static void dasdfmt_print_info(dasdfmt_info_t *info, char *devname, volume_label_t *vlabel, unsigned int cylinders, unsigned int heads, format_data_t *p) { char volser[6], vollbl[4]; printf("Drive Geometry: %d Cylinders * %d Heads = %d Tracks\n", cylinders, heads, (cylinders * heads)); printf("\nI am going to format the device "); printf("%s in the following way:\n", devname); printf(" Device number of device : 0x%x\n", info->dasd_info.devno); printf(" Labelling device : %s\n", (info->writenolabel) ? "no" : "yes"); if (!info->writenolabel) { vtoc_volume_label_get_label(vlabel, vollbl); printf(" Disk label : %.4s\n", vollbl); vtoc_volume_label_get_volser(vlabel, volser); printf(" Disk identifier : %.6s\n", volser); } printf(" Extent start (trk no) : %u\n", p->start_unit); printf(" Extent end (trk no) : %u\n", p->stop_unit); printf(" Compatible Disk Layout : %s\n", (p->intensity & DASD_FMT_INT_COMPAT) ? "yes" : "no"); printf(" Blocksize : %d\n", p->blksize); printf(" Mode : %s\n", mode_str[mode]); if (info->testmode) printf("Test mode active, omitting ioctl.\n"); } /* * get volser */ static int dasdfmt_get_volser(char *devname, dasd_information2_t *dasd_info, char *volser) { unsigned int blksize; volume_label_t vlabel; get_blocksize(devname, &blksize); if ((strncmp(dasd_info->type, "ECKD", 4) == 0) && !dasd_info->FBA_layout) { /* OS/390 and zOS compatible disk layout */ vtoc_read_volume_label(devname, dasd_info->label_block * blksize, &vlabel); vtoc_volume_label_get_volser(&vlabel, volser); return 0; } else { return -1; } } /* * do all the labeling (volume label and initial VTOC) */ static void dasdfmt_write_labels(dasdfmt_info_t *info, volume_label_t *vlabel, unsigned int cylinders, unsigned int heads) { int label_position; struct hd_geometry geo; format4_label_t f4; format5_label_t f5; format7_label_t f7; unsigned int blksize; int rc, fd; void *ipl1_record, *ipl2_record; int ipl1_record_len, ipl2_record_len; if (info->verbosity > 0) printf("Retrieving dasd information... "); get_blocksize(dev_filename, &blksize); /* * Don't rely on the cylinders returned by HDIO_GETGEO, they might be * to small. geo is only used to get the number of sectors, which may * vary depending on the format. */ rc = dasd_get_geo(dev_filename, &geo); if (rc != 0) ERRMSG_EXIT(EXIT_FAILURE, "%s: (write labels) IOCTL " "HDIO_GETGEO failed (%s).\n", prog_name, strerror(rc)); if (info->verbosity > 0) printf("ok\n"); /* write empty bootstrap (initial IPL records) */ if (info->verbosity > 0) printf("Writing empty bootstrap...\n"); /* * Note: ldl labels do not contain the key field */ if (info->cdl_format) { /* Prepare copy with key (CDL) */ ipl1_record = &ipl1; ipl2_record = &ipl2; ipl1_record_len = sizeof(ipl1); ipl2_record_len = sizeof(ipl2); } else { /* Prepare copy without key (LDL) */ ipl1_record = ipl1.data; ipl2_record = ipl2.data; ipl1_record_len = sizeof(ipl1.data); ipl2_record_len = sizeof(ipl2.data); } fd = open(dev_filename, O_RDWR); if (fd < 0) ERRMSG_EXIT(EXIT_FAILURE, "%s: Unable to open device " "'%s' (%s)\n", prog_name, dev_filename, strerror(errno)); if (lseek(fd, 0, SEEK_SET) != 0) { close(fd); ERRMSG_EXIT(EXIT_FAILURE, "%s: lseek command 0 failed " "(%s)\n", prog_name, strerror(errno)); } rc = write(fd, ipl1_record, ipl1_record_len); if (rc != ipl1_record_len) { close(fd); ERRMSG_EXIT(EXIT_FAILURE, "%s: Writing the bootstrap IPL1 " "failed, only wrote %d bytes.\n", prog_name, rc); } label_position = blksize; rc = lseek(fd, label_position, SEEK_SET); if (rc != label_position) { close(fd); ERRMSG_EXIT(EXIT_FAILURE, "%s: lseek command to %i failed " "(%s).\n", prog_name, label_position, strerror(errno)); } rc = write(fd, ipl2_record, ipl2_record_len); if (rc != ipl2_record_len) { close(fd); ERRMSG_EXIT(EXIT_FAILURE, "%s: Writing the bootstrap IPL2 " "failed, only wrote %d bytes.\n", prog_name, rc); } /* write VTOC */ vtoc_init_format4_label(&f4, geo.cylinders, cylinders, heads, geo.sectors, blksize, info->dasd_info.dev_type); vtoc_init_format5_label(&f5); vtoc_init_format7_label(&f7); vtoc_set_freespace(&f4, &f5, &f7, '+', 0, FIRST_USABLE_TRK, (cylinders * heads - 1), cylinders, heads); label_position = info->dasd_info.label_block * blksize; if (info->verbosity > 0) printf("Writing label...\n"); rc = lseek(fd, label_position, SEEK_SET); if (rc != label_position) { close(fd); ERRMSG_EXIT(EXIT_FAILURE, "%s: lseek command to %i failed " "(%s).\n", prog_name, label_position, strerror(errno)); } /* * Note: cdl volume labels do not contain the 'formatted_blocks' part * and ldl labels do not contain the key field */ if (info->cdl_format) { rc = write(fd, vlabel, (sizeof(*vlabel) - sizeof(vlabel->formatted_blocks))); } else { vlabel->ldl_version = 0xf2; /* EBCDIC '2' */ vlabel->formatted_blocks = cylinders * heads * geo.sectors; rc = write(fd, &vlabel->vollbl, (sizeof(*vlabel) - sizeof(vlabel->volkey))); } if (((rc != sizeof(*vlabel) - sizeof(vlabel->formatted_blocks)) && info->cdl_format) || ((rc != (sizeof(*vlabel) - sizeof(vlabel->volkey))) && !info->cdl_format)) { close(fd); ERRMSG_EXIT(EXIT_FAILURE, "%s: Error writing volume label " "(%d).\n", prog_name, rc); } if (info->verbosity > 0) printf("Writing VTOC... "); label_position = (VTOC_START_CC * heads + VTOC_START_HH) * geo.sectors * blksize; rc = lseek(fd, label_position, SEEK_SET); if (rc != label_position) { close(fd); ERRMSG_EXIT(EXIT_FAILURE, "%s: lseek command to %i failed " "(%s).\n", prog_name, label_position, strerror(errno)); } /* write VTOC FMT4 DSCB */ rc = write(fd, &f4, sizeof(format4_label_t)); if (rc != sizeof(format4_label_t)) { close(fd); ERRMSG_EXIT(EXIT_FAILURE, "%s: Error writing FMT4 label " "(%d).\n", prog_name, rc); } label_position += blksize; rc = lseek(fd, label_position, SEEK_SET); if (rc != label_position) { close(fd); ERRMSG_EXIT(EXIT_FAILURE, "%s: lseek to %i failed (%s).\n", prog_name, label_position, strerror(errno)); } /* write VTOC FMT5 DSCB */ rc = write(fd, &f5, sizeof(format5_label_t)); if (rc != sizeof(format5_label_t)) { close(fd); ERRMSG_EXIT(EXIT_FAILURE, "%s: Error writing FMT5 label " "(%d).\n", prog_name, rc); } if ((cylinders * heads) > BIG_DISK_SIZE) { label_position += blksize; rc = lseek(fd, label_position, SEEK_SET); if (rc != label_position) { close(fd); ERRMSG_EXIT(EXIT_FAILURE, "%s: lseek to %i failed " "(%s).\n", prog_name, label_position, strerror(errno)); } /* write VTOC FMT 7 DSCB (only on big disks) */ rc = write(fd, &f7, sizeof(format7_label_t)); if (rc != sizeof(format7_label_t)) { close(fd); ERRMSG_EXIT(EXIT_FAILURE, "%s: Error writing FMT7 " "label (rc=%d).\n", prog_name, rc); } } fsync(fd); close(fd); if (info->verbosity > 0) printf("ok\n"); } /* * This function will search for the beginning of an unformatted area * on the device. It checks selected tracks beforehand and makes sure * that the device is formatted to a certain extent. Otherwise the * process is terminated. */ static void dasdfmt_find_start(dasdfmt_info_t *info, unsigned int cylinders, unsigned heads, format_data_t *format_params) { format_check_t cdata; unsigned int middle; unsigned int left = 2; unsigned int right = (cylinders * heads) - 1; unsigned int first = left; check_blocksize(info, format_params->blksize); format_params->start_unit = 0; format_params->stop_unit = 4; cdata = check_track_format(info, format_params); if (cdata.result) { evaluate_format_error(info, &cdata, heads); ERRMSG_EXIT(EXIT_FAILURE, "Use --mode=full to perform a " "clean format.\n"); } printf("Expansion mode active. Searching for starting position...\n"); while (left <= right) { /* new track number to look at */ middle = left + ((right - left) / 2); format_params->start_unit = middle; format_params->stop_unit = middle; cdata = check_track_format(info, format_params); if (cdata.blksize != format_params->blksize) { first = middle; right = middle - 1; } else { left = middle + 1; } } if (first == 2 && cdata.blksize == format_params->blksize) ERRMSG_EXIT(EXIT_FAILURE, "No unformatted part found, aborting.\n"); printf("Done. Unformatted part starts at track %d.\n", first); /* return format_params with start_unit set to the correct value */ format_params->start_unit = first; } /* * formats the disk cylinderwise */ static void dasdfmt_format(dasdfmt_info_t *info, unsigned int cylinders, unsigned int heads, format_data_t *format_params) { process_tracks(info, cylinders, heads, format_params); } static void dasdfmt_prepare_and_format(dasdfmt_info_t *info, unsigned int cylinders, unsigned int heads, format_data_t *p) { format_data_t temp = { .start_unit = 0, .stop_unit = 0, .blksize = p->blksize, .intensity = ((p->intensity & ~DASD_FMT_INT_FMT_NOR0) | DASD_FMT_INT_INVAL) }; int err; if (!(info->withoutprompt && (info->verbosity < 1))) printf("Formatting the device. This may take a while " "(get yourself a coffee).\n"); if (info->verbosity > 0) printf("Detaching the device...\n"); disk_disable(dev_filename); if (info->verbosity > 0) printf("Invalidate first track...\n"); err = dasd_format_disk(filedes, &temp); if (err != 0) ERRMSG_EXIT(EXIT_FAILURE, "%s: (invalidate first track) IOCTL " "BIODASDFMT failed. (%s)\n", prog_name, strerror(err)); /* except track 0 from standard formatting procss */ p->start_unit = 1; dasdfmt_format(info, cylinders, heads, p); if (info->verbosity > 0) printf("formatting tracks complete...\n"); temp.intensity = p->intensity; if (info->verbosity > 0) printf("Revalidate first track...\n"); err = dasd_format_disk(filedes, &temp); if (err != 0) ERRMSG_EXIT(EXIT_FAILURE, "%s: (re-validate first track) IOCTL" " BIODASDFMT failed (%s)\n", prog_name, strerror(err)); if (info->verbosity > 0) printf("Re-accessing the device...\n"); disk_enable(); } /* * This function will start the expand format process. */ static void dasdfmt_expand_format(dasdfmt_info_t *info, unsigned int cylinders, unsigned int heads, format_data_t *p) { if (!(info->withoutprompt && (info->verbosity < 1))) printf("Formatting the device. This may take a while " "(get yourself a coffee).\n"); if (info->verbosity > 0) printf("Detaching the device...\n"); disk_disable(dev_filename); dasdfmt_format(info, cylinders, heads, p); if (info->verbosity > 0) printf("Formatting tracks complete...\n"); if (info->verbosity > 0) printf("Re-accessing the device...\n"); disk_enable(); } /* * This function will only format the first two tracks of a DASD. * The rest of the DASD is untouched and left as is. */ static void dasdfmt_quick_format(dasdfmt_info_t *info, unsigned int cylinders, unsigned int heads, format_data_t *p) { format_check_t cdata = { .expect = {0}, 0 }; format_data_t tmp = *p; int err; if (info->force) { printf("Skipping format check due to --force.\n"); } else { check_blocksize(info, p->blksize); printf("Checking the format of selected tracks...\n"); /* Check device format on the first and last 3 regular tracks */ tmp.start_unit = 2; tmp.stop_unit = 4; cdata = check_track_format(info, &tmp); if (!cdata.result) { tmp.start_unit = (cylinders * heads) - 3; tmp.stop_unit = (cylinders * heads) - 1; cdata = check_track_format(info, &tmp); } if (cdata.result) { evaluate_format_error(info, &cdata, heads); ERRMSG_EXIT(EXIT_FAILURE, "Use --mode=full to perform " "a clean format.\n"); } else { printf("Done. Disk seems fine.\n"); } } if (!(info->withoutprompt && (info->verbosity < 1))) printf("Formatting the first two tracks of the device.\n"); /* Disable the device before we do anything */ disk_disable(dev_filename); /* Now do the actual formatting of our first two tracks */ err = dasd_format_disk(filedes, p); if (err != 0) ERRMSG_EXIT(EXIT_FAILURE, "%s: the ioctl to format the device " "failed. (%s)\n", prog_name, strerror(err)); /* Re-Enable the device so that we can continue working with it */ disk_enable(); } static void do_format_dasd(dasdfmt_info_t *info, char *devname, volume_label_t *vlabel, format_data_t *p, unsigned int cylinders, unsigned int heads) { char inp_buffer[5]; int count, err; p->start_unit = 0; switch (mode) { case FULL: /* all tracks */ p->stop_unit = (cylinders * heads) - 1; break; case QUICK: /* just the first two */ p->stop_unit = 1; break; case EXPAND: /* only the end of the disk */ dasdfmt_find_start(info, cylinders, heads, p); p->stop_unit = (cylinders * heads) - 1; break; } if ((info->verbosity > 0) || !info->withoutprompt || info->testmode) dasdfmt_print_info(info, devname, vlabel, cylinders, heads, p); count = u2s_get_host_access_count(devname); if (info->force_host) { if (count > 1) { ERRMSG_EXIT(EXIT_FAILURE, "\n%s: Disk %s is online on OS instances in %d different LPARs.\n" "Note: Your installation might include z/VM systems that are configured to\n" "automatically vary on disks, regardless of whether they are subsequently used.\n\n", prog_name, devname, count); } else if (count < 0) { ERRMSG("\nHosts access information not available for disk %s.\n\n", devname); return; } } else if (count > 1) ERRMSG("\nWARNING:\n" "Disk %s is online on operating system instances in %d different LPARs.\n" "Ensure that the disk is not being used by a system outside your LPAR.\n" "Note: Your installation might include z/VM systems that are configured to\n" "automatically vary on disks, regardless of whether they are subsequently used.\n", devname, count); if (!info->testmode) { if (!info->withoutprompt) { printf("\n"); if (mode != EXPAND) printf("--->> ATTENTION! <<---\nAll data of " "that device will be lost.\n"); printf("Type \"yes\" to continue, no will leave the " "disk untouched: "); if (fgets(inp_buffer, sizeof(inp_buffer), stdin) == NULL) return; if (strcasecmp(inp_buffer, "yes") && strcasecmp(inp_buffer, "yes\n")) { printf("Omitting ioctl call (disk will " "NOT be formatted).\n"); return; } } switch (mode) { case FULL: dasdfmt_prepare_and_format(info, cylinders, heads, p); break; case QUICK: dasdfmt_quick_format(info, cylinders, heads, p); break; case EXPAND: dasdfmt_expand_format(info, cylinders, heads, p); break; } printf("Finished formatting the device.\n"); if (!(info->writenolabel || mode == EXPAND)) dasdfmt_write_labels(info, vlabel, cylinders, heads); printf("Rereading the partition table... "); err = dasd_reread_partition_table(dev_filename, 5); if (err != 0) { ERRMSG("%s: error during rereading the partition " "table: %s.\n", prog_name, strerror(err)); } else { printf("ok\n"); } } } int main(int argc, char *argv[]) { dasdfmt_info_t info = { .dasd_info = {0}, }; volume_label_t vlabel; char old_volser[7]; char str[ERR_LENGTH]; char buf[7]; char *blksize_param_str = NULL; char *reqsize_param_str = NULL; char *hashstep_str = NULL; int rc; unsigned int cylinders, heads; /* Establish a handler for interrupt signals. */ signal(SIGTERM, program_interrupt_signal); signal(SIGINT, program_interrupt_signal); signal(SIGQUIT, program_interrupt_signal); /******************* initialization ********************/ prog_name = argv[0]; util_prg_init(&prg); util_opt_init(opt_vec, NULL); /* set default values */ vtoc_volume_label_init(&vlabel); format_params.blksize = DEFAULT_BLOCKSIZE; format_params.intensity = DASD_FMT_INT_COMPAT; mode = FULL; /*************** parse parameters **********************/ while (1) { rc = util_opt_getopt_long(argc, argv); switch (rc) { case 'F': info.force = 1; break; case 'd': if (strcasecmp(optarg, "cdl") == 0) { format_params.intensity |= DASD_FMT_INT_COMPAT; if (info.writenolabel) { printf("WARNING: using the cdl " "format without writing a " "label doesn't make much " "sense!\n"); exit(1); } } else if (strcasecmp(optarg, "ldl") == 0) { format_params.intensity &= ~DASD_FMT_INT_COMPAT; } else { printf("%s is not a valid option!\n", optarg); exit(1); } info.layout_specified = 1; break; case 'y': info.withoutprompt = 1; break; case OPT_NOZERO: format_params.intensity |= DASD_FMT_INT_FMT_NOR0; break; case 't': info.testmode = 1; break; case 'p': if (!(info.print_hashmarks || info.print_percentage)) info.print_progressbar = 1; break; case 'm': if (!(info.print_progressbar || info.print_percentage)) { hashstep_str = optarg; info.print_hashmarks = 1; } break; case 'P': if (!(info.print_hashmarks || info.print_progressbar)) info.print_percentage = 1; break; case 'v': info.verbosity = 1; break; case 'h': util_prg_print_help(); util_opt_print_help(); exit(EXIT_SUCCESS); case 'V': util_prg_print_version(); exit(EXIT_SUCCESS); case 'l': strncpy(buf, optarg, 6); if (check_volser(buf, 0) < 0) break; vtoc_volume_label_set_volser(&vlabel, buf); info.labelspec = 1; break; case 'L': if (format_params.intensity & DASD_FMT_INT_COMPAT) { printf("WARNING: using the cdl format " "without writing a label doesn't " "make much sense!\n"); exit(1); } info.writenolabel = 1; break; case 'b': blksize_param_str = optarg; info.blksize_specified = 1; break; case 'r': reqsize_param_str = optarg; info.reqsize_specified = 1; break; case 'k': info.keep_volser = 1; break; case 'C': info.force_host = 1; break; case 'M': if (strcasecmp(optarg, "full") == 0) mode = FULL; else if (strcasecmp(optarg, "quick") == 0) mode = QUICK; else if (strcasecmp(optarg, "expand") == 0) mode = EXPAND; else ERRMSG_EXIT(EXIT_FAILURE, "%s: The specified mode '%s' is " "invalid. Consult the man page for " "more information.\n", prog_name, optarg); break; case OPT_CHECK: info.check = 1; break; case -1: /* End of options string - start of devices list */ break; default: ERRMSG_EXIT(EXIT_MISUSE, "Try '%s --help' for more" " information.\n", prog_name); } if (rc == -1) break; /* exit loop if finished */ } CHECK_SPEC_MAX_ONCE(info.blksize_specified, "blocksize"); CHECK_SPEC_MAX_ONCE(info.labelspec, "label"); CHECK_SPEC_MAX_ONCE(info.writenolabel, "omit-label-writing flag"); if (info.blksize_specified) PARSE_PARAM_INTO(format_params.blksize, blksize_param_str, 10, "blocksize"); if (info.reqsize_specified) { PARSE_PARAM_INTO(reqsize, reqsize_param_str, 10, "requestsize"); if (reqsize < 1 || reqsize > 255) ERRMSG_EXIT(EXIT_FAILURE, "invalid requestsize %d specified\n", reqsize); } else { reqsize = DEFAULT_REQUESTSIZE; } if (info.print_hashmarks) PARSE_PARAM_INTO(info.hashstep, hashstep_str, 10, "hashstep"); get_device_name(dev_filename, optind, argc, argv); rc = dasd_get_info(dev_filename, &info.dasd_info); if (rc != 0) ERRMSG_EXIT(EXIT_FAILURE, "%s: the ioctl call to retrieve " "device information failed (%s).\n", prog_name, strerror(rc)); /* Either let the user specify the blksize or get it from the kernel */ if (!info.blksize_specified) { if (!(mode == FULL || info.dasd_info.format == DASD_FORMAT_NONE) || info.check) get_blocksize(dev_filename, &format_params.blksize); else format_params = ask_user_for_blksize(format_params); } if (info.keep_volser) { if (info.labelspec) { ERRMSG_EXIT(EXIT_MISUSE, "%s: The -k and -l options " "are mutually exclusive\n", prog_name); } if (!(format_params.intensity & DASD_FMT_INT_COMPAT)) { printf("WARNING: VOLSER cannot be kept " "when using the ldl format!\n"); exit(1); } if (dasdfmt_get_volser(dev_filename, &info.dasd_info, old_volser) == 0) vtoc_volume_label_set_volser(&vlabel, old_volser); else ERRMSG_EXIT(EXIT_FAILURE, "%s: VOLSER not found on device %s\n", prog_name, dev_filename); } check_disk(&info, dev_filename); if (check_param(str, ERR_LENGTH, &format_params) < 0) ERRMSG_EXIT(EXIT_MISUSE, "%s: %s\n", prog_name, str); set_geo(&info, &cylinders, &heads); set_label(&info, &vlabel, &format_params, cylinders); if (info.check) check_disk_format(&info, cylinders, heads, &format_params); else do_format_dasd(&info, dev_filename, &vlabel, &format_params, cylinders, heads); return 0; } s390-tools-2.3.0/dasdfmt/dasdfmt.h000066400000000000000000000123511323410431100165570ustar00rootroot00000000000000/* * dasdfmt - Format DASD ECKD devices for use by Linux * * Copyright IBM Corp. 2002, 2017 * * s390-tools is free software; you can redistribute it and/or modify * it under the terms of the MIT license. See LICENSE for details. */ #ifndef DASDFMT_H #define DASDFMT_H #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include /**************************************************************************** * SECTION: Definition needed for DASD-API (see dasd.h) * ****************************************************************************/ /* * Represents possible format modes that can be specified when formatting * a DASD. */ typedef enum format_mode_t { FULL, /* default mode */ QUICK, /* format only the first 2 tracks */ EXPAND, /* search for unformatted area and format only that part*/ } format_mode_t; static const char mode_str[3][10] = { "Full", "Quick", "Expand" }; /* * values to be used for format_data_t.intensity * 0/8: normal format * 1/9: also write record zero * 3/11: also write home address * 4/12: invalidate track */ #define DASD_FMT_INT_FMT_R0 1 /* write record zero */ #define DASD_FMT_INT_FMT_HA 2 /* write home address, also set FMT_R0 ! */ #define DASD_FMT_INT_INVAL 4 /* invalidate tracks */ #define DASD_FMT_INT_COMPAT 8 /* use OS/390 compatible disk layout */ #define DASD_FMT_INT_FMT_NOR0 16 /* remove permission to write record zero */ /* * values to be used in format_check_t for indicating * possible format errors */ #define DASD_FMT_ERR_TOO_FEW_RECORDS 1 #define DASD_FMT_ERR_TOO_MANY_RECORDS 2 #define DASD_FMT_ERR_BLKSIZE 3 #define DASD_FMT_ERR_RECORD_ID 4 #define DASD_FMT_ERR_KEY_LENGTH 5 /* * values to be used for dasd_information2_t.format * 0x00: NOT formatted * 0x01: Linux disc layout * 0x02: Common disc layout */ #define DASD_FORMAT_NONE 0 #define DASD_FORMAT_LDL 1 #define DASD_FORMAT_CDL 2 /**************************************************************************** * SECTION: DASDFMT internal types * ****************************************************************************/ #define DASD_PARTN_BITS 2 #define PARTN_MASK ((1 << DASD_PARTN_BITS) - 1) #define EXIT_MISUSE 1 #define EXIT_BUSY 2 #define LABEL_LENGTH 14 #define VLABEL_CHARS 84 #define LINE_LENGTH 80 #define ERR_LENGTH 90 #define DEFAULT_BLOCKSIZE 4096 /* requestsize - number of cylinders in one format step */ #define DEFAULT_REQUESTSIZE 10 #define USABLE_PARTITIONS ((1 << DASD_PARTN_BITS) - 1) #define ERRMSG(x...) {fflush(stdout);fprintf(stderr,x);} #define ERRMSG_EXIT(ec,x...) {fflush(stdout);fprintf(stderr,x);exit(ec);} #define CHECK_SPEC_MAX_ONCE(i,str) \ {if (i>1) ERRMSG_EXIT(EXIT_MISUSE,"%s: " str " " \ "can only be specified once\n",prog_name);} #define PARSE_PARAM_INTO(x,param,base,str) \ {char *endptr=NULL; x=(int)strtol(param,&endptr,base); \ if (*endptr) ERRMSG_EXIT(EXIT_MISUSE,"%s: " str " " \ "is in invalid format\n",prog_name);} typedef struct bootstrap1 { u_int32_t key; u_int32_t data[6]; } __attribute__ ((packed)) bootstrap1_t; typedef struct bootstrap2 { u_int32_t key; u_int32_t data[36]; } __attribute__ ((packed)) bootstrap2_t; typedef struct dasdfmt_info { dasd_information2_t dasd_info; int verbosity; int testmode; int withoutprompt; int print_progressbar; int print_hashmarks, hashstep; int print_percentage; int force; int writenolabel; int labelspec; int cdl_format; int blksize_specified; int reqsize_specified; int keep_volser; int force_host; int layout_specified; int check; } dasdfmt_info_t; /* C9D7D3F1 000A0000 0000000F 03000000 00000001 00000000 00000000 */ static bootstrap1_t ipl1 = { 0xC9D7D3F1, { 0x000A0000, 0x0000000F, 0x03000000, 0x00000001, 0x00000000, 0x00000000 } }; /* C9D7D3F2 07003AB8 40000006 31003ABE 40000005 08003AA0 00000000 06000000 20000000 00000000 00000000 00000400 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 */ static bootstrap2_t ipl2 = { 0xC9D7D3F2, { 0x07003AB8, 0x40000006, 0x31003ABE, 0x40000005, 0x08003AA0, 0x00000000, 0x06000000, 0x20000000, 0x00000000, 0x00000000, 0x00000400, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000 } }; #endif /* DASDFMT_H */ s390-tools-2.3.0/dasdinfo/000077500000000000000000000000001323410431100151275ustar00rootroot00000000000000s390-tools-2.3.0/dasdinfo/Makefile000066400000000000000000000007411323410431100165710ustar00rootroot00000000000000include ../common.mak libs = $(rootdir)/libu2s/libu2s.a \ $(rootdir)/libutil/libutil.a \ $(rootdir)/libdasd/libdasd.a all: dasdinfo dasdinfo: dasdinfo.o $(libs) install: all $(INSTALL) -d -m 755 $(DESTDIR)$(BINDIR) \ $(DESTDIR)$(MANDIR)/man8 $(INSTALL) -g $(GROUP) -o $(OWNER) -m 755 dasdinfo $(DESTDIR)$(BINDIR) $(INSTALL) -g $(GROUP) -o $(OWNER) -m 644 dasdinfo.8 \ $(DESTDIR)$(MANDIR)/man8 clean: rm -f *.o *~ dasdinfo core .PHONY: all install clean s390-tools-2.3.0/dasdinfo/dasdinfo.8000066400000000000000000000056011323410431100170110ustar00rootroot00000000000000.\" Copyright 2017 IBM Corp. .\" s390-tools is free software; you can redistribute it and/or modify .\" it under the terms of the MIT license. See LICENSE for details. .\" .TH DASDINFO 8 "Febr 2007" "s390-tools" "Linux Administrator's Manual" .SH NAME .B "dasdinfo " \- tool to read unique id from s390 DASD device .SH SYNOPSIS .BI "dasdinfo [-a] [-l] [-u] [-x] [-e] {-i " .BI "| -b " .BI " | -d " .BI "}" .sp .BI "dasdinfo [-h] [-v]" .SH DESCRIPTION .B dasdinfo displays specific information about a specified DASD device. It is normally called from a udev rule, to provide udev with a unique id string and additional information (type, serial) for an S390 DASD drive. Udev can use this information to create symlinks in /dev/disk/by-id and /dev/disk/by-label to the real device node. .SH OPTIONS .TP .BI "-a|--all" Same as -u -x -l .TP .BI "-x|--extended-uid" Print DASD uid This option prints the full uid of the DASD. When z/VM provides two virtual devices that are actually located on the same real device, the first four tokens of the uid will be identical for both devices. z/VM may provide an additional token that allows to distinguish between different minidisks. You need both support in the Linux kernel and z/VM to receive such an additional token. For z/VM: VM support for the hypervisor injected Special Node Element Qualifier (SNEQ) (or hypervisor injected self-description data) is available by applying the PTFs for VM APAR VM64273 on z/VM 5.2.0 and higher. .TP .BI "-u|--uid" Print DASD uid without z/VM minidisk token z/VM may provide an additional token that allows to distinguish between different minidisks (see --extended-uid option). To remain compatibile with systems that were installed on older Linux or z/VM levels, the -u option will print the uid excluding any z/VM-provided minidisk token. For example, if the extended uid is IBM.75000000092461.e900.10.00000000000037400000000000000000 then the uid is IBM.75000000092461.e900.10. If the extended uid contains no minidisk token, e.g. in an LPAR environment, then both uids are the same. .TP .BI "-l|--label" Print DASD volume label (volser). .TP .BI "-i|--busid " Use the bus ID as input parameter, e.g. 0.0.e910. .TP .BI "-b|--block " Use the block device name as input parameter, e.g. dasdb. .TP .BI "-d|--devnode " Use a device node as input parameter, e.g. /dev/dasdb. .TP .BI "-e|--export" Print all values (ID_BUS, ID_TYPE, ID_SERIAL). .TP .BI "-h|--help" Print usage text. .TP .BI "-v|--version" Print version number. .SH EXAMPLES dasdinfo -u -i 0.0.e910 dasdinfo -u -b dasdb dasdinfo -u -d /dev/dasdb All three examples should return the same unique ID for the same DASD device, e.g. IBM.75000000092461.e900.10. In case this uid is not available, dasdinfo will return the volume label instead, e.g. 0XE910. .SH SEE ALSO .BR udev (7) .SH AUTHORS Volker Sameske s390-tools-2.3.0/dasdinfo/dasdinfo.c000066400000000000000000000430631323410431100170700ustar00rootroot00000000000000/* * dasdinfo - Display unique DASD ID, either UID or volser * * Copyright IBM Corp. 2007, 2017 * * 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include "lib/dasd_base.h" #include "lib/util_base.h" #include "lib/util_file.h" #include "lib/util_opt.h" #include "lib/util_prg.h" #include "lib/zt_common.h" #define RD_BUFFER_SIZE 80 #define TEMP_DEV_MAX_RETRIES 1000 static const struct util_prg prg = { .desc = "Display DASD volume serial number and ID information", .args = "-i BUSID | -b BLOCKDEV | -d DEVNODE", .copyright_vec = { { .owner = "IBM Corp.", .pub_first = 2007, }, UTIL_PRG_COPYRIGHT_END } }; static struct util_opt opt_vec[] = { UTIL_OPT_SECTION("DEVICE"), { .option = { "block", required_argument, NULL, 'b' }, .argument = "BLOCKDEV", .desc = "Block device name, e.g. dasdb", }, { .option = { "devnode", required_argument, NULL, 'd' }, .argument = "DEVNODE", .desc = "Device node, e.g. /dev/dasda", }, { .option = { "busid", required_argument, NULL, 'i' }, .argument = "BUSID", .desc = "Bus ID, e.g. 0.0.e910", }, UTIL_OPT_SECTION("OPTIONS"), { .option = { "label", no_argument, NULL, 'l' }, .desc = "Print DASD volume label (volser)", }, { .option = { "uid", no_argument, NULL, 'u' }, .desc = "Print DASD uid (without z/VM minidisk token)", }, { .option = { "extended-uid", no_argument, NULL, 'x' }, .desc = "Print DASD uid (including z/VM minidisk token)", }, { .option = { "all", no_argument, NULL, 'a' }, .desc = "Same as -u -x -l", }, { .option = { "export", no_argument, NULL, 'e' }, .desc = "Export ID_BUS, ID_TYPE, ID_SERIAL for use in udev", }, UTIL_OPT_HELP, UTIL_OPT_VERSION, UTIL_OPT_END }; /* needed because ftw can not pass arbitrary arguments */ static char *searchbusid; static char *busiddir; struct volume_label { char volkey[4]; char vollbl[4]; char volid[6]; } __attribute__ ((packed)); static char EBCtoASC[256] = { /* 0x00 NUL SOH STX ETX *SEL HT *RNL DEL */ 0x00, 0x01, 0x02, 0x03, 0x07, 0x09, 0x07, 0x7F, /* 0x08 -GE -SPS -RPT VT FF CR SO SI */ 0x07, 0x07, 0x07, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, /* 0x10 DLE DC1 DC2 DC3 -RES -NL BS -POC */ 0x10, 0x11, 0x12, 0x13, 0x07, 0x0A, 0x08, 0x07, /* 0x18 CAN EM -UBS -CU1 -IFS -IGS -IRS -ITB */ 0x18, 0x19, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, /* 0x20 -DS -SOS FS -WUS -BYP LF ETB ESC */ 0x07, 0x07, 0x1C, 0x07, 0x07, 0x0A, 0x17, 0x1B, /* 0x28 -SA -SFE -SM -CSP -MFA ENQ ACK BEL */ 0x07, 0x07, 0x07, 0x07, 0x07, 0x05, 0x06, 0x07, /* 0x30 ---- ---- SYN -IR -PP -TRN -NBS EOT */ 0x07, 0x07, 0x16, 0x07, 0x07, 0x07, 0x07, 0x04, /* 0x38 -SBS -IT -RFF -CU3 DC4 NAK ---- SUB */ 0x07, 0x07, 0x07, 0x07, 0x14, 0x15, 0x07, 0x1A, /* 0x40 SP RSP ? ---- */ 0x20, 0xFF, 0x83, 0x84, 0x85, 0xA0, 0x07, 0x86, /* 0x48 . < ( + | */ 0x87, 0xA4, 0x9B, 0x2E, 0x3C, 0x28, 0x2B, 0x7C, /* 0x50 & ---- */ 0x26, 0x82, 0x88, 0x89, 0x8A, 0xA1, 0x8C, 0x07, /* 0x58 ? ! $ * ) ; */ 0x8D, 0xE1, 0x21, 0x24, 0x2A, 0x29, 0x3B, 0xAA, /* 0x60 - / ---- ? ---- ---- ---- */ 0x2D, 0x2F, 0x07, 0x8E, 0x07, 0x07, 0x07, 0x8F, /* 0x68 ---- , % _ > ? */ 0x80, 0xA5, 0x07, 0x2C, 0x25, 0x5F, 0x3E, 0x3F, /* 0x70 --- ---- ---- ---- ---- ---- ---- */ 0x07, 0x90, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, /* 0x78 * ` : # @ ' = " */ 0x70, 0x60, 0x3A, 0x23, 0x40, 0x27, 0x3D, 0x22, /* 0x80 * a b c d e f g */ 0x07, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, /* 0x88 h i ---- ---- ---- */ 0x68, 0x69, 0xAE, 0xAF, 0x07, 0x07, 0x07, 0xF1, /* 0x90 ? j k l m n o p */ 0xF8, 0x6A, 0x6B, 0x6C, 0x6D, 0x6E, 0x6F, 0x70, /* 0x98 q r ---- ---- */ 0x71, 0x72, 0xA6, 0xA7, 0x91, 0x07, 0x92, 0x07, /* 0xA0 ~ s t u v w x */ 0xE6, 0x7E, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, /* 0xA8 y z ---- ---- ---- ---- */ 0x79, 0x7A, 0xAD, 0xAB, 0x07, 0x07, 0x07, 0x07, /* 0xB0 ^ ---- ? ---- */ 0x5E, 0x9C, 0x9D, 0xFA, 0x07, 0x07, 0x07, 0xAC, /* 0xB8 ---- [ ] ---- ---- ---- ---- */ 0xAB, 0x07, 0x5B, 0x5D, 0x07, 0x07, 0x07, 0x07, /* 0xC0 { A B C D E F G */ 0x7B, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, /* 0xC8 H I ---- ? ---- */ 0x48, 0x49, 0x07, 0x93, 0x94, 0x95, 0xA2, 0x07, /* 0xD0 } J K L M N O P */ 0x7D, 0x4A, 0x4B, 0x4C, 0x4D, 0x4E, 0x4F, 0x50, /* 0xD8 Q R ---- ? */ 0x51, 0x52, 0x07, 0x96, 0x81, 0x97, 0xA3, 0x98, /* 0xE0 \ S T U V W X */ 0x5C, 0xF6, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, /* 0xE8 Y Z ---- ? ---- ---- ---- */ 0x59, 0x5A, 0xFD, 0x07, 0x99, 0x07, 0x07, 0x07, /* 0xF0 0 1 2 3 4 5 6 7 */ 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, /* 0xF8 8 9 ---- ---- ? ---- ---- ---- */ 0x38, 0x39, 0x07, 0x07, 0x9A, 0x07, 0x07, 0x07 }; static char *dinfo_ebcdic_dec(char *source, char *target, int l) { int i; for (i = 0; i < l; i++) target[i] = EBCtoASC[(unsigned char)(source[i])]; return target; } static int dinfo_read_dasd_uid(char *uidfile, char *readbuf) { return util_file_read_line(readbuf, RD_BUFFER_SIZE, uidfile); } static int dinfo_read_dasd_vlabel(char *device, struct volume_label *vlabel, char *readbuf) { struct volume_label tmp; int vlsize = sizeof(struct volume_label); dasd_information2_t dasd_info; unsigned long vlabel_start; unsigned int blksize; char vollbl[5]; int f; char *space; if (dasd_get_blocksize(device, &blksize) != 0) { printf("Unable to figure out block size.\n"); goto error; } if (dasd_get_info(device, &dasd_info) != 0) { printf("Unable to figure out DASD informations.\n"); goto error; } f = open(device, O_RDONLY); if (f < 0) { printf("Could not open device node.\n"); goto error; } vlabel_start = dasd_info.label_block * blksize; if (lseek(f, vlabel_start, SEEK_SET) < 0) goto error_close; bzero(vlabel, vlsize); if (read(f, vlabel, vlsize) != vlsize) { printf("Could not read volume label.\n"); goto error_close; } if (dasd_info.FBA_layout) { bzero(&tmp, vlsize); memcpy(&tmp, vlabel, vlsize); memcpy(vlabel->vollbl, &tmp, vlsize - 4); } close(f); bzero(readbuf, 7); bzero(vollbl, 5); strncpy(vollbl, vlabel->vollbl, 4); dinfo_ebcdic_dec(vollbl, vollbl, 4); if ((strncmp(vollbl, "VOL1", 4) == 0) || (strncmp(vollbl, "LNX1", 4) == 0) || (strncmp(vollbl, "CMS1", 4) == 0)) { strncpy(readbuf, vlabel->volid, 6); dinfo_ebcdic_dec(readbuf, readbuf, 6); space = strchr(readbuf, ' '); if (space) *space = 0; } else { strcpy(readbuf, ""); } return 0; error_close: close(f); error: return -1; } static void *dinfo_malloc(size_t size) { void *result; result = malloc(size); if (result == NULL) { printf("Could not allocate %lld bytes of memory", (unsigned long long)size); } return result; } static char *dinfo_make_path(char *dirname, char *filename) { char *result; size_t len; len = strlen(dirname) + strlen(filename) + 2; result = (char *)dinfo_malloc(len); if (result == NULL) return NULL; sprintf(result, "%s/%s", dirname, filename); return result; } static int dinfo_create_devnode(dev_t dev, char **devno) { char *result; char *pathname[] = { "/dev", getenv("TMPDIR"), "/tmp", getenv("HOME"), ".", "/"}; char filename[] = "dasdinfo0000"; mode_t mode; unsigned int path; int retry; int rc; int fd; mode = S_IFBLK | S_IRWXU; /* Try several locations for the temporary device node. */ for (path = 0; path < UTIL_ARRAY_SIZE(pathname); path++) { if (pathname[path] == NULL) continue; for (retry = 0; retry < TEMP_DEV_MAX_RETRIES; retry++) { sprintf(filename, "dasdinfo%04d", retry); result = dinfo_make_path(pathname[path], filename); if (result == NULL) return -1; rc = mknod(result, mode, dev); if (rc == 0) { /* Need this test to cover * 'nodev'-mounted * filesystems. */ fd = open(result, O_RDONLY); if (fd != -1) { close(fd); *devno = result; return 0; } remove(result); retry = TEMP_DEV_MAX_RETRIES; } else if (errno != EEXIST) { retry = TEMP_DEV_MAX_RETRIES; } free(result); } } printf("Error: Unable to create temporary device node"); return -1; } static void dinfo_free_devnode(char *device) { if (remove(device)) { printf("Warning: Could not remove " "temporary file %s", device); } } static int dinfo_extract_dev(dev_t *dev, char *str) { char tmp[RD_BUFFER_SIZE]; char *p = NULL; int ma, mi; bzero(tmp, RD_BUFFER_SIZE); strncpy(tmp, str, RD_BUFFER_SIZE); p = strchr(tmp, ':'); if (p == NULL) { printf("Error: unable to extract major/minor\n"); return -1; } *p = '\0'; ma = atoi(tmp); mi = atoi(p + sizeof(char)); *dev = makedev(ma, mi); return 0; } static int dinfo_get_dev_from_blockdev(char *blockdev, dev_t *dev) { char *readbuf = NULL; readbuf = dinfo_malloc(RD_BUFFER_SIZE); if (!readbuf) { printf("Error: Not enough memory to allocate readbuffer\n"); return -1; } if (util_file_read_line(readbuf, RD_BUFFER_SIZE, "/sys/block/%s/dev", blockdev) < 0) return -1; if (dinfo_extract_dev(dev, readbuf) != 0) return -1; return 0; } static int dinfo_is_busiddir(const char *fpath, const struct stat *UNUSED(sb), int tflag, struct FTW *ftwbuf) { char *tempdir; char linkdir[128]; ssize_t i; if (tflag != FTW_D || (strncmp((fpath + ftwbuf->base), searchbusid, strlen(searchbusid)) != 0)) return FTW_CONTINUE; /* * ensure that the found entry is a busid and not a * subchannel ID * for large systems subchannel IDs may look like busids */ if (asprintf(&tempdir, "%s/driver", fpath) < 0) return -1; i = readlink(tempdir, linkdir, 128); free(tempdir); if ((i < 0) || (i >= 128)) return -1; /* append '\0' because readlink returns non zero terminated string */ tempdir[i + 1] = '\0'; if (strstr(linkdir, "dasd") == NULL) return FTW_CONTINUE; free(busiddir); busiddir = strdup(fpath); if (busiddir == NULL) return -1; return FTW_STOP; } static int dinfo_find_entry(const char *dir, const char *searchstring, char type, char **result) { DIR *directory = NULL; struct dirent *dir_entry = NULL; directory = opendir(dir); if (directory == NULL) return -1; while ((dir_entry = readdir(directory)) != NULL) { /* compare if the found entry has exactly the same name and type * as searched */ if ((strncmp(dir_entry->d_name, searchstring, strlen(searchstring)) == 0) && (dir_entry->d_type & type)) { *result = strdup(dir_entry->d_name); if (*result == NULL) goto out; closedir(directory); return 0; /* found */ } } out: closedir(directory); return -1; /* nothing found or error */ } static int dinfo_get_blockdev_from_busid(char *busid, char **blkdev) { int flags = FTW_PHYS; /* do not follow links */ int rc = -1; char *tempdir = NULL; char *result = NULL; char *sysfsdir = "/sys/devices/"; /* dinfo_is_devnode needs to know the busid */ searchbusid = busid; if (nftw(sysfsdir, dinfo_is_busiddir, 200, flags) != FTW_STOP) goto out; /* * new sysfs: busid directory contains a directory 'block' * which contains a directory 'dasdXXX' */ rc = dinfo_find_entry(busiddir, "block", DT_DIR, &result); if (rc == 0) { if (asprintf(&tempdir, "%s/%s/", busiddir, result) < 0) { rc = -1; goto out2; } rc = dinfo_find_entry(tempdir, "dasd", DT_DIR, blkdev); } else { /* * old sysfs: entry for busiddir contain a link * 'block:dasdXXX' */ rc = dinfo_find_entry(busiddir, "block:", DT_LNK, &result); if (rc != 0) goto out2; *blkdev = strdup(strchr(result, ':') + 1); if (*blkdev == NULL) rc = -1; } out: free(tempdir); out2: free(busiddir); free(result); return rc; } static int dinfo_get_uid_from_devnode(char **uidfile, char *devnode) { struct stat stat_buffer; char stat_dev[RD_BUFFER_SIZE]; char *readbuf; DIR *directory = NULL; struct dirent *dir_entry = NULL; int rc = 0; if (stat(devnode, &stat_buffer) != 0) { printf("Error: could not stat %s\n", devnode); return -1; } sprintf(stat_dev, "%d:%d", major(stat_buffer.st_rdev), minor(stat_buffer.st_rdev)); directory = opendir("/sys/block/"); if (directory == NULL) { printf("Error: could not open directory /sys/block\n"); return -1; } readbuf = dinfo_malloc(RD_BUFFER_SIZE); if (!readbuf) { printf("Error: Not enough memory to allocate readbuffer\n"); return -1; } while ((dir_entry = readdir(directory)) != NULL) { if (util_file_read_line(readbuf, RD_BUFFER_SIZE, "/sys/block/%s/dev", dir_entry->d_name) < 0) continue; if (strncmp(stat_dev, readbuf, MAX(strlen(stat_dev), strlen(readbuf) - 1)) == 0) { rc = snprintf(*uidfile, RD_BUFFER_SIZE, "/sys/block/%s/device/uid", dir_entry->d_name); if (rc >= RD_BUFFER_SIZE) { fprintf(stderr, "Error: Device name was truncated\n"); return -1; } break; } } closedir(directory); return 0; } int main(int argc, char *argv[]) { struct utsname uname_buf; int version, release; char *uidfile = NULL; char *device = NULL; char *readbuf = NULL; dev_t dev; int export = 0; int c; int print_uid = 0; int print_extended_uid = 0; int print_vlabel = 0; char *blockdev = NULL; char *busid = NULL; char *devnode = NULL; struct volume_label vlabel; char *srchuid; int i, rc = 0; util_prg_init(&prg); util_opt_init(opt_vec, NULL); while (1) { c = util_opt_getopt_long(argc, argv); if (c == -1) break; switch (c) { case 'a': print_uid = 1; print_vlabel = 1; print_extended_uid = 1; break; case 'u': print_uid = 1; break; case 'x': print_extended_uid = 1; break; case 'l': print_vlabel = 1; break; case 'i': busid = strdup(optarg); break; case 'b': blockdev = strdup(optarg); break; case 'd': devnode = strdup(optarg); break; case 'e': export = 1; break; case 'h': util_prg_print_help(); util_opt_print_help(); exit(EXIT_SUCCESS); case 'v': util_prg_print_version(); exit(EXIT_SUCCESS); default: fprintf(stderr, "Try 'dasdinfo --help' for more " "information.\n"); exit(1); } } uname(&uname_buf); sscanf(uname_buf.release, "%d.%d", &version, &release); if (strcmp(uname_buf.sysname, "Linux") || version < 2 || (version == 2 && release < 6)) { printf("%s %d.%d is not supported\n", uname_buf.sysname, version, release); exit(1); } if (!busid && !blockdev && !devnode) { printf("Error: please specify a device using either -b, -i " "or -d\n"); exit(1); } if ((busid && blockdev) || (busid && devnode) || (blockdev && devnode)) { printf("Error: please specify device only once, either -b, -i " "or -d\n"); exit(1); } if (!print_uid && !print_extended_uid && !print_vlabel) { printf("Error: no action specified (e.g. -u)\n"); exit(1); } readbuf = dinfo_malloc(RD_BUFFER_SIZE); uidfile = dinfo_malloc(RD_BUFFER_SIZE); if (!(readbuf && uidfile)) exit(1); /* try to read the uid attribute */ if (busid) { sprintf(uidfile, "/sys/bus/ccw/devices/%s/uid", busid); } else if (blockdev) { sprintf(uidfile, "/sys/block/%s/device/uid", blockdev); } else if (devnode) { if (dinfo_get_uid_from_devnode(&uidfile, devnode) != 0) goto error; } if (export) { printf("ID_BUS=ccw\n"); printf("ID_TYPE=disk\n"); } if (print_uid) { if (dinfo_read_dasd_uid(uidfile, readbuf) == 0) { /* look for the 4th '.' and cut there */ srchuid = readbuf - 1; for (i = 0; i < 4; ++i) { srchuid = index(srchuid + 1, '.'); if (!srchuid) break; } if (srchuid) srchuid[0] = '\0'; if (export) printf("ID_UID=%s\n", readbuf); else printf("%s\n", readbuf); if (!print_vlabel && !print_extended_uid) goto out; } } if (print_extended_uid) { if (dinfo_read_dasd_uid(uidfile, readbuf) == 0) { if (export) printf("ID_XUID=%s\n", readbuf); else printf("%s\n", readbuf); if (!print_vlabel) goto out; } } /* there is no uid, try to read the volume serial */ if (busid) { char *blockdev_name = NULL; if (dinfo_get_blockdev_from_busid(busid, &blockdev_name) != 0) goto error; if (dinfo_get_dev_from_blockdev(blockdev_name, &dev) != 0) goto error; if (dinfo_create_devnode(dev, &device) != 0) goto error; free(blockdev_name); } else if (blockdev) { if (dinfo_get_dev_from_blockdev(blockdev, &dev) != 0) goto error; if (dinfo_create_devnode(dev, &device) != 0) goto error; } else if (devnode) { device = dinfo_malloc(RD_BUFFER_SIZE); if (!device) exit(1); strcpy(device, devnode); } if (dinfo_read_dasd_vlabel(device, &vlabel, readbuf) == 0) { if (export) printf("ID_SERIAL=%s\n", readbuf); else printf("%s\n", readbuf); goto out; } error: printf("Error: could not read unique DASD ID\n"); rc = 1; out: if (device && (busid || blockdev)) dinfo_free_devnode(device); free(uidfile); free(device); free(readbuf); exit(rc); } s390-tools-2.3.0/dasdview/000077500000000000000000000000001323410431100151465ustar00rootroot00000000000000s390-tools-2.3.0/dasdview/Makefile000066400000000000000000000010501323410431100166020ustar00rootroot00000000000000include ../common.mak ALL_CPPFLAGS += -DSYSFS all: dasdview libs = $(rootdir)/libdasd/libdasd.a \ $(rootdir)/libzds/libzds.a \ $(rootdir)/libvtoc/libvtoc.a \ $(rootdir)/libu2s/libu2s.a \ $(rootdir)/libutil/libutil.a dasdview: dasdview.o $(libs) install: all $(INSTALL) -d -m 755 $(DESTDIR)$(BINDIR) $(DESTDIR)$(MANDIR)/man8 $(INSTALL) -g $(GROUP) -o $(OWNER) -m 755 dasdview $(DESTDIR)$(BINDIR) $(INSTALL) -g $(GROUP) -o $(OWNER) -m 644 dasdview.8 \ $(DESTDIR)$(MANDIR)/man8 clean: rm -f *.o *~ dasdview core .PHONY: all install clean s390-tools-2.3.0/dasdview/dasdview.8000066400000000000000000000136641323410431100170570ustar00rootroot00000000000000.\" Copyright 2017 IBM Corp. .\" s390-tools is free software; you can redistribute it and/or modify .\" it under the terms of the MIT license. See LICENSE for details. .\" .TH DASDVIEW 8 "Apr 2006" "s390-tools" .SH NAME dasdview \- Display DASD and VTOC information and dump the content of a DASD to the console. .SH SYNOPSIS \fBdasdview\fR [-h] [-v] .br [-b \fIbegin\fR] [-s \fIsize\fR] [-1|-2] .br [-i] [-x] [-j] [-c] .br [-l] [-t {\fIinfo\fR|\fIf1\fR|\fIf3\fR|\fIf4\fR|\fIf5\fR|\fIf7\fR|\fIf8\fR|\fIf9\fR}] .br \fIdevice\fR .SH DESCRIPTION \fBdasdview\fR prints you some useful information of your disks to the console. You can display a disk dump by specifying start point and offset and you can print the volume label and VTOC entries. The \fIdevice\fR is the node of the device (e.g. '/dev/dasda'). Any device node created by udev for kernel 2.6 can be used (e.g. '/dev/dasd/0.0.b100/disc'). DASD devices in raw_track_access mode are supported and detected automatically. When in raw_track_access mode, the same basic functions are available as in the regular mode, but the output may have a slightly different layout: .IP \(bu 2 The disk dump functions (\fB-b\fR and \fB-s\fR) print the count, key and data information for the whole track, and not just the contents of the data areas. .IP \(bu 2 The VTOC listing (\fB-t\fR) print all specified DSCBs in the same format as in the regular mode, but in the sequence as they appear in the VTOC. The \fB-t info\fR overview contains more details for each data set than in the regular mode, to support the larger variety of data set layouts. .SH OPTIONS .TP \fB-h\fR or \fB--help\fR Print usage and exit. .TP \fB-v\fR or \fB--version\fR Print version number and exit. .TP \fB-b\fR \fIbegin\fR or \fB--begin=\fR\fIbegin\fR Print a disk dump to the console, starting with \fIbegin\fR. The content of the disk will be displayed in hexadecimal numbers, ASCII text and EBCDIC text. If no size is specified dasdview will take the default size. The variable \fIbegin\fR can be specified in one of the following ways: .br begin[k|m|b|t|c] .br The default for \fIbegin\fR is \fI0\fR. .br \fBNote 1:\fR dasdview will show you the content of your disk using the DASD driver. If this driver decides to hide or add some parts of the disk, you have to live with it. This happens for example with the first two tracks of a cdl-formatted disk. In this case the DASD driver fills up shorter blocks with zeros to have a constant blocksize. And all applications, including dasdview, believe it. .br \fBNote 2:\fR In raw_track_access mode \fIbegin\fR must be aligned to track boundaries. A simple way to do that is to specify a track or cylinder as starting point. .br examples: .br -b 32 --> start printing at Byte 32 .br -b 32k --> start printing at kByte 32 .br -b 32m --> start printing at MByte 32 .br -b 32b --> start printing at block 32 .br -b 32t --> start printing at track 32 .br -b 32c --> start printing at cylinder 32 .TP \fB-s\fR \fIsize\fR or \fB--size=\fR\fIsize\fR Print a disk dump to the console, starting with \fIbegin\fR, specified with the \fB-b\fR option and size \fIsize\fR. The content of the disk will be displayed in hexadecimal numbers, ASCII text and EBCDIC text. If no start value is specified dasdview will take the default start value. The variable \fIsize\fR can be specified in one of the following ways: .br size[k|m|b|t|c] .br \fBNote:\fR In raw_track_access mode \fIsize\fR must be a multiple of one track. A simple way to do that is to specify the size in tracks or cylinders. .br The default for \fIsize\fR is \fI128\fR in regular mode and \fI1t\fR in raw_track_access mode. .br examples: .br -s 16 --> use a 16 Byte size .br -s 16k --> use a 16 kByte size .br -s 16m --> use a 16 MByte size .br -s 16b --> use a 16 block size .br -s 16t --> use a 16 track size .br -s 16c --> use a 16 cylinder size .TP \fB-1\fR This option tells dasdview to print the disk dump using format 1. This means you will get 16 Bytes per line in hex, ascii and ebcdic. There is no line number. .br The \fB-1\fR option makes only sense with the \fB-b\fR and/or the \fB-s\fR options. .br This is the default. .TP \fB-2\fR This option tells dasdview to print the disk dump using format 2. This means you will get 8 Bytes per line in hex, ascii and ebcdic. And in addition a line number and a decimal and hexadecimal byte count will be printed. .br The \fB-2\fR option makes only sense with the \fB-b\fR and/or the \fB-s\fR options. In raw_track_access mode this format is not supported and the option will be ignored. .TP \fB-i\fR or \fB--info\fR Print some useful information (e.g. device node/number/type or geometry data). When running dasdview on a kernel 2.6 based distribution the busid is printed instead of the device number. .TP \fB-x\fR or \fB--extended\fR Print some more DASD information (e.g. open count, subchannel identifier). .TP \fB-j\fR or \fB--volser\fR Print volume serial number (volume identifier). .TP \fB-l\fR or \fB--label\fR Print the volume label. .TP \fB-c\fR or \fB--characteristic\fR Print some information about the device e.g. if it is encrypted. .TP \fB-t\fR \fIspec\fR or \fB--vtoc=\fR\fIspec\fR Print the VTOC (table of content) or single VTOC entries to the console. \fIspec\fR can be one of the following strings: .br \fIinfo\fR: .br Gives you a VTOC overview. You will see what other S/390 or zSeries operating systems would see (e.g. data set names and sizes). .br \fIf1\fR: .br Print the content of all format 1 DSCBs. .br \fIf3\fR: .br Print the content of all format 3 DSCBs. .br \fIf4\fR: .br Print the content of the format 4 DSCB. .br \fIf5\fR: .br Print the content of the format 5 DSCB. .br \fIf7\fR: .br Print the content of the format 7 DSCB. .br \fIf8\fR: .br Print the content of all format 8 DSCBs. .br \fIf9\fR: .br Print the content of all format 9 DSCBs. .br \fIall\fR: .br Print the content of all DSCBs. s390-tools-2.3.0/dasdview/dasdview.c000066400000000000000000002076331323410431100171330ustar00rootroot00000000000000/* * dasdview - Display DASD and VTOC information or dump the contents of a DASD * * Copyright IBM Corp. 2001, 2017 * * s390-tools is free software; you can redistribute it and/or modify * it under the terms of the MIT license. See LICENSE for details. */ #define _LARGEFILE64_SOURCE /* needed for unistd.h */ #define _FILE_OFFSET_BITS 64 /* needed for unistd.h */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "lib/dasd_base.h" #include "lib/dasd_sys.h" #include "lib/libzds.h" #include "lib/u2s.h" #include "lib/util_base.h" #include "lib/util_opt.h" #include "lib/util_prg.h" #include "lib/vtoc.h" #include "lib/zt_common.h" #include "dasdview.h" /* Characters per line */ #define DASDVIEW_CPL 16 static const struct util_prg prg = { .desc = "Display DASD and VTOC information and dump the content of " "a DASD to the console.\n" "DEVICE is the node of the device (e.g. '/dev/dasda').", .args = "DEVICE", .copyright_vec = { { .owner = "IBM Corp.", .pub_first = 2001, .pub_last = 2006, }, UTIL_PRG_COPYRIGHT_END } }; static struct util_opt opt_vec[] = { UTIL_OPT_SECTION("DUMP OPTIONS"), { .option = { NULL, no_argument, NULL, '1' }, .desc = "Show DASD content in short Hex/EBCDIC/ASCII format", .flags = UTIL_OPT_FLAG_NOLONG, }, { .option = { NULL, no_argument, NULL, '2' }, .desc = "Show DASD content in detailed Hex/EBCDIC/ASCII format", .flags = UTIL_OPT_FLAG_NOLONG, }, { .option = { "begin", required_argument, NULL, 'b' }, .argument = "BEGIN", .desc = "Specify start of dump in kilobytes (suffix k), " "megabytes (m), blocks (b), tracks (t), or cylinders (c)", }, { .option = { "size", required_argument, NULL, 's' }, .argument = "SIZE", .desc = "Specify size of dump in kilobytes (suffix k), " "megabytes (m), blocks (b), tracks (t), or cylinders (c)", }, UTIL_OPT_SECTION("MISC"), { .option = { "characteristic", no_argument, NULL, 'c' }, .desc = "Print the characteristics of a device", }, { .option = { "info", no_argument, NULL, 'i' }, .desc = "Print general DASD information and geometry", }, { .option = { "volser", no_argument, NULL, 'j' }, .desc = "Print the volume serial number", }, { .option = { "label", no_argument, NULL, 'l' }, .desc = "Print information about the volume label", }, { .option = { "vtoc", required_argument, NULL, 't' }, .argument = "SPEC", .desc = "Print the table of content (VTOC)", }, { .option = { "extended", no_argument, NULL, 'x' }, .desc = "Print extended DASD information", }, UTIL_OPT_HELP, UTIL_OPT_VERSION, UTIL_OPT_END }; /* * Generate and print an error message based on the formatted * text string FMT and a variable amount of extra arguments. */ static void zt_error_print(const char *fmt, ...) { va_list args; va_start(args, fmt); vsnprintf(error_str, ERROR_STRING_SIZE, fmt, args); va_end(args); fprintf(stderr, "Error: %s\n", error_str); } /* * replace special characters with dots and question marks */ static void dot(char label[]) { int i; char c; for (i = 0; i < 16; i++) { c = label[i]; if (c <= 0x20) label[i] = '?'; if (c == 0x00) label[i] = '.'; if (c == 0x60) label[i] = '?'; if (c >= 0x7f) label[i] = '?'; } } static void dasdview_get_info(dasdview_info_t *info) { struct dasd_eckd_characteristics *characteristics; int err; err = dasd_get_geo(info->device, &info->geo); if (err != 0) { /* Test for unknown device in the first call to libdasd to avoid * spitting out two different error messages to the user */ if (err != EBADF) zt_error_print("dasdview: " "Could not retrieve geo information!\n"); exit(-1); } if (dasd_get_blocksize(info->device, &info->blksize) != 0) { zt_error_print("dasdview: " "Could not retrieve blocksize information!\n"); exit(-1); } if (dasd_get_info(info->device, &info->dasd_info) != 0) { zt_error_print("dasdview: " "Could not retrieve disk information!\n"); exit(-1); } characteristics = (struct dasd_eckd_characteristics *) &info->dasd_info.characteristics; if (characteristics->no_cyl == LV_COMPAT_CYL && characteristics->long_no_cyl) info->hw_cylinders = characteristics->long_no_cyl; else info->hw_cylinders = characteristics->no_cyl; if (u2s_getbusid(info->device, info->busid) == -1) info->busid_valid = 0; else info->busid_valid = 1; info->raw_track_access = dasd_sys_raw_track_access(info->device); } static void dasdview_parse_input(unsigned long long *p, dasdview_info_t *info, char *s) { unsigned long long l; char *endp; char suffix; l = strtoull(s, &endp, 0); if ((endp == s) || ((l + 1) == 0)) goto error; if (*endp) { if (!strchr("kmtbcKMTBC", *endp) || *(endp + 1)) goto error; suffix = tolower(*endp); } else { suffix = 0; } if (info->raw_track_access) { switch (suffix) { case 't': l *= RAWTRACKSIZE; break; case 'c': l *= (unsigned long long)info->geo.heads * RAWTRACKSIZE; break; case 0: if (l % RAWTRACKSIZE) { zt_error_print("dasdview: only full tracks can" " be accessd on devices with " " raw_track_access enabled.\n", s); goto error; } break; default: zt_error_print("dasdview: only types t and c are" " allowed for devices with" " raw_track_access enabled.\n", s); goto error; } } else { switch (suffix) { case 'k': l *= 1024LL; break; case 'm': l *= 1024LL * 1024LL; break; case 't': l *= (unsigned long long)info->blksize * (unsigned long long)info->geo.sectors; break; case 'b': l *= (unsigned long long)info->blksize; break; case 'c': l *= (unsigned long long)info->blksize * (unsigned long long)info->geo.sectors * (unsigned long long)info->geo.heads; break; default: break; } } *p = l; return; error: zt_error_print("dasdview: usage error\n" "%s is not a valid begin/size value!", s); exit(-1); } /* * Print general DASD information. */ static void dasdview_print_general_info(dasdview_info_t *info) { printf("\n--- general DASD information -----------------" "---------------------------------\n"); printf("device node : %s\n", info->device); #ifdef SYSFS struct utsname buf; unsigned char a, b, c; char suffix[sizeof(buf.release)]; int rc; rc = uname(&buf); if (!rc) { sscanf(buf.release, "%c.%c.%c-%s", &a, &b, &c, suffix); if (KERNEL_VERSION(2, 5, 0) <= KERNEL_VERSION(a, b, c)) { if (info->busid_valid) printf("busid : %s\n", info->busid); else printf("busid :" " \n"); } else { #endif printf("device number : hex %x \tdec %d\n", info->dasd_info.devno, info->dasd_info.devno); #ifdef SYSFS } } #endif printf("type : %4s\n", info->dasd_info.type); printf("device type : hex %x \tdec %d\n", info->dasd_info.dev_type, info->dasd_info.dev_type); printf("\n--- DASD geometry ----------------------------" "---------------------------------\n"); printf("number of cylinders : hex %x \tdec %d\n", info->hw_cylinders, info->hw_cylinders); printf("tracks per cylinder : hex %x \tdec %d\n", info->geo.heads, info->geo.heads); printf("blocks per track : hex %x \tdec %d\n", info->geo.sectors, info->geo.sectors); printf("blocksize : hex %x \tdec %d\n", info->blksize, info->blksize); } /* * Loop over the given character array and HEXdump the content. */ static inline void dasdview_dump_array(char *name, int size, unsigned char *addr) { int i; for (i = 0; i < size; i++) { if (i % DASDVIEW_CPL == 0) { if (i == 0) printf("%-23.23s: ", name); else printf("\n%25s", ""); } else { if (i % 8 == 0) printf(" "); if (i % 4 == 0) printf(" "); } printf("%02x", addr[i]); } printf("\n"); } /* * Print extended DASD information. */ static void dasdview_print_extended_info(dasdview_info_t *info) { unsigned int i; struct dasd_information2_t *dasd_info; struct { unsigned int mask; char *name; } flist[2] = { {DASD_FEATURE_READONLY, "ro" }, {DASD_FEATURE_USEDIAG, "diag"} }; dasd_info = &info->dasd_info; printf("\n--- extended DASD information ----------------" "---------------------------------\n"); printf("real device number : hex %x \tdec %d\n", dasd_info->real_devno, dasd_info->real_devno); printf("subchannel identifier : hex %x \tdec %d\n", dasd_info->schid, dasd_info->schid); printf("CU type (SenseID) : hex %x \tdec %d\n", dasd_info->cu_type, dasd_info->cu_type); printf("CU model (SenseID) : hex %x \tdec %d\n", dasd_info->cu_model, dasd_info->cu_model); printf("device type (SenseID) : hex %x \tdec %d\n", dasd_info->dev_type, dasd_info->dev_type); printf("device model (SenseID) : hex %x \tdec %d\n", dasd_info->dev_model, dasd_info->dev_model); printf("open count : hex %x \tdec %d\n", dasd_info->open_count, dasd_info->open_count); printf("req_queue_len : hex %x \tdec %d\n", dasd_info->req_queue_len, dasd_info->req_queue_len); printf("chanq_len : hex %x \tdec %d\n", dasd_info->chanq_len, dasd_info->chanq_len); printf("status : hex %x \tdec %d\n", dasd_info->status, dasd_info->status); printf("label_block : hex %x \tdec %d\n", dasd_info->label_block, dasd_info->label_block); printf("FBA_layout : hex %x \tdec %d\n", dasd_info->FBA_layout, dasd_info->FBA_layout); printf("characteristics_size : hex %x \tdec %d\n", dasd_info->characteristics_size, dasd_info->characteristics_size); printf("confdata_size : hex %x \tdec %d\n", dasd_info->confdata_size, dasd_info->confdata_size); printf("format : hex %x \tdec %d \t%s\n", dasd_info->format, dasd_info->format, dasd_info->format == DASD_FORMAT_NONE ? "NOT formatted" : dasd_info->format == DASD_FORMAT_LDL ? "LDL formatted" : dasd_info->format == DASD_FORMAT_CDL ? "CDL formatted" : "unknown format"); printf("features : hex %x \tdec %d \t", dasd_info->features, dasd_info->features); if (dasd_info->features == DASD_FEATURE_DEFAULT) { printf("default\n"); } else { for (i = 0; i < UTIL_ARRAY_SIZE(flist); i++) if (dasd_info->features & flist[i].mask) printf("%s ", flist[i].name); printf("\n"); } printf("\n"); dasdview_dump_array("characteristics", dasd_info->characteristics_size, dasd_info->characteristics); printf("\n"); dasdview_dump_array("configuration_data", dasd_info->confdata_size, dasd_info->configuration_data); } static void dasdview_read_vlabel(dasdview_info_t *info, volume_label_t *vlabel) { volume_label_t tmp; unsigned long pos; pos = info->dasd_info.label_block * info->blksize; bzero(vlabel, sizeof(volume_label_t)); if ((strncmp(info->dasd_info.type, "ECKD", 4) == 0) && !info->dasd_info.FBA_layout) { /* OS/390 and zOS compatible disk layout */ vtoc_read_volume_label(info->device, pos, vlabel); } else { /* standard LINUX disk layout */ vtoc_read_volume_label(info->device, pos, &tmp); memcpy(vlabel->vollbl, &tmp, sizeof(tmp) - 4); } } static void dasdview_print_vlabel(dasdview_info_t *info) { volume_label_t vlabel; volume_label_t *tmpvlabel; int rc; unsigned char s4[5], t4[5], s5[6], t5[6], s6[7], t6[7]; char s14[15], t14[15], s29[30], t29[30]; int i; if (info->raw_track_access) { rc = lzds_dasd_read_vlabel(info->dasd); if (rc) { zt_error_print("error when reading label from device:" " rc=%d\n", rc); exit(-1); } lzds_dasd_get_vlabel(info->dasd, &tmpvlabel); memcpy(&vlabel, tmpvlabel, sizeof(vlabel)); } else { dasdview_read_vlabel(info, &vlabel); } printf("\n--- volume label -----------------------------" "---------------------------------\n"); bzero(s4, 5); bzero(t4, 5); strncpy((char *)s4, vlabel.volkey, 4); printf("volume label key : ascii '%4s'\n", s4); vtoc_ebcdic_dec((char *)s4, (char *)t4, 4); printf(" : ebcdic '%4s'\n", t4); printf(" : hex "); for (i = 0; i < 4; i++) printf("%02x", s4[i]); bzero(s4, 5); bzero(s4, 5); strncpy((char *)s4, vlabel.vollbl, 4); printf("\n\nvolume label identifier : ascii '%4s'\n", s4); vtoc_ebcdic_dec((char *)s4, (char *)t4, 4); printf(" : ebcdic '%4s'\n", t4); printf(" : hex "); for (i = 0; i < 4; i++) printf("%02x", s4[i]); bzero(s6, 7); bzero(t6, 7); strncpy((char *)s6, vlabel.volid, 6); printf("\n\nvolume identifier : ascii '%6s'\n", s6); vtoc_ebcdic_dec((char *)s6, (char *)t6, 6); printf(" : ebcdic '%6s'\n", t6); printf(" : hex "); for (i = 0; i < 6; i++) printf("%02x", s6[i]); printf("\n\nsecurity byte : hex %02x\n", vlabel.security); printf("\n\nVTOC pointer : hex %04x%04x%02x ", vlabel.vtoc.cc, vlabel.vtoc.hh, vlabel.vtoc.b); if ((vlabel.vtoc.cc == 0x4040) && (vlabel.vtoc.hh == 0x4040) && (vlabel.vtoc.b == 0x40)) printf("\n"); else printf("\n " "(cyl %d, trk %d, blk %d)\n\n", vtoc_get_cyl_from_cchhb(&vlabel.vtoc), vtoc_get_head_from_cchhb(&vlabel.vtoc), vlabel.vtoc.b); bzero(s5, 6); bzero(t5, 6); strncpy((char *)s5, vlabel.res1, 5); printf("reserved : ascii '%5s'\n", s5); vtoc_ebcdic_dec((char *)s5, (char *)t5, 5); printf(" : ebcdic '%5s'\n", t5); printf(" : hex "); for (i = 0; i < 5; i++) printf("%02x", s5[i]); bzero(s4, 5); bzero(t4, 5); strncpy((char *)s4, vlabel.cisize, 4); printf("\n\nCI size for FBA : ascii '%4s'\n", s4); vtoc_ebcdic_dec((char *)s4, (char *)t4, 4); printf(" : ebcdic '%4s'\n", t4); printf(" : hex "); for (i = 0; i < 4; i++) printf("%02x", s4[i]); bzero(s4, 5); bzero(t4, 5); strncpy((char *)s4, vlabel.blkperci, 4); printf("\n\nblocks per CI (FBA) : ascii '%4s'\n", s4); vtoc_ebcdic_dec((char *)s4, (char *)t4, 4); printf(" : ebcdic '%4s'\n", t4); printf(" : hex "); for (i = 0; i < 4; i++) printf("%02x", s4[i]); bzero(s4, 5); bzero(t4, 5); strncpy((char *)s4, vlabel.labperci, 4); printf("\n\nlabels per CI (FBA) : ascii '%4s'\n", s4); vtoc_ebcdic_dec((char *)s4, (char *)t4, 4); printf(" : ebcdic '%4s'\n", t4); printf(" : hex "); for (i = 0; i < 4; i++) printf("%02x", s4[i]); bzero(s4, 5); bzero(t4, 5); strncpy((char *)s4, vlabel.res2, 4); printf("\n\nreserved : ascii '%4s'\n", s4); vtoc_ebcdic_dec((char *)s4, (char *)t4, 4); printf(" : ebcdic '%4s'\n", t4); printf(" : hex "); for (i = 0; i < 4; i++) printf("%02x", s4[i]); bzero(s14, 15); bzero(t14, 15); strncpy(s14, vlabel.lvtoc, 14); printf("\n\nowner code for VTOC : ascii '%14s'\n", s14); vtoc_ebcdic_dec(s14, t14, 14); printf(" ebcdic '%14s'\n", t14); printf(" hex "); for (i = 0; i < 14; i++) { printf("%02x", s14[i]); if ((i + 1) % 4 == 0) printf(" "); if ((i + 1) % 8 == 0) printf(" "); } bzero(s29, 30); strncpy(s29, vlabel.res3, 28); printf("\n\nreserved : ascii '%28s'\n", s29); bzero(t29, 30); vtoc_ebcdic_dec(s29, t29, 28); printf(" ebcdic '%28s'\n", t29); printf(" hex "); for (i = 0; i < 28; i++) { printf("%02x", s29[i]); if ((i + 1) % 4 == 0) printf(" "); if ((i + 1) % 8 == 0) printf(" "); if ((i + 1) % 16 == 0) printf("\n " " "); } bzero(s4, 5); bzero(t4, 5); s4[0] = vlabel.ldl_version; printf("\n\nldl_version : ascii '%1s'\n", s4); vtoc_ebcdic_dec((char *)s4, (char *)t4, 1); printf(" : ebcdic '%1s'\n", t4); printf(" : hex %02x", s4[0]); printf("\n\nformatted_blocks : dec %llu", vlabel.formatted_blocks); printf("\n : hex %016llx", vlabel.formatted_blocks); printf("\n"); } static void dasdview_print_volser(dasdview_info_t *info) { volume_label_t vlabel; volume_label_t *tmpvlabel; char volser[7]; char vollbl[5]; int rc; if (info->raw_track_access) { rc = lzds_dasd_read_vlabel(info->dasd); if (rc) { zt_error_print("error when reading label from device:" " rc=%d\n", rc); exit(-1); } lzds_dasd_get_vlabel(info->dasd, &tmpvlabel); memcpy(&vlabel, tmpvlabel, sizeof(vlabel)); } else { dasdview_read_vlabel(info, &vlabel); } bzero(vollbl, 5); bzero(volser, 7); strncpy(vollbl, vlabel.vollbl, 4); vtoc_ebcdic_dec(vollbl, vollbl, 4); if ((strncmp(vollbl, "VOL1", 4) == 0) || (strncmp(vollbl, "LNX1", 4) == 0)) { strncpy(volser, vlabel.volid, 6); vtoc_ebcdic_dec(volser, volser, 6); } else { strncpy(volser, " ", 6); } printf("%6.6s\n", volser); } static void dasdview_read_vtoc(dasdview_info_t *info) { volume_label_t vlabel; format1_label_t tmp; unsigned long maxblk, pos; u_int64_t vtocblk; int i; pos = info->dasd_info.label_block * info->blksize; bzero(&vlabel, sizeof(vlabel)); if ((strncmp(info->dasd_info.type, "ECKD", 4) == 0) && !info->dasd_info.FBA_layout) { /* OS/390 and zOS compatible disk layout */ vtoc_read_volume_label(info->device, pos, &vlabel); } else { zt_error_print("dasdview: disk layout error\n" "%s is not formatted with the z/OS " "compatible disk layout!\n", info->device); exit(-1); } vtocblk = (u_int64_t)vtoc_get_cyl_from_cchhb(&vlabel.vtoc) * info->geo.heads * info->geo.sectors + vtoc_get_head_from_cchhb(&vlabel.vtoc) * info->geo.sectors + vlabel.vtoc.b; /* * geo.cylinders is the minimum of hw_cylinders and LV_COMPAT_CYL * Actually the vtoc should be located in in the first 65k-1 tracks * so this check could be even more restrictive, but it doesn't * hurt the way it is. Linux cdl format restricts the vtoc to * the first two tracks anyway. */ maxblk = info->geo.cylinders * info->geo.heads * info->geo.sectors; if ((vtocblk <= 0) || (vtocblk > maxblk)) { zt_error_print("dasdview: VTOC error\n" "Volume label VTOC pointer is not valid!\n"); exit(-1); } vtoc_read_label(info->device, (vtocblk - 1) * info->blksize, NULL, &info->f4, NULL, NULL); if ((info->f4.DS4KEYCD[0] != 0x04) || (info->f4.DS4KEYCD[43] != 0x04) || (info->f4.DS4IDFMT != 0xf4)) { /* format4 DSCB is invalid */ zt_error_print("dasdview: VTOC error\n" "Format 4 DSCB is invalid!\n"); exit(-1); } info->f4c++; pos = (vtocblk - 1) * info->blksize; for (i = 1; i < info->geo.sectors; i++) { pos += info->blksize; vtoc_read_label(info->device, pos, &tmp, NULL, NULL, NULL); switch (tmp.DS1FMTID) { case 0xf1: memcpy(&info->f1[info->f1c], &tmp, sizeof(format1_label_t)); info->f1c++; break; case 0xf4: info->f4c++; break; case 0xf5: memcpy(&info->f5, &tmp, sizeof(format1_label_t)); info->f5c++; break; case 0xf7: memcpy(&info->f7, &tmp, sizeof(format1_label_t)); info->f7c++; break; case 0xf8: memcpy(&info->f8[info->f8c], &tmp, sizeof(format1_label_t)); info->f8c++; break; case 0xf9: memcpy(&info->f9[info->f9c], &tmp, sizeof(format1_label_t)); info->f9c++; break; case 0x00: break; default: printf("Unknown label in VTOC detected (id=%x)\n", tmp.DS1FMTID); } } if (info->f4c > 1) { zt_error_print("dasdview: VTOC error\n" "More than one FMT4 DSCB!\n"); exit(-1); } if (info->f5c > 1) { zt_error_print("dasdview: VTOC error\n" "More than one FMT5 DSCB!\n"); exit(-1); } if (info->f7c > 1) { zt_error_print("dasdview: VTOC error\n" "More than one FMT7 DSCB!\n"); exit(-1); } } static void dasdview_print_format1_8_short_info(format1_label_t *f1, struct hd_geometry *geo) { char s6[7], s13[14], s44[45]; unsigned long track_low, track_up; bzero(s44, 45); strncpy(s44, f1->DS1DSNAM, 44); vtoc_ebcdic_dec(s44, s44, 44); bzero(s6, 7); strncpy(s6, (char *)f1->DS1DSSN, 6); vtoc_ebcdic_dec(s6, s6, 6); bzero(s13, 14); strncpy(s13, (char *)f1->DS1SYSCD, 13); vtoc_ebcdic_dec(s13, s13, 13); track_low = cchh2trk(&f1->DS1EXT1.llimit, geo); track_up = cchh2trk(&f1->DS1EXT1.ulimit, geo); printf(" | %44s | trk | trk |\n", s44); printf(" | data set serial number :" " '%6s' |" " %12ld | %12ld |\n", s6, track_low, track_up); printf(" | system code :" " '%13s' |" " cyl/trk | cyl/trk |\n", s13); printf(" | creation date :" " year %4d, day %3d |" " %8d/%3d | %8d/%3d |\n", f1->DS1CREDT.year + 1900, f1->DS1CREDT.day, vtoc_get_cyl_from_cchh(&f1->DS1EXT1.llimit), vtoc_get_head_from_cchh(&f1->DS1EXT1.llimit), vtoc_get_cyl_from_cchh(&f1->DS1EXT1.ulimit), vtoc_get_head_from_cchh(&f1->DS1EXT1.ulimit)); printf(" +-----------------------------------------" "-----+--------------+--------------+\n"); } static void dasdview_print_vtoc_info(dasdview_info_t *info) { int i; printf("--- VTOC info --------------------------------" "---------------------------------\n"); printf("The VTOC contains:\n"); printf(" %d format 1 label(s)\n", info->f1c); printf(" %d format 4 label(s)\n", info->f4c); printf(" %d format 5 label(s)\n", info->f5c); printf(" %d format 7 label(s)\n", info->f7c); printf(" %d format 8 label(s)\n", info->f8c); printf(" %d format 9 label(s)\n", info->f9c); if ((info->f1c < 1) && (info->f8c < 1)) { printf("There are no partitions defined.\n"); } else { printf("Other mainframe operating systems would see " "the following data sets:\n"); printf(" +----------------------------------------------+" "--------------+--------------+\n"); printf(" | data set |" " start | end |\n"); printf(" +----------------------------------------------+" "--------------+--------------+\n"); for (i = 0; i < info->f1c; i++) dasdview_print_format1_8_short_info(&info->f1[i], &info->geo); for (i = 0; i < info->f8c; i++) dasdview_print_format1_8_short_info(&info->f8[i], &info->geo); } } static void dasdview_print_short_info_extent_raw(extent_t *ext) { if (ext->typeind > 0x00) printf(" %3d (%5d,%5d) (%5d,%5d)\n", ext->seqno, vtoc_get_cyl_from_cchh(&ext->llimit), vtoc_get_head_from_cchh(&ext->llimit), vtoc_get_cyl_from_cchh(&ext->ulimit), vtoc_get_head_from_cchh(&ext->ulimit)); } static void dasdview_print_format1_8_short_info_raw(format1_label_t *f1, dasdview_info_t *info) { char s6[7], s13[14], s44[45]; unsigned long long j; format3_label_t *f3; format9_label_t *f9; struct dscb *dscb; int rc; bzero(s44, 45); strncpy(s44, f1->DS1DSNAM, 44); vtoc_ebcdic_dec(s44, s44, 44); bzero(s6, 7); strncpy(s6, (char *)f1->DS1DSSN, 6); vtoc_ebcdic_dec(s6, s6, 6); bzero(s13, 14); strncpy(s13, (char *)f1->DS1SYSCD, 13); vtoc_ebcdic_dec(s13, s13, 13); printf("data set name : '%44s'\n", s44); printf("data set serial number : '%6s'\n", s6); printf("system code : '%13s'\n", s13); printf("creation date : year %4d, day %3d\n", f1->DS1CREDT.year + 1900, f1->DS1CREDT.day); printf("flags : "); if (f1->DS1FLAG1 & 0x80) printf("DS1COMPR "); if (f1->DS1FLAG1 & 0x40) printf("DS1CPOIT "); if (f1->DS1FLAG1 & 0x20) printf("DS1EXPBY "); if (f1->DS1FLAG1 & 0x10) printf("DS1RECAL "); if (f1->DS1FLAG1 & 0x08) printf("DS1LARGE "); if (f1->DS1FLAG1 & 0x04) printf("unknown "); if ((f1->DS1FLAG1 & 0x01) && (f1->DS1FLAG1 & 0x02)) printf("DS1EATTR=not used "); if (!(f1->DS1FLAG1 & 0x01) && (f1->DS1FLAG1 & 0x02)) printf("DS1EATTR=optional "); if ((f1->DS1FLAG1 & 0x01) && !(f1->DS1FLAG1 & 0x02)) printf("DS1EATTR=no "); if (f1->DS1FLAG1 & 0x00) printf("DS1EATTR=default "); printf("\n"); printf("SMS flags : "); if (f1->DS1SMSFG & 0x80) printf("DS1SMSDS "); if (f1->DS1SMSFG & 0x40) printf("DS1SMSUC "); if (f1->DS1SMSFG & 0x20) printf("DS1REBLK "); if (f1->DS1SMSFG & 0x10) printf("DS1CRSDB "); if (f1->DS1SMSFG & 0x08) printf("DS1PDSE "); if (f1->DS1SMSFG & 0x04) printf("DS1STRP "); if (f1->DS1SMSFG & 0x02) printf("DS1PDSEX "); if (f1->DS1SMSFG & 0x01) printf("DS1DSAE "); printf("\n"); printf("organisation : "); if (f1->DS1DSRG1 & 0x80) printf("DS1DSGIS "); if (f1->DS1DSRG1 & 0x40) printf("DS1DSGPS "); if (f1->DS1DSRG1 & 0x20) printf("DS1DSGDA "); if (f1->DS1DSRG1 & 0x10) printf("DS1DSGCX "); if (f1->DS1DSRG1 & 0x08) printf("reserved "); if (f1->DS1DSRG1 & 0x04) printf("reserved "); if (f1->DS1DSRG1 & 0x02) printf("DS1DSGPO "); if (f1->DS1DSRG1 & 0x01) printf("DS1DSGU "); if (f1->DS1DSRG2 & 0x80) printf("DS1DSGGS "); if (f1->DS1DSRG2 & 0x40) printf("DS1DSGTX "); if (f1->DS1DSRG2 & 0x20) printf("DS1DSGTQ "); if (f1->DS1DSRG2 & 0x10) printf("reserved "); if (f1->DS1DSRG2 & 0x08) printf("DS1ACBM "); if (f1->DS1DSRG2 & 0x04) printf("DS1DSGTR "); if (f1->DS1DSRG2 & 0x02) printf("reserved "); if (f1->DS1DSRG2 & 0x01) printf("reserved"); printf("\n"); printf("record format : "); if ((f1->DS1RECFM & 0x80) && !(f1->DS1RECFM & 0x40)) printf("DS1RECFF "); if (!(f1->DS1RECFM & 0x80) && (f1->DS1RECFM & 0x40)) printf("DS1RECFV "); if ((f1->DS1RECFM & 0x80) && (f1->DS1RECFM & 0x40)) printf("DS1RECFU "); if (f1->DS1RECFM & 0x20) printf("DS1RECFT "); if (f1->DS1RECFM & 0x10) printf("DS1RECFB "); if (f1->DS1RECFM & 0x08) printf("DS1RECFS "); if (f1->DS1RECFM & 0x04) printf("DS1RECFA "); if (f1->DS1RECFM & 0x02) printf("DS1RECMC "); if (f1->DS1RECFM & 0x01) printf("reserved"); printf("\n"); printf("(max) block length : %u\n", f1->DS1BLKL); printf("logical record length : %u\n", f1->DS1LRECL); printf("extents belonging to this dataset:\n"); printf(" seqno llimit (cyl, trk) ulimit (cyl, trk)\n"); /* The format 1 label can point to a chain of f3 labels * The format 8 label points to a (chain of) f9 labels * The format 9 label may contain several format 3 labels, but as * far as I know, it is still OK to follow the 'next dscb' chain. * So for a format 9 label I have to follow this chain until I * find the first format 3 dscb and then I can follow the format 3 * dscbs to the end of the chain. */ rc = lzds_raw_vtoc_get_dscb_from_cchhb(info->rawvtoc, &f1->DS1PTRDS, &dscb); /* The first f9 label contains extra data that we may want to print here */ while (!rc && dscb && dscb->fmtid == 0xf9) { f9 = (format9_label_t *)dscb; rc = lzds_raw_vtoc_get_dscb_from_cchhb(info->rawvtoc, &f9->DS9PTRDS, &dscb); } if (rc) { zt_error_print("dasdview: Broken format 3 DSCB chain \n"); exit(-1); } f3 = (dscb && dscb->fmtid == 0xf3) ? (format3_label_t *)dscb : NULL; /* first print the extents that are part of the f1/f8 label itself */ dasdview_print_short_info_extent_raw(&f1->DS1EXT1); dasdview_print_short_info_extent_raw(&f1->DS1EXT2); dasdview_print_short_info_extent_raw(&f1->DS1EXT3); /* now follow the f3 chain into the rabbit hole */ while (f3) { /* sanity check */ if (f3->DS3FMTID != 0xf3) { zt_error_print("dasdview: Broken format 3 DSCB" " chain \n"); exit(-1); } for (j = 0; j < 4; ++j) dasdview_print_short_info_extent_raw(&f3->DS3EXTNT[j]); for (j = 0; j < 9; ++j) dasdview_print_short_info_extent_raw(&f3->DS3ADEXT[j]); rc = lzds_raw_vtoc_get_dscb_from_cchhb(info->rawvtoc, &f3->DS3PTRDS, (struct dscb **)&f3); if (rc) { zt_error_print("dasdview: Broken format 3 DSCB" " chain \n"); exit(-1); } } printf("\n"); } static void dasdview_print_vtoc_info_raw(dasdview_info_t *info) { struct dscbiterator *it; struct dscb *dscb; int rc; int f1c, f3c, f4c, f5c, f7c, f8c, f9c; f1c = 0; f3c = 0; f4c = 0; f5c = 0; f7c = 0; f8c = 0; f9c = 0; rc = lzds_raw_vtoc_alloc_dscbiterator(info->rawvtoc, &it); if (rc) { zt_error_print("dasdview: could not allocate DSCB iterator \n"); exit(-1); } while (!lzds_dscbiterator_get_next_dscb(it, &dscb)) { if (dscb->fmtid == 0xf1) ++f1c; else if (dscb->fmtid == 0xf3) ++f3c; else if (dscb->fmtid == 0xf4) ++f4c; else if (dscb->fmtid == 0xf5) ++f5c; else if (dscb->fmtid == 0xf7) ++f7c; else if (dscb->fmtid == 0xf8) ++f8c; else if (dscb->fmtid == 0xf9) ++f9c; } lzds_dscbiterator_free(it); printf("--- VTOC info --------------------------------" "---------------------------------\n"); printf("The VTOC contains:\n"); printf(" %d format 1 label(s)\n", f1c); printf(" %d format 3 label(s)\n", f3c); printf(" %d format 4 label(s)\n", f4c); printf(" %d format 5 label(s)\n", f5c); printf(" %d format 7 label(s)\n", f7c); printf(" %d format 8 label(s)\n", f8c); printf(" %d format 9 label(s)\n", f9c); rc = lzds_raw_vtoc_alloc_dscbiterator(info->rawvtoc, &it); if (rc) { zt_error_print("dasdview: could not allocate DSCB iterator \n"); exit(-1); } while (!lzds_dscbiterator_get_next_dscb(it, &dscb)) { if (dscb->fmtid == 0xf1 || dscb->fmtid == 0xf8) dasdview_print_format1_8_short_info_raw( (format1_label_t *)dscb, info); } lzds_dscbiterator_free(it); } /* * Note: the explicit cylinder/head conversion for large volume * adresses should not be necessary for entries that point to * vtoc labels, as those must be located in the first 65K-1 tracks, * but we do it anyway to be on the safe side. */ static void dasdview_print_format1_8_no_head(format1_label_t *f1) { char s6[7], s13[14], s44[45]; int i; bzero(s6, 7); bzero(s13, 14); bzero(s44, 45); strncpy(s44, f1->DS1DSNAM, 44); printf("DS1DSNAM : ascii '%44s'\n", s44); vtoc_ebcdic_dec(s44, s44, 44); printf(" ebcdic '%44s'\n", s44); printf("DS1FMTID : dec %d, hex %02x\n", f1->DS1FMTID, f1->DS1FMTID); printf("DS1DSSN : hex "); for (i = 0; i < 6; i++) printf("%02x", f1->DS1DSSN[i]); strncpy(s6, (char *)f1->DS1DSSN, 6); printf("\n ascii '%6s'\n", s6); vtoc_ebcdic_dec(s6, s6, 6); printf(" ebcdic '%6s'\n", s6); printf("DS1VOLSQ : dec %d, hex %04x\n", f1->DS1VOLSQ, f1->DS1VOLSQ); printf("DS1CREDT : hex %02x%04x " "(year %d, day %d)\n", f1->DS1CREDT.year, f1->DS1CREDT.day, f1->DS1CREDT.year + 1900, f1->DS1CREDT.day); printf("DS1EXPDT : hex %02x%04x " "(year %d, day %d)\n", f1->DS1EXPDT.year, f1->DS1EXPDT.day, f1->DS1EXPDT.year + 1900, f1->DS1EXPDT.day); printf("DS1NOEPV : dec %d, hex %02x\n", f1->DS1NOEPV, f1->DS1NOEPV); printf("DS1NOBDB : dec %d, hex %02x\n", f1->DS1NOBDB, f1->DS1NOBDB); printf("DS1FLAG1 : dec %d, hex %02x\n", f1->DS1FLAG1, f1->DS1FLAG1); printf("DS1SYSCD : hex "); for (i = 0; i < 13; i++) printf("%02x", f1->DS1SYSCD[i]); strncpy(s13, (char *)f1->DS1SYSCD, 13); printf("\n ascii '%13s'\n", s13); vtoc_ebcdic_dec(s13, s13, 13); printf(" ebcdic '%13s'\n", s13); printf("DS1REFD : hex %02x%04x " "(year %d, day %d)\n", f1->DS1REFD.year, f1->DS1REFD.day, f1->DS1REFD.year + 1900, f1->DS1REFD.day); printf("DS1SMSFG : dec %d, hex %02x\n", f1->DS1SMSFG, f1->DS1SMSFG); printf("DS1SCXTF : dec %d, hex %02x\n", f1->DS1SCXTF, f1->DS1SCXTF); printf("DS1SCXTV : dec %d, hex %04x\n", f1->DS1SCXTV, f1->DS1SCXTV); printf("DS1DSRG1 : dec %d, hex %02x\n", f1->DS1DSRG1, f1->DS1DSRG1); printf("DS1DSRG2 : dec %d, hex %02x\n", f1->DS1DSRG2, f1->DS1DSRG2); printf("DS1RECFM : dec %d, hex %02x\n", f1->DS1RECFM, f1->DS1RECFM); printf("DS1OPTCD : dec %d, hex %02x\n", f1->DS1OPTCD, f1->DS1OPTCD); printf("DS1BLKL : dec %d, hex %04x\n", f1->DS1BLKL, f1->DS1BLKL); printf("DS1LRECL : dec %d, hex %04x\n", f1->DS1LRECL, f1->DS1LRECL); printf("DS1KEYL : dec %d, hex %02x\n", f1->DS1KEYL, f1->DS1KEYL); printf("DS1RKP : dec %d, hex %04x\n", f1->DS1RKP, f1->DS1RKP); printf("DS1DSIND : dec %d, hex %02x\n", f1->DS1DSIND, f1->DS1DSIND); printf("DS1SCAL1 : dec %d, hex %02x\n", f1->DS1SCAL1, f1->DS1SCAL1); printf("DS1SCAL3 : hex "); for (i = 0; i < 3; i++) printf("%02x", f1->DS1SCAL3[i]); printf("\nDS1LSTAR : hex %04x%02x " "(trk %d, blk %d)\n", f1->DS1LSTAR.tt, f1->DS1LSTAR.r, f1->DS1LSTAR.tt, f1->DS1LSTAR.r); printf("DS1TRBAL : dec %d, hex %04x\n", f1->DS1TRBAL, f1->DS1TRBAL); printf("reserved : dec %d, hex %04x\n", f1->res1, f1->res1); printf("DS1EXT1 : hex %02x%02x%04x%04x%04x%04x\n", f1->DS1EXT1.typeind, f1->DS1EXT1.seqno, f1->DS1EXT1.llimit.cc, f1->DS1EXT1.llimit.hh, f1->DS1EXT1.ulimit.cc, f1->DS1EXT1.ulimit.hh); printf(" typeind : dec %d, hex %02x\n", f1->DS1EXT1.typeind, f1->DS1EXT1.typeind); printf(" seqno : dec %d, hex %02x\n", f1->DS1EXT1.seqno, f1->DS1EXT1.seqno); printf(" llimit : hex %04x%04x " "(cyl %d, trk %d)\n", f1->DS1EXT1.llimit.cc, f1->DS1EXT1.llimit.hh, vtoc_get_cyl_from_cchh(&f1->DS1EXT1.llimit), vtoc_get_head_from_cchh(&f1->DS1EXT1.llimit)); printf(" ulimit : hex %04x%04x " "(cyl %d, trk %d)\n", f1->DS1EXT1.ulimit.cc, f1->DS1EXT1.ulimit.hh, vtoc_get_cyl_from_cchh(&f1->DS1EXT1.ulimit), vtoc_get_head_from_cchh(&f1->DS1EXT1.ulimit)); printf("DS1EXT2 : hex %02x%02x%04x%04x%04x%04x\n", f1->DS1EXT2.typeind, f1->DS1EXT2.seqno, f1->DS1EXT2.llimit.cc, f1->DS1EXT2.llimit.hh, f1->DS1EXT2.ulimit.cc, f1->DS1EXT2.ulimit.hh); printf(" typeind : dec %d, hex %02x\n", f1->DS1EXT2.typeind, f1->DS1EXT2.typeind); printf(" seqno : dec %d, hex %02x\n", f1->DS1EXT2.seqno, f1->DS1EXT2.seqno); printf(" llimit : hex %04x%04x " "(cyl %d, trk %d)\n", f1->DS1EXT2.llimit.cc, f1->DS1EXT2.llimit.hh, vtoc_get_cyl_from_cchh(&f1->DS1EXT2.llimit), vtoc_get_head_from_cchh(&f1->DS1EXT2.llimit)); printf(" ulimit : hex %04x%04x " "(cyl %d, trk %d)\n", f1->DS1EXT2.ulimit.cc, f1->DS1EXT2.ulimit.hh, vtoc_get_cyl_from_cchh(&f1->DS1EXT2.ulimit), vtoc_get_head_from_cchh(&f1->DS1EXT2.ulimit)); printf("DS1EXT3 : hex %02x%02x%04x%04x%04x%04x\n", f1->DS1EXT3.typeind, f1->DS1EXT3.seqno, f1->DS1EXT3.llimit.cc, f1->DS1EXT3.llimit.hh, f1->DS1EXT3.ulimit.cc, f1->DS1EXT3.ulimit.hh); printf(" typeind : dec %d, hex %02x\n", f1->DS1EXT3.typeind, f1->DS1EXT3.typeind); printf(" seqno : dec %d, hex %02x\n", f1->DS1EXT3.seqno, f1->DS1EXT3.seqno); printf(" llimit : hex %04x%04x " "(cyl %d, trk %d)\n", f1->DS1EXT3.llimit.cc, f1->DS1EXT3.llimit.hh, vtoc_get_cyl_from_cchh(&f1->DS1EXT3.llimit), vtoc_get_head_from_cchh(&f1->DS1EXT3.llimit)); printf(" ulimit : hex %04x%04x " "(cyl %d, trk %d)\n", f1->DS1EXT3.ulimit.cc, f1->DS1EXT3.ulimit.hh, vtoc_get_cyl_from_cchh(&f1->DS1EXT3.ulimit), vtoc_get_head_from_cchh(&f1->DS1EXT3.ulimit)); printf("DS1PTRDS : %04x%04x%02x " "(cyl %d, trk %d, blk %d)\n", f1->DS1PTRDS.cc, f1->DS1PTRDS.hh, f1->DS1PTRDS.b, vtoc_get_cyl_from_cchhb(&f1->DS1PTRDS), vtoc_get_head_from_cchhb(&f1->DS1PTRDS), f1->DS1PTRDS.b); } static void dasdview_print_vtoc_f1_raw(format1_label_t *f1) { printf("\n--- VTOC format 1 label -----------------------" "---------------------------------\n"); dasdview_print_format1_8_no_head(f1); } /* Note: A format 8 label uses the same type as format 1 */ static void dasdview_print_vtoc_f8_raw(format1_label_t *f8) { printf("\n--- VTOC format 8 label -----------------------" "---------------------------------\n"); dasdview_print_format1_8_no_head(f8); } static void dasdview_print_extent(extent_t *ext, char *name, int index) { printf("%s[%d] : hex %02x%02x%04x%04x%04x%04x\n", name, index, ext->typeind, ext->seqno, ext->llimit.cc, ext->llimit.hh, ext->ulimit.cc, ext->ulimit.hh); printf(" typeind : dec %d, hex %02x\n", ext->typeind, ext->typeind); printf(" seqno : dec %d, hex %02x\n", ext->seqno, ext->seqno); printf(" llimit : hex %04x%04x " "(cyl %d, trk %d)\n", ext->llimit.cc, ext->llimit.hh, vtoc_get_cyl_from_cchh(&ext->llimit), vtoc_get_head_from_cchh(&ext->llimit)); printf(" ulimit : hex %04x%04x " "(cyl %d, trk %d)\n", ext->ulimit.cc, ext->ulimit.hh, vtoc_get_cyl_from_cchh(&ext->ulimit), vtoc_get_head_from_cchh(&ext->ulimit)); } static void dasdview_print_vtoc_f3_raw(format3_label_t *f3) { int i; printf("\n--- VTOC format 3 label ----------------------" "---------------------------------\n"); printf("DS3KEYID : "); for (i = 0; i < 4; i++) printf("%02x", f3->DS3KEYID[i]); printf("\n"); for (i = 0; i < 4; ++i) dasdview_print_extent(&f3->DS3EXTNT[i], "DS3EXTNT", i); printf("DS3FMTID : dec %d, hex %02x\n", f3->DS3FMTID, f3->DS3FMTID); for (i = 0; i < 9; ++i) dasdview_print_extent(&f3->DS3ADEXT[i], "DS3ADEXT", i); printf("DS3PTRDS : %04x%04x%02x " "(cyl %d, trk %d, blk %d)\n", f3->DS3PTRDS.cc, f3->DS3PTRDS.hh, f3->DS3PTRDS.b, vtoc_get_cyl_from_cchhb(&f3->DS3PTRDS), vtoc_get_head_from_cchhb(&f3->DS3PTRDS), f3->DS3PTRDS.b); } static void dasdview_print_vtoc_f4_raw(format4_label_t *f4) { int i; printf("\n--- VTOC format 4 label ----------------------" "---------------------------------\n"); printf("DS4KEYCD : "); for (i = 0; i < 44; i++) printf("%02x", f4->DS4KEYCD[i]); printf("\nDS4IDFMT : dec %d, hex %02x\n", f4->DS4IDFMT, f4->DS4IDFMT); printf("DS4HPCHR : %04x%04x%02x " "(cyl %d, trk %d, blk %d)\n", f4->DS4HPCHR.cc, f4->DS4HPCHR.hh, f4->DS4HPCHR.b, vtoc_get_cyl_from_cchhb(&f4->DS4HPCHR), vtoc_get_head_from_cchhb(&f4->DS4HPCHR), f4->DS4HPCHR.b); printf("DS4DSREC : dec %d, hex %04x\n", f4->DS4DSREC, f4->DS4DSREC); printf("DS4HCCHH : %04x%04x (cyl %d, trk %d)\n", f4->DS4HCCHH.cc, f4->DS4HCCHH.hh, vtoc_get_cyl_from_cchh(&f4->DS4HCCHH), vtoc_get_head_from_cchh(&f4->DS4HCCHH)); printf("DS4NOATK : dec %d, hex %04x\n", f4->DS4NOATK, f4->DS4NOATK); printf("DS4VTOCI : dec %d, hex %02x\n", f4->DS4VTOCI, f4->DS4VTOCI); printf("DS4NOEXT : dec %d, hex %02x\n", f4->DS4NOEXT, f4->DS4NOEXT); printf("DS4SMSFG : dec %d, hex %02x\n", f4->DS4SMSFG, f4->DS4SMSFG); printf("DS4DEVAC : dec %d, hex %02x\n", f4->DS4DEVAC, f4->DS4DEVAC); printf("DS4DSCYL : dec %d, hex %04x\n", f4->DS4DEVCT.DS4DSCYL, f4->DS4DEVCT.DS4DSCYL); printf("DS4DSTRK : dec %d, hex %04x\n", f4->DS4DEVCT.DS4DSTRK, f4->DS4DEVCT.DS4DSTRK); printf("DS4DEVTK : dec %d, hex %04x\n", f4->DS4DEVCT.DS4DEVTK, f4->DS4DEVCT.DS4DEVTK); printf("DS4DEVI : dec %d, hex %02x\n", f4->DS4DEVCT.DS4DEVI, f4->DS4DEVCT.DS4DEVI); printf("DS4DEVL : dec %d, hex %02x\n", f4->DS4DEVCT.DS4DEVL, f4->DS4DEVCT.DS4DEVL); printf("DS4DEVK : dec %d, hex %02x\n", f4->DS4DEVCT.DS4DEVK, f4->DS4DEVCT.DS4DEVK); printf("DS4DEVFG : dec %d, hex %02x\n", f4->DS4DEVCT.DS4DEVFG, f4->DS4DEVCT.DS4DEVFG); printf("DS4DEVTL : dec %d, hex %04x\n", f4->DS4DEVCT.DS4DEVTL, f4->DS4DEVCT.DS4DEVTL); printf("DS4DEVDT : dec %d, hex %02x\n", f4->DS4DEVCT.DS4DEVDT, f4->DS4DEVCT.DS4DEVDT); printf("DS4DEVDB : dec %d, hex %02x\n", f4->DS4DEVCT.DS4DEVDB, f4->DS4DEVCT.DS4DEVDB); printf("DS4AMTIM : hex "); for (i = 0; i < 8; i++) printf("%02x", f4->DS4AMTIM[i]); printf("\nDS4AMCAT : hex "); for (i = 0; i < 3; i++) printf("%02x", f4->DS4AMCAT[i]); printf("\nDS4R2TIM : hex "); for (i = 0; i < 8; i++) printf("%02x", f4->DS4R2TIM[i]); printf("\nres1 : hex "); for (i = 0; i < 5; i++) printf("%02x", f4->res1[i]); printf("\nDS4F6PTR : hex "); for (i = 0; i < 5; i++) printf("%02x", f4->DS4F6PTR[i]); printf("\nDS4VTOCE : hex %02x%02x%04x%04x%04x%04x\n", f4->DS4VTOCE.typeind, f4->DS4VTOCE.seqno, f4->DS4VTOCE.llimit.cc, f4->DS4VTOCE.llimit.hh, f4->DS4VTOCE.ulimit.cc, f4->DS4VTOCE.ulimit.hh); printf(" typeind : dec %d, hex %02x\n", f4->DS4VTOCE.typeind, f4->DS4VTOCE.typeind); printf(" seqno : dec %d, hex %02x\n", f4->DS4VTOCE.seqno, f4->DS4VTOCE.seqno); printf(" llimit : hex %04x%04x (cyl %d, trk %d)\n", f4->DS4VTOCE.llimit.cc, f4->DS4VTOCE.llimit.hh, vtoc_get_cyl_from_cchh(&f4->DS4VTOCE.llimit), vtoc_get_head_from_cchh(&f4->DS4VTOCE.llimit)); printf(" ulimit : hex %04x%04x (cyl %d, trk %d)\n", f4->DS4VTOCE.ulimit.cc, f4->DS4VTOCE.ulimit.hh, vtoc_get_cyl_from_cchh(&f4->DS4VTOCE.ulimit), vtoc_get_head_from_cchh(&f4->DS4VTOCE.ulimit)); printf("res2 : hex "); for (i = 0; i < 10; i++) printf("%02x", f4->res2[i]); printf("\nDS4EFLVL : dec %d, hex %02x\n", f4->DS4EFLVL, f4->DS4EFLVL); printf("DS4EFPTR : hex %04x%04x%02x " "(cyl %d, trk %d, blk %d)\n", f4->DS4EFPTR.cc, f4->DS4EFPTR.hh, f4->DS4EFPTR.b, vtoc_get_cyl_from_cchhb(&f4->DS4EFPTR), vtoc_get_head_from_cchhb(&f4->DS4EFPTR), f4->DS4EFPTR.b); printf("res3 : hex %02x\n", f4->res3); printf("DS4DCYL : dec %d, hex %08x\n", f4->DS4DCYL, f4->DS4DCYL); printf("res4 : hex "); for (i = 0; i < 2; i++) printf("%02x", f4->res4[i]); printf("\nDS4DEVF2 : dec %d, hex %02x\n", f4->DS4DEVF2, f4->DS4DEVF2); printf("res5 : hex %02x\n", f4->res5); } static void dasdview_print_vtoc_f5_raw(format5_label_t *f5) { int i; printf("\n--- VTOC format 5 label ----------------------" "---------------------------------\n"); printf("key identifier\n DS5KEYID : "); for (i = 0; i < 4; i++) printf("%02x", f5->DS5KEYID[i]); printf("\nfirst extent description\n"); printf(" DS5AVEXT : %04x%04x%02x " "(start trk: %d, length: %d cyl, %d trk)\n", f5->DS5AVEXT.t, f5->DS5AVEXT.fc, f5->DS5AVEXT.ft, f5->DS5AVEXT.t, f5->DS5AVEXT.fc, f5->DS5AVEXT.ft); printf("next 7 extent descriptions\n"); for (i = 0; i < 7; i++) { printf(" DS5EXTAV[%d] : %04x%04x%02x " "(start trk: %d, length: %d cyl, %d trk)\n", i + 2, f5->DS5EXTAV[i].t, f5->DS5EXTAV[i].fc, f5->DS5EXTAV[i].ft, f5->DS5EXTAV[i].t, f5->DS5EXTAV[i].fc, f5->DS5EXTAV[i].ft); } printf("format identifier\n" " DS5FMTID : dec %d, hex %02x\n", f5->DS5FMTID, f5->DS5FMTID); printf("next 18 extent descriptions\n"); for (i = 0; i < 18; i++) { printf(" DS5MAVET[%d] : %04x%04x%02x " "(start trk: %d, length: %d cyl, %d trk)\n", i + 9, f5->DS5MAVET[i].t, f5->DS5MAVET[i].fc, f5->DS5MAVET[i].ft, f5->DS5MAVET[i].t, f5->DS5MAVET[i].fc, f5->DS5MAVET[i].ft); } printf("pointer to next format 5 label\n" " DS5PTRDS : %04x%04x%02x " "(cyl %d, trk %d, blk %d)\n", f5->DS5PTRDS.cc, f5->DS5PTRDS.hh, f5->DS5PTRDS.b, vtoc_get_cyl_from_cchhb(&f5->DS5PTRDS), vtoc_get_head_from_cchhb(&f5->DS5PTRDS), f5->DS5PTRDS.b); } static void dasdview_print_vtoc_f7_raw(format7_label_t *f7) { int i; printf("\n--- VTOC format 7 label ----------------------" "---------------------------------\n"); printf("key identifier\n DS7KEYID : "); for (i = 0; i < 4; i++) printf("%02x", f7->DS7KEYID[i]); printf("\nfirst 5 extent descriptions\n"); for (i = 0; i < 5; i++) { printf(" DS7EXTNT[%d] : %08x %08x " "(start trk %d, end trk %d)\n", i + 1, f7->DS7EXTNT[i].a, f7->DS7EXTNT[i].b, f7->DS7EXTNT[i].a, f7->DS7EXTNT[i].b); } printf("format identifier\n" " DS7FMTID : dec %d, hex %02x\n", f7->DS7FMTID, f7->DS7FMTID); printf("next 11 extent descriptions\n"); for (i = 0; i < 11; i++) { printf(" DS7ADEXT[%d] : %08x %08x " "(start trk %d, end trk %d)\n", i + 6, f7->DS7ADEXT[i].a, f7->DS7ADEXT[i].b, f7->DS7ADEXT[i].a, f7->DS7ADEXT[i].b); } printf("reserved field\n res1 : "); for (i = 0; i < 2; i++) printf("%02x", f7->res1[i]); printf("\npointer to next format 7 label\n" " DS7PTRDS : %04x%04x%02x " "(cyl %d, trk %d, blk %d)\n", f7->DS7PTRDS.cc, f7->DS7PTRDS.hh, f7->DS7PTRDS.b, vtoc_get_cyl_from_cchhb(&f7->DS7PTRDS), vtoc_get_head_from_cchhb(&f7->DS7PTRDS), f7->DS7PTRDS.b); } static void dasdview_print_vtoc_f9_nohead(format9_label_t *f9) { unsigned int i; printf("DS9KEYID : dec %d, hex %02x\n", f9->DS9KEYID, f9->DS9KEYID); printf("DS9SUBTY : dec %d, hex %02x\n", f9->DS9SUBTY, f9->DS9SUBTY); printf("DS9NUMF9 : dec %d, hex %02x\n", f9->DS9NUMF9, f9->DS9NUMF9); printf("res1 : hex "); for (i = 0; i < sizeof(f9->res1); i++) { if ((i > 0) && (i % 16 == 0)) printf("\n "); printf("%02x", f9->res1[i]); if ((i + 9) % 16 == 0) printf(" "); } printf("\n"); printf("DS9FMTID : dec %d, hex %02x\n", f9->DS9FMTID, f9->DS9FMTID); printf("res2 : hex "); for (i = 0; i < sizeof(f9->res2); i++) { if ((i > 0) && (i % 16 == 0)) printf("\n "); printf("%02x", f9->res2[i]); if ((i + 9) % 16 == 0) printf(" "); } printf("\n"); printf("pointer to next format 9 label\n" " DS9PTRDS : %04x%04x%02x " "(cyl %d, trk %d, blk %d)\n", f9->DS9PTRDS.cc, f9->DS9PTRDS.hh, f9->DS9PTRDS.b, vtoc_get_cyl_from_cchhb(&f9->DS9PTRDS), vtoc_get_head_from_cchhb(&f9->DS9PTRDS), f9->DS9PTRDS.b); } static void dasdview_print_vtoc_f9_raw(format9_label_t *f9) { printf("\n--- VTOC format 9 label ----------------------" "---------------------------------\n"); dasdview_print_vtoc_f9_nohead(f9); } static void dasdview_print_vtoc_dscb(dasdview_info_t *info, void *dscb) { format1_label_t *tmp = dscb; switch (tmp->DS1FMTID) { case 0x00: break; case 0xf1: if (info->vtoc_f1 || info->vtoc_all) dasdview_print_vtoc_f1_raw(dscb); break; case 0xf3: if (info->vtoc_f3 || info->vtoc_all) dasdview_print_vtoc_f3_raw(dscb); break; case 0xf4: if (info->vtoc_f4 || info->vtoc_all) dasdview_print_vtoc_f4_raw(dscb); break; case 0xf5: if (info->vtoc_f5 || info->vtoc_all) dasdview_print_vtoc_f5_raw(dscb); break; case 0xf7: if (info->vtoc_f7 || info->vtoc_all) dasdview_print_vtoc_f7_raw(dscb); break; case 0xf8: if (info->vtoc_f8 || info->vtoc_all) dasdview_print_vtoc_f8_raw(dscb); break; case 0xf9: if (info->vtoc_f9 || info->vtoc_all) dasdview_print_vtoc_f9_raw(dscb); break; default: printf("unrecognized DSCB of type: %x \n\n", tmp->DS1FMTID); break; } } static void dasdview_print_vtoc_f1(dasdview_info_t *info) { int j; printf("--- VTOC format 1 labels ----------------------" "---------------------------------\n"); if (info->f1c < 1) { printf("This VTOC doesn't contain a format 1 label.\n"); return; } for (j = 0; j < info->f1c; j++) { printf("\n--- format 1 DSCB number %d ---\n", j + 1); dasdview_print_format1_8_no_head(&info->f1[j]); } } static void dasdview_print_vtoc_f8(dasdview_info_t *info) { int j; printf("--- VTOC format 8 labels ----------------------" "---------------------------------\n"); if (info->f8c < 1) { printf("This VTOC doesn't contain a format 8 label.\n"); return; } for (j = 0; j < info->f8c; j++) { printf("\n--- format 8 DSCB number %d ---\n", j + 1); dasdview_print_format1_8_no_head(&info->f8[j]); } } static void dasdview_print_vtoc_f4(dasdview_info_t *info) { if (info->f4c < 1) { printf("\n--- VTOC format 4 label ----------------------" "---------------------------------\n"); printf("This VTOC doesn't contain a format 4 label.\n"); return; } dasdview_print_vtoc_f4_raw(&info->f4); } static void dasdview_print_vtoc_f5(dasdview_info_t *info) { if (info->f5c < 1) { printf("\n--- VTOC format 5 label ----------------------" "---------------------------------\n"); printf("This VTOC doesn't contain a format 5 label.\n"); return; } dasdview_print_vtoc_f5_raw(&info->f5); } static void dasdview_print_vtoc_f7(dasdview_info_t *info) { if (info->f7c < 1) { printf("\n--- VTOC format 7 label ----------------------" "---------------------------------\n"); printf("This VTOC doesn't contain a format 7 label.\n"); return; } dasdview_print_vtoc_f7_raw(&info->f7); } static void dasdview_print_vtoc_f9(dasdview_info_t *info) { int j; printf("\n--- VTOC format 9 label ----------------------" "---------------------------------\n"); if (info->f9c < 1) { printf("This VTOC doesn't contain a format 9 label.\n"); return; } for (j = 0; j < info->f9c; j++) { printf("\n--- format 9 DSCB number %d ---\n", j + 1); dasdview_print_vtoc_f9_nohead(&info->f9[j]); } } static void dasdview_print_vtoc_f3(void) { /* dasdfmt formatted DASD devices have no format3 labels, but since the * option exists for raw DASDs, we need to have some sensible message */ printf("\n--- VTOC format 3 label ----------------------" "---------------------------------\n"); printf("This VTOC doesn't contain a format 3 label.\n"); } static void dasdview_print_vtoc_standard(dasdview_info_t *info) { dasdview_read_vtoc(info); if (info->vtoc_info || info->vtoc_all) dasdview_print_vtoc_info(info); if (info->vtoc_f4 || info->vtoc_all) dasdview_print_vtoc_f4(info); if (info->vtoc_f5 || info->vtoc_all) dasdview_print_vtoc_f5(info); if (info->vtoc_f7 || info->vtoc_all) dasdview_print_vtoc_f7(info); if (info->vtoc_f1 || info->vtoc_all) dasdview_print_vtoc_f1(info); if (info->vtoc_f8 || info->vtoc_all) dasdview_print_vtoc_f8(info); if (info->vtoc_f9 || info->vtoc_all) dasdview_print_vtoc_f9(info); if (info->vtoc_f3 || info->vtoc_all) dasdview_print_vtoc_f3(); } /* a simple routine to print all records in the vtoc */ static void dasdview_print_vtoc_raw(dasdview_info_t *info) { struct dscbiterator *it; struct dscb *record; int rc; rc = lzds_dasd_read_vlabel(info->dasd); if (rc) { zt_error_print("error when reading label from device:" " rc=%d\n", rc); exit(-1); } rc = lzds_dasd_read_rawvtoc(info->dasd); if (rc == EINVAL) { zt_error_print("dasdview: Cannot read VTOC because disk does" " not contain valid VOL1 label.\n", info->device); exit(-1); } else if (rc) { zt_error_print("error when reading vtoc from device:" " rc=%d\n", rc); exit(-1); } rc = lzds_dasd_get_rawvtoc(info->dasd, &info->rawvtoc); if (rc || !info->rawvtoc) { zt_error_print("dasdview: libvtoc could not read vtoc\n"); exit(-1); } if (info->vtoc_info || info->vtoc_all) dasdview_print_vtoc_info_raw(info); rc = lzds_raw_vtoc_alloc_dscbiterator(info->rawvtoc, &it); if (rc) { zt_error_print("dasdview: could not allocate DSCB iterator\n"); exit(-1); } while (!lzds_dscbiterator_get_next_dscb(it, &record)) dasdview_print_vtoc_dscb(info, record); lzds_dscbiterator_free(it); } static void dasdview_print_vtoc(dasdview_info_t *info) { if (info->raw_track_access) dasdview_print_vtoc_raw(info); else dasdview_print_vtoc_standard(info); } static int dasdview_print_format1(unsigned int size, unsigned char *dumpstr) { unsigned int i; char asc[17], ebc[17]; for (i = 0; i < size; i++) { if ((i / 16) * 16 == i) { printf("\n| "); strncpy(asc, (char *)dumpstr + i, 16); strncpy(ebc, (char *)dumpstr + i, 16); asc[16] = '\0'; ebc[16] = '\0'; } printf("%02X", dumpstr[i]); if (((i + 1) / 4) * 4 == i + 1) printf(" "); if (((i + 1) / 8) * 8 == i + 1) printf(" "); if (((i + 1) / 16) * 16 == i + 1) { vtoc_ebcdic_dec(asc, asc, 16); dot(asc); dot(ebc); printf("| %16.16s | %16.16s |", asc, ebc); } } return 0; } static int dasdview_print_format2(unsigned int size, unsigned char *dumpstr, unsigned long long begin) { unsigned int i; char asc[17], ebc[17]; for (i = 0; i < size; i++) { if ((i / 8) * 8 == i) { printf("\n | %13llu | %13llX | ", begin + (unsigned long long)i, begin + (unsigned long long)i); strncpy(asc, (char *)dumpstr + i, 8); strncpy(ebc, (char *)dumpstr + i, 8); } printf("%02X", dumpstr[i]); if (((i + 1) / 4) * 4 == i + 1) printf(" "); if (((i + 1) / 8) * 8 == i + 1) { vtoc_ebcdic_dec(asc, asc, 8); dot(asc); dot(ebc); printf("| %8.8s | %8.8s |", asc, ebc); } } return 0; } static void dasdview_view_standard(dasdview_info_t *info) { unsigned char dumpstr[DUMP_STRING_SIZE]; unsigned long long i = 0, j = 0, k = 0, count = 0; int fd, rc; unsigned long long a = 0; int b = 0; k = ((info->size) % 16LL); if (k != 0) info->size += (16LL - k); fd = open(info->device, O_RDONLY); if (fd == -1) { zt_error_print("dasdview: open error\n" "Unable to open device %s in read-only mode!\n", info->device); exit(-1); } j = (info->begin / SEEK_STEP); k = (info->begin % SEEK_STEP); /* seek in SEEK_STEP steps */ for (i = 1; i <= j; i++) { rc = lseek64(fd, SEEK_STEP, SEEK_CUR); if (rc == -1) { printf("*** rc: %d (%d) ***\n", rc, errno); printf("*** j: %llu ***\n", j); printf("*** k: %llu ***\n", k); printf("*** a: %llu ***\n", a); printf("*** b: %d ***\n", b); close(fd); zt_error_print("dasdview: seek error\n" "Unable to seek in device %s!\n", info->device); exit(-1); } b++; a += SEEK_STEP; } if (k > 0) { rc = lseek(fd, k, SEEK_CUR); if (rc == -1) { close(fd); zt_error_print("dasdview: seek error\n" "Unable to seek in device %s!\n", info->device); exit(-1); } } j = info->size / DUMP_STRING_SIZE; k = info->size % DUMP_STRING_SIZE; if (info->format1) { printf("+----------------------------------------+" "------------------+------------------+\n"); printf("| HEXADECIMAL |" " EBCDIC | ASCII |\n"); printf("| 01....04 05....08 09....12 13....16 |" " 1.............16 | 1.............16 |\n"); printf("+----------------------------------------+" "------------------+------------------+"); } else if (info->format2) { printf(" +---------------+---------------+----------------" "------+----------+----------+\n"); printf(" | BYTE | BYTE | HEXADECIMAL" " | EBCDIC | ASCII |\n"); printf(" | DECIMAL | HEXADECIMAL | 1 2 3 4 5 6 " "7 8 | 12345678 | 12345678 |\n"); printf(" +---------------+---------------+----------------" "------+----------+----------+"); } count = info->begin; for (i = 1; i <= j; i++) { bzero(dumpstr, DUMP_STRING_SIZE); rc = read(fd, &dumpstr, DUMP_STRING_SIZE); if (rc != DUMP_STRING_SIZE) { close(fd); zt_error_print("dasdview: read error\n" "Unable to read from device %s!\n", info->device); exit(-1); } if (info->format1) dasdview_print_format1(DUMP_STRING_SIZE, dumpstr); else if (info->format2) dasdview_print_format2(DUMP_STRING_SIZE, dumpstr, count); count += DUMP_STRING_SIZE; } if (k > 0) { bzero(dumpstr, DUMP_STRING_SIZE); rc = read(fd, &dumpstr, k); if (rc != (int)k) { close(fd); zt_error_print("dasdview: read error\n" "Unable to read from device %s!\n", info->device); exit(-1); } if (info->format1) dasdview_print_format1((unsigned int)k, dumpstr); else if (info->format2) dasdview_print_format2((unsigned int)k, dumpstr, count); } close(fd); if (info->format1) printf("\n+----------------------------------------+" "------------------+------------------+\n\n"); else if (info->format2) printf("\n +---------------+---------------+----------------" "------+----------+----------+\n\n"); } static void dasdview_print_format_raw(unsigned int size, char *dumpstr) { unsigned int i; char asc[17], ebc[17]; unsigned int residual, count; char *data; data = dumpstr; residual = size; while (residual) { /* we handle at most 16 bytes per line */ count = MIN(residual, 16u); bzero(asc, 17); bzero(ebc, 17); printf("|"); memcpy(asc, data, count); memcpy(ebc, data, count); for (i = 0; i < 16; ++i) { if ((i % 4) == 0) printf(" "); if ((i % 8) == 0) printf(" "); if (i < count) printf("%02X", data[i]); else printf(" "); } vtoc_ebcdic_dec(asc, asc, count); dot(asc); dot(ebc); printf(" | %16.16s | %16.16s |\n", asc, ebc); data += count; residual -= count; } } /* gets the pointer to an eckd record structure in memory and * prints a hex/ascii/ebcdic dump for it */ static void dasdview_print_raw_record(char *rec) { struct eckd_count *ecount; unsigned int cyl, head; ecount = (struct eckd_count *)rec; /* Note: the first 5 bytes of the count area are the * record ID and by convention these bytes are interpreted * as CCHHR (or ccccCCChR for large volumes) */ cyl = vtoc_get_cyl_from_cchhb(&ecount->recid); head = vtoc_get_head_from_cchhb(&ecount->recid); printf("+-----------------------------------------" "-------------------------------------+\n"); printf("| count area: " " |\n"); printf("| hex: %016llX " " |\n", *((unsigned long long *)ecount)); printf("| cylinder: %9d " " |\n", cyl); printf("| head: %9d " " |\n", head); printf("| record: %9d " " |\n", ecount->recid.b); printf("| key length: %9d " " |\n", ecount->kl); printf("| data length: %9d " " |\n", ecount->dl); printf("+-----------------------------------------" "-------------------------------------+\n"); printf("| key area: " " |\n"); printf("| HEXADECIMAL |" " EBCDIC | ASCII |\n"); printf("| 01....04 05....08 09....12 13....16 |" " 1.............16 | 1.............16 |\n"); printf("+----------------------------------------+" "------------------+------------------+\n"); dasdview_print_format_raw(ecount->kl, rec + sizeof(*ecount)); printf("+----------------------------------------+" "------------------+------------------+\n"); printf("| data area: " " |\n"); printf("| HEXADECIMAL |" " EBCDIC | ASCII |\n"); printf("| 01....04 05....08 09....12 13....16 |" " 1.............16 | 1.............16 |\n"); printf("+----------------------------------------+" "------------------+------------------+\n"); dasdview_print_format_raw(ecount->dl, rec + sizeof(*ecount) + ecount->kl); printf("+----------------------------------------+" "------------------+------------------+\n"); } static void dasdview_print_raw_track(char *trackdata, unsigned int cyl, unsigned int head) { struct eckd_count *ecount; char *data; u_int32_t record; record = 0; data = trackdata; do { printf("cylinder %u, head %u, record %u\n", cyl, head, record); dasdview_print_raw_record(data); printf("\n"); ecount = (struct eckd_count *)data; data += sizeof(*ecount) + ecount->kl + ecount->dl; ++record; if ((*(unsigned long long *)data) == ENDTOKEN) break; if ((unsigned long)data >= (unsigned long)trackdata + RAWTRACKSIZE) break; } while (1); } static void dasdview_view_raw(dasdview_info_t *info) { u_int64_t residual, trckstart, trckend, track, trckbuffsize; u_int64_t tracks_to_read, trckcount, i; char *trackdata; char *data; int rc; struct dasdhandle *dasdh; trckstart = info->begin / RAWTRACKSIZE; tracks_to_read = info->size / RAWTRACKSIZE; /* TODO: how large should we make our buffer? * The DASD device driver cannot read more than 16 tracks at once * but we can read a larger blob and the block layer will split up * the requests for us. */ trckbuffsize = MIN(tracks_to_read, 16u); /* track data must be page aligned for O_DIRECT */ trackdata = memalign(4096, trckbuffsize * RAWTRACKSIZE); if (!trackdata) { zt_error_print("failed to allocate memory\n"); exit(-1); } rc = lzds_dasd_alloc_dasdhandle(info->dasd, &dasdh); if (rc) { zt_error_print("failed to allocate memory\n"); exit(-1); } rc = lzds_dasdhandle_open(dasdh); if (rc) { lzds_dasdhandle_free(dasdh); zt_error_print("failed to open device\n"); exit(-1); } /* residual is the number of tracks we still have to read */ residual = tracks_to_read; track = trckstart; while (residual) { trckcount = MIN(trckbuffsize, residual); trckend = track + trckcount - 1; rc = lzds_dasdhandle_read_tracks_to_buffer(dasdh, track, trckend, trackdata); if (rc) { perror("Error on read"); exit(-1); } data = trackdata; for (i = 0; i < trckcount; ++i) { dasdview_print_raw_track(data, track / info->geo.heads, track % info->geo.heads); data += RAWTRACKSIZE; ++track; } residual -= trckcount; } free(trackdata); rc = lzds_dasdhandle_close(dasdh); lzds_dasdhandle_free(dasdh); if (rc < 0) { perror("Error on closing file"); exit(-1); } } static void dasdview_view(dasdview_info_t *info) { if (info->raw_track_access) dasdview_view_raw(info); else dasdview_view_standard(info); } static void dasdview_print_characteristic(dasdview_info_t *info) { dasd_information2_t dasd_info = info->dasd_info; printf("encrypted disk : %s\n", (dasd_info.characteristics[46] & 0x80) ? "yes" : "no"); printf("solid state device : %s\n", (dasd_info.characteristics[46] & 0x40) ? "yes" : "no"); } int main(int argc, char *argv[]) { dasdview_info_t info; int oc; unsigned long long max = 0LL; char *begin_param_str = NULL; char *size_param_str = NULL; int rc; util_prg_init(&prg); util_opt_init(opt_vec, NULL); bzero(&info, sizeof(info)); while (1) { oc = util_opt_getopt_long(argc, argv); switch (oc) { case 'h': util_prg_print_help(); util_opt_print_help(); exit(EXIT_SUCCESS); case 'v': util_prg_print_version(); exit(EXIT_SUCCESS); case 'b': begin_param_str = optarg; info.action_specified = 1; info.begin_specified = 1; break; case 's': size_param_str = optarg; info.action_specified = 1; info.size_specified = 1; break; case '1': info.format1 = 1; info.format2 = 0; break; case '2': info.format1 = 0; info.format2 = 1; break; case 'i': /* print general DASD information and geometry */ info.action_specified = 1; info.general_info = 1; break; case 'x': /* print extended DASD information */ info.action_specified = 1; info.extended_info = 1; break; case 'j': info.action_specified = 1; info.volser = 1; break; case 't': if (strcmp(optarg, "info") == 0) { info.vtoc_info = 1; } else if (strcmp(optarg, "f1") == 0) { info.vtoc_f1 = 1; } else if (strcmp(optarg, "f3") == 0) { info.vtoc_f3 = 1; } else if (strcmp(optarg, "f4") == 0) { info.vtoc_f4 = 1; } else if (strcmp(optarg, "f5") == 0) { info.vtoc_f5 = 1; } else if (strcmp(optarg, "f7") == 0) { info.vtoc_f7 = 1; } else if (strcmp(optarg, "f8") == 0) { info.vtoc_f8 = 1; } else if (strcmp(optarg, "f9") == 0) { info.vtoc_f9 = 1; } else if (strcmp(optarg, "all") == 0) { info.vtoc_all = 1; } else { zt_error_print("dasdview: usage error\n" "%s is no valid argument for" " option -t/--vtoc\n", optarg); exit(-1); } info.vtoc = 1; info.action_specified = 1; break; case 'l': info.action_specified = 1; info.vlabel_info = 1; break; case 'c': info.action_specified = 1; info.characteristic_specified = 1; break; case -1: /* End of options string - start of devices list */ info.device_id = optind; break; default: fprintf(stderr, "Try 'dasdview --help' for more" " information.\n"); exit(1); } if (oc == -1) break; } /* do some tests */ if (info.device_id >= argc) { zt_error_print("dasdview: usage error\n" "No device specified!"); exit(EXIT_FAILURE); } if (info.device_id + 1 < argc) { zt_error_print("dasdview: usage error\n" "More than one device specified!"); exit(EXIT_FAILURE); } if (info.device_id < argc) strcpy(info.device, argv[info.device_id]); dasdview_get_info(&info); if (info.raw_track_access) { rc = lzds_zdsroot_alloc(&info.zdsroot); if (rc) { zt_error_print("Could not allocate index\n"); exit(-1); } rc = lzds_zdsroot_add_device(info.zdsroot, info.device, &info.dasd); if (rc) { zt_error_print("Could not add device to index\n"); exit(-1); } } if (info.begin_specified) dasdview_parse_input(&info.begin, &info, begin_param_str); else info.begin = DEFAULT_BEGIN; if (info.raw_track_access) max = (unsigned long long)info.hw_cylinders * (unsigned long long)info.geo.heads * RAWTRACKSIZE; else max = (unsigned long long)info.hw_cylinders * (unsigned long long)info.geo.heads * (unsigned long long)info.geo.sectors * (unsigned long long)info.blksize; if (info.begin > max) { zt_error_print("dasdview: usage error\n" "'begin' value is not within disk range!"); exit(-1); } if (info.size_specified) dasdview_parse_input(&info.size, &info, size_param_str); else if (info.raw_track_access) info.size = RAWTRACKSIZE; else info.size = DEFAULT_SIZE; if ((info.begin_specified || info.size_specified) && ((info.begin + info.size) > max)) { zt_error_print("dasdview: usage error\n" "'begin' + 'size' is not within " "disk range!"); exit(-1); } if ((info.begin_specified || info.size_specified) && (!info.format1 && !info.format2)) info.format1 = 1; if ((info.format1 || info.format2) && (!info.size_specified && !info.begin_specified)) { zt_error_print("dasdview: usage error\n" "Options -1 or -2 make only sense with " "options -b or -s!"); exit(-1); } /* do the output */ if (info.begin_specified || info.size_specified) dasdview_view(&info); if (info.general_info || info.extended_info) dasdview_print_general_info(&info); if (info.extended_info) dasdview_print_extended_info(&info); if (info.volser) dasdview_print_volser(&info); if (info.vlabel_info) dasdview_print_vlabel(&info); if (info.vtoc) dasdview_print_vtoc(&info); if (!info.action_specified) printf("No action specified.\n"); if (info.characteristic_specified) dasdview_print_characteristic(&info); return 0; } s390-tools-2.3.0/dasdview/dasdview.h000066400000000000000000000051731323410431100171330ustar00rootroot00000000000000/* * dasdview - Display DASD and VTOC information or dump the contents of a DASD * * Copyright IBM Corp. 2002, 2017 * * s390-tools is free software; you can redistribute it and/or modify * it under the terms of the MIT license. See LICENSE for details. */ #ifndef DASDVIEW_H #define DASDVIEW_H #include #include "lib/u2s.h" /******************************************************************************** * SECTION: Definitions needed for DASD-API (see dasd.h) *******************************************************************************/ /* * values to be used for dasd_information2_t.format * 0x00: NOT formatted * 0x01: Linux disc layout * 0x02: Common disc layout */ #define DASD_FORMAT_NONE 0 #define DASD_FORMAT_LDL 1 #define DASD_FORMAT_CDL 2 /* * values to be used for dasd_information2_t.features * 0x00: default features * 0x01: readonly (ro) * 0x02: use diag discipline (diag) */ #define DASD_FEATURE_DEFAULT 0 #define DASD_FEATURE_READONLY 1 #define DASD_FEATURE_USEDIAG 2 /******************************************************************************** * SECTION: DASDVIEW internal types *******************************************************************************/ #define LINE_LENGTH 80 #define DASDVIEW_ERROR "dasdview:" #define DEFAULT_BEGIN 0 #define DEFAULT_SIZE 128 #define NO_PART_LABELS 8 /* for partition related labels (f1,f8 and f9) */ #define SEEK_STEP 4194304LL #define DUMP_STRING_SIZE 1024LL #define ERROR_STRING_SIZE 1024 static char error_str[ERROR_STRING_SIZE]; enum dasdview_failure { open_error, seek_error, read_error, ioctl_error, usage_error, disk_layout, vtoc_error }; typedef struct dasdview_info { char device[PATH_MAX]; dasd_information2_t dasd_info; int dasd_info_version; unsigned int blksize; struct hd_geometry geo; u_int32_t hw_cylinders; unsigned long long begin; unsigned long long size; int format1; int format2; int action_specified; int begin_specified; int size_specified; int characteristic_specified; int device_id; int general_info; int extended_info; int volser; int vtoc; int vtoc_info; int vtoc_f1; int vtoc_f3; int vtoc_f4; int vtoc_f5; int vtoc_f7; int vtoc_f8; int vtoc_f9; int vtoc_all; int vlabel_info; format1_label_t f1[NO_PART_LABELS]; format4_label_t f4; format5_label_t f5; format7_label_t f7; format1_label_t f8[NO_PART_LABELS]; format9_label_t f9[NO_PART_LABELS]; int f1c; int f4c; int f5c; int f7c; int f8c; int f9c; char busid[U2S_BUS_ID_SIZE]; int busid_valid; int raw_track_access; struct zdsroot *zdsroot; struct raw_vtoc *rawvtoc; struct dasd *dasd; } dasdview_info_t; #endif /* DASDVIEW_H */ s390-tools-2.3.0/dump2tar/000077500000000000000000000000001323410431100150765ustar00rootroot00000000000000s390-tools-2.3.0/dump2tar/Makefile000066400000000000000000000002351323410431100165360ustar00rootroot00000000000000# Common definitions include ../common.mak all: $(MAKE) -C src install: all $(MAKE) -C src install $(MAKE) -C man install clean: $(MAKE) -C src clean s390-tools-2.3.0/dump2tar/include/000077500000000000000000000000001323410431100165215ustar00rootroot00000000000000s390-tools-2.3.0/dump2tar/include/buffer.h000066400000000000000000000032761323410431100201530ustar00rootroot00000000000000/* * dump2tar - tool to dump files and command output into a tar archive * * Data buffering functions * * Copyright IBM Corp. 2016, 2017 * * s390-tools is free software; you can redistribute it and/or modify * it under the terms of the MIT license. See LICENSE for details. */ #ifndef BUFFER_H #define BUFFER_H #include #include #include /* Buffers for building tar file entries */ struct buffer { size_t total; /* Total number of bytes in buffer */ size_t off; /* Current offset to next free byte in memory buffer */ size_t size; /* Memory buffer size */ char *addr; /* Memory buffer address */ bool fd_open; /* Has fd been openend yet? */ FILE *file; /* FILE * of file containing previous buffer data */ int fd; /* Handle of file containing previous buffer data */ }; void buffer_init(struct buffer *buffer, size_t size); struct buffer *buffer_alloc(size_t size); void buffer_reset(struct buffer *buffer); void buffer_close(struct buffer *buffer); void buffer_free(struct buffer *buffer, bool dyn); int buffer_open(struct buffer *buffer); int buffer_flush(struct buffer *buffer); ssize_t buffer_make_room(struct buffer *buffer, size_t size, bool usefile, size_t max_buffer_size); int buffer_truncate(struct buffer *buffer, size_t len); ssize_t buffer_read_fd(struct buffer *buffer, int fd, size_t chunk, bool usefile, size_t max_buffer_size); int buffer_add_data(struct buffer *buffer, char *addr, size_t len, bool usefile, size_t max_buffer_size); typedef int (*buffer_cb_t)(void *data, void *addr, size_t len); int buffer_iterate(struct buffer *buffer, buffer_cb_t cb, void *data); void buffer_print(struct buffer *buffer); #endif /* BUFFER_H */ s390-tools-2.3.0/dump2tar/include/dref.h000066400000000000000000000012161323410431100176120ustar00rootroot00000000000000/* * dump2tar - tool to dump files and command output into a tar archive * * Reference counting for directory handles * * Copyright IBM Corp. 2016, 2017 * * s390-tools is free software; you can redistribute it and/or modify * it under the terms of the MIT license. See LICENSE for details. */ #ifndef DREF_H #define DREF_H #include #include /* Multiple jobs may refer to an open DIR * - need reference counting */ struct dref { DIR *dd; int dirfd; unsigned int count; }; struct dref *dref_create(const char *dirname); struct dref *dref_get(struct dref *dref); void dref_put(struct dref *dref); #endif /* DREF_H */ s390-tools-2.3.0/dump2tar/include/dump.h000066400000000000000000000024101323410431100176340ustar00rootroot00000000000000/* * dump2tar - tool to dump files and command output into a tar archive * * Main dump logic * * Copyright IBM Corp. 2016, 2017 * * s390-tools is free software; you can redistribute it and/or modify * it under the terms of the MIT license. See LICENSE for details. */ #ifndef DUMP_H #define DUMP_H #include #include #include #include "strarray.h" #define NUM_EXCLUDE_TYPES 7 struct dump_spec { char *inname; char *outname; bool is_cmd; }; struct dump_opts { bool add_cmd_status; bool append; bool dereference; bool exclude_type[NUM_EXCLUDE_TYPES]; bool gzip; bool ignore_failed_read; bool no_eof; bool quiet; bool recursive; bool threaded; bool verbose; const char *output_file; int file_timeout; int timeout; long jobs; long jobs_per_cpu; size_t file_max_size; size_t max_buffer_size; size_t max_size; size_t read_chunk_size; struct strarray exclude; struct dump_spec *specs; unsigned int num_specs; }; struct dump_opts *dump_opts_new(void); int dump_opts_set_type_excluded(struct dump_opts *opts, char c); void dump_opts_add_spec(struct dump_opts *opts, char *inname, char *outname, bool is_cmd); void dump_opts_free(struct dump_opts *opts); int dump_to_tar(struct dump_opts *opts); #endif /* DUMP_H */ s390-tools-2.3.0/dump2tar/include/global.h000066400000000000000000000007631323410431100201400ustar00rootroot00000000000000/* * dump2tar - tool to dump files and command output into a tar archive * * Global variables * * Copyright IBM Corp. 2016, 2017 * * s390-tools is free software; you can redistribute it and/or modify * it under the terms of the MIT license. See LICENSE for details. */ #ifndef GLOBAL_H #define GLOBAL_H #include extern bool global_threaded; extern bool global_debug; extern bool global_verbose; extern bool global_quiet; extern bool global_timestamps; #endif /* GLOBAL_H */ s390-tools-2.3.0/dump2tar/include/idcache.h000066400000000000000000000012221323410431100202470ustar00rootroot00000000000000/* * dump2tar - tool to dump files and command output into a tar archive * * Caches for user and group ID lookups * * Copyright IBM Corp. 2016, 2017 * * s390-tools is free software; you can redistribute it and/or modify * it under the terms of the MIT license. See LICENSE for details. */ #ifndef IDCACHE_H #define IDCACHE_H #include #include /* Buffer sizes for getpwuid_r and getgid_r calls (bytes) */ #define PWD_BUFFER_SIZE 4096 #define GRP_BUFFER_SIZE 4096 void uid_to_name(uid_t uid, char *name, size_t len); void gid_to_name(gid_t gid, char *name, size_t len); void idcache_cleanup(void); #endif /* IDCACHE_H */ s390-tools-2.3.0/dump2tar/include/misc.h000066400000000000000000000057221323410431100176330ustar00rootroot00000000000000/* * dump2tar - tool to dump files and command output into a tar archive * * Helper functions * * Copyright IBM Corp. 2016, 2017 * * s390-tools is free software; you can redistribute it and/or modify * it under the terms of the MIT license. See LICENSE for details. */ #ifndef MISC_H #define MISC_H #include #include #include #include #include "lib/util_libc.h" #include "global.h" #define MSG_LEN 256 #define DBG(...) \ do { \ if (global_debug) \ debug(__FILE__, __LINE__, ##__VA_ARGS__); \ } while (0) #define mwarn(fmt, ...) _mwarn(true, (fmt), ##__VA_ARGS__) #define mwarnx(fmt, ...) _mwarn(false, (fmt), ##__VA_ARGS__) /* Helper macro for constructing messages in variables */ #define HANDLE_RC(rc, max, off, label) \ do { \ if ((rc) > 0) \ (off) += (rc); \ if ((off) > (max)) \ goto label; \ } while (0) /* Program exit codes */ #define EXIT_OK 0 #define EXIT_RUNTIME 1 #define EXIT_USAGE 2 /* Number of nanoseconds in a second */ #define NSEC_PER_SEC 1000000000L #define NSEC_PER_MSEC 1000000L #define NSEC_PER_USEC 1000L extern struct timespec main_start_ts; struct dref; int misc_write_data(int fd, char *addr, size_t len); ssize_t misc_read_data(int fd, char *addr, size_t len); void inc_timespec(struct timespec *ts, time_t sec, long nsec); void set_timespec(struct timespec *ts, time_t sec, long nsec); bool ts_before(struct timespec *a, struct timespec *b); int snprintf_duration(char *buff, size_t len, struct timespec *start, struct timespec *end); char *get_threadname(void); void debug(const char *file, unsigned long line, const char *format, ...); void _mwarn(bool print_errno, const char *format, ...); void verb(const char *format, ...); void info(const char *format, ...); #define mmalloc(len) util_zalloc(len) #define mcalloc(n, len) util_zalloc((n) * (len)) #define mrealloc(ptr, len) util_realloc((ptr), (len)) #define mstrdup(str) util_strdup(str) #define masprintf(fmt, ...) __masprintf(__func__, __FILE__, __LINE__, \ (fmt), ##__VA_ARGS__) char *__masprintf(const char *func, const char *file, int line, const char *fmt, ...); #define set_threadname(fmt, ...) __set_threadname(__func__, __FILE__, \ __LINE__, (fmt), \ ##__VA_ARGS__) void __set_threadname(const char *func, const char *file, int line, const char *fmt, ...); void clear_threadname(void); void chomp(char *str, char *c); void lchomp(char *str, char *c); void remove_double_slashes(char *str); int stat_file(bool dereference, const char *abs, const char *rel, struct dref *dref, struct stat *st); void set_dummy_stat(struct stat *st); bool starts_with(const char *str, const char *prefix); bool ends_with(const char *str, const char *suffix); int cmd_child(int fd, char *cmd); int cmd_open(char *cmd, pid_t *pid_ptr); int cmd_close(int fd, pid_t pid, int *status_ptr); void misc_init(void); void misc_cleanup(void); void set_stdout_data(void); #endif /* MISC_H */ s390-tools-2.3.0/dump2tar/include/strarray.h000066400000000000000000000013111323410431100205350ustar00rootroot00000000000000/* * dump2tar - tool to dump files and command output into a tar archive * * Dynamically growing string arrays * * Copyright IBM Corp. 2016, 2017 * * s390-tools is free software; you can redistribute it and/or modify * it under the terms of the MIT license. See LICENSE for details. */ #ifndef STRARRAY_H #define STRARRAY_H /* A string array that can grow in size */ struct strarray { unsigned int num; char **str; }; void free_strarray(struct strarray *array); void add_str_to_strarray(struct strarray *array, const char *str); void add_vstr_to_strarray(struct strarray *array, const char *fmt, ...); int add_file_to_strarray(struct strarray *array, const char *filename); #endif /* STRARRAY_H */ s390-tools-2.3.0/dump2tar/include/tar.h000066400000000000000000000023161323410431100174620ustar00rootroot00000000000000/* * dump2tar - tool to dump files and command output into a tar archive * * TAR file generation * * Copyright IBM Corp. 2016, 2017 * * s390-tools is free software; you can redistribute it and/or modify * it under the terms of the MIT license. See LICENSE for details. */ #ifndef TAR_H #define TAR_H #include #include #include #define TYPE_REGULAR '0' #define TYPE_LINK '2' #define TYPE_DIR '5' #define TAR_BLOCKSIZE 512 struct buffer; /* emit_cb_t - Callback used for emitting chunks of a byte stream * @data: Arbitrary pointer passed via the @data parameter of the * tar_emit_file_* functions * @addr: Pointer to data * @len: Size of data * Return %0 on success. Returning non-zero will indicate failure and abort * further data emission. */ typedef int (*emit_cb_t)(void *data, void *addr, size_t len); int tar_emit_file_from_buffer(char *filename, char *link, size_t len, struct stat *stat, char type, struct buffer *content, emit_cb_t emit_cb, void *data); int tar_emit_file_from_data(char *filename, char *link, size_t len, struct stat *stat, char type, void *addr, emit_cb_t emit_cb, void *data); #endif /* TAR_H */ s390-tools-2.3.0/dump2tar/man/000077500000000000000000000000001323410431100156515ustar00rootroot00000000000000s390-tools-2.3.0/dump2tar/man/Makefile000066400000000000000000000003021323410431100173040ustar00rootroot00000000000000# Common definitions include ../../common.mak all: install: $(INSTALL) -d -m 755 $(DESTDIR)$(MANDIR)/man1 $(INSTALL) -m 644 -c dump2tar.1 $(DESTDIR)$(MANDIR)/man1 clean: .PHONY: all clean s390-tools-2.3.0/dump2tar/man/dump2tar.1000066400000000000000000000234101323410431100174710ustar00rootroot00000000000000.\" Copyright 2017 IBM Corp. .\" s390-tools is free software; you can redistribute it and/or modify .\" it under the terms of the MIT license. See LICENSE for details. .\" .\" Macro for inserting an option description prologue. .\" .OD [] [args] .de OD . ds args " . if !'\\$3'' .as args \fI\\$3\fP . if !'\\$4'' .as args \\$4 . if !'\\$5'' .as args \fI\\$5\fP . if !'\\$6'' .as args \\$6 . if !'\\$7'' .as args \fI\\$7\fP . PD 0 . if !'\\$2'' .IP "\fB\-\\$2\fP \\*[args]" 4 . if !'\\$1'' .IP "\fB\-\-\\$1\fP \\*[args]" 4 . PD .. .\" Macro for inserting code line. .\" .CL .de CL . ds pfont \\n[.f] . nh . na . ft CW \\$* . ft \\*[pfont] . ad . hy . br .. .\" Macro for inserting a man page reference. .\" .MP man-page section [suffix] .de MP . nh . na . BR \\$1 (\\$2)\\$3 . ad . hy .. . .TH "dump2tar" "1" "2016\-09\-02" "" "" . .SH "NAME" dump2tar - Gather file contents and command output into a tar archive . . .SH "SYNOPSIS" .B "dump2tar " .RI "[" "OPTIONS" "] " "SPECS" . . .SH "DESCRIPTION" .B dump2tar creates a tar archive from the contents of any files, including files of unknown size. Examples for files of unknown size are: .IP \(bu 3 Named pipes (FIFOs) .PP .IP \(bu 3 Particular Linux kernel debugfs or sysfs files .PP .IP \(bu 3 Character or block devices .PP When adding such a file, .B dump2tar first reads all available data until an end-of-file indication is found. From this data, it then creates a regular file entry in the resulting tar archive. By default, symbolic links and directories are preserved in the archive in their original form. .B dump2tar can also: .IP \(bu 3 Add files under a different name .PP .IP \(bu 3 Run arbitrary commands and add the resulting command output as a regular file .PP . . .SH "FILE SPECIFICATIONS" . This section describes the format of the .I SPECS argument mentioned in the command synopsis. Use the following command line syntax to identify data sources and to specify file names within the archive: .PP .TP .I "PATH" Adds the contents of the file system subtree at file system location .I PATH (with possible exceptions described by options) in the archive under the same file name as on the file system. .PP . . .TP .IR "FILENAME" ":=" "PATH" Adds the contents of the file at file system location .I PATH in the archive under the name specified by .IR FILENAME . .PP . . .TP .IR "FILENAME" "|=" "CMDLINE" Runs the command .IR CMDLINE and captures both the resulting standard output and standard error streams. Adds the collected output as a regular file named .I FILENAME in the resulting archive. You can also include the resulting program exit code by using option \-\-add\-cmd\-status. .PP . You can also specify "\-\-". All specifications that follow are interpreted as simple file names. This is useful for archiving files that contain ":=" or "|=". .PP . . .SH "OUTPUT OPTIONS" . .OD "output\-file" "o" "TARFILE" Writes the resulting tar archive to .IR TARFILE . An existing file at the specified file system location is overwritten. If this option is omitted or if "\-" is specified for .IR TARFILE , the archive is written to the standard output stream. .PP . . .OD "gzip" "z" "" Compresses the resulting tar archive using gzip. .PP . . .OD "max\-size" "m" "VALUE" Sets an upper size limit, in bytes, for the resulting archive. If this limit is exceeded after adding a file, no further files are added. .PP . . .OD "timeout" "t" "VALUE" Sets an upper time limit, in seconds, for the archiving process. If this limit is exceeded while adding a file, that file is truncated and no further files are added. .PP . . .OD "no-eof" "" "" Does not write an end-of-file marker. Use this option if you want to create an archive that can be extended by appending additional tar archive data. Note: Do not use this option for the final data to be added. A valid tar archive requires a trailing end-of-file marker. .PP . . .OD "append" "" "" Appends data to the end of the archive. Use this option to incrementally build a tar file by repeatedly calling .BR dump2tar . You must specify the \-\-no\-eof option for each but the final call of .BR dump2tar . .PP . . .OD "add-cmd-status" "" "" Adds a separate file named .RI \(dq FILENAME .cmdstatus\(dq for each command output added through the .RI \(dq FILENAME |= CMDLINE \(dq notation (see FILE SPECIFICATIONS). This file contains information about the exit status of the process that executed the command: . .RS 8 .TP .RI EXITSTATUS= VALUE Unless .I VALUE is -1, the process ended normally with the specified exit value. .PP . .TP .RI TERMSIG= VALUE Unless .I VALUE is -1, the process was stopped by a signal of the specified number. .PP . .TP .RI WAITPID_ERRNO= VALUE Unless .I VALUE is -1, an attempt to obtain the status of the process failed with the specified error. .PP .RE . . . .SH "INPUT OPTIONS" . .OD "files\-from" "F" "FILENAME" Reads input data specifications (see FILE SPECIFICATIONS) from .IR FILENAME , one specification per line. Each line contains either a file name or a .IR FILENAME := PATH or .IR FILENAME |= CMDLINE specification. Empty lines are ignored. A line can also consist of only "\-\-". All lines following this specification are interpreted as simple file names. This is useful for archiving files that contain ":=" or "|=". .PP . . .OD "ignore\-failed\-read" "i" "" Continues after read errors. By default, .B dump2tar stops processing after encountering errors while reading an input file. With this option, .B dump2tar prints a warning message and adds an empty entry for the erroneous file in the archive. .PP . . .OD "buffer\-size" "b" "VALUE" Reads data from input files in chunks of .I VALUE bytes. Large values can accelerate the archiving process for large files at the cost of increased memory usage. The default value is 1048576. .PP . . .OD "file\-timeout" "T" "VALUE" Sets an upper time limit, in seconds, for reading an input file. .B dump2tar stops processing a file when the time limit is exceeded. Archive entries for such files are truncated to the amount of data that is collected by the time the limit is reached. .PP . . .OD "file\-max\-size" "M" "N" Sets an upper size limit, in bytes, for an input file. .B dump2tar stops processing a file when the size limit is exceeded. Archive entries for such files are truncated to the specified size. .PP . . .OD "jobs" "j" "N" By default, .B dump2tar processes one file at a time. With this option, .B dump2tar processes .I N files in parallel. Parallel processing can accelerate the archiving process, especially if input files are located on slow devices, or when output from multiple commands is added to the archive. Note: Use .B tar option \-\-delay\-directory\-restore when extracting files from an archive created with \-\-jobs to prevent conflicts with directory permissions and modification times. .PP . . .OD "jobs\-per\-cpu" "J" "N" Processes .I N files for each online CPU in parallel. Parallel processing can accelerate the archiving process, especially if input files are located on slow devices, or when output from multiple commands is added to the archive. Note: Use .B tar option \-\-delay\-directory\-restore when extracting files from an archive created with \-\-jobs\-per\-cpu to prevent conflicts with directory permissions and modification times. .PP . . .OD "exclude" "x" "PATTERN" Does not add files to the archive if their file names match .IR PATTERN . .I PATTERN is an expression that uses the shell wildcards. .PP . . .OD "exclude\-from" "X" "FILENAME" Does not add files to the archive if their names match at least one of the patterns listed in the pattern file with name .IR FILENAME . In the pattern file, each line specifies an expression that uses the shell wildcards. .PP . . .OD "exclude\-type" "" "TYPE" Does not add files to the archive if they match at least one of the file types specified with .IR TYPE . .I TYPE uses one or more of the characters "fdcbpls", where: .RS 8 .IP f 3 regular files .PP .IP d 3 directories .PP .IP c 3 character devices .PP .IP b 3 block devices .PP .IP p 3 named pipes (FIFOs) .PP .IP l 3 symbolic links .PP .IP s 3 sockets .PP .RE . .PP . . .OD "dereference" "" "" Adds the content of link targets instead of symbolic links. .PP . . .OD "no\-recursion" "" "" Does not add files from sub\-directories. By default, .B dump2tar adds archive entries for specified directories, and for the files within these directories. With this option, a specified directory results in a single entry for the directory. Any contained files to be included must be specified explicitly. .PP . . .SH "MISC OPTIONS" . .OD "help" "h" "" Prints an overview of available options, then exits. .PP . . .OD "verbose" "V" "" Prints additional informational output. .PP . . .OD "quiet" "q" "" Suppresses printing of informational output. .PP . . . .SH "EXAMPLES" . .\fB .CL # dump2tar a b \-o archive.tar .\fR .RS 4 Creates a tar archive named archive.tar containing files a and b. .RE .PP . .\fB .CL # dump2tar /proc \-o procdump.tar.gz \-z \-i \-T 1 \-M 1048576 .\fR .RS 4 Creates a gzip compressed tar archive named procdump.tar.gz that contains all procfs files. Unreadable files are ignored. Files are truncated when the first of the two limiting conditions is reached, either 1048576 bytes of content or the reading time of 1 second. .RE .PP . .\fB .CL # dump2tar '|=dmesg' '|=lspci' \-o data.tar .\fR .RS 4 Creates a tar archive named data.tar containing the output of the 'dmesg' and 'lspci' commands. .RE .PP . .\fB .CL # dump2tar /sys/kernel/debug/ -x '*/tracing/*' -o debug.tar -i .\fR .RS 4 Creates a tar archive named debug.tar containing the contents of directory /sys/kernel/debug/ while excluding any file that is located in a sub-directory named 'tracing'. .RE .PP . . .SH "EXIT CODES" .TP .B 0 The program finished successfully .TP .B 1 A run-time error occurred .TP .B 2 The specified command was not valid .PP . . .SH "SEE ALSO" .MP dump2tar 1 , .MP tar 1 s390-tools-2.3.0/dump2tar/src/000077500000000000000000000000001323410431100156655ustar00rootroot00000000000000s390-tools-2.3.0/dump2tar/src/Makefile000066400000000000000000000012021323410431100173200ustar00rootroot00000000000000# Common definitions include ../../common.mak ALL_CPPFLAGS += -I../include -std=gnu99 -Wno-unused-parameter LDLIBS += -lpthread -lrt ifneq ($(HAVE_ZLIB),0) ALL_CPPFLAGS += -DHAVE_ZLIB LDLIBS += -lz endif core_objects = buffer.o dref.o global.o dump.o idcache.o misc.o strarray.o tar.o libs = $(rootdir)/libutil/libutil.a check_dep_zlib: $(call check_dep, \ "dump2tar", \ "zlib.h", \ "zlib-devel or libz-dev", \ "HAVE_ZLIB=0") all: check_dep_zlib dump2tar dump2tar: $(core_objects) dump2tar.o $(libs) install: dump2tar $(INSTALL) -c dump2tar $(DESTDIR)$(USRBINDIR) clean: @rm -f dump2tar *.o .PHONY: all install clean s390-tools-2.3.0/dump2tar/src/buffer.c000066400000000000000000000145701323410431100173110ustar00rootroot00000000000000/* * dump2tar - tool to dump files and command output into a tar archive * * Data buffering functions * * Copyright IBM Corp. 2016, 2017 * * 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 #include #include #include #include #include "buffer.h" #include "misc.h" void buffer_print(struct buffer *buffer) { fprintf(stderr, "DEBUG: buffer at %p\n", (void *) buffer); if (!buffer) return; fprintf(stderr, "DEBUG: total=%zu\n", buffer->total); fprintf(stderr, "DEBUG: off=%zu\n", buffer->off); fprintf(stderr, "DEBUG: size=%zu\n", buffer->size); fprintf(stderr, "DEBUG: addr=%p\n", (void *) buffer->addr); fprintf(stderr, "DEBUG: fd_open=%d\n", buffer->fd_open); fprintf(stderr, "DEBUG: fd=%d\n", buffer->fd); if (buffer->fd_open) { fprintf(stderr, "DEBUG: fd->pos=%zu\n", lseek(buffer->fd, 0, SEEK_CUR)); } } /* Initialize @buffer to hold @size bytes in memory */ void buffer_init(struct buffer *buffer, size_t size) { memset(buffer, 0, sizeof(struct buffer)); buffer->addr = mmalloc(size); buffer->size = size; } /* Allocate a new buffer for holding @size bytes in memory */ struct buffer *buffer_alloc(size_t size) { struct buffer *buffer; buffer = mmalloc(sizeof(struct buffer)); buffer_init(buffer, size); return buffer; } /* Forget about any data stored in @buffer */ void buffer_reset(struct buffer *buffer) { buffer->total = 0; buffer->off = 0; if (buffer->fd_open) { if (ftruncate(buffer->fd, 0)) mwarn("Cannot truncate temporary file"); if (lseek(buffer->fd, 0, SEEK_SET) == (off_t) -1) mwarn("Cannot seek in temporary file"); } } /* Close buffer file associated with @buffer */ void buffer_close(struct buffer *buffer) { if (!buffer->fd_open) return; fclose(buffer->file); buffer->fd = 0; buffer->fd_open = false; } /* Release all resources associated with @buffer. If @dyn is %true, also free * @buffer itself. */ void buffer_free(struct buffer *buffer, bool dyn) { if (!buffer) return; buffer_reset(buffer); buffer_close(buffer); free(buffer->addr); if (dyn) free(buffer); } /* Open a buffer file for @buffer. Return %EXIT_OK on success, %EXIT_RUNTIME * otherwise. */ int buffer_open(struct buffer *buffer) { if (buffer->fd_open) return EXIT_OK; buffer->file = tmpfile(); if (!buffer->file) { mwarn("Could not create temporary file"); return EXIT_RUNTIME; } buffer->fd = fileno(buffer->file); buffer->fd_open = true; return EXIT_OK; } /* Write data in memory of @buffer to buffer file. Return %EXIT_OK on success, * %EXIT_RUNTIME otherwise. */ int buffer_flush(struct buffer *buffer) { if (buffer->off == 0) return EXIT_OK; if (buffer_open(buffer)) return EXIT_RUNTIME; if (misc_write_data(buffer->fd, buffer->addr, buffer->off)) { mwarn("Could not write to temporary file"); return EXIT_RUNTIME; } buffer->off = 0; return EXIT_OK; } /* Try to ensure that at least @size bytes are available at * @buffer->addr[buffer->off]. Return the actual number of bytes available or * @-1 on error. If @usefile is %true, make use of a buffer file if * the total buffer size exceeds @max_buffer_size. */ ssize_t buffer_make_room(struct buffer *buffer, size_t size, bool usefile, size_t max_buffer_size) { size_t needsize; if (size > max_buffer_size && usefile) size = max_buffer_size; needsize = buffer->off + size; if (needsize <= buffer->size) { /* Room available */ return size; } if (needsize > max_buffer_size && usefile) { /* Need to write out memory buffer to buffer file */ if (buffer_flush(buffer)) return -1; if (size <= buffer->size) return size; needsize = size; } /* Need to increase memory buffer size */ buffer->size = needsize; buffer->addr = mrealloc(buffer->addr, buffer->size); return size; } /* Try to read @chunk bytes from @fd to @buffer. Return the number of bytes * read on success, %0 on EOF or %-1 on error. */ ssize_t buffer_read_fd(struct buffer *buffer, int fd, size_t chunk, bool usefile, size_t max_buffer_size) { ssize_t c = buffer_make_room(buffer, chunk, usefile, max_buffer_size); DBG("buffer_read_fd wanted %zd got %zd", chunk, c); if (c < 0) return c; c = read(fd, buffer->addr + buffer->off, c); if (c > 0) { buffer->total += c; buffer->off += c; } return c; } /* Add @len bytes at @addr to @buffer. If @addr is %NULL, add zeroes. Return * %EXIT_OK on success, %EXIT_RUNTIME otherwise. */ int buffer_add_data(struct buffer *buffer, char *addr, size_t len, bool usefile, size_t max_buffer_size) { ssize_t c; while (len > 0) { c = buffer_make_room(buffer, len, usefile, max_buffer_size); if (c < 0) return EXIT_RUNTIME; if (addr) { memcpy(buffer->addr + buffer->off, addr, c); addr += c; } else { memset(buffer->addr + buffer->off, 0, c); } buffer->total += c; buffer->off += c; len -= c; } return EXIT_OK; } /* Call @cb for all chunks of data in @buffer. @data is passed to @cb. */ int buffer_iterate(struct buffer *buffer, buffer_cb_t cb, void *data) { int rc; ssize_t r; if (buffer->total == 0) return EXIT_OK; if (!buffer->fd_open) return cb(data, buffer->addr, buffer->off); /* Free memory buffer to be used as copy buffer */ if (buffer_flush(buffer)) return EXIT_RUNTIME; if (lseek(buffer->fd, 0, SEEK_SET) == (off_t) -1) { mwarn("Cannot seek in temporary file"); return EXIT_RUNTIME; } /* Copy data from temporary file to target file */ while ((r = misc_read_data(buffer->fd, buffer->addr, buffer->size)) != 0) { if (r < 0) { mwarn("Cannot read from temporary file"); return EXIT_RUNTIME; } rc = cb(data, buffer->addr, r); if (rc) return rc; } return EXIT_OK; } /* Truncate @buffer to at most @len bytes */ int buffer_truncate(struct buffer *buffer, size_t len) { size_t delta; if (buffer->total <= len) return EXIT_OK; delta = buffer->total - len; buffer->total = len; if (buffer->fd_open && delta > buffer->off) { /* All of memory and some of file buffer is truncated */ buffer->off = 0; if (ftruncate(buffer->fd, len)) { mwarn("Cannot truncate temporary file"); return EXIT_RUNTIME; } if (lseek(buffer->fd, len, SEEK_SET) == (off_t) -1) { mwarn("Cannot seek in temporary file"); return EXIT_RUNTIME; } } else { /* Only memory buffer is truncated */ buffer->off -= delta; } return EXIT_OK; } s390-tools-2.3.0/dump2tar/src/dref.c000066400000000000000000000035701323410431100167560ustar00rootroot00000000000000/* * dump2tar - tool to dump files and command output into a tar archive * * Reference counting for directory handles * * Copyright IBM Corp. 2016, 2017 * * 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 #include #include #include "dref.h" #include "global.h" #include "misc.h" /* dref_mutex serializes access to drefs */ static pthread_mutex_t dref_mutex = PTHREAD_MUTEX_INITIALIZER; static unsigned long num_open_dirs; static unsigned long num_open_dirs_max; /* Lock dref mutex */ static void dref_lock(void) { if (!global_threaded) return; pthread_mutex_lock(&dref_mutex); } /* Unlock dref mutex */ static void dref_unlock(void) { if (!global_threaded) return; pthread_mutex_unlock(&dref_mutex); } /* Create a reference count managed directory handle for @dirname */ struct dref *dref_create(const char *dirname) { struct dref *dref; DIR *dd; dd = opendir(dirname); DBG("opendir(%s)=%p (total=%lu)", dirname, dd, ++num_open_dirs); if (!dd) { num_open_dirs--; return NULL; } if (num_open_dirs > num_open_dirs_max) num_open_dirs_max = num_open_dirs; dref = mmalloc(sizeof(struct dref)); dref->dd = dd; dref->dirfd = dirfd(dd); dref->count = 1; return dref; } /* Obtain a reference to @dref */ struct dref *dref_get(struct dref *dref) { if (dref) { dref_lock(); dref->count++; dref_unlock(); } return dref; } /* Release a reference to @dref. If this was the last reference, lose the * associated directory handle and free @dref. */ void dref_put(struct dref *dref) { if (dref) { dref_lock(); dref->count--; if (dref->count == 0) { num_open_dirs--; DBG("closedir(%p) (total=%lu, max=%lu)", dref->dd, num_open_dirs, num_open_dirs_max); closedir(dref->dd); free(dref); } dref_unlock(); } } s390-tools-2.3.0/dump2tar/src/dump.c000066400000000000000000001276231323410431100170110ustar00rootroot00000000000000/* * dump2tar - tool to dump files and command output into a tar archive * * Main dump logic * * Copyright IBM Corp. 2016, 2017 * * 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 #include #include #include #include #include #include #include #include #include #include #ifdef HAVE_ZLIB #include #endif /* HAVE_ZLIB */ #include "buffer.h" #include "dref.h" #include "dump.h" #include "global.h" #include "idcache.h" #include "misc.h" #include "tar.h" /* Default input file read size (bytes) */ #define DEFAULT_READ_CHUNK_SIZE (512 * 1024) #define DEFAULT_MAX_BUFFER_SIZE (2 * 1024 * 1024) #define _SET_ABORTED(task) _set_aborted((task), __func__, __LINE__) #define SET_ABORTED(task) set_aborted((task), __func__, __LINE__) #define read_error(task, filename, fmt, ...) \ do { \ if (!(task)->opts->ignore_failed_read) \ SET_ABORTED((task)); \ _mwarn(true, "%s: " fmt, (filename), ##__VA_ARGS__); \ } while (0) #define write_error(task, fmt, ...) \ do { \ SET_ABORTED((task)); \ _mwarn(true, "%s: " fmt, (task)->opts->output_file, \ ##__VA_ARGS__); \ } while (0) #define tverb(fmt, ...) \ do { \ if (task->opts->verbose) \ verb((fmt), ##__VA_ARGS__); \ } while (0) /* Jobs representing a file or command output to add */ struct job { struct job *next_job; enum job_type { JOB_INIT, /* Initialization work */ JOB_FILE, /* Add a regular file */ JOB_LINK, /* Add a symbolic link */ JOB_DIR, /* Add a directory */ JOB_CMD, /* Add command output */ } type; enum job_status { JOB_QUEUED, /* Transient: Job processing has not started */ JOB_IN_PROGRESS,/* Transient: Job processing has started */ JOB_EXCLUDED, /* Final: File was excluded */ JOB_FAILED, /* Final: Data could not be obtained */ JOB_DONE, /* Final: All data was obtained */ JOB_PARTIAL, /* Final: Only some data was obtained */ } status; char *outname; char *inname; char *relname; struct stat stat; bool timed; struct timespec deadline; struct dref *dref; int cmd_status; struct buffer *content; }; /* Run-time statistics */ struct stats { unsigned long num_done; unsigned long num_excluded; unsigned long num_failed; unsigned long num_partial; }; /* Information specific to a single dump task */ struct task { /* Input */ struct dump_opts *opts; /* State */ /* mutex serializes access to global data */ pthread_mutex_t mutex; pthread_cond_t worker_cond; pthread_cond_t cond; unsigned long num_jobs_active; struct job *jobs_head; struct job *jobs_tail; bool aborted; /* output_mutex serializes access to output file */ pthread_mutex_t output_mutex; int output_fd; size_t output_written; #ifdef HAVE_ZLIB gzFile output_gzfd; #endif /* HAVE_ZLIB */ unsigned long output_num_files; /* No protection needed (only accessed in single-threaded mode) */ struct stats stats; struct timespec start_ts; }; /* Per thread management data */ struct per_thread { long num; pthread_t thread; bool running; bool timed_out; struct stats stats; struct job *job; struct buffer buffer; struct task *task; }; static const struct { mode_t mode; char c; } exclude_types[NUM_EXCLUDE_TYPES] = { { S_IFREG, 'f' }, { S_IFDIR, 'd' }, { S_IFCHR, 'c' }, { S_IFBLK, 'b' }, { S_IFIFO, 'p' }, { S_IFLNK, 'l' }, { S_IFSOCK, 's' }, }; /* Lock main mutex */ static void main_lock(struct task *task) { if (!global_threaded) return; DBG("main lock"); pthread_mutex_lock(&task->mutex); } /* Unlock main mutex */ static void main_unlock(struct task *task) { if (!global_threaded) return; DBG("main unlock"); pthread_mutex_unlock(&task->mutex); } /* Lock output mutex */ static void output_lock(struct task *task) { if (!global_threaded) return; pthread_mutex_lock(&task->output_mutex); } /* Unlock output mutex */ static void output_unlock(struct task *task) { if (!global_threaded) return; pthread_mutex_unlock(&task->output_mutex); } /* Wake up all waiting workers */ static void _worker_wakeup_all(struct task *task) { if (!global_threaded) return; DBG("waking up all worker threads"); pthread_cond_broadcast(&task->worker_cond); } /* Wake up one waiting worker */ static void _worker_wakeup_one(struct task *task) { if (!global_threaded) return; DBG("waking up one worker thread"); pthread_cond_signal(&task->worker_cond); } /* Wait for a signal to a worker */ static int _worker_wait(struct task *task) { int rc; DBG("waiting for signal to worker"); rc = pthread_cond_wait(&task->worker_cond, &task->mutex); DBG("waiting for signal to worker done (rc=%d)", rc); return rc; } /* Wake up main thread */ static void _main_wakeup(struct task *task) { if (!global_threaded) return; DBG("waking up main thread"); pthread_cond_broadcast(&task->cond); } /* Wait for a signal to the main thread */ static int _main_wait(struct task *task) { int rc; DBG("waiting for status change"); rc = pthread_cond_wait(&task->cond, &task->mutex); DBG("waiting for status change done (rc=%d)", rc); return rc; } /* Wait for a signal to the main thread. Abort waiting after @deadline */ static int _main_wait_timed(struct task *task, struct timespec *deadline) { int rc; DBG("timed waiting for status change"); rc = pthread_cond_timedwait(&task->cond, &task->mutex, deadline); DBG("timed waiting for status change done (rc=%d)", rc); return rc; } /* Allow thread to be canceled */ static void cancel_enable(void) { if (!global_threaded) return; if (pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, NULL) != 0) mwarn("pthread_setcancelstate"); } /* Prevent thread from being canceled */ static void cancel_disable(void) { if (!global_threaded) return; if (pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, NULL) != 0) mwarn("pthread_setcancelstate"); } /* Abort processing and inform all threads to shutdown. Must be called with * task->mutex locked */ static void _set_aborted(struct task *task, const char *func, unsigned int line) { DBG("set aborted at %s:%u", func, line); task->aborted = true; _worker_wakeup_all(task); _main_wakeup(task); } /* Abort processing and inform all threads to shutdown */ static void set_aborted(struct task *task, const char *func, unsigned int line) { main_lock(task); _set_aborted(task, func, line); main_unlock(task); } /* Check if abort processing has been initiated */ static bool is_aborted(struct task *task) { bool result; main_lock(task); result = task->aborted; main_unlock(task); return result; } /* Release resources associated with @job */ static void free_job(struct task *task, struct job *job) { DBG("free job %p (%s)", job, job->inname); if (!job) return; free(job->inname); free(job->outname); free(job->relname); dref_put(job->dref); free(job); } /* Check if file type specified by mode @m was marked as excluded */ static bool is_type_excluded(struct dump_opts *opts, mode_t m) { int i; m &= S_IFMT; for (i = 0; i < NUM_EXCLUDE_TYPES; i++) { if (exclude_types[i].mode == m) return opts->exclude_type[i]; } return false; } /* Replace all '/' characters in @filename with '_' */ static void escape_filename(char *filename) { for (; *filename; filename++) { if (*filename == '/') *filename = '_'; } } /* Determine filename in archive from original filename @inname and * requested new filename @outname and depending on @type. */ static void set_outname(char **result_ptr, const char *outname, const char *inname, enum job_type type) { const char *prefix = "", *name, *suffix; char *result, *end; size_t olen = outname ? strlen(outname) : 0, plen, nlen; if (olen == 0) { /* No output name specified: outname = inname */ name = inname; } else if (outname[olen - 1] == '/') { /* Output name is a directory: outname = outname/inname */ prefix = outname; name = inname; } else { /* Output name is a filename: outname = inname */ name = outname; } if (type == JOB_DIR) suffix = "/"; else suffix = ""; plen = strlen(prefix); nlen = strlen(name); result = mmalloc(plen + nlen + strlen(suffix) + /* NUL */ 1); /* Add prefix */ strcpy(result, prefix); /* Add name */ end = result + plen; strcpy(end, name); if (type == JOB_CMD) escape_filename(end); /* Add suffix */ end = end + nlen; strcpy(end, suffix); remove_double_slashes(result); *result_ptr = result; } static void sanitize_dirname(char **name_ptr) { char *name; name = mmalloc(strlen(*name_ptr) + /* Slash */ 1 + /* NUL */ 1); strcpy(name, *name_ptr); remove_double_slashes(name); chomp(name, "/"); strcat(name, "/"); free(*name_ptr); *name_ptr = name; } /* Allocate and initialize a new job representation to add an entry according * to the specified parameters. @relname and @dref are used for opening files * more efficiently using *at() functions if specified. @is_cmd specifies if * the specified inname is a command line. */ static struct job *create_job(struct task *task, const char *inname, const char *outname, bool is_cmd, const char *relname, struct dref *dref, struct stats *stats) { struct job *job = mmalloc(sizeof(struct job)); int rc; DBG("create job inname=%s outname=%s is_cmd=%d relname=%s dref=%p", inname, outname, is_cmd, relname, dref); job->status = JOB_QUEUED; if (!inname) { job->type = JOB_INIT; return job; } job->inname = mstrdup(inname); if (is_cmd) { /* Special case - read from command output */ job->type = JOB_CMD; set_dummy_stat(&job->stat); goto out; } if (!relname && strcmp(job->inname, "-") == 0) { /* Special case - read from standard input */ job->type = JOB_FILE; set_dummy_stat(&job->stat); goto out; } rc = stat_file(task->opts->dereference, job->inname, relname, dref, &job->stat); if (rc < 0) { read_error(task, job->inname, "Cannot stat file"); free_job(task, job); stats->num_failed++; return NULL; } if (is_type_excluded(task->opts, job->stat.st_mode)) { free_job(task, job); stats->num_excluded++; return NULL; } if (S_ISLNK(job->stat.st_mode)) { job->type = JOB_LINK; } else if (S_ISDIR(job->stat.st_mode)) { job->type = JOB_DIR; sanitize_dirname(&job->inname); /* No need to keep parent directory open */ relname = NULL; dref = NULL; } else { job->type = JOB_FILE; } if (relname) job->relname = mstrdup(relname); job->dref = dref_get(dref); out: set_outname(&job->outname, outname, inname, job->type); return job; } void job_print(struct job *job) { printf("DEBUG: job_print at %p\n", job); printf("DEBUG: next_job=%p\n", job->next_job); printf("DEBUG: type=%d\n", job->type); printf("DEBUG: status==%d\n", job->status); printf("DEBUG: outname=%s\n", job->outname); printf("DEBUG: inname=%s\n", job->inname); printf("DEBUG: relname=%s\n", job->relname); printf("DEBUG: timed=%d\n", job->timed); printf("DEBUG: dref=%p\n", job->dref); printf("DEBUG: cmd_status=%d\n", job->cmd_status); printf("DEBUG: content=%p\n", job->content); } /* Return the number of bytes written to the output file */ static size_t get_output_size(struct task *task) { #ifdef HAVE_ZLIB if (task->opts->gzip) { gzflush(task->output_gzfd, Z_SYNC_FLUSH); return gztell(task->output_gzfd); } #endif /* HAVE_ZLIB */ return task->output_written; } /* Write @len bytes at address @ptr to the output file */ static int write_output(struct task *task, const char *ptr, size_t len) { size_t todo = len; ssize_t w; #ifdef HAVE_ZLIB if (task->opts->gzip) { if (gzwrite(task->output_gzfd, ptr, len) == 0) goto err_write; task->output_written += len; return EXIT_OK; } #endif /* HAVE_ZLIB */ while (todo > 0) { w = write(task->output_fd, ptr, todo); if (w < 0) goto err_write; todo -= w; ptr += w; } task->output_written += len; return EXIT_OK; err_write: write_error(task, "Cannot write output"); return EXIT_RUNTIME; } /* Write an end-of-file marker to the output file */ static void write_eof(struct task *task) { char zeroes[TAR_BLOCKSIZE]; memset(zeroes, 0, sizeof(zeroes)); write_output(task, zeroes, TAR_BLOCKSIZE); write_output(task, zeroes, TAR_BLOCKSIZE); } /* Callback for writing out chunks of job data */ static int _write_job_data_cb(void *data, void *addr, size_t len) { struct task *task = data; return write_output(task, addr, len); } /* Write tar entry for a file containing the exit status of the process that * ran command job @job */ static int write_job_status_file(struct task *task, struct job *job) { char *name, *content; size_t len; struct stat st; int rc, status = job->cmd_status, exitstatus = -1, termsig = -1, waitpid_errno = -1; name = masprintf("%s.cmdstatus", job->outname); if (status < 0) waitpid_errno = -status; else if (WIFEXITED(status)) exitstatus = WEXITSTATUS(status); else if (WIFSIGNALED(status)) termsig = WTERMSIG(status); content = masprintf("EXITSTATUS=%d\n" "TERMSIG=%d\n" "WAITPID_ERRNO=%d\n", exitstatus, termsig, waitpid_errno); len = strlen(content); set_dummy_stat(&st); rc = tar_emit_file_from_data(name, NULL, len, &st, TYPE_REGULAR, content, _write_job_data_cb, task); free(name); free(content); return rc; } /* Write tar entry for data in @job to output. Must be called with output_lock * held. */ static void _write_job_data(struct task *task, struct job *job) { struct buffer *buffer = job->content; switch (job->status) { case JOB_DONE: case JOB_PARTIAL: break; case JOB_FAILED: /* Create empty entries for failed reads */ if (task->opts->ignore_failed_read) break; return; default: return; } switch (job->type) { case JOB_CMD: tar_emit_file_from_buffer(job->outname, NULL, buffer->total, &job->stat, TYPE_REGULAR, buffer, _write_job_data_cb, task); task->output_num_files++; if (task->opts->add_cmd_status) { write_job_status_file(task, job); task->output_num_files++; } break; case JOB_FILE: tar_emit_file_from_buffer(job->outname, NULL, buffer->total, &job->stat, TYPE_REGULAR, buffer, _write_job_data_cb, task); task->output_num_files++; break; case JOB_LINK: tar_emit_file_from_buffer(job->outname, buffer->addr, 0, &job->stat, TYPE_LINK, NULL, _write_job_data_cb, task); task->output_num_files++; break; case JOB_DIR: tar_emit_file_from_buffer(job->outname, NULL, 0, &job->stat, TYPE_DIR, NULL, _write_job_data_cb, task); task->output_num_files++; break; default: break; } if (task->opts->max_size > 0 && get_output_size(task) > task->opts->max_size) { mwarnx("Archive size exceeds maximum of %ld bytes - aborting", task->opts->max_size); SET_ABORTED(task); } } /* Read the contents of the symbolic link at @filename. On success, the * contents is returned in @buffer and the return value is %EXIT_OK. * If @relname is non-null it points to the name of the file relative * to its parent directory for which @dirfd is an open file handle. */ static int read_symlink(struct task *task, const char *filename, const char *relname, int dirfd, struct buffer *buffer) { ssize_t actual = 0; size_t currlen = buffer->size ? buffer->size : task->opts->read_chunk_size; int rc = EXIT_OK; while (!is_aborted(task)) { buffer_make_room(buffer, currlen, false, task->opts->max_buffer_size); cancel_enable(); if (relname) actual = readlinkat(dirfd, relname, buffer->addr, buffer->size); else actual = readlink(filename, buffer->addr, buffer->size); cancel_disable(); if (actual == -1) { read_error(task, filename, "Cannot read link"); rc = EXIT_RUNTIME; /* Reset actual counter to get an empty buffer */ actual = 0; break; } /* Ensure that content doesn't exceed --file-max-size limit */ if (task->opts->file_max_size > 0 && (size_t) actual > task->opts->file_max_size) { actual = task->opts->file_max_size;/* Don't count NUL */ mwarnx("%s: Warning: Data exceeds maximum size of %ld " "bytes - truncating", filename, task->opts->file_max_size); break; } if ((size_t) actual < buffer->size) break; currlen += task->opts->read_chunk_size; } if (rc == EXIT_OK && is_aborted(task)) rc = EXIT_RUNTIME; buffer->addr[actual] = 0; buffer->total = actual + 1; return rc; } /* Read data from the file descriptor @fd until an end-of-file condition is * encountered. On success, *@done bytes in @buffer contain the read data * and the return value is %EXIT_OK. */ static int read_fd(struct task *task, const char *name, int fd, struct buffer *buffer) { ssize_t rc = 0; size_t c = buffer->size ? buffer->size : task->opts->read_chunk_size; while (!is_aborted(task)) { cancel_enable(); rc = buffer_read_fd(buffer, fd, c, true, task->opts->max_buffer_size); cancel_disable(); if (rc <= 0) break; /* Ensure that content doesn't exceed --file-max-size limit */ if (task->opts->file_max_size > 0 && buffer->total >= task->opts->file_max_size) { buffer_truncate(buffer, task->opts->file_max_size); rc = 0; mwarnx("%s: Warning: Data exceeds maximum size of %ld " "bytes - truncating", name, task->opts->file_max_size); break; } c = buffer->size - buffer->off; if (c > 0) { /* Read to memory */ } else if (buffer->size + task->opts->read_chunk_size < task->opts->max_buffer_size) { /* Enlarge memory buffer */ c = task->opts->read_chunk_size; } else { /* Use full memory buffer size */ c = task->opts->max_buffer_size; } } if (is_aborted(task) || rc != 0) return EXIT_RUNTIME; return EXIT_OK; } /* Read data from the file at @filename until an end-of-file condition is * encountered. On success, @buffer contains the data read and the return * value is %EXIT_OK. If @relname is non-null it points to the name of the * file relative to its parent directory for which @dirfd is an open file * handle. */ static int read_regular(struct task *task, const char *filename, const char *relname, int dirfd, struct buffer *buffer) { int fd, rc = EXIT_OK; bool need_close = true; /* Opening a named pipe can block when peer is not ready */ cancel_enable(); if (strcmp(filename, "-") == 0) { fd = STDIN_FILENO; need_close = false; filename = "Standard input"; } else if (relname) fd = openat(dirfd, relname, O_RDONLY); else fd = open(filename, O_RDONLY); cancel_disable(); if (fd < 0) { read_error(task, filename, "Cannot open file"); return EXIT_RUNTIME; } rc = read_fd(task, filename, fd, buffer); if (rc) { if (is_aborted(task)) mwarnx("%s: Read aborted", filename); else read_error(task, filename, "Cannot read file"); } if (need_close) close(fd); return rc; } /* Read the output of command @cmd until an end-of-file condition is * encountered. On success, @buffer contain the output and the return value * is %EXIT_OK. When not %NULL, use @status_ptr to store the resulting process * status. */ static int read_cmd_output(struct task *task, char *cmd, struct buffer *buffer, int *status_ptr) { int fd, rc = EXIT_RUNTIME; pid_t pid; fd = cmd_open(cmd, &pid); if (fd < 0) { read_error(task, cmd, "Cannot run command"); return rc; } if (read_fd(task, cmd, fd, buffer)) { if (is_aborted(task)) mwarnx("%s: Command aborted", cmd); else read_error(task, cmd, "Cannot read command output"); } else rc = EXIT_OK; cmd_close(fd, pid, status_ptr); return rc; } /* Check the exclude patterns in @task->opts->exclude for a match of @filename. * If found, return the matching pattern string, otherwise return %NULL. */ static const char *get_exclude_match(struct task *task, const char *filename) { unsigned int i; int mode = FNM_PERIOD | FNM_NOESCAPE; for (i = 0; i < task->opts->exclude.num; i++) { if (fnmatch(task->opts->exclude.str[i], filename, mode) == 0) return task->opts->exclude.str[i]; } return NULL; } /* Add the specified @job to the start of the job queue */ static void _queue_job_head(struct task *task, struct job *job) { DBG("queue job type=%d inname=%s at head", job->type, job->inname); job->next_job = task->jobs_head; task->jobs_head = job; if (!task->jobs_tail) task->jobs_tail = job; } /* Add the specified @job to the end of the job queue */ static void _queue_job_tail(struct task *task, struct job *job) { DBG("queue job type=%d inname=%s at tail", job->type, job->inname); if (task->jobs_tail) task->jobs_tail->next_job = job; else task->jobs_head = job; task->jobs_tail = job; } /* Add the specified @job to the job queue and trigger processing. * If @head is %true, the new job is inserted at the start of the job queue, * otherwise at the end. */ static void queue_job(struct task *task, struct job *job, bool head) { main_lock(task); task->num_jobs_active++; if (head) _queue_job_head(task, job); else _queue_job_tail(task, job); _worker_wakeup_one(task); main_unlock(task); } /* Add the specified list of jobs starting with @first up to @last to the start * of the job queue and trigger processing */ static void queue_jobs(struct task *task, struct job *first, struct job *last, int num) { main_lock(task); last->next_job = task->jobs_head; task->jobs_head = first; task->num_jobs_active += num; _worker_wakeup_all(task); main_unlock(task); } /* Remove the head of the job queue and return it to the caller */ static struct job *_dequeue_job(struct task *task) { struct job *job = NULL; if (task->jobs_head) { job = task->jobs_head; task->jobs_head = job->next_job; job->next_job = NULL; if (job == task->jobs_tail) task->jobs_tail = NULL; DBG("dequeueing job type=%d inname=%s", job->type, job->inname); job->status = JOB_IN_PROGRESS; } else { DBG("no job to dequeue"); } return job; } /* Create and queue job for file at @filename */ static void queue_file(struct task *task, const char *inname, const char *outname, bool is_cmd, const char *relname, struct dref *dref, struct stats *stats, bool head) { struct job *job; job = create_job(task, inname, outname, is_cmd, relname, dref, stats); if (job) queue_job(task, job, head); } /* Queue initial job */ static void init_queue(struct task *task) { queue_file(task, NULL, NULL, false, NULL, NULL, NULL, true); } /* Create and queue jobs for all files found in @dirname */ static void queue_dir(struct task *task, const char *dirname, const char *outname, struct stats *stats) { struct dirent *de; char *inpath, *outpath; struct dref *dref; struct job *job, *first = NULL, *last = NULL; int num = 0; dref = dref_create(dirname); if (!dref) { read_error(task, dirname, "Cannot read directory"); return; } while ((de = readdir(dref->dd))) { if (de->d_name[0] == '.') { if (de->d_name[1] == 0) continue; if (de->d_name[1] == '.' && de->d_name[2] == 0) continue; } DBG("next file %s", de->d_name); inpath = masprintf("%s%s", dirname, de->d_name); outpath = masprintf("%s%s", outname, de->d_name); job = create_job(task, inpath, outpath, false, de->d_name, dref, stats); if (job) { if (last) { last->next_job = job; last = job; } else { first = job; last = job; } num++; } free(inpath); free(outpath); } if (first) queue_jobs(task, first, last, num); dref_put(dref); } /* Create and queue jobs for all files specified on the command line */ static void queue_jobs_from_opts(struct task *task, struct stats *stats) { struct dump_opts *opts = task->opts; unsigned int i; /* Queue directly specified entries */ for (i = 0; i < opts->num_specs && !is_aborted(task); i++) { queue_file(task, opts->specs[i].inname, opts->specs[i].outname, opts->specs[i].is_cmd, NULL, NULL, stats, false); } } /* Prepare output stream */ static int open_output(struct task *task) { bool to_stdout = !task->opts->output_file || strcmp(task->opts->output_file, "-") == 0; int rc = EXIT_OK; struct stat st; if (to_stdout) { set_stdout_data(); task->opts->output_file = "Standard output"; } cancel_enable(); #ifdef HAVE_ZLIB if (task->opts->gzip) { if (to_stdout) { task->output_gzfd = gzdopen(STDOUT_FILENO, task->opts->append ? "ab" : "wb"); } else { task->output_gzfd = gzopen(task->opts->output_file, task->opts->append ? "ab" : "wb"); } if (!task->output_gzfd) rc = EXIT_RUNTIME; goto out; } #endif /* HAVE_ZLIB */ if (to_stdout) { task->output_fd = STDOUT_FILENO; } else { task->output_fd = open(task->opts->output_file, O_WRONLY | O_CREAT | (task->opts->append ? O_APPEND : 0), 0666); } if (task->output_fd < 0) rc = EXIT_RUNTIME; else if (!task->opts->append) { if (fstat(task->output_fd, &st) == -1) rc = EXIT_RUNTIME; else if (S_ISREG(st.st_mode) && ftruncate(task->output_fd, 0) == -1) rc = EXIT_RUNTIME; } #ifdef HAVE_ZLIB out: #endif /* HAVE_ZLIB */ cancel_disable(); if (rc != EXIT_OK) { mwarn("%s: Cannot open output file", task->opts->output_file); return rc; } return EXIT_OK; } /* Determine if the specified @job should be excluded from archiving */ static bool is_job_excluded(struct task *task, struct job *job) { const char *pat; if (job->type == JOB_INIT || job->type == JOB_CMD) return false; pat = get_exclude_match(task, job->inname); if (!pat) return false; tverb("Excluding '%s' due to exclude pattern '%s'\n", job->inname, pat); return true; } /* Perform all actions necessary to process @job and add resulting tar * data buffers to the buffer list of @thread. */ static void process_job(struct per_thread *thread, struct job *job) { struct task *task = thread->task; const char *relname = job->dref ? job->relname : NULL; int dirfd = job->dref ? job->dref->dirfd : -1; struct buffer *buffer = &thread->buffer; enum job_status status = JOB_DONE; DBG("processing job type=%d inname=%s", job->type, job->inname); if (is_job_excluded(task, job)) { status = JOB_EXCLUDED; goto out; } switch (job->type) { case JOB_INIT: /* Perform initial setup steps */ if (open_output(task)) { SET_ABORTED(task); status = JOB_FAILED; goto out; } queue_jobs_from_opts(task, &thread->stats); break; case JOB_CMD: /* Capture command output */ tverb("Dumping command output '%s'\n", job->inname); set_dummy_stat(&job->stat); if (read_cmd_output(task, job->inname, buffer, &job->cmd_status)) status = JOB_FAILED; break; case JOB_LINK: /* Read symbolic link */ tverb("Dumping link '%s'\n", job->inname); if (read_symlink(task, job->inname, relname, dirfd, buffer)) status = JOB_FAILED; break; case JOB_DIR: /* Read directory contents */ tverb("Dumping directory '%s'\n", job->inname); if (task->opts->recursive) { queue_dir(task, job->inname, job->outname, &thread->stats); } break; case JOB_FILE: /* Read file contents */ tverb("Dumping file '%s'\n", job->inname); if (read_regular(task, job->inname, relname, dirfd, buffer)) status = JOB_FAILED; break; default: break; } out: job->status = status; DBG("processing done status=%d", job->status); } /* Add @job results to statistics @stats */ static void account_stats(struct task *task, struct stats *stats, struct job *job) { DBG("accounting job %s", job->inname); if (job->type == JOB_INIT) return; switch (job->status) { case JOB_DONE: stats->num_done++; if (job->type == JOB_CMD && task->opts->add_cmd_status) stats->num_done++; break; case JOB_PARTIAL: stats->num_done++; stats->num_partial++; if (job->type == JOB_CMD && task->opts->add_cmd_status) stats->num_done++; break; case JOB_FAILED: stats->num_failed++; break; case JOB_EXCLUDED: stats->num_excluded++; break; default: break; } } /* Add statistics @from to @to */ static void add_stats(struct stats *to, struct stats *from) { to->num_done += from->num_done; to->num_partial += from->num_partial; to->num_excluded += from->num_excluded; to->num_failed += from->num_failed; } /* Release resources allocated to @thread */ static void cleanup_thread(struct per_thread *thread) { if (thread->job) free_job(thread->task, thread->job); buffer_free(&thread->buffer, false); } /* Register activate @job at @thread */ static void start_thread_job(struct per_thread *thread, struct job *job) { struct task *task = thread->task; thread->job = job; job->content = &thread->buffer; if (task->opts->file_timeout > 0 && job->type != JOB_INIT) { /* Set up per-job timeout */ set_timespec(&job->deadline, task->opts->file_timeout, 0); job->timed = true; /* Signal main thread to update deadline timeout */ _main_wakeup(task); } } /* Unregister active @job at @thread */ static void stop_thread_job(struct per_thread *thread, struct job *job) { thread->job = NULL; job->content = NULL; buffer_reset(&thread->buffer); } /* Wait until a job is available in the job queue. When a job becomes * available, dequeue and return it. Return %NULL if no more jobs are * available, or if processing was aborted. Must be called with task->mutex * locked. */ static struct job *_get_next_job(struct task *task) { struct job *job = NULL; do { DBG("checking for jobs"); if (task->aborted) break; job = _dequeue_job(task); if (job) break; if (task->num_jobs_active == 0) break; DBG("found no jobs (%d active)", task->num_jobs_active); } while (_worker_wait(task) == 0); return job; } /* Unlock the mutex specified by @data */ static void cleanup_unlock(void *data) { pthread_mutex_t *mutex = data; pthread_mutex_unlock(mutex); } /* Write entry for data in @job to output */ static void write_job_data(struct task *task, struct job *job) { DBG("write_job_data"); output_lock(task); pthread_cleanup_push(cleanup_unlock, &task->output_mutex); cancel_enable(); _write_job_data(task, job); cancel_disable(); pthread_cleanup_pop(0); output_unlock(task); } /* Perform second part of job processing for @job at @thread by writing the * resulting tar file entry */ static void postprocess_job(struct per_thread *thread, struct job *job, bool cancelable) { struct task *task = thread->task; account_stats(task, &thread->stats, job); if (cancelable) write_job_data(task, job); else _write_job_data(task, job); } /* Mark @job as complete by releasing all associated resources. If this was * the last active job inform main thread. Must be called with main_lock * mutex held. */ static void _complete_job(struct task *task, struct job *job) { task->num_jobs_active--; if (task->num_jobs_active == 0) _main_wakeup(task); free_job(task, job); } static void init_thread(struct per_thread *thread, struct task *task, long num) { memset(thread, 0, sizeof(struct per_thread)); thread->task = task; thread->num = num; } /* Dequeue and process all jobs on the job queue */ static int process_queue(struct task *task) { struct job *job; struct per_thread thread; init_thread(&thread, task, 0); while ((job = _dequeue_job(task)) && !is_aborted(task)) { start_thread_job(&thread, job); process_job(&thread, job); postprocess_job(&thread, job, false); stop_thread_job(&thread, job); _complete_job(task, job); } task->stats = thread.stats; cleanup_thread(&thread); return EXIT_OK; } /* Return %true if @job is in a final state, %false otherwise */ static bool job_is_final(struct job *job) { switch (job->status) { case JOB_DONE: case JOB_PARTIAL: case JOB_EXCLUDED: case JOB_FAILED: return true; default: break; } return false; } /* Main thread function: process jobs on the job queue until all jobs * are processed or processing was aborted. */ static void *worker_thread_main(void *d) { struct per_thread *thread = d; struct task *task = thread->task; struct job *job; /* Allow cancel only at specific code points */ cancel_disable(); set_threadname("%*sworker %d", (thread->num + 1) * 2, "", thread->num); /* Handle jobs left over from canceled thread */ job = thread->job; if (job) { DBG("handle aborted job %p", job); postprocess_job(thread, job, true); main_lock(task); if (thread->timed_out) goto out; stop_thread_job(thread, job); _complete_job(task, job); main_unlock(task); } DBG("enter worker loop"); main_lock(task); while ((job = _get_next_job(task))) { start_thread_job(thread, job); main_unlock(task); process_job(thread, job); postprocess_job(thread, job, true); main_lock(task); if (thread->timed_out) goto out; stop_thread_job(thread, job); _complete_job(task, job); } out: thread->running = false; _main_wakeup(task); main_unlock(task); cancel_enable(); DBG("leave work loop"); return NULL; } /* Start a worker thread associated with the specified @data. Return %EXIT_OK on * success. */ static int start_worker_thread(struct per_thread *data) { int rc; DBG("start thread"); global_threaded = true; data->timed_out = false; rc = pthread_create(&data->thread, NULL, &worker_thread_main, data); if (rc) { mwarnx("Cannot start thread: %s", strerror(rc)); return EXIT_RUNTIME; } data->running = true; return EXIT_OK; } /* Perform timeout handling for thread associated with @data by canceling and * restarting the corresponding thread. Must be called with task->mutex * held. */ static void _timeout_thread(struct per_thread *data) { struct task *task = data->task; struct job *job = data->job; pthread_t thread = data->thread; const char *op, *action; if (!job) { /* Timeout raced with job completion */ return; } if (job_is_final(job)) { /* Job processing done, timeout does not apply */ return; } data->timed_out = true; /* Allow thread to obtain main lock during cancel handling */ main_unlock(task); DBG("cancel num=%d thread=%p", data->num, thread); pthread_cancel(thread); DBG("join num=%d thread=%p", data->num, thread); pthread_join(thread, NULL); main_lock(task); DBG("join done"); if (job->type == JOB_CMD) op = "Command"; else op = "Read"; if (task->opts->ignore_failed_read) action = "skipping"; else action = "aborting"; if (!job->inname || !*job->inname) job_print(job); mwarnx("%s: %s%s timed out after %d second%s - %s", job->inname, task->opts->ignore_failed_read ? "Warning: " : "", op, task->opts->file_timeout, task->opts->file_timeout > 1 ? "s" : "", action); if (!task->opts->ignore_failed_read) _SET_ABORTED(task); /* Interrupted job will be handled by new thread - adjust status */ if (job->status == JOB_IN_PROGRESS) job->status = JOB_PARTIAL; else if (!job_is_final(job)) job->status = JOB_FAILED; if (start_worker_thread(data)) _SET_ABORTED(task); } /* Return the number of currently running jobs */ static long num_jobs_running(struct task *task, struct per_thread *threads) { long i, num = 0; for (i = 0; i < task->opts->jobs; i++) { if (threads[i].running) num++; } return num; } /* Wait until all jobs are done or timeout occurs */ static int wait_for_completion(struct task *task, struct per_thread *threads) { int rc = 0, earliest_timeout; long i; struct per_thread *earliest_thread; struct timespec tool_deadline_ts, deadline_ts, *earliest_ts; struct job *job; /* Set tool deadline */ tool_deadline_ts = task->start_ts; inc_timespec(&tool_deadline_ts, task->opts->timeout, 0); main_lock(task); while (!task->aborted && task->num_jobs_active > 0) { /* Calculate nearest timeout */ earliest_timeout = 0; earliest_ts = NULL; earliest_thread = NULL; if (task->opts->timeout > 0) { earliest_timeout = task->opts->timeout; earliest_ts = &tool_deadline_ts; } for (i = 0; i < task->opts->jobs; i++) { job = threads[i].job; if (!job || !job->timed) continue; if (task->opts->file_timeout == 0) continue; if (!earliest_ts || ts_before(&job->deadline, earliest_ts)) { earliest_timeout = task->opts->file_timeout; earliest_ts = &job->deadline; earliest_thread = &threads[i]; } } /* Wait for status change or timeout */ if (earliest_ts) { deadline_ts = *earliest_ts; rc = _main_wait_timed(task, &deadline_ts); } else { rc = _main_wait(task); } if (rc == 0) continue; if (rc != ETIMEDOUT) { mwarnx("Cannot wait for status change: %s", strerror(rc)); _SET_ABORTED(task); break; } /* Timeout handling */ if (earliest_thread) { /* Per-file timeout, restart */ _timeout_thread(earliest_thread); rc = 0; } else { /* Global timeout, abort */ mwarnx("Operation timed out after %d second%s - " "aborting", earliest_timeout, earliest_timeout > 1 ? "s" : ""); _SET_ABORTED(task); break; } } if (task->aborted) DBG("aborted"); else DBG("all work done"); _worker_wakeup_all(task); /* Allow jobs to finish */ set_timespec(&deadline_ts, 0, NSEC_PER_SEC / 4); while (!task->aborted && num_jobs_running(task, threads) > 0) { DBG("waiting for %lu processes", num_jobs_running(task, threads)); if (_main_wait_timed(task, &deadline_ts)) break; } main_unlock(task); return rc; } /* Finalize output stream */ static void close_output(struct task *task) { #ifdef HAVE_ZLIB if (task->opts->gzip) { gzclose(task->output_gzfd); return; } #endif /* HAVE_ZLIB */ if (task->output_fd != STDOUT_FILENO) close(task->output_fd); } /* Start multi-threaded processing of job queue */ static int process_queue_threaded(struct task *task) { struct per_thread *threads, *thread; int rc; long i; tverb("Using %ld threads\n", task->opts->jobs); threads = mcalloc(sizeof(struct per_thread), task->opts->jobs); rc = 0; for (i = 0; i < task->opts->jobs; i++) { init_thread(&threads[i], task, i); rc = start_worker_thread(&threads[i]); if (rc) break; } if (!rc) wait_for_completion(task, threads); DBG("thread cleanup"); for (i = 0; i < task->opts->jobs; i++) { thread = &threads[i]; if (thread->running) { DBG("cancel %p", thread->thread); pthread_cancel(thread->thread); } DBG("join %p", thread->thread); pthread_join(thread->thread, NULL); add_stats(&task->stats, &thread->stats); cleanup_thread(thread); } free(threads); return rc; } /* Abort any remaining queued jobs and account to @stats */ static void abort_queued_jobs(struct task *task) { struct job *job; while ((job = _dequeue_job(task))) { DBG("aborting job %s", job->inname); task->stats.num_failed++; job->status = JOB_FAILED; _complete_job(task, job); } } /* Print a summary line */ static void print_summary(struct task *task) { char msg[MSG_LEN]; size_t off = 0; int rc; struct stats *stats = &task->stats; struct timespec end_ts; int num_special; unsigned long num_added; if (task->opts->quiet) return; set_timespec(&end_ts, 0, 0); num_special = 0; num_special += stats->num_partial > 0 ? 1 : 0; num_special += stats->num_excluded > 0 ? 1 : 0; num_special += stats->num_failed > 0 ? 1 : 0; num_added = stats->num_done; if (task->opts->ignore_failed_read) num_added += stats->num_partial + stats->num_failed; rc = snprintf(&msg[off], MSG_LEN - off, "Dumped %lu entries ", num_added); HANDLE_RC(rc, MSG_LEN, off, out); if (num_special > 0) { rc = snprintf(&msg[off], MSG_LEN - off, "("); HANDLE_RC(rc, MSG_LEN, off, out); if (stats->num_partial > 0) { rc = snprintf(&msg[off], MSG_LEN - off, "%lu partial", stats->num_partial); HANDLE_RC(rc, MSG_LEN, off, out); if (--num_special > 0) { rc = snprintf(&msg[off], MSG_LEN - off, ", "); HANDLE_RC(rc, MSG_LEN, off, out); } } if (stats->num_excluded > 0) { rc = snprintf(&msg[off], MSG_LEN - off, "%lu excluded", stats->num_excluded); HANDLE_RC(rc, MSG_LEN, off, out); if (--num_special > 0) { rc = snprintf(&msg[off], MSG_LEN - off, ", "); HANDLE_RC(rc, MSG_LEN, off, out); } } if (stats->num_failed > 0) { rc = snprintf(&msg[off], MSG_LEN - off, "%lu failed", stats->num_failed); HANDLE_RC(rc, MSG_LEN, off, out); } rc = snprintf(&msg[off], MSG_LEN - off, ") "); HANDLE_RC(rc, MSG_LEN, off, out); } rc = snprintf(&msg[off], MSG_LEN - off, "in "); HANDLE_RC(rc, MSG_LEN, off, out); snprintf_duration(&msg[off], MSG_LEN - off, &task->start_ts, &end_ts); out: info("%s\n", msg); } static int init_task(struct task *task, struct dump_opts *opts) { pthread_condattr_t attr; memset(task, 0, sizeof(struct task)); set_timespec(&task->start_ts, 0, 0); task->opts = opts; pthread_mutex_init(&task->mutex, NULL); pthread_mutex_init(&task->output_mutex, NULL); pthread_cond_init(&task->worker_cond, NULL); pthread_condattr_init(&attr); if (pthread_condattr_setclock(&attr, CLOCK_MONOTONIC) || pthread_cond_init(&task->cond, &attr)) { mwarn("Could not adjust pthread clock"); return EXIT_RUNTIME; } return EXIT_OK; } struct dump_opts *dump_opts_new(void) { struct dump_opts *opts = mmalloc(sizeof(struct dump_opts)); opts->recursive = true; opts->read_chunk_size = DEFAULT_READ_CHUNK_SIZE; opts->max_buffer_size = DEFAULT_MAX_BUFFER_SIZE; return opts; } void dump_opts_free(struct dump_opts *opts) { unsigned int i; if (!opts) return; free_strarray(&opts->exclude); for (i = 0; i < opts->num_specs; i++) { free(opts->specs[i].inname); free(opts->specs[i].outname); } free(opts->specs); free(opts); } void dump_opts_print(struct dump_opts *opts) { unsigned int i; printf("DEBUG: dump_opts at %p\n", opts); if (!opts) return; printf("DEBUG: add_cmd_status=%d\n", opts->add_cmd_status); printf("DEBUG: append=%d\n", opts->append); printf("DEBUG: dereference=%d\n", opts->dereference); for (i = 0; i < NUM_EXCLUDE_TYPES; i++) printf("DEBUG: exclude_type[%d]=%d\n", i, opts->exclude_type[i]); printf("DEBUG: gzip=%d\n", opts->gzip); printf("DEBUG: ignore_failed_read=%d\n", opts->ignore_failed_read); printf("DEBUG: no_eof=%d\n", opts->no_eof); printf("DEBUG: quiet=%d\n", opts->quiet); printf("DEBUG: recursive=%d\n", opts->recursive); printf("DEBUG: threaded=%d\n", opts->threaded); printf("DEBUG: verbose=%d\n", opts->verbose); printf("DEBUG: output_file=%s\n", opts->output_file); printf("DEBUG: file_timeout=%d\n", opts->file_timeout); printf("DEBUG: timeout=%d\n", opts->timeout); printf("DEBUG: jobs=%ld\n", opts->jobs); printf("DEBUG: jobs_per_cpu=%ld\n", opts->jobs_per_cpu); printf("DEBUG: file_max_size=%zu\n", opts->file_max_size); printf("DEBUG: max_buffer_size=%zu\n", opts->max_buffer_size); printf("DEBUG: max_size=%zu\n", opts->max_size); printf("DEBUG: read_chunk_size=%zu\n", opts->read_chunk_size); for (i = 0; i < opts->exclude.num; i++) printf("DEBUG: exclude[%d]=%s\n", i, opts->exclude.str[i]); for (i = 0; i < opts->num_specs; i++) { printf("DEBUG: specs[%d]:\n", i); printf("DEBUG: inname=%s\n", opts->specs[i].inname); printf("DEBUG: outname=%s\n", opts->specs[i].outname); printf("DEBUG: is_cmd=%d\n", opts->specs[i].is_cmd); } } /* Mark file type associated with character @c as excluded */ int dump_opts_set_type_excluded(struct dump_opts *opts, char c) { int i; for (i = 0; i < NUM_EXCLUDE_TYPES; i++) { if (exclude_types[i].c == c) { opts->exclude_type[i] = true; return 0; } } return -1; } /* Add entry specification defined by @iname, @outname and @op to @opts. */ void dump_opts_add_spec(struct dump_opts *opts, char *inname, char *outname, bool is_cmd) { unsigned int i = opts->num_specs; opts->specs = mrealloc(opts->specs, (i + 1) * sizeof(struct dump_spec)); opts->specs[i].inname = mstrdup(inname); if (outname) opts->specs[i].outname = mstrdup(outname); else opts->specs[i].outname = NULL; opts->specs[i].is_cmd = is_cmd; opts->num_specs++; } int dump_to_tar(struct dump_opts *opts) { struct task task; int rc; long num_cpus; if (opts->jobs_per_cpu > 0) { num_cpus = sysconf(_SC_NPROCESSORS_ONLN); if (num_cpus < 1) { mwarn("Cannot determine number of CPUs - assuming 1 " "CPU"); num_cpus = 1; } opts->jobs = num_cpus; } if (opts->jobs == 0 && (opts->timeout > 0 || opts->file_timeout > 0)) { /* Separate thread needed to implement timeout via cancel */ opts->jobs = 1; } rc = init_task(&task, opts); if (rc) return rc; /* Queue initial job */ init_queue(&task); /* Process queue */ if (opts->jobs > 0) rc = process_queue_threaded(&task); else rc = process_queue(&task); abort_queued_jobs(&task); if (task.output_num_files > 0 && !opts->no_eof) write_eof(&task); print_summary(&task); close_output(&task); if (rc == 0 && task.aborted) rc = EXIT_RUNTIME; return rc; } s390-tools-2.3.0/dump2tar/src/dump2tar.c000066400000000000000000000261621323410431100175760ustar00rootroot00000000000000/* * dump2tar - tool to dump files and command output into a tar archive * * Command line interface * * Copyright IBM Corp. 2016, 2017 * * 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 #include #include #include #include #include #include #include "lib/util_opt.h" #include "lib/util_prg.h" #include "dump.h" #include "global.h" #include "idcache.h" #include "misc.h" #include "strarray.h" #define MIN_BUFFER_SIZE 4096 #define OPT_NOSHORT_BASE 256 #define OPT_DEREFERENCE (OPT_NOSHORT_BASE + 0) #define OPT_NORECURSION (OPT_NOSHORT_BASE + 1) #define OPT_EXCLUDETYPE (OPT_NOSHORT_BASE + 2) /* Program description */ static const struct util_prg dump2tar_prg = { .desc = "Use dump2tar to create a tar archive from the contents " "of arbitrary files.\nIt works even when the size of actual " "file content is not known beforehand,\nsuch as with FIFOs, " "character devices or certain Linux debugfs or sysfs files.\n" "\nYou can also add files under different names and add " "command output using the\nformat described in section SPECS " "below. When no additional options are\nspecified, the " "resulting archive is written to the standard output stream\n" "in uncompressed tar format.", .args = "SPECS", .copyright_vec = { { "IBM Corp.", 2016, 2017 }, UTIL_PRG_COPYRIGHT_END }, }; /* Definition of command line options */ static struct util_opt dump2tar_opts[] = { UTIL_OPT_SECTION("OUTPUT OPTIONS"), { .option = { "output-file", required_argument, NULL, 'o' }, .argument = "FILE", .desc = "Write archive to FILE (default: standard output)", }, #ifdef HAVE_ZLIB { .option = { "gzip", no_argument, NULL, 'z' }, .desc = "Write a gzip compressed archive", }, #endif /* HAVE_ZLIB */ { .option = { "max-size", required_argument, NULL, 'm' }, .argument = "N", .desc = "Stop adding files when archive size exceeds N bytes", }, { .option = { "timeout", required_argument, NULL, 't' }, .argument = "SEC", .desc = "Stop adding files after SEC seconds", }, { .option = { "no-eof", no_argument, NULL, 131 }, .desc = "Do not write an end-of-file marker", .flags = UTIL_OPT_FLAG_NOSHORT, }, { .option = { "add-cmd-status", no_argument, NULL, 132 }, .desc = "Add status of commands as separate file", .flags = UTIL_OPT_FLAG_NOSHORT, }, { .option = { "append", no_argument, NULL, 133 }, .desc = "Append output to end of file", .flags = UTIL_OPT_FLAG_NOSHORT, }, UTIL_OPT_SECTION("INPUT OPTIONS"), { .option = { "files-from", required_argument, NULL, 'F' }, .argument = "FILE", .desc = "Read filenames from FILE (- for standard input)", }, { .option = { "ignore-failed-read", no_argument, NULL, 'i' }, .desc = "Continue after read errors", }, { .option = { "buffer-size", required_argument, NULL, 'b' }, .argument = "N", .desc = "Read data in chunks of N byte (default: 16384)", }, { .option = { "file-timeout", required_argument, NULL, 'T' }, .desc = "Stop reading file after SEC seconds", }, { .option = { "file-max-size", required_argument, NULL, 'M' }, .argument = "N", .desc = "Stop reading file after N bytes", }, { .option = { "jobs", required_argument, NULL, 'j' }, .argument = "N", .desc = "Read N files in parallel (default: 1)", }, { .option = { "jobs-per-cpu", required_argument, NULL, 'J' }, .argument = "N", .desc = "Read N files per CPU in parallel", }, { .option = { "exclude", required_argument, NULL, 'x' }, .argument = "PATTERN", .desc = "Don't add files matching PATTERN", }, { .option = { "exclude-from", required_argument, NULL, 'X' }, .argument = "FILE", .desc = "Don't add files matching patterns in FILE", }, { .option = { "exclude-type", required_argument, NULL, OPT_EXCLUDETYPE }, .argument = "TYPE", .desc = "Don't add files of specified TYPE (one of: fdcbpls)", .flags = UTIL_OPT_FLAG_NOSHORT, }, { .option = { "dereference", no_argument, NULL, OPT_DEREFERENCE }, .desc = "Add link targets instead of links", .flags = UTIL_OPT_FLAG_NOSHORT, }, { .option = { "no-recursion", no_argument, NULL, OPT_NORECURSION }, .desc = "Don't add files from sub-directories", .flags = UTIL_OPT_FLAG_NOSHORT, }, UTIL_OPT_SECTION("MISC OPTIONS"), UTIL_OPT_HELP, UTIL_OPT_VERSION, { .option = { "verbose", no_argument, NULL, 'V' }, .desc = "Print additional informational output", }, { .option = { "quiet", no_argument, NULL, 'q' }, .desc = "Suppress printing of informational output", }, UTIL_OPT_END, }; /* Split buffer size specification in @arg into two numbers to be stored in * @from_ptr and @to_ptr. Return %EXIT_OK on success. */ static int parse_buffer_size(char *arg, size_t *from_ptr, size_t *to_ptr) { char *err; unsigned long from, to; if (!*arg) { mwarnx("Empty buffer size specified"); return EXIT_USAGE; } from = strtoul(arg, &err, 10); if (*err == '-') to = strtoul(err + 1, &err, 10); else to = *to_ptr; if (*err) { mwarnx("Invalid buffer size: %s", arg); return EXIT_USAGE; } if (from < MIN_BUFFER_SIZE || to < MIN_BUFFER_SIZE) { mwarnx("Buffer size too low (minimum %u)", MIN_BUFFER_SIZE); return EXIT_USAGE; } if (to < from) to = from; *from_ptr = from; *to_ptr = to; return EXIT_OK; } static void parse_and_add_spec(struct dump_opts *opts, const char *spec) { char *op, *s, *inname, *outname = NULL; bool is_cmd = false; s = mstrdup(spec); op = strstr(s, "|="); if (op) is_cmd = true; else op = strstr(s, ":="); if (op) { *op = 0; inname = op + 2; outname = s; } else { inname = s; } dump_opts_add_spec(opts, inname, outname, is_cmd); free(s); } static int add_specs_from_file(struct dump_opts *opts, const char *filename) { FILE *fd; char *line = NULL; size_t line_size; int rc = EXIT_RUNTIME; bool need_close = false, parse_spec = true; if (strcmp(filename, "-") == 0) fd = stdin; else { fd = fopen(filename, "r"); if (!fd) { mwarn("%s: Cannot open file", filename); goto out; } need_close = true; } while ((getline(&line, &line_size, fd) != -1)) { chomp(line, "\n"); if (line[0] == 0) continue; if (parse_spec && strcmp(line, "--") == 0) { /* After a line containing --, no more := or |= specs * are expected */ parse_spec = false; continue; } if (parse_spec) parse_and_add_spec(opts, line); else dump_opts_add_spec(opts, line, NULL, false); } if (ferror(fd)) mwarn("%s: Cannot read file", filename); else rc = EXIT_OK; out: if (need_close) fclose(fd); free(line); return rc; } static void print_help(void) { static const struct { const char *name; const char *desc; } specs[] = { { "PATH", "Add file or directory at PATH" }, { "NEWPATH:=PATH", "Add file or directory at PATH as NEWPATH" }, { "NEWPATH|=CMDLINE", "Add output of command line CMDLINE as " "NEWPATH" }, { NULL, NULL }, }; int i; util_prg_print_help(); printf("SPECS\n"); for (i = 0; specs[i].name; i++) util_opt_print_indented(specs[i].name, specs[i].desc); printf("\n"); util_opt_print_help(); } int main(int argc, char *argv[]) { int rc = EXIT_USAGE, opt; long i; struct dump_opts *opts; if (getenv("DUMP2TAR_DEBUG")) global_debug = true; util_prg_init(&dump2tar_prg); util_opt_init(dump2tar_opts, "-"); misc_init(); opts = dump_opts_new(); opterr = 0; while ((opt = util_opt_getopt_long(argc, argv)) != -1) { switch (opt) { case 'h': /* --help */ print_help(); rc = EXIT_OK; goto out; case 'v': /* --version */ util_prg_print_version(); rc = EXIT_OK; goto out; case 'V': /* --verbose */ global_verbose = true; global_quiet = false; opts->verbose = true; opts->quiet = false; break; case 'q': /* --quiet */ global_quiet = true; global_verbose = false; opts->quiet = true; opts->verbose = false; break; case 'i': /* --ignore-failed-read */ opts->ignore_failed_read = true; break; case 'j': /* --jobs N */ opts->jobs = atoi(optarg); if (opts->jobs < 1) { mwarnx("Invalid number of jobs: %s", optarg); goto out; } break; case 'J': /* --jobs-per-cpu N */ opts->jobs_per_cpu = atoi(optarg); if (opts->jobs_per_cpu < 1) { mwarnx("Invalid number of jobs: %s", optarg); goto out; } break; case 'b': /* --buffer-size N */ if (parse_buffer_size(optarg, &opts->read_chunk_size, &opts->max_buffer_size)) goto out; break; case 'x': /* --exclude PATTERN */ add_str_to_strarray(&opts->exclude, optarg); break; case 'X': /* --exclude-from FILE */ if (add_file_to_strarray(&opts->exclude, optarg)) goto out; break; case 'F': /* --files-from FILE */ if (add_specs_from_file(opts, optarg)) goto out; break; case 'o': /* --output-file FILE */ if (opts->output_file) { mwarnx("Output file specified multiple times"); goto out; } opts->output_file = optarg; break; case OPT_DEREFERENCE: /* --dereference */ opts->dereference = true; break; case OPT_NORECURSION: /* --no-recursion */ opts->recursive = false; break; case OPT_EXCLUDETYPE: /* --exclude-type TYPE */ for (i = 0; optarg[i]; i++) { if (dump_opts_set_type_excluded(opts, optarg[i])) break; } if (optarg[i]) { mwarnx("Unrecognized file type: %c", optarg[i]); goto out; } break; case 131: /* --no-eof */ opts->no_eof = true; break; case 132: /* --add-cmd-status */ opts->add_cmd_status = true; break; case 133: /* --append */ opts->append = true; break; case 't': /* --timeout VALUE */ opts->timeout = atoi(optarg); if (opts->timeout < 1) { mwarnx("Invalid timeout value: %s", optarg); goto out; } break; case 'T': /* --file-timeout VALUE */ opts->file_timeout = atoi(optarg); if (opts->file_timeout < 1) { mwarnx("Invalid timeout value: %s", optarg); goto out; } break; case 'm': /* --max-size N */ opts->max_size = atol(optarg); if (opts->max_size < 2) { mwarnx("Invalid maximum size: %s", optarg); goto out; } break; case 'M': /* --file-max-size N */ opts->file_max_size = atol(optarg); if (opts->file_max_size < 2) { mwarnx("Invalid maximum size: %s", optarg); goto out; } break; case 'z': /* --gzip */ opts->gzip = true; break; case 1: /* Filename specification or unrecognized option */ if (optarg[0] == '-') { mwarnx("Invalid option '%s'", optarg); goto out; } parse_and_add_spec(opts, optarg); break; case '?': /* Unrecognized option */ if (optopt) mwarnx("Invalid option '-%c'", optopt); else mwarnx("Invalid option '%s'", argv[optind - 1]); goto out; case ':': /* Missing argument */ mwarnx("Option '%s' requires an argument", argv[optind - 1]); goto out; default: break; } } if (optind >= argc && opts->num_specs == 0) { mwarnx("Please specify files to dump"); goto out; } for (i = optind; i < argc; i++) dump_opts_add_spec(opts, argv[i], NULL, false); rc = dump_to_tar(opts); out: idcache_cleanup(); misc_cleanup(); dump_opts_free(opts); if (rc == EXIT_USAGE) util_prg_print_parse_error(); return rc; } s390-tools-2.3.0/dump2tar/src/global.c000066400000000000000000000006561323410431100173000ustar00rootroot00000000000000/* * dump2tar - tool to dump files and command output into a tar archive * * Global variables * * Copyright IBM Corp. 2016, 2017 * * 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 "global.h" /* Global settings */ bool global_threaded; bool global_debug; bool global_verbose; bool global_quiet; bool global_timestamps; s390-tools-2.3.0/dump2tar/src/idcache.c000066400000000000000000000071471323410431100174220ustar00rootroot00000000000000/* * dump2tar - tool to dump files and command output into a tar archive * * Caches for user and group ID lookups * * Copyright IBM Corp. 2016, 2017 * * 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 #include #include #include #include #include "global.h" #include "idcache.h" #include "misc.h" /* Maximum user and group name lengths as defined in tar header */ #define ID_NAME_MAXLEN 32 /* Types for user and group ID caches */ typedef uid_t generic_id_t; /* Assumes that uid_t == gid_t */ struct id_cache_entry { generic_id_t id; char name[ID_NAME_MAXLEN]; }; struct id_cache { unsigned int num; struct id_cache_entry entries[]; }; /* cache_mutex serializes access to cached uid and gid data */ static pthread_mutex_t id_cache_mutex = PTHREAD_MUTEX_INITIALIZER; static struct id_cache *id_cache_uid; static struct id_cache *id_cache_gid; /* Lock cache mutex */ static void cache_lock(void) { if (!global_threaded) return; pthread_mutex_lock(&id_cache_mutex); } /* Unlock cache mutex */ static void cache_unlock(void) { if (!global_threaded) return; pthread_mutex_unlock(&id_cache_mutex); } /* Copy the name associated with @id in @cache to at most @len bytes at @dest. * Return %true if name was found in cache, %false otherwise. */ static bool strncpy_id_cache_entry(char *dest, struct id_cache *cache, generic_id_t id, size_t len) { unsigned int i; bool hit = false; cache_lock(); if (cache) { for (i = 0; i < cache->num; i++) { if (cache->entries[i].id == id) { strncpy(dest, cache->entries[i].name, len); hit = true; break; } } } cache_unlock(); return hit; } /* Add a new entry consisting of @id and @name to ID cache in @*cache_ptr. * Update @cache_ptr if necessary. */ static void add_id_cache_entry(struct id_cache **cache_ptr, generic_id_t id, char *name) { struct id_cache *cache; unsigned int cache_num; size_t new_size; struct id_cache *new_cache; cache_lock(); cache = *cache_ptr; cache_num = cache ? cache->num : 0; new_size = sizeof(struct id_cache) + sizeof(struct id_cache_entry) * (cache_num + 1); new_cache = mrealloc(cache, new_size); if (cache_num == 0) new_cache->num = 0; new_cache->entries[cache_num].id = id; strncpy(new_cache->entries[cache_num].name, name, ID_NAME_MAXLEN); new_cache->num++; *cache_ptr = new_cache; cache_unlock(); } /* Copy the user name corresponding to user ID @uid to at most @len bytes * at @name */ void uid_to_name(uid_t uid, char *name, size_t len) { struct passwd pwd, *pwd_ptr; char buffer[PWD_BUFFER_SIZE], *result; if (strncpy_id_cache_entry(name, id_cache_uid, uid, len)) return; /* getpwuid() can be slow so cache results */ getpwuid_r(uid, &pwd, buffer, PWD_BUFFER_SIZE, &pwd_ptr); if (!pwd_ptr || !pwd_ptr->pw_name) return; result = pwd_ptr->pw_name; add_id_cache_entry(&id_cache_uid, uid, result); strncpy(name, result, len); } /* Copy the group name corresponding to group ID @gid to at most @len bytes * at @name */ void gid_to_name(gid_t gid, char *name, size_t len) { struct group grp, *grp_ptr; char buffer[GRP_BUFFER_SIZE], *result; if (strncpy_id_cache_entry(name, id_cache_gid, gid, len)) return; /* getgrgid() can be slow so cache results */ getgrgid_r(gid, &grp, buffer, GRP_BUFFER_SIZE, &grp_ptr); if (!grp_ptr || !grp_ptr->gr_name) return; result = grp_ptr->gr_name; add_id_cache_entry(&id_cache_gid, gid, result); strncpy(name, result, len); } void idcache_cleanup(void) { free(id_cache_uid); free(id_cache_gid); } s390-tools-2.3.0/dump2tar/src/misc.c000066400000000000000000000240561323410431100167730ustar00rootroot00000000000000/* * dump2tar - tool to dump files and command output into a tar archive * * Helper functions * * Copyright IBM Corp. 2016, 2017 * * 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 #include #include #include #include #include #include #include #include #include "dref.h" #include "global.h" #include "misc.h" struct timespec main_start_ts; static pthread_key_t thread_name_key; static bool stdout_data; /* Write @len bytes at @addr to @fd. Return %EXIT_OK on success, %EXIT_RUNTIME * otherwise. */ int misc_write_data(int fd, char *addr, size_t len) { ssize_t w; while (len > 0) { w = write(fd, addr, len); if (w < 0) return EXIT_RUNTIME; len -= w; addr += w; } return EXIT_OK; } /* Read at most @len bytes from @fd to @addr. Return the number of bytes read * or %-1 on error. */ ssize_t misc_read_data(int fd, char *addr, size_t len) { size_t done = 0; ssize_t r; while (len > 0) { r = read(fd, addr, len); if (r < 0) return -1; if (r == 0) break; len -= r; addr += r; done += r; } return done; } /* Advance timespec @ts by @sec seconds and @nsec nanoseconds */ void inc_timespec(struct timespec *ts, time_t sec, long nsec) { ts->tv_nsec += nsec; ts->tv_sec += sec; if (ts->tv_nsec > NSEC_PER_SEC) { ts->tv_nsec -= NSEC_PER_SEC; ts->tv_sec++; } } /* Set timespec @ts to point to @sec seconds and @nsec nanoseconds in the * future */ void set_timespec(struct timespec *ts, time_t sec, long nsec) { clock_gettime(CLOCK_MONOTONIC, ts); inc_timespec(ts, sec, nsec); } /* Return true if timespec @a refers to a point in time before @b */ bool ts_before(struct timespec *a, struct timespec *b) { if (a->tv_sec < b->tv_sec || (a->tv_sec == b->tv_sec && a->tv_nsec < b->tv_nsec)) return true; return false; } /* Store a string representing the time duration between @start and @end in * at most @len bytes of @buff. */ int snprintf_duration(char *buff, size_t len, struct timespec *start, struct timespec *end) { time_t sec; long nsec, msec, s, m, h; sec = end->tv_sec - start->tv_sec; nsec = end->tv_nsec - start->tv_nsec; if (nsec < 0) { nsec += NSEC_PER_SEC; sec--; } msec = nsec / NSEC_PER_MSEC; s = sec % 60; sec /= 60; m = sec % 60; sec /= 60; h = sec; if (h > 0) return snprintf(buff, len, "%luh%lum%lu.%03lus", h, m, s, msec); else if (m > 0) return snprintf(buff, len, "%lum%lu.%03lus", m, s, msec); else return snprintf(buff, len, "%lu.%03lus", s, msec); } /* Return the name of the current thread */ char *get_threadname(void) { return pthread_getspecific(thread_name_key); } static int snprintf_timestamp(char *str, size_t size) { struct timespec now_ts; set_timespec(&now_ts, 0, 0); now_ts.tv_sec -= main_start_ts.tv_sec; now_ts.tv_nsec -= main_start_ts.tv_nsec; if (now_ts.tv_nsec < 0) { now_ts.tv_nsec += NSEC_PER_SEC; now_ts.tv_sec--; } return snprintf(str, size, "[%3lu.%06lu] ", now_ts.tv_sec, now_ts.tv_nsec / NSEC_PER_USEC); } /* When DUMP2TAR_DEBUG is set to non-zero, print debugging information */ void debug(const char *file, unsigned long line, const char *format, ...) { char msg[MSG_LEN]; size_t off = 0; int rc; va_list args; /* Debug marker */ rc = snprintf(&msg[off], MSG_LEN - off, "DEBUG: "); HANDLE_RC(rc, MSG_LEN, off, out); /* Timestamp */ rc = snprintf_timestamp(&msg[off], MSG_LEN - off); HANDLE_RC(rc, MSG_LEN, off, out); /* Thread name */ rc = snprintf(&msg[off], MSG_LEN - off, "%s: ", get_threadname()); HANDLE_RC(rc, MSG_LEN, off, out); /* Message */ va_start(args, format); rc = vsnprintf(&msg[off], MSG_LEN - off, format, args); va_end(args); HANDLE_RC(rc, MSG_LEN, off, out); /* Call site */ rc = snprintf(&msg[off], MSG_LEN - off, " (%s:%lu)", file, line); out: fprintf(stderr, "%s\n", msg); } /* Print a warning message consisting of @format and variable arguments. * If @print_errno is true, also print the text corresponding to errno. * We're not using err.h's warn since we want timestamps and synchronized * output. */ void _mwarn(bool print_errno, const char *format, ...) { char msg[MSG_LEN]; size_t off = 0; int rc; va_list args; if (global_timestamps) { rc = snprintf_timestamp(&msg[off], MSG_LEN - off); HANDLE_RC(rc, MSG_LEN, off, out); } rc = snprintf(&msg[off], MSG_LEN - off, "%s: ", program_invocation_short_name); HANDLE_RC(rc, MSG_LEN, off, out); va_start(args, format); rc = vsnprintf(&msg[off], MSG_LEN - off, format, args); va_end(args); HANDLE_RC(rc, MSG_LEN, off, out); if (print_errno) snprintf(&msg[off], MSG_LEN - off, ": %s", strerror(errno)); out: fprintf(stderr, "%s\n", msg); } /* Provide informational output if --verbose was specified */ void verb(const char *format, ...) { char msg[MSG_LEN]; size_t off = 0; int rc; va_list args; FILE *fd; if (!global_verbose) return; if (stdout_data) fd = stderr; else fd = stdout; if (global_timestamps) { rc = snprintf_timestamp(&msg[off], MSG_LEN - off); HANDLE_RC(rc, MSG_LEN, off, out); } va_start(args, format); rc = vsnprintf(&msg[off], MSG_LEN - off, format, args); va_end(args); out: fprintf(fd, "%s", msg); } /* Provide informational output. */ void info(const char *format, ...) { char msg[MSG_LEN]; size_t off = 0; int rc; va_list args; FILE *fd; if (global_quiet) return; if (stdout_data) fd = stderr; else fd = stdout; if (global_timestamps) { rc = snprintf_timestamp(&msg[off], MSG_LEN - off); HANDLE_RC(rc, MSG_LEN, off, out); } va_start(args, format); rc = vsnprintf(&msg[off], MSG_LEN - off, format, args); va_end(args); out: fprintf(fd, "%s", msg); } /* Return a newly allocated buffer containing the result of the specified * string format arguments */ char *__masprintf(const char *func, const char *file, int line, const char *fmt, ...) { char *str; va_list args; va_start(args, fmt); __util_vasprintf(func, file, line, &str, fmt, args); va_end(args); return str; } /* Set the internal name of the calling thread */ void __set_threadname(const char *func, const char *file, int line, const char *fmt, ...) { char *str; va_list args; va_start(args, fmt); __util_vasprintf(func, file, line, &str, fmt, args); va_end(args); pthread_setspecific(thread_name_key, str); } /* Clear any previously set thread name */ void clear_threadname(void) { void *addr = pthread_getspecific(thread_name_key); if (addr) { pthread_setspecific(thread_name_key, NULL); free(addr); } } /* Remove any number of trailing characters @c in @str */ void chomp(char *str, char *c) { ssize_t i; for (i = strlen(str) - 1; i >= 0 && strchr(c, str[i]); i--) str[i] = 0; } /* Remove any number of leading characters @c in @str */ void lchomp(char *str, char *c) { char *from; for (from = str; *from && strchr(c, *from); from++) ; if (str != from) memmove(str, from, strlen(from) + 1); } /* Perform a stat on file referenced by either @abs or @rel and @dref. Store * results in @stat and return stat()'s return code. */ int stat_file(bool dereference, const char *abs, const char *rel, struct dref *dref, struct stat *st) { int rc; if (dref) { if (dereference) rc = fstatat(dref->dirfd, rel, st, 0); else rc = fstatat(dref->dirfd, rel, st, AT_SYMLINK_NOFOLLOW); } else { if (dereference) rc = stat(abs, st); else rc = lstat(abs, st); } return rc; } /* Fill stat buffer @st with dummy values. */ void set_dummy_stat(struct stat *st) { /* Fake stat */ memset(st, 0, sizeof(struct stat)); st->st_mode = S_IRUSR | S_IWUSR | S_IFREG; st->st_uid = geteuid(); st->st_gid = getegid(); st->st_mtime = time(NULL); } /* Redirect all output streams to @fd and execute command @CMD */ int cmd_child(int fd, char *cmd) { char *argv[] = { "/bin/sh", "-c", NULL, NULL }; char *env[] = { NULL }; argv[2] = cmd; if (dup2(fd, STDOUT_FILENO) == -1 || dup2(fd, STDERR_FILENO) == -1) { mwarn("Could not redirect command output"); return EXIT_RUNTIME; } execve("/bin/sh", argv, env); return EXIT_RUNTIME; } #define PIPE_READ 0 #define PIPE_WRITE 1 /* Run command @cmd as a child process and store its PID in @pid_ptr. On * success, return a file descriptor that is an output pipe to the standard * output and standard error streams of the child process. Return %-1 on * error. */ int cmd_open(char *cmd, pid_t *pid_ptr) { int pfd[2]; pid_t pid; if (pipe(pfd) < 0) return -1; pid = fork(); if (pid < 0) { /* Fork error */ close(pfd[PIPE_READ]); close(pfd[PIPE_WRITE]); return -1; } else if (pid == 0) { /* Child process */ close(pfd[PIPE_READ]); exit(cmd_child(pfd[PIPE_WRITE], cmd)); } /* Parent process */ close(pfd[PIPE_WRITE]); *pid_ptr = pid; return pfd[PIPE_READ]; } /* Close the file descriptor @fd and end the process with PID @pid. When * not %NULL, use @status_ptr to store the resulting process status. */ int cmd_close(int fd, pid_t pid, int *status_ptr) { int status, rc = EXIT_OK; close(fd); kill(pid, SIGQUIT); if (waitpid(pid, &status, 0) == -1) { status = -errno; rc = EXIT_RUNTIME; } if (status_ptr) *status_ptr = status; return rc; } void misc_init(void) { set_timespec(&main_start_ts, 0, 0); pthread_key_create(&thread_name_key, free); set_threadname("main"); } void misc_cleanup(void) { clear_threadname(); pthread_key_delete(thread_name_key); } void set_stdout_data(void) { stdout_data = true; } bool starts_with(const char *str, const char *prefix) { size_t len; len = strlen(prefix); if (strncmp(str, prefix, len) == 0) return true; return false; } bool ends_with(const char *str, const char *suffix) { size_t str_len, s_len; str_len = strlen(str); s_len = strlen(suffix); if (str_len < s_len) return false; if (strcmp(str + str_len - s_len, suffix) != 0) return false; return true; } /* Remove subsequent slashes in @str */ void remove_double_slashes(char *str) { size_t i, to; char last; last = 0; for (i = 0, to = 0; str[i]; i++) { if (last != '/' || str[i] != '/') last = str[to++] = str[i]; } str[to] = 0; } s390-tools-2.3.0/dump2tar/src/strarray.c000066400000000000000000000034751323410431100177110ustar00rootroot00000000000000/* * dump2tar - tool to dump files and command output into a tar archive * * Dynamically growing string arrays * * Copyright IBM Corp. 2016, 2017 * * 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 #include #include #include "misc.h" #include "strarray.h" /* Release resources associated with string array @array */ void free_strarray(struct strarray *array) { unsigned int i; for (i = 0; i < array->num; i++) free(array->str[i]); free(array->str); array->str = NULL; array->num = 0; } /* Add string @str to string array @array */ void add_str_to_strarray(struct strarray *array, const char *str) { array->str = mrealloc(array->str, sizeof(char *) * (array->num + 2)); array->str[array->num + 1] = NULL; array->str[array->num] = mstrdup(str); array->num++; } /* Add string resulting from @fmt and additional arguments to @array */ void add_vstr_to_strarray(struct strarray *array, const char *fmt, ...) { va_list args; char *str; va_start(args, fmt); util_vasprintf(&str, fmt, args); va_end(args); array->str = mrealloc(array->str, sizeof(char *) * (array->num + 2)); array->str[array->num + 1] = NULL; array->str[array->num] = str; array->num++; } /* Add all lines in file at @filename to @array */ int add_file_to_strarray(struct strarray *array, const char *filename) { FILE *fd; char *line = NULL; size_t line_size; int rc = EXIT_OK; fd = fopen(filename, "r"); if (!fd) { mwarn("%s: Cannot open file", filename); return EXIT_RUNTIME; } while (!feof(fd) && !ferror(fd)) { if (getline(&line, &line_size, fd) == -1) continue; chomp(line, "\n"); add_str_to_strarray(array, line); } if (ferror(fd)) rc = EXIT_RUNTIME; free(line); fclose(fd); return rc; } s390-tools-2.3.0/dump2tar/src/tar.c000066400000000000000000000154671323410431100166340ustar00rootroot00000000000000/* * dump2tar - tool to dump files and command output into a tar archive * * TAR file generation * * Copyright IBM Corp. 2016, 2017 * * 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 #include #include "buffer.h" #include "idcache.h" #include "misc.h" #include "tar.h" #define LONGLINK "././@LongLink" #define TYPE_LONGLINK 'K' #define TYPE_LONGNAME 'L' #define BLOCKSIZE 512 /* Basic TAR header */ struct tar_header { char name[100]; char mode[8]; char uid[8]; char gid[8]; char size[12]; char mtime[12]; char chksum[8]; char typeflag; char linkname[100]; char magic[6]; char version[2]; char uname[32]; char gname[32]; char devmajor[8]; char devminor[8]; char prefix[155]; }; /* Store the octal value of @value to at most @len bytes at @dest */ static void set_octal(char *dest, size_t len, unsigned long value) { int i; dest[len - 1] = 0; for (i = len - 2; i >= 0; i--) { dest[i] = '0' + (value & 7); value >>= 3; } } /* Store time @value to at most @len bytes at @dest */ static void set_time(char *dest, size_t len, time_t value) { time_t max = (1ULL << (3 * (len - 1))) - 1; if (value >= 0 && value <= max) { set_octal(dest, len, value); return; } for (; len > 0; len--) { dest[len - 1] = value & 0xff; value >>= 8; } dest[0] |= 0x80; } #define SET_FIELD(obj, name, value) \ set_octal((obj)->name, sizeof((obj)->name), (unsigned long) (value)) #define SET_TIME_FIELD(obj, name, value) \ set_time((obj)->name, sizeof((obj)->name), (time_t) (value)) #define SET_STR_FIELD(obj, name, value) \ strncpy((obj)->name, (value), sizeof((obj)->name)) /* Initialize the tar file @header with the provided data */ static void init_header(struct tar_header *header, const char *filename, const char *link, size_t len, struct stat *stat, char type) { unsigned int i, checksum; unsigned char *c; memset(header, 0, sizeof(*header)); /* Fill in header fields */ SET_STR_FIELD(header, name, filename); if (link) SET_STR_FIELD(header, linkname, link); SET_FIELD(header, size, len); if (stat) { SET_FIELD(header, mode, stat->st_mode & 07777); SET_FIELD(header, uid, stat->st_uid); SET_FIELD(header, gid, stat->st_gid); SET_TIME_FIELD(header, mtime, stat->st_mtime); uid_to_name(stat->st_uid, header->uname, sizeof(header->uname)); gid_to_name(stat->st_gid, header->gname, sizeof(header->gname)); } else { SET_FIELD(header, mode, 0644); SET_FIELD(header, uid, 0); SET_FIELD(header, gid, 0); SET_TIME_FIELD(header, mtime, 0); uid_to_name(0, header->uname, sizeof(header->uname)); gid_to_name(0, header->gname, sizeof(header->gname)); } header->typeflag = type; memcpy(header->magic, "ustar ", sizeof(header->magic)); memcpy(header->version, " ", sizeof(header->version)); /* Calculate checksum */ memset(header->chksum, ' ', sizeof(header->chksum)); checksum = 0; c = (unsigned char *) header; for (i = 0; i < sizeof(*header); i++) checksum += c[i]; snprintf(header->chksum, 7, "%06o", checksum); } /* Emit zero bytes via @emit_cb to pad @len to a multiple of BLOCKSIZE */ static int emit_padding(emit_cb_t emit_cb, void *data, size_t len) { size_t pad = BLOCKSIZE - len % BLOCKSIZE; char zeroes[BLOCKSIZE]; if (len % BLOCKSIZE > 0) { memset(zeroes, 0, BLOCKSIZE); return emit_cb(data, zeroes, pad); } return 0; } /* Emit @len bytes at @addr via @emit_cb and pad data to BLOCKSIZE with zero * bytes */ static int emit_data(emit_cb_t emit_cb, void *data, void *addr, size_t len) { int rc; if (len == 0) return 0; rc = emit_cb(data, addr, len); if (rc) return rc; return emit_padding(emit_cb, data, len); } /* Emit a tar header via @emit_cb */ static int emit_header(emit_cb_t emit_cb, void *data, char *filename, char *link, size_t len, struct stat *stat, char type) { struct tar_header header; size_t namelen = strlen(filename); size_t linklen; int rc; /* /proc can contain unreadable links which causes tar to complain * during extract - use a dummy value to handle this more gracefully */ if (link && !*link) link = " "; linklen = link ? strlen(link) : 0; if (linklen > sizeof(header.linkname)) { rc = emit_header(emit_cb, data, LONGLINK, NULL, linklen + 1, NULL, TYPE_LONGLINK); if (rc) return rc; rc = emit_data(emit_cb, data, link, linklen + 1); if (rc) return rc; } if (namelen > sizeof(header.name)) { rc = emit_header(emit_cb, data, LONGLINK, NULL, namelen + 1, NULL, TYPE_LONGNAME); if (rc) return rc; rc = emit_data(emit_cb, data, filename, namelen + 1); if (rc) return rc; } init_header(&header, filename, link, len, stat, type); return emit_data(emit_cb, data, &header, sizeof(header)); } struct emit_content_cb_data { emit_cb_t emit_cb; void *data; size_t len; int rc; }; /* Callback for emitting a single chunk of data of a buffer */ static int emit_content_cb(void *data, void *addr, size_t len) { struct emit_content_cb_data *cb_data = data; if (len > cb_data->len) len = cb_data->len; cb_data->len -= len; cb_data->rc = cb_data->emit_cb(cb_data->data, addr, len); if (cb_data->rc || cb_data->len == 0) return 1; return 0; } /* Emit at most @len bytes of contents of @buffer via @emit_cb and pad output * to BLOCKSIZE with zero bytes */ static int emit_content(emit_cb_t emit_cb, void *data, struct buffer *buffer, size_t len) { struct emit_content_cb_data cb_data; cb_data.emit_cb = emit_cb; cb_data.data = data; cb_data.len = len; cb_data.rc = 0; buffer_iterate(buffer, emit_content_cb, &cb_data); if (cb_data.rc) return cb_data.rc; return emit_padding(emit_cb, data, buffer->total); } /* Convert file meta data and content specified as @content into a * stream of bytes that is reported via the @emit_cb callback. @data is * passed through to the callback for arbitrary use. */ int tar_emit_file_from_buffer(char *filename, char *link, size_t len, struct stat *stat, char type, struct buffer *content, emit_cb_t emit_cb, void *data) { int rc; DBG("emit tar file=%s type=%d len=%zu", filename, type, len); rc = emit_header(emit_cb, data, filename, link, len, stat, type); if (rc) return rc; if (content) rc = emit_content(emit_cb, data, content, len); return rc; } /* Convert file meta data and content specified as @addr and @len into a * stream of bytes that is reported via the @emit_cb callback. @data is * passed through to the callback for arbitrary use. */ int tar_emit_file_from_data(char *filename, char *link, size_t len, struct stat *stat, char type, void *addr, emit_cb_t emit_cb, void *data) { int rc; DBG("emit tar file=%s type=%d len=%zu", filename, type, len); rc = emit_header(emit_cb, data, filename, link, len, stat, type); if (rc) return rc; if (addr) rc = emit_data(emit_cb, data, addr, len); return rc; } s390-tools-2.3.0/etc/000077500000000000000000000000001323410431100141135ustar00rootroot00000000000000s390-tools-2.3.0/etc/Makefile000066400000000000000000000005651323410431100155610ustar00rootroot00000000000000include ../common.mak CONFIG_FILES = ifneq (${SYSTEMDSYSTEMUNITDIR},) CONFIG_FILES += cpuplugd.conf sysconfig/cpi sysconfig/dumpconf \ sysconfig/mon_fsstatd sysconfig/mon_procd endif install: for file in $(CONFIG_FILES); do \ $(INSTALL) -g $(GROUP) -o $(OWNER) \ -m 644 $$file $(DESTDIR)$(SYSCONFDIR)/$$file ; \ done .PHONY: all install clean s390-tools-2.3.0/etc/cpuplugd.conf000066400000000000000000000126711323410431100166140ustar00rootroot00000000000000# # Exemplary configuration file for the Linux on System z CPU and memory hotplug # daemon # # The file is evaluated by cpuplugd at startup when called with -c. # It does not contain shell environment variables. ## Type: integer ## Default: 1 # # The minimum number of CPUs must be > 0. # CPU_MIN="1" ## Type: integer ## Default: 0 # # The maximum number of CPUs to be enabled. If 0 is specified here, # the maximum number of CPUs equals the number of CPUs detected. # CPU_MAX="0" ## Type: integer ## Default: 5 # # The update interval described how often the current system state # is checked against the configured set of hotplug and hotunplug rules. # The update interval is defined in seconds. # UPDATE="1" ## Type: integer ## Default: 0 # # The minimum size of the static page pool (in 4K pages) # CMM_MIN="0" ## Type: integer ## Default: 131072 # # The maximum size of the static page pool (in 4K pages) # # Recommended setting is system size minus 256 MB CMM_MAX="131072" # 512 MB # # Variables # # User-defined variables are case-sensitive and must not match a pre-defined # variable or keyword. In the configuration file, definitions # for user-defined variables must precede their use in expressions. # Variable names consist of alphanumeric characters (a-z,A-Z,0-9) and # the "_" character. The maximum name length for a variable is 128 characters, # and the maximum total size for user-defined variables (names + values) is 4096 # characters. # pgscan_d="vmstat.pgscan_direct_dma[0] + vmstat.pgscan_direct_normal[0] + vmstat.pgscan_direct_movable[0]" pgscan_d1="vmstat.pgscan_direct_dma[1] + vmstat.pgscan_direct_normal[1] + vmstat.pgscan_direct_movable[1]" # page scan rate in pages / timer tick pgscanrate="(pgscan_d - pgscan_d1) / (cpustat.total_ticks[0] - cpustat.total_ticks[1])" # cache usage in kilobytes avail_cache="meminfo.Cached - meminfo.Shmem" user_0="(cpustat.user[0] - cpustat.user[1])" nice_0="(cpustat.nice[0] - cpustat.nice[1])" system_0="(cpustat.system[0] - cpustat.system[1])" user_2="(cpustat.user[2] - cpustat.user[3])" nice_2="(cpustat.nice[2] - cpustat.nice[3])" system_2="(cpustat.system[2] - cpustat.system[3])" CP_Active0="(user_0 + nice_0 + system_0) / (cpustat.total_ticks[0] - cpustat.total_ticks[1])" CP_Active2="(user_2 + nice_2 + system_2) / (cpustat.total_ticks[2] - cpustat.total_ticks[3])" CP_ActiveAVG="(CP_Active0+CP_Active2) / 2" idle_0="(cpustat.idle[0] - cpustat.idle[1])" iowait_0="(cpustat.iowait[0] - cpustat.iowait[1])" idle_2="(cpustat.idle[2] - cpustat.idle[3])" iowait_2="(cpustat.iowait[2] - cpustat.iowait[3])" CP_idle0="(idle_0 + iowait_0) / (cpustat.total_ticks[0] - cpustat.total_ticks[1])" CP_idle2="(idle_2 + iowait_2) / (cpustat.total_ticks[2] - cpustat.total_ticks[3])" CP_idleAVG="(CP_idle0 + CP_idle2) / 2" ## Type: string ## Default: "(meminfo.MemFree + cache) / 40" # # The amount of pages the static page pool # is increased if a memunplug rule is matched. # # cmm_inc: 10% of free memory, in 4K pages CMM_INC="meminfo.MemFree / 40" ## Type: string ## Default: "meminfo.MemTotal / 40" # # The amount of pages the static page pool # is decreased if a memplug rule is matched. # # cmm_dec: 10% of total memory, in 4K pages CMM_DEC="meminfo.MemTotal / 40" # # Ruledefinitions # # Four kinds of rules are distinguished # (1) hotplug rules, used to enable CPUs # (2) hotunplug rules, to disable CPUs # (3) memplug rules, used to increase memory (decrease cmm balloon) # (4) memunplug rules, used to decrease memory (increase cmm balloon) # # Within the hotplug/hotunplug rule definitions the following variables # can be used: # - loadavg: the current loadaverage # - onumcpus: the current number of CPUs which are online # - runnable_proc: the current amount of runnable processes # - user: the current user percentage # - nice: the current nice percentage # - system: the current system percentage # - idle: the current idle percentage # - iowait: the current iowait percentage # - irq: the current irq percentage # - softirq: the current softirq percentage # - steal: the current steal percentage # - guest: the current guest percentage # - guest_nice: the current guest_nice percentage # - cpustat.: data from /proc/stat and /proc/loadavg # - time: floating point timestamp in "seconds.microseconds" # since the Unix Epoch (1970-01-01 00:00:00 +0000 (UTC)) # ## Type: string ## Default: "(loadavg > onumcpus + 0.75) & (idle < 10.0)" # HOTPLUG="((1 - CP_ActiveAVG) * onumcpus) < 0.08" ## Type: string ## Default: "(loadavg < onumcpus - 0.25) | (idle > 50)" # HOTUNPLUG="(CP_idleAVG * onumcpus) > 1.15" # # Memplug and memunplug can contain the following keywords: # - apcr: the amount of page cache operations, # i.e. pgpin + pgpout from /proc/vmstat # (in 512 byte blocks / second) # - freemem: the amount of free memory (in megabytes) # - swaprate: the number of swap operations, i.e. pswpin + pswpout # from /proc/vmstat (in pages / second) # - meminfo.: any value from /proc/meminfo # - vmstat.: any value from /proc/vmstat # - time: floating point timestamp in "seconds.microseconds" # since the Unix Epoch (1970-01-01 00:00:00 +0000 (UTC)) # # This function is disabled by default, because this rule has to be # adjusted for each production system, depending on the environment. # ## Type: string ## Default: "0" # #MEMPLUG="pgscanrate > 20" MEMPLUG="0" ## Type: string ## Default: "0" # #MEMUNPLUG="(meminfo.MemFree + avail_cache) > (meminfo.MemTotal / 10)" MEMUNPLUG="0" s390-tools-2.3.0/etc/init.d/000077500000000000000000000000001323410431100153005ustar00rootroot00000000000000s390-tools-2.3.0/etc/init.d/cpacfstatsd000077500000000000000000000025331323410431100175300ustar00rootroot00000000000000#!/bin/bash ### BEGIN INIT INFO # Provides: cpacfstatsd # Required-Start: $local_fs # Required-Stop: $local_fs # Should-Start: # Should-Stop: # Default-Start: 2 3 5 # Default-Stop: 0 1 6 # Short-Description: Start the cpacfstatsd daemon for Linux on System z # Description: CPACF statistics collection daemon process for Linux on System z ### END INIT INFO DAEMON=cpacfstatsd DAEMON_PATH=/usr/sbin/cpacfstatsd RUN_PID_FILE=/var/run/cpacfstatsd.pid RETVAL=0 OPTIONS="" # source function library . /lib/lsb/init-functions start() { if [ ! -f $RUN_PID_FILE ]; then echo -n $"Starting $DAEMON:" $DAEMON_PATH $OPTIONS if [ $? == "0" ]; then log_success_msg else log_failure_msg fi echo else echo "$DAEMON (pid $(cat $RUN_PID_FILE)) is already running..." echo fi } stop() { echo -n $"Stopping $DAEMON:" if [ -f $RUN_PID_FILE ]; then killproc $DAEMON_PATH -TERM log_success_msg rm -f $RUN_PID_FILE else log_failure_msg fi echo } restart() { stop sleep 1 start } status() { if [ ! -f $RUN_PID_FILE ]; then echo "$DAEMON is not running." echo else echo "$DAEMON (pid $(cat $RUN_PID_FILE)) is running." echo fi } # How are we called? case "$1" in start) start ;; stop) stop ;; status) status ;; restart) restart ;; *) echo "Usage: $DAEMON {start|stop|status|restart}" RETVAL=1 esac exit $RETVAL s390-tools-2.3.0/etc/init.d/cpuplugd000077500000000000000000000041151323410431100170520ustar00rootroot00000000000000#!/bin/bash ### BEGIN INIT INFO # Provides: cpuplugd # Required-Start: $local_fs $remote_fs # Required-Stop: $local_fs $remote_fs # Should-Start: # Should-Stop: # Default-Start: 2 3 5 # Default-Stop: 0 1 6 # Short-Description: Start the cpu hotplug daemon for Linux on System z # Description: Starts the cpuplugd. It uses the configuration # file /etc/cpuplugd.conf ### END INIT INFO # chkconfig: 235 01 99 DAEMON=cpuplugd DAEMON_PATH=/usr/sbin/cpuplugd CONFIG_FILE=/etc/cpuplugd.conf RUN_PID_FILE=/var/run/cpuplugd.pid RETVAL=0 OPTIONS="-c $CONFIG_FILE" # source function library . /lib/lsb/init-functions start() { if [ ! -f $RUN_PID_FILE ]; then echo -n $"Starting $DAEMON:" $DAEMON_PATH $OPTIONS if [ $? == "0" ]; then log_success_msg else log_failure_msg fi echo else echo "$DAEMON (pid $(cat $RUN_PID_FILE)) is already running..." echo fi } stop() { echo -n $"Stopping $DAEMON:" if [ -f $RUN_PID_FILE ]; then killproc $DAEMON_PATH -TERM log_success_msg rm -f $RUN_PID_FILE else log_failure_msg fi echo } restart() { stop # # We have to wait 2-3 seconds here. When the daemon is stopped it takes # the time we sleep to reactivate cpus. If we restart to fast and # cpuplugd wasn't able to restore some settings we may get a undesired # online cpu count after cpuplugd shutdown # sleep 4 start } status() { if [ ! -f $RUN_PID_FILE ]; then echo "$DAEMON is not running." echo else echo "$DAEMON (pid $(cat $RUN_PID_FILE), options: $OPTIONS) is running." echo fi } reload() { echo -n $"Reloading $DAEMON: " if [ -f $RUN_PID_FILE ]; then killproc $DAEMON_PATH -HUP log_success_msg else log_failure_msg fi RETVAL=$? echo } # How are we called? case "$1" in start) start ;; stop) stop ;; status) status ;; restart) restart ;; reload|force-reload) reload ;; *) echo "Usage: $DAEMON {start|stop|status|restart|reload}" RETVAL=1 esac exit $RETVAL s390-tools-2.3.0/etc/init.d/dumpconf000077500000000000000000000270031323410431100170430ustar00rootroot00000000000000#!/bin/bash ### BEGIN INIT INFO # Provides: dumpconf # Required-Start: $local_fs # Required-Stop: $local_fs # Should-Start: # Should-Stop: # Default-Start: 0 1 2 3 5 6 # Default-Stop: # Short-Description: Configure s390 dump feature # Description: Configures the s390 dump feature. It uses the configuration file # /etc/sysconfig/dumpconf # X-Systemd-RemainAfterExit: true ### END INIT INFO # chkconfig: 012356 01 99 # This script can be either used stand-alone or as System V init script. DUMPCONF_BIN=/etc/init.d/dumpconf DUMP_CONFIG_FILE=/etc/sysconfig/dumpconf CMDFULL=$0 CMD="dumpconf" LOCKFILE=/var/lock/$CMD PIDFILE=/var/run/$CMD.pid ERRMSG="Check $DUMP_CONFIG_FILE!" RETVAL=0 BACKGROUND=0 pr_info() { if [ $BACKGROUND -eq 0 ]; then echo "$@" else echo "$@" | logger -t dumpconf fi } pr_error() { if [ $BACKGROUND -eq 0 ]; then echo "$@" >&2 else echo "$@" | logger -t dumpconf fi } check_environment() { if [ ! -f $DUMP_CONFIG_FILE ]; then pr_error "no config file found: $DUMP_CONFIG_FILE" exit 1 fi if [ "$(cat /proc/filesystems|grep sysfs)" = "" ]; then pr_error "no sysfs found" exit 1 fi SYSFSDIR=$(cat /proc/mounts|awk '$3=="sysfs"{print $2; exit}') if [ "$SYSFSDIR" = "" ]; then pr_error "sysfs not mounted" exit 1 fi DUMP_CONFIG_DIR=/$SYSFSDIR/firmware/dump ON_PANIC_CONFIG_FILE=/$SYSFSDIR/firmware/shutdown_act\ ions/on_panic ON_RESTART_CONFIG_FILE=/$SYSFSDIR/firmware/shutdown_act\ ions/on_restart if [ ! -d $DUMP_CONFIG_DIR ]; then pr_info "kernel has no dump on panic support" exit 0 fi REIPL_CONFIG_DIR=/$SYSFSDIR/firmware/reipl if [ ! -d $REIPL_CONFIG_DIR ]; then pr_info "kernel has no dump on panic support" exit 0 fi VMCMD_CONFIG_DIR=/$SYSFSDIR/firmware/vmcmd . $DUMP_CONFIG_FILE } printhelp() { cat </dev/null exit 0 } delay_activation() { # Open lock file with file descriptor 123 exec 123>$LOCKFILE if flock -n -x 123; then if [ -f $PIDFILE ]; then # concurrent process was faster exit 0 fi trap handle_stop_request TERM echo $$ > $PIDFILE else # Nothing to do, "dumpconf start" is already in progress exit 0 fi # Close file descriptor 123 exec 123>&- # Do multiple sleeps in order to be interruptible for ((i=0; i < $DELAY_MINUTES * 60; i++)); do sleep 1 done rm -f $PIDFILE } # $1: dump device bus id (e.g. 0.0.4711) verify_ccw_dump_device() { line=$(lsdasd -c $1) if [ $? -ne 0 ]; then line=$(lsdasd $1) fi if [ "$line" == "" ]; then pr_info "WARNING: device $1 not found!" return 1 fi found=false for i in $line do if [ $found == true ]; then break fi if [ "$i" == "is" ]; then found=true fi done zgetdump -d /dev/$i > /dev/null 2>&1 if [ $? == 0 ]; then return 0 else pr_info "WARNING: $1 is no valid dump device!" return 1 fi } #------------------------------------------------------------------------------ # Helper function to check a device string. #------------------------------------------------------------------------------ function CheckDeviceString() { local X X=$( echo "$1" | awk --posix -F. ' function PrintBusID(css, grp, devno) { while(length(devno) < 4) devno = "0" devno print css "." grp "." devno } NF == 1 && $1 ~ /^[0-9a-fA-F]{1,4}$/ { PrintBusID("0","0", $1) next } NF != 3 || $1 !~ /^[0-9a-fA-F]{1,2}$/ { next } $2 !~ /^[0-9a-fA-F]{1,2}$/ { next } $3 !~ /^[0-9a-fA-F]{1,4}$/ { next } { PrintBusID($1, $2, $3) } ' ) if [ "$X" != "" ]; then echo $X return 0 fi } setup_device() { DEV="$(CheckDeviceString $DEVICE)" if [ "$DEV" != "" ]; then echo $DEV > $1/$2/device else RETVAL=1 pr_error "ERROR: Invalid DEVICE '$DEVICE'." $ERRMSG return fi if [ $2 == "fcp" ]; then echo $WWPN > $1/fcp/wwpn 2>/dev/null || RETVAL=1 if [ $RETVAL -eq 1 ]; then pr_error "ERROR: Invalid WWPN '$WWPN'." $ERRMSG return fi echo $LUN > $1/fcp/lun 2>/dev/null || RETVAL=1 if [ $RETVAL -eq 1 ]; then pr_error "ERROR: Invalid LUN '$LUN'." $ERRMSG return fi echo $BOOTPROG > $1/fcp/bootprog 2>/dev/null || RETVAL=1 if [ $RETVAL -eq 1 ]; then pr_error "ERROR: Invalid BOOTPROG '$BOOTPROG'." $ERRMSG return fi echo $BR_LBA > $1/fcp/br_lba 2>/dev/null || RETVAL=1 if [ $RETVAL -eq 1 ]; then pr_error "ERROR: Invalid BR_LBA '$BR_LBA'." $ERRMSG return fi fi } setup_nss_device() { echo $NSS_NAME > $1/nss/name || RETVAL=1 } setup_reipl() { if [ "$REIPL_TYPE" == "" ]; then pr_info "reipl on panic configured: Using default reipl values." return fi if [ "$REIPL_TYPE" == "ccw" ] || [ "$REIPL_TYPE" == "fcp" ]; then setup_device $REIPL_CONFIG_DIR $REIPL_TYPE elif [ "$REIPL_TYPE" == "nss" ]; then setup_nss_device $REIPL_CONFIG_DIR else pr_error "ERROR: Unknown reipl type '$REIPL_TYPE'." $ERRMSG RETVAL=1 return fi echo $REIPL_TYPE > $REIPL_CONFIG_DIR/reipl_type || RETVAL=1 if [ $RETVAL -eq 1 ]; then return fi pr_info "$REIPL_TYPE reipl device configured." } setup_dump() { if [ "$DUMP_TYPE" == "ccw" ] || [ "$DUMP_TYPE" == "fcp" ]; then setup_device $DUMP_CONFIG_DIR $DUMP_TYPE elif [ "$DUMP_TYPE" != "none" ]; then pr_error "ERROR: Unknown dump type '$DUMP_TYPE'." $ERRMSG RETVAL=1 return fi echo $DUMP_TYPE > $DUMP_CONFIG_DIR/dump_type || RETVAL=1 if [ $RETVAL -eq 1 ]; then echo none > $DUMP_CONFIG_DIR/dump_type return fi pr_info "$ON_PANIC on panic configured: Using $DUMP_TYPE dump device." } setup_on_panic_vmcmd() { for I in "$VMCMD_1" "$VMCMD_2" "$VMCMD_3" "$VMCMD_4" "$VMCMD_5" "$VMCMD_6" "$VMCMD_7" "$VMCMD_8"; do if [ "$I" != "" ]; then if [ "$VMCMD" != "" ]; then VMCMD="$VMCMD\\n$I" else VMCMD=$I fi fi done if [ ! -d $VMCMD_CONFIG_DIR ]; then pr_error "ERROR: No vmcmd support. Are you running on LPAR?" RETVAL=1 elif [ "$VMCMD" == "" ]; then pr_error "ERROR: No VMCMD_x keyword specified." $ERRMSG RETVAL=1 else echo -en "$VMCMD" | cat > $VMCMD_CONFIG_DIR/on_panic || RETVAL=1 fi if [ $RETVAL -eq 0 ]; then pr_info "vmcmd on panic configured:" pr_info -e "$VMCMD" fi } print_fcp_device() { DEVICE=$(cat $1/fcp/device) || RETVAL=1 pr_info "device..: $DEVICE" WWPN=$(cat $1/fcp/wwpn) || RETVAL=1 pr_info "wwpn....: $WWPN" LUN=$(cat $1/fcp/lun) || RETVAL=1 pr_info "lun.....: $LUN" BOOTPROG=$(cat $1/fcp/bootprog) || RETVAL=1 pr_info "bootprog: $BOOTPROG" BR_LBA=$(cat $1/fcp/br_lba) || RETVAL=1 pr_info "br_lba..: $BR_LBA" } print_ccw_device() { DEVICE=$(cat $1/ccw/device) || RETVAL=1 pr_info "device..: $DEVICE" } print_nss_name() { NAME=$(cat $1/nss/device) || RETVAL=1 pr_info "device..: $NAME" } status_dump() { CONF_DUMP_TYPE=$(cat $DUMP_CONFIG_DIR/dump_type) || RETVAL=1 if [ "$CONF_DUMP_TYPE" == "none" ]; then pr_info "type....: no dump device configured" elif [ "$CONF_DUMP_TYPE" == "ccw" ]; then pr_info "type....: ccw" print_ccw_device $DUMP_CONFIG_DIR verify_ccw_dump_device $(cat $DUMP_CONFIG_DIR/ccw/device) elif [ "$CONF_DUMP_TYPE" == "fcp" ]; then pr_info "type....: fcp" print_fcp_device $DUMP_CONFIG_DIR else pr_error "ERROR: Unknown dump device type '$CONF_DUMP_TYPE'!" pr_error " Please check if you have the latest dumpconf package!" fi } status_reipl() { REIPL_TYPE=$(cat $REIPL_CONFIG_DIR/reipl_type) || RETVAL=1 pr_info "type....: $REIPL_TYPE" if [ "$REIPL_TYPE" == "ccw" ]; then print_ccw_device $REIPL_CONFIG_DIR elif [ "$REIPL_TYPE" == "fcp" ]; then print_fcp_device $REIPL_CONFIG_DIR elif [ "$REIPL_TYPE" == "nss" ]; then print_nss_name $REIPL_CONFIG_DIR else pr_error "ERROR: Unknown reipl device type '$REIPL_TYPE'!" pr_error " Please check if you have the latest dumpconf package!" fi } status_dump_reipl() { pr_info -e "\ndump:" status_dump pr_info -e "\nreipl:" status_reipl } status_vmcmd() { VMCMD=$(cat $VMCMD_CONFIG_DIR/on_panic) || RETVAL=1 if [ "$VMCMD" == "" ]; then pr_info "WARNING: No VM command specified!" else pr_info "---------------" pr_info "$VMCMD" fi } start() { if [ "$1" == "background" ]; then BACKGROUND=1 fi test -n "$DELAY_MINUTES" || DELAY_MINUTES=0 test "$DELAY_MINUTES" -ge 0 2>/dev/null || RETVAL=1 if [ $RETVAL -eq 1 ]; then pr_error "ERROR: Invalid DELAY_MINUTES parameter" \ "'$DELAY_MINUTES'." $ERRMSG return fi if [ "$ON_PANIC" != "stop" -a $DELAY_MINUTES -gt 0 ]; then if [ -f $PIDFILE ]; then pr_info "A delayed instance of" $CMD \ "is already active." return fi if [ $BACKGROUND -eq 1 ]; then delay_activation else pr_info "The activation of dumpconf is being delayed" \ "for" $DELAY_MINUTES "minutes" $CMDFULL start background > /dev/null 2>&1 & return fi fi if [ "$ON_PANIC" == "" ]; then ON_PANIC="$(cat $ON_PANIC_CONFIG_FILE)" fi case "$ON_PANIC" in reipl) setup_reipl ;; dump|dump_reipl) setup_dump ;; vmcmd) setup_on_panic_vmcmd ;; stop) pr_info "stop on panic configured." ;; *) pr_error "ERROR: Unknown 'on panic'" \ "type '$ON_PANIC'." $ERRMSG RETVAL=1 ;; esac if [ $RETVAL -eq 1 ]; then return fi if [ -f $ON_RESTART_CONFIG_FILE ]; then echo $ON_PANIC > $ON_RESTART_CONFIG_FILE 2> /dev/null || RETVAL=1 fi echo $ON_PANIC > $ON_PANIC_CONFIG_FILE 2> /dev/null || RETVAL=1 # check for errors if [ $RETVAL -eq 1 ]; then echo stop > $ON_PANIC_CONFIG_FILE pr_error "ERROR: $ON_PANIC not supported by hardware!" fi } stop() { if [ -f $PIDFILE ]; then PID=$(cat $PIDFILE) kill -TERM $PID 2> /dev/null rm -f $PIDFILE fi echo none > $DUMP_CONFIG_DIR/dump_type || RETVAL=1 if [ -f $ON_RESTART_CONFIG_FILE ]; then echo stop > $ON_RESTART_CONFIG_FILE 2> /dev/null || RETVAL=1 fi echo stop > $ON_PANIC_CONFIG_FILE || RETVAL=1 if [ $RETVAL -eq 0 ]; then pr_info "Dump on panic is disabled now" else pr_error "Disabling dump on panic failed" fi return $RETVAL } status() { ON_PANIC=$(cat $ON_PANIC_CONFIG_FILE) || RETVAL=1 if [ -f $PIDFILE ]; then pr_info "on_panic: $ON_PANIC - dumpconf activation is being" \ "delayed for $DELAY_MINUTES minutes" else pr_info "on_panic: $ON_PANIC" fi case "$ON_PANIC" in vmcmd) status_vmcmd ;; reipl) status_reipl ;; dump) status_dump ;; dump_reipl) status_dump_reipl ;; stop) ;; *) pr_error "ERROR: Unknown on_panic type '$ON_PANIC'" ;; esac } case "$1" in -h|--help) printhelp exit 0 ;; -v|--version) printversion exit 0 ;; esac check_environment # If system crashed, an invalid $PIDFILE might still exist if [ -f $PIDFILE ]; then cleanup_pidfile $(cat $PIDFILE) fi # See how we were called. case "$1" in restart|reload|force-reload|try-restart) stop DELAY_MINUTES=0 start ;; start) start $2 ;; stop) stop ;; status) status ;; *) print_invalid_option $1 RETVAL=1 ;; esac exit $RETVAL s390-tools-2.3.0/etc/init.d/mon_statd000077500000000000000000000056031323410431100172220ustar00rootroot00000000000000#!/bin/bash ### BEGIN INIT INFO # Provides: mon_statd # Required-Start: $local_fs $remote_fs # Required-Stop: $local_fs $remote_fs # Should-Start: # Should-Stop: # Default-Start: 2 3 5 # Default-Stop: 0 1 6 # Short-Description: Configure the mon_fsstatd and mon_procd daemons. # Description: Configures the mon_fsstatd and mon_procd daemons. It uses the # configuration file /etc/sysconfig/mon_statd. ### END INIT INFO # chkconfig: 235 01 99 DAEMON=mon_statd FSSTATD=mon_fsstatd PROCD=mon_procd FSSTATD_PATH=/usr/sbin/$FSSTATD PROCD_PATH=/usr/sbin/$PROCD CONFIG_FILE=/etc/sysconfig/$DAEMON FSSTATD_PID_FILE=/var/run/$FSSTATD.pid PROCD_PID_FILE=/var/run/$PROCD.pid # source function library . /lib/lsb/init-functions # Source config file if [ -f $CONFIG_FILE ]; then . $CONFIG_FILE fi UDEVSETTLE=/sbin/udevadm if [ ! -e $UDEVSETTLE ] then UDEVSETTLE=/sbin/udevsettle UDEVSETTLE_CALL="$UDEVSETTLE --timeout=10" else UDEVSETTLE_CALL="$UDEVSETTLE settle --timeout=10" fi load_kernel_module() { if [ ! -e /dev/monwriter ]; then echo "Loading monwriter module..." modprobe monwriter 2>&1 if [ $? -ne 0 ]; then exit 1 fi if [ -e $UDEVSETTLE ] then $UDEVSETTLE_CALL fi fi } start_daemon() { local daemon_name=$1 local daemon_interval=$2 local daemon_pid_file=$3 local daemon_path=$4 if [ ! -f $daemon_pid_file ]; then load_kernel_module echo -n "Starting $daemon_name:" $daemon_path -i $daemon_interval if [ $? -eq 0 ]; then log_success_msg else log_failure_msg fi else echo "$daemon_name (pid $(cat $daemon_pid_file)) is already running..." fi } start() { if [ "$FSSTAT" = "yes" ]; then start_daemon $FSSTATD $FSSTAT_INTERVAL $FSSTATD_PID_FILE \ $FSSTATD_PATH fi if [ "$PROC" = "yes" ]; then start_daemon $PROCD $PROC_INTERVAL $PROCD_PID_FILE \ $PROCD_PATH fi } stop_daemon() { local daemon_name=$1 local daemon_pid_file=$2 local daemon_path=$3 echo -n "Stopping $daemon_name:" if [ -f $daemon_pid_file ]; then killproc $daemon_path -TERM log_success_msg rm -f $daemon_pid_file else log_failure_msg fi } stop() { if [ "$FSSTAT" = "yes" ]; then stop_daemon $FSSTATD $FSSTATD_PID_FILE $FSSTATD_PATH fi if [ "$PROC" = "yes" ]; then stop_daemon $PROCD $PROCD_PID_FILE $PROCD_PATH fi } restart() { stop start } status_daemon() { local daemon_name=$1 local daemon_pid_file=$2 local daemon_interval=$3 if [ ! -f $daemon_pid_file ]; then echo "$daemon_name is not running." else echo "$daemon_name (pid $(cat $daemon_pid_file), interval: $daemon_interval) is running." fi } status() { status_daemon $FSSTATD $FSSTATD_PID_FILE $FSSTAT_INTERVAL status_daemon $PROCD $PROCD_PID_FILE $PROC_INTERVAL } # How are we called? case "$1" in start) start ;; stop) stop ;; status) status ;; restart|reload|force-reload) restart ;; *) echo "Usage: $DAEMON {start|stop|status|restart|reload}" exit 1 esac exit 0 s390-tools-2.3.0/etc/sysconfig/000077500000000000000000000000001323410431100161175ustar00rootroot00000000000000s390-tools-2.3.0/etc/sysconfig/cpi000066400000000000000000000004661323410431100166230ustar00rootroot00000000000000# # Apply control program identification (CPI) settings # # The system and sysplex names consist of up to eight characters of # the following set: A-Z, 0-9, $, @, #, and blank. # # CPI system type # CPI_SYSTEM_TYPE="LINUX" # # CPI system name # CPI_SYSTEM_NAME="" # # CPI sysplex name # CPI_SYSPLEX_NAME="" s390-tools-2.3.0/etc/sysconfig/dumpconf000066400000000000000000000030351323410431100176560ustar00rootroot00000000000000# # s390 dump config # # Configures the actions which should be performed after a kernel panic # and on PSW restart. # # The following actions are supported: # # * stop: Stop Linux (default) # * dump: Dump Linux with stand-alone dump tool # * vmcmd: Issue z/VM CP commands # * reipl: Re-IPL Linux using setting under /sys/firmware/reipl # * dump_reipl: First dump Linux with stand-alone dump tool, then re-IPL Linux # using setting under /sys/firmware/reipl # # For the actions "reipl" and "dump_reipl" the DELAY_MINUTES keyword may # be used to delay the activation of dumpconf. # Thus potential reipl loops caused by kernel panics # which persistently occur early in the boot process can be prevented. # Dump on CCW device (DASD) and re-IPL after dump is complete. # The re-IPL device, as specified under "/sys/firmware/reipl", is used. # The activation of dumpconf is delayed by 5 minutes. # # ON_PANIC=dump_reipl # DUMP_TYPE=ccw # DEVICE=0.0.4e13 # DELAY_MINUTES=5 # # Dump on fcp device (SCSI Disk) # # ON_PANIC=dump # DUMP_TYPE=fcp # DEVICE=0.0.4711 # WWPN=0x5005076303004711 # LUN=0x4711000000000000 # BOOTPROG=0 # BR_LBA=0 # # Use VMDUMP # # ON_PANIC=vmcmd # VMCMD_1="MESSAGE * Starting VMDUMP" # VMCMD_2="VMDUMP" # VMCMD_3="IPL 4711" # # Stop Linux (default) # # ON_PANIC=stop # # Re-IPL Linux # The re-IPL device, as specified under "/sys/firmware/reipl", is used. # Since the DELAY_MINUTES keyword is omitted, there is no delay and # dumpconf becomes active immediately during system startup. # # ON_PANIC=reipl s390-tools-2.3.0/etc/sysconfig/mon_fsstatd000066400000000000000000000003641323410431100203660ustar00rootroot00000000000000## Path: System/Monitoring/s390-tools/mon_fsstatd ## Description: Linux - z/VM Monitor Daemon for process and filesystem data. ## Type: integer(1:) ## Default: 60 # # Monitor interval in seconds for filesystem monitoring. # FSSTAT_INTERVAL=60 s390-tools-2.3.0/etc/sysconfig/mon_procd000066400000000000000000000003551323410431100200250ustar00rootroot00000000000000## Path: System/Monitoring/s390-tools/mon_procd ## Description: Linux - z/VM Monitor Daemon for process and filesystem data. ## Type: integer(1:) ## Default: 60 # # Monitor interval in seconds for process monitoring. # PROC_INTERVAL=60 s390-tools-2.3.0/etc/sysconfig/mon_statd000066400000000000000000000010711323410431100200310ustar00rootroot00000000000000## Path: System/Monitoring/s390-tools/mon_statd ## Description: Linux - z/VM Monitor Daemon for process and filesystem data. ## Type: integer(1:) ## Default: 60 # # Monitor interval in seconds for filesystem monitoring. # FSSTAT_INTERVAL=60 ## Type: integer(1:) ## Default: 60 # # Monitor interval in seconds for process monitoring. # PROC_INTERVAL=60 ## Type: yesno ## Default: no # # Set this to "yes" if you want to enable filesystem monitoring. FSSTAT="no" ## Type: yesno ## Default: no # # Set this to "yes" if you want to enable process monitoring. PROC="no" s390-tools-2.3.0/etc/udev/000077500000000000000000000000001323410431100150565ustar00rootroot00000000000000s390-tools-2.3.0/etc/udev/rules.d/000077500000000000000000000000001323410431100164325ustar00rootroot00000000000000s390-tools-2.3.0/etc/udev/rules.d/40-z90crypt.rules000066400000000000000000000002211323410431100214260ustar00rootroot00000000000000# # Rule for z90crypt character device node permissions # This file should be installed in /etc/udev/rules.d # KERNEL=="z90crypt", MODE="0666" s390-tools-2.3.0/etc/udev/rules.d/57-osasnmpd.rules000066400000000000000000000001231323410431100215570ustar00rootroot00000000000000SUBSYSTEM=="ccwgroup", ACTION=="change", RUN+="/usr/bin/killall -SIGUSR1 osasnmpd" s390-tools-2.3.0/etc/udev/rules.d/59-dasd.rules000066400000000000000000000025311323410431100206550ustar00rootroot00000000000000# # Rules for unique DASD device nodes created in /dev/disk/ # This file should be installed in /etc/udev/rules.d # SUBSYSTEM!="block", GOTO="dasd_symlinks_end" KERNEL!="dasd*", GOTO="dasd_symlinks_end" ACTION!="change", GOTO="dasd_block_end" # by-id (hardware serial number) KERNEL=="dasd*[!0-9]", ATTRS{status}=="online", IMPORT{program}="/sbin/dasdinfo -a -e -b $kernel" KERNEL=="dasd*[!0-9]", ENV{ID_SERIAL}=="?*", SYMLINK+="disk/by-id/$env{ID_BUS}-$env{ID_SERIAL}" KERNEL=="dasd*[!0-9]", ENV{ID_UID}=="?*", SYMLINK+="disk/by-id/$env{ID_BUS}-$env{ID_UID}" KERNEL=="dasd*[!0-9]", ENV{ID_XUID}=="?*", SYMLINK+="disk/by-id/$env{ID_BUS}-$env{ID_XUID}" LABEL="dasd_block_end" ACTION!="change|add", GOTO="dasd_symlinks_end" # for partitions import parent information KERNEL=="dasd*[0-9]", IMPORT{parent}=="ID_*" KERNEL=="dasd*[0-9]", ENV{ID_SERIAL}=="?*", SYMLINK+="disk/by-id/$env{ID_BUS}-$env{ID_SERIAL}-part%n" KERNEL=="dasd*[0-9]", ENV{ID_UID}=="?*", SYMLINK+="disk/by-id/$env{ID_BUS}-$env{ID_UID}-part%n" KERNEL=="dasd*[0-9]", ENV{ID_XUID}=="?*", SYMLINK+="disk/by-id/$env{ID_BUS}-$env{ID_XUID}-part%n" LABEL="dasd_symlinks_end" # on device add set request queue scheduler to deadline SUBSYSTEM!="block", GOTO="sched_end" ACTION!="change", GOTO="sched_end" KERNEL=="dasd*[!0-9]", TEST=="queue/scheduler", ATTR{queue/scheduler}="deadline" LABEL="sched_end" s390-tools-2.3.0/etc/udev/rules.d/60-readahead.rules000066400000000000000000000010641323410431100216300ustar00rootroot00000000000000# # Rules to set an increased default max readahead size # This file should be installed in /etc/udev/rules.d # SUBSYSTEM!="block", GOTO="ra_end" ACTION!="add", GOTO="ra_change" # on device add set initial readahead to 512 (instead of in kernel 128) KERNEL=="sd*[!0-9]", WAIT_FOR="queue/read_ahead_kb", ATTR{queue/read_ahead_kb}="512" LABEL="ra_change" ACTION!="change", GOTO="ra_end" # on device change set initial readahead to 512 (instead of in kernel 128) KERNEL=="dasd*[!0-9]", TEST=="queue/read_ahead_kb", ATTR{queue/read_ahead_kb}="512" LABEL="ra_end" s390-tools-2.3.0/etc/udev/rules.d/90-cpi.rules000066400000000000000000000003251323410431100205070ustar00rootroot00000000000000# # Rules to trigger cpictl when KVM is used # This file should be installed in /etc/udev/rules.d # SUBSYSTEM=="misc", KERNEL=="kvm", ENV{CREATED}=="1", ENV{EVENT}=="create", RUN+="/lib/s390-tools/cpictl -b kvm" s390-tools-2.3.0/fdasd/000077500000000000000000000000001323410431100144215ustar00rootroot00000000000000s390-tools-2.3.0/fdasd/Makefile000066400000000000000000000007751323410431100160720ustar00rootroot00000000000000include ../common.mak libs = $(rootdir)/libvtoc/libvtoc.a \ $(rootdir)/libzds/libzds.a \ $(rootdir)/libdasd/libdasd.a \ $(rootdir)/libu2s/libu2s.a \ $(rootdir)/libutil/libutil.a all: fdasd fdasd: fdasd.o $(libs) install: all $(INSTALL) -d -m 755 $(DESTDIR)$(BINDIR) $(DESTDIR)$(MANDIR)/man8 $(INSTALL) -g $(GROUP) -o $(OWNER) -m 755 fdasd $(DESTDIR)$(BINDIR) $(INSTALL) -g $(GROUP) -o $(OWNER) -m 644 fdasd.8 \ $(DESTDIR)$(MANDIR)/man8 clean: rm -f *.o *~ fdasd core .PHONY: all install clean s390-tools-2.3.0/fdasd/fdasd.8000066400000000000000000000120161323410431100155730ustar00rootroot00000000000000.\" Copyright 2017 IBM Corp. .\" s390-tools is free software; you can redistribute it and/or modify .\" it under the terms of the MIT license. See LICENSE for details. .\" .TH FDASD 8 "Apr 2006" "s390-tools" .SH NAME fdasd \- partitioning tool. .SH SYNOPSIS interactive mode: .br \fBfdasd\fR [-s] [-r] [-C] \fIdevice\fR .br command line mode: .br \fBfdasd\fR [-s] [-r] [-C] {-a[-k|-l \fIvolser\fR]|-i|-p|-c \fIconf_file\fR} [-f \fI[type,blocksize]\fR] \fIdevice\fR .br help: .br \fBfdasd\fR {-h|-v} .SH DESCRIPTION \fBfdasd\fR writes a partition table to a cdl (compatible disk layout) formatted DASD, in the form of a VTOC (volume table of contents) for usage with Linux for S/390 or zSeries. If fdasd detects a valid \fBVOL1\fR volume label, it will use it, otherwise it asks to write a new one. .br \fBAttention\fR: Careless use of \fBfdasd\fR can result in loss of data. .SH OPTIONS .TP \fB-h\fR or \fB--help\fR Print usage information, then exit. .TP \fB-v\fR or \fB--version\fR Print version information, then exit. .TP \fB-s\fR or \fB--silent\fR Suppress messages in non-interactive mode. .TP \fB-r\fR or \fB--verbose\fR Provide more verbose output. .TP \fB-a\fR or \fB--auto\fR Automatically create a partition using the entire disk in non-interactive mode. .TP \fB-k\fR or \fB--keep_volser\fR Keeps the volume serial when writing the volume label. .br This is useful, if the volume serial has been written before and should not be overwritten. This option is only applicable in non-interactive mode. .TP \fB-l\fR \fIvolser\fR or \fB--label\fR \fIvolser\fR Specify the volume serial. .br \fIvolser\fR is interpreted as ASCII string and is automatically converted to uppercase, padded with blanks and finally converted to EBCDIC to be written to disk. This option is only applicable in non-interactive mode. .br Do not use the following reserved volume serial: SCRTCH, PRIVAT, MIGRAT, or Lnnnnn (L with five digit number); These are used as keywords by other operating systems (OS/390). .br A volume serial is 1 through 6 alphanumeric characters or one of the following special characters: $, #, @, %. All other characters are simply ignored. .br Try to avoid using special characters in the volume serial. This may cause problems accessing a disk by volser. In case you really have to use special characters, make sure you are using quotes. In addition there is a special handling for the '$' sign. Please specify it using '\\$' if necessary. .br e.g. -l 'a@b\\$c#' to get A@B$C# .br Omitting this parameter causes fdasd to ask for it in case it is needed. .br .TP \fB-c\fR \fIconf_file\fR or \fB--config\fR \fIconf_file\fR Use this option to create multiple partitions according to specifications in a configuration file, \fIconf_file\fR. .br The configuration file contains one line for each partition. The lines have this format: .sp [,,] .br where: .br and are numbers that specify the first and the last track of the partition. Instead of a numerical value, you can specify 'first' for the first possible track on the disk and 'last' for the last possible track on disk. .br is optional and specifies the partition type. can be one of: native, swap, raid, lvm, or gpfs. If omitted, 'native' is used. .br The configuration file of the following example specifies three partitions that use the entire disk: .sp [first,1000] .br [1001,2000,swap] .br [2001,last,lvm] .br .TP \fB-i\fR or \fB--volser\fR Print the volume serial, then exit. .TP \fB-p\fR or \fB--table\fR Print partition table, then exit. .br In combination with the -s option fdasd will display a short version of the partition table. .TP \fB-C\fR or \fB--check_host_count\fR Force fdasd to check the host access open count to ensure the device is not online on another operating system instance .TP \fB-f\fR \fI[type,blocksize]\fR or \fB--force\fR \fI[type,blocksize]\fR Force fdasd to work on non DASD devices. .br If fdasd is to be used on a block device that is neither a native DASD nor exposes the proper disk geometry of a DASD of type 3390, then the --force option can be used to assume the geometry of a given device type. The default device type is 3390 and the default block size is 4096. An optional argument of , can be used to specify type and blocksize explicitly. For example: -f has the same effect as -f3390,4096 or --force=3390,4096 Valid device types are: 3390, 3380, 9345 .br Valid block sizes are: 4096, 2048, 1024, 512 You can use the verbose option to get information about the disk geometry that fdasd has computed from device type and block size. .TP \fIdevice\fR This parameter specifies the device to be partitioned: .sp \fB/dev/dasd/\fR\fIxxxx\fR\fB/device\fR .br where \fIxxxx\fR is the four-letter devno (device number). .br In case your are not using the device file system, please specify: .sp \fB/dev/dasd\fR\fIx\fR .br where \fIx\fR is one or more lowercase letter(s) or any other device node specification configured by udev for kernel 2.6 or higher. .SH SEE ALSO .BR dasdfmt (8) s390-tools-2.3.0/fdasd/fdasd.c000066400000000000000000002267011323410431100156560ustar00rootroot00000000000000/* * fdasd - Create or modify partitions on ECKD DASDs * * Copyright IBM Corp. 2001, 2017 * * 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 #include #include #include "lib/dasd_base.h" #include "lib/dasd_sys.h" #include "lib/util_base.h" #include "lib/util_opt.h" #include "lib/util_prg.h" #include "lib/vtoc.h" #include "lib/zt_common.h" #include "fdasd.h" /* global variables */ static struct hd_geometry geo; static char line_buffer[LINE_LENGTH]; static char *line_ptr = line_buffer; /* * Array of all supported partition types and its corresponding name and * DSNAME (Data Set Name) */ static partition_type_t partition_types[] = { [PARTITION_NEW] = { .name = "Linux native", .dsname = "NEW", .type = PARTITION_NATIVE, }, [PARTITION_NATIVE] = { .name = "Linux native", .dsname = "NATIVE", .type = PARTITION_NATIVE, }, [PARTITION_SWAP] = { .name = "Linux swap", .dsname = "SWAP", .type = PARTITION_SWAP, }, [PARTITION_RAID] = { .name = "Linux raid", .dsname = "RAID", .type = PARTITION_RAID, }, [PARTITION_LVM] = { .name = "Linux lvm", .dsname = "LVM", .type = PARTITION_LVM, }, [PARTITION_GPFS] = { .name = "GPFS NSD", .dsname = "GPFS", .type = PARTITION_GPFS, }, }; /* * Return the partition name corresponding to DSNAME (Data Set Name) */ static int get_part_name_by_dsname(char *dsname, char **name) { unsigned int i; if (!dsname) { *name = partition_types[PARTITION_NATIVE].name; return 0; } for (i = 0; i < UTIL_ARRAY_SIZE(partition_types); i++) { if (strstr(dsname, partition_types[i].dsname) != NULL) { *name = partition_types[i].name; return 0; } } return 1; } /* * Return the partition type corresponding to DSNAME (Data Set Name) */ static int get_part_type_by_dsname(char *dsname, int *type) { unsigned int i; if (!dsname) { *type = PARTITION_NATIVE; return 0; } for (i = 0; i < UTIL_ARRAY_SIZE(partition_types); i++) { if (strstr(dsname, partition_types[i].dsname) != NULL) { *type = partition_types[i].type; return 0; } } return 1; } /* * Return the DSNAME (Data Set Name) of a partition corresponding to its type */ static int get_part_dsname_by_type(unsigned int type, char **dsname) { if (type > UTIL_ARRAY_SIZE(partition_types)) return 1; if (type == 0) *dsname = partition_types[PARTITION_NATIVE].dsname; else *dsname = partition_types[type].dsname; return 0; } static const struct util_prg prg = { .desc = "Manage partitions on DASD volumes.\n" "DEVICE is the node of the device (e.g. '/dev/dasda').", .args = "DEVICE", .copyright_vec = { { .owner = "IBM Corp.", .pub_first = 2001, .pub_last = 2017, }, UTIL_PRG_COPYRIGHT_END } }; static struct util_opt opt_vec[] = { UTIL_OPT_SECTION("NON-INTERACTIVE MODE"), { .option = { "auto", no_argument, NULL, 'a' }, .desc = "Create a single partition spanning the entire disk", }, { .option = { "config", required_argument, NULL, 'c' }, .argument = "FILE", .desc = "Create partitions(s) based on content of FILE", }, { .option = { "keep_volser", no_argument, NULL, 'k' }, .desc = "Do not change the current volume serial", }, { .option = { "label", required_argument, NULL, 'l' }, .argument = "VOLSER", .desc = "Set the volume serial to VOLSER", }, UTIL_OPT_SECTION("MISC"), { .option = { "check_host_count", no_argument, NULL, 'C' }, .desc = "Check if device is in use by other hosts", }, { .option = { "force", optional_argument, NULL, 'f' }, .argument = "TYPE,SIZE", .desc = "Force fdasd to work on non DASD devices with assumed " "TYPE (3390, 3380, or 9345) and blocksize SIZE", }, { .option = { "volser", no_argument, NULL, 'i' }, .desc = "Print volume serial", }, { .option = { "table", no_argument, NULL, 'p' }, .desc = "Print partition table", }, { .option = { "verbose", no_argument, NULL, 'r' }, .desc = "Provide more verbose output", }, { .option = { "silent", no_argument, NULL, 's' }, .desc = "Suppress messages", }, UTIL_OPT_HELP, UTIL_OPT_VERSION, UTIL_OPT_END }; static int getpos(fdasd_anchor_t *anc, int dsn) { return anc->partno[dsn]; } static int getdsn(fdasd_anchor_t *anc, int pos) { int i; for (i = 0; i < USABLE_PARTITIONS; i++) { if (anc->partno[i] == pos) return i; } return -1; } static void setpos(fdasd_anchor_t *anc, int dsn, int pos) { anc->partno[dsn] = pos; } static u_int32_t get_usable_cylinders(fdasd_anchor_t *anc) { u_int32_t cyl; /* large volume */ if (anc->f4->DS4DEVCT.DS4DSCYL == LV_COMPAT_CYL && anc->f4->DS4DCYL > anc->f4->DS4DEVCT.DS4DSCYL) return anc->f4->DS4DCYL; /* normal volume */ if (anc->f4->DS4DEVCT.DS4DEVFG & ALTERNATE_CYLINDERS_USED) cyl = anc->f4->DS4DEVCT.DS4DSCYL - (u_int16_t)anc->f4->DS4DEVAC; else cyl = anc->f4->DS4DEVCT.DS4DSCYL; return cyl; } static void get_addr_of_highest_f1_f8_label(fdasd_anchor_t *anc, cchhb_t *addr) { u_int8_t record; /* We have to count the following labels: * one format 4 * one format 5 * format 7 only if we have moren then BIG_DISK_SIZE tracks * one for each format 1 or format 8 label == one for each partition * one for each format 9 label before the last format 8 * We assume that all partitions use format 8 labels when * anc->formatted_cylinders > LV_COMPAT_CYL * Note: Record zero is special, so block 0 on our disk is record 1! */ record = anc->used_partitions + 2; if (anc->big_disk) record++; if (anc->formatted_cylinders > LV_COMPAT_CYL) record += anc->used_partitions - 1; vtoc_set_cchhb(addr, VTOC_START_CC, VTOC_START_HH, record); } /* * Check for valid volume serial characters (max. 6) - remove invalid. * If volser is empty, fill with default volser. */ static void fdasd_check_volser(char *volser, int devno) { int from, to; for (from = 0, to = 0; volser[from] && from < VOLSER_LENGTH; from++) if ((volser[from] >= 0x23 && volser[from] <= 0x25) || /* # $ % */ (volser[from] >= 0x30 && volser[from] <= 0x39) || /* 0-9 */ (volser[from] >= 0x40 && volser[from] <= 0x5a) || /* @ A-Z */ (volser[from] >= 0x61 && volser[from] <= 0x7a)) /* a-z */ volser[to++] = toupper(volser[from]); volser[to] = 0x00; if (volser[0] == 0x00) sprintf(volser, "0X%04x", devno); } /* * Free memory of fdasd anchor struct. */ static void fdasd_cleanup(fdasd_anchor_t *anchor) { partition_info_t *part_info, *next; int i; if (anchor == NULL) return; if (anchor->f4 != NULL) free(anchor->f4); if (anchor->f5 != NULL) free(anchor->f5); if (anchor->f7 != NULL) free(anchor->f7); if (anchor->vlabel != NULL) free(anchor->vlabel); part_info = anchor->first; for (i = 1; i <= USABLE_PARTITIONS && part_info != NULL; i++) { next = part_info->next; free(part_info->f1); free(part_info); part_info = next; } } /* * Exit fdasd. */ static void __noreturn fdasd_exit(fdasd_anchor_t *anchor, int rc) { fdasd_cleanup(anchor); exit(rc); } /* * */ static void fdasd_error(fdasd_anchor_t *anc, enum fdasd_failure why, char *str) { char err_str[ERROR_STRING_SIZE]; switch (why) { case parser_failed: snprintf(err_str, ERROR_STRING_SIZE, "%s parser error\n%s\n", FDASD_ERROR, str); break; case unable_to_open_disk: snprintf(err_str, ERROR_STRING_SIZE, "%s open error\n%s\n", FDASD_ERROR, str); break; case unable_to_seek_disk: snprintf(err_str, ERROR_STRING_SIZE, "%s seek error\n%s\n", FDASD_ERROR, str); break; case unable_to_read_disk: snprintf(err_str, ERROR_STRING_SIZE, "%s read error\n%s\n", FDASD_ERROR, str); break; case read_only_disk: snprintf(err_str, ERROR_STRING_SIZE, "%s write error\n%s\n", FDASD_ERROR, str); break; case unable_to_ioctl: snprintf(err_str, ERROR_STRING_SIZE, "%s IOCTL error\n%s\n", FDASD_ERROR, str); break; case wrong_disk_type: snprintf(err_str, ERROR_STRING_SIZE, "%s Unsupported disk type\n%s\n", FDASD_ERROR, str); break; case wrong_disk_format: snprintf(err_str, ERROR_STRING_SIZE, "%s Unsupported disk format\n%s\n", FDASD_ERROR, str); break; case disk_in_use: snprintf(err_str, ERROR_STRING_SIZE, "%s Disk in use\n%s\n", FDASD_ERROR, str); break; case config_syntax_error: snprintf(err_str, ERROR_STRING_SIZE, "%s Config file syntax error\n%s\n", FDASD_ERROR, str); break; case vlabel_corrupted: snprintf(err_str, ERROR_STRING_SIZE, "%s Volume label is corrupted.\n%s\n", FDASD_ERROR, str); break; case dsname_corrupted: snprintf(err_str, ERROR_STRING_SIZE, "%s a data set name is corrupted.\n%s\n", FDASD_ERROR, str); break; case malloc_failed: snprintf(err_str, ERROR_STRING_SIZE, "%s space allocation\n%s\n", FDASD_ERROR, str); break; case device_verification_failed: snprintf(err_str, ERROR_STRING_SIZE, "%s device verification failed\n%s\n", FDASD_ERROR, str); break; case volser_not_found: snprintf(err_str, ERROR_STRING_SIZE, "%s VOLSER not found on device %s\n", FDASD_ERROR, str); break; default: snprintf(err_str, ERROR_STRING_SIZE, "%s Fatal error\n%s\n", FDASD_ERROR, str); } fputc('\n', stderr); fputs(err_str, stderr); fdasd_exit(anc, -1); } /* * Read line from stdin into global line_buffer * and set global line_ptr to first printing character except space. */ static int read_line(void) { bzero(line_buffer, LINE_LENGTH); line_ptr = line_buffer; if (!fgets(line_buffer, LINE_LENGTH, stdin)) return 0; while (*line_ptr && !isgraph(*line_ptr)) line_ptr++; return *line_ptr; } /* * */ static char read_char(char *mesg) { fputs(mesg, stdout); read_line(); return *line_ptr; } /* * Print question string an enforce y/n answer. */ static int yes_no(char *question_str) { ssize_t bytes_read; char *answer; size_t size; size = 0; answer = NULL; while (1) { printf("%s (y/n): ", question_str); bytes_read = getline(&answer, &size, stdin); if (bytes_read < 0) return -1; if (answer[0] == 'y') return 0; if (answer[0] == 'n') return 1; } free(answer); } static char *fdasd_partition_type(char *dsname) { char *name = NULL; if (get_part_name_by_dsname(dsname, &name)) name = "unknown"; return name; } /* * prints the menu */ static void fdasd_menu(void) { printf("Command action\n" " m print this menu\n" " p print the partition table\n" " n add a new partition\n" " d delete a partition\n" " l list known partition types\n" " v change volume serial\n" " t change partition type\n" " r re-create VTOC and delete all partitions\n" " u re-create VTOC re-using existing partition sizes\n" " s show mapping (partition number - data set name)\n" " q quit without saving changes\n" " w write table to disk and exit\n"); } /* * initializes the anchor structure and allocates some * memory for the labels */ static void fdasd_initialize_anchor(fdasd_anchor_t *anc) { partition_info_t *part_info, *prev_part_info = NULL; volume_label_t *vlabel; int i; bzero(anc, sizeof(fdasd_anchor_t)); for (i = 0; i < USABLE_PARTITIONS; i++) setpos(anc, i, -1); anc->f4 = malloc(sizeof(format4_label_t)); if (anc->f4 == NULL) fdasd_error(anc, malloc_failed, "FMT4 DSCB memory allocation failed."); anc->f5 = malloc(sizeof(format5_label_t)); if (anc->f5 == NULL) fdasd_error(anc, malloc_failed, "FMT5 DSCB memory allocation failed."); anc->f7 = malloc(sizeof(format7_label_t)); if (anc->f7 == NULL) fdasd_error(anc, malloc_failed, "FMT7 DSCB memory allocation failed."); /* template for all format 9 labels */ anc->f9 = malloc(sizeof(format9_label_t)); if (anc->f9 == NULL) fdasd_error(anc, malloc_failed, "FMT9 DSCB memory allocation failed."); bzero(anc->f4, sizeof(format4_label_t)); bzero(anc->f5, sizeof(format5_label_t)); bzero(anc->f7, sizeof(format7_label_t)); bzero(anc->f9, sizeof(format9_label_t)); vtoc_init_format9_label(anc->f9); vlabel = malloc(sizeof(volume_label_t)); if (vlabel == NULL) fdasd_error(anc, malloc_failed, "Volume label memory allocation failed."); bzero(vlabel, sizeof(volume_label_t)); anc->vlabel = vlabel; for (i = 1; i <= USABLE_PARTITIONS; i++) { part_info = malloc(sizeof(partition_info_t)); if (part_info == NULL) fdasd_error(anc, malloc_failed, "Partition info memory allocation failed."); memset(part_info, 0, sizeof(partition_info_t)); /* add part_info to double pointered list */ if (i == 1) { anc->first = part_info; } else if (i == USABLE_PARTITIONS) { anc->last = part_info; part_info->next = NULL; } part_info->f1 = malloc(sizeof(format1_label_t)); if (part_info->f1 == NULL) fdasd_error(anc, malloc_failed, "FMT1 DSCB memory allocation failed."); bzero(part_info->f1, sizeof(format1_label_t)); if (prev_part_info) { prev_part_info->next = part_info; part_info->prev = prev_part_info; } else { part_info->prev = NULL; } prev_part_info = part_info; } anc->hw_cylinders = 0; anc->formatted_cylinders = 0; } static void fdasd_parse_force_options(fdasd_anchor_t *anc, char *optarg) { char err_str[ERROR_STRING_SIZE]; unsigned int devtype, blksize; int rc; if (optarg) { rc = sscanf(optarg, "%x,%d", &devtype, &blksize); if (rc != 2) { snprintf(err_str, ERROR_STRING_SIZE, "Force parameter '%s' could not be parsed.\n", optarg); fdasd_error(anc, parser_failed, err_str); } if (devtype == DASD_3390_TYPE || devtype == DASD_3380_TYPE || devtype == DASD_9345_TYPE) { anc->dev_type = devtype; } else { snprintf(err_str, ERROR_STRING_SIZE, "Force parameter '%x' is not a supported" " device type.\n", devtype); fdasd_error(anc, parser_failed, err_str); } if (blksize == 4096 || blksize == 2048 || blksize == 1024 || blksize == 512) { anc->blksize = blksize; } else { snprintf(err_str, ERROR_STRING_SIZE, "Force parameter '%d' is not a supported" " block size.\n", blksize); fdasd_error(anc, parser_failed, err_str); } } else { /* force option was used without the optional argument */ anc->dev_type = DASD_3390_TYPE; anc->blksize = 4096; } } /* * parses the command line options */ static void fdasd_parse_options(fdasd_anchor_t *anc, struct fdasd_options *options, int argc, char *argv[]) { int opt; util_prg_init(&prg); util_opt_init(opt_vec, NULL); do { opt = util_opt_getopt_long(argc, argv); switch (opt) { case 'v': util_prg_print_version(); fdasd_exit(anc, 0); case 'h': util_prg_print_help(); util_opt_print_help(); fdasd_exit(anc, 0); case 'l': if (options->volser) fdasd_error(anc, parser_failed, "Option 'label' specified more " "than once.\n"); options->volser = optarg; break; case 'a': anc->auto_partition++; break; case 's': anc->silent++; break; case 'r': anc->verbose++; break; case 'p': anc->print_table++; break; case 'i': anc->print_volser++; anc->silent++; break; case 'c': if (options->conffile) fdasd_error(anc, parser_failed, "Option 'config' specified more" " than once.\n"); options->conffile = optarg; break; case 'k': anc->keep_volser++; break; case 'f': anc->force_virtual++; fdasd_parse_force_options(anc, optarg); break; case 'C': anc->force_host++; break; case -1: /* End of options string - start of devices list */ break; default: fprintf(stderr, "Try 'fdasd --help' for more" " information.\n"); fdasd_exit(anc, 1); } } while (opt != -1); /* save device */ if (optind >= argc) fdasd_error(anc, parser_failed, "No device specified.\n"); if (optind + 1 < argc) fdasd_error(anc, parser_failed, "More than one device specified.\n"); options->device = argv[optind]; } static int gettoken(char *str, char *ch, char *token[], int max) { int i; token[0] = strtok(str, ch); if (!token[0]) return 0; for (i = 1; i < max; i++) { token[i] = strtok(NULL, ch); if (!token[i]) break; } return i; } /* * parses config file */ static int fdasd_parse_conffile(fdasd_anchor_t *anc, struct fdasd_options *options) { char err_str[ERROR_STRING_SIZE], *c1, *c2, *token[CONFIG_MAX]; char buffer[CONFIG_FILE_SIZE + 1]; int fd, rc; int i; /* if name of config file was not specified, select the default */ if (options->conffile == NULL) options->conffile = DEFAULT_FDASD_CONF; if (!anc->silent) printf("parsing config file '%s'...\n", options->conffile); fd = open(options->conffile, O_RDONLY); if (fd < 0) { snprintf(err_str, ERROR_STRING_SIZE, "Could not open config file '%s' " "in read-only mode!\n", options->conffile); fdasd_error(anc, unable_to_open_disk, err_str); } memset(buffer, 0, sizeof(buffer)); rc = read(fd, buffer, sizeof(buffer) - 1); if (rc < 0) return -1; close(fd); for (i = 0; i < rc; i++) buffer[i] = toupper(buffer[i]); c1 = buffer; for (i = 0; i < USABLE_PARTITIONS; i++) { c1 = strchr(c1, '['); if (c1 == NULL) { if (!anc->silent) printf("no config file entry for " "partition %d found...\n", i + 1); break; } c1 += 1; c2 = strchr(c1, ']'); if (c2 == NULL) { snprintf(err_str, ERROR_STRING_SIZE, "']' missing in config file " "%s\n", options->conffile); fdasd_error(anc, config_syntax_error, err_str); } strcpy(c2, ""); memset(token, 0, sizeof(token)); if (gettoken(c1, ",", token, CONFIG_MAX) < 2) { snprintf(err_str, ERROR_STRING_SIZE, "Missing parameter in config file " "%s\n", options->conffile); fdasd_error(anc, config_syntax_error, err_str); } if (strstr(token[0], "FIRST") != NULL) { anc->confdata[i].start = FIRST_USABLE_TRK; } else { errno = 0; anc->confdata[i].start = strtol(token[0], (char **)NULL, 10); if (errno != 0 || anc->confdata[i].start == 0) { snprintf(err_str, ERROR_STRING_SIZE, "invalid partition start in config " "file %s\n", options->conffile); fdasd_error(anc, config_syntax_error, err_str); } } if (strstr(token[1], "LAST") != NULL) { anc->confdata[i].stop = anc->formatted_cylinders * geo.heads - 1; } else { errno = 0; anc->confdata[i].stop = strtol(token[1], (char **)NULL, 10); if (errno != 0 || anc->confdata[i].stop == 0) { snprintf(err_str, ERROR_STRING_SIZE, "invalid partition end in config " "file %s\n", options->conffile); fdasd_error(anc, config_syntax_error, err_str); } } if (get_part_type_by_dsname(token[2], &anc->confdata[i].type)) { snprintf(err_str, ERROR_STRING_SIZE, "invalid partition type in config file %s\n", options->conffile); fdasd_error(anc, config_syntax_error, err_str); } c1 = c2 + 1; } return 0; } /* * checks input from config file */ static void fdasd_check_conffile_input(fdasd_anchor_t *anc, struct fdasd_options *options) { unsigned long start, stop, first_trk, last_trk; partition_info_t *part_info = anc->first; char err_str[ERROR_STRING_SIZE]; int i; if (anc->verbose) printf("checking config file data...\n"); for (i = 0; i < USABLE_PARTITIONS; i++) { start = anc->confdata[i].start; stop = anc->confdata[i].stop; if (start == 0 || stop == 0) break; first_trk = FIRST_USABLE_TRK; last_trk = anc->formatted_cylinders * geo.heads - 1; if (start < first_trk || start > last_trk) { snprintf(err_str, ERROR_STRING_SIZE, "One of the lower partition limits " "(%ld) is not within the range of\n" "available tracks on disk (%ld-%ld)!\n", start, first_trk, last_trk); fdasd_error(anc, config_syntax_error, err_str); } if (stop < first_trk || stop > last_trk) { snprintf(err_str, ERROR_STRING_SIZE, "One of the upper partition limits " "(%ld) is not within the range of\n " "available tracks on disk (%ld-%ld)!\n", stop, first_trk, last_trk); fdasd_error(anc, config_syntax_error, err_str); } if (start >= stop) { snprintf(err_str, ERROR_STRING_SIZE, "Lower partition limit (%ld) is not " "less than upper partition\nlimit (%ld) " "in config file %s!\n", start, stop, options->conffile); fdasd_error(anc, config_syntax_error, err_str); } if (i > 0 && start <= anc->confdata[i - 1].stop) { snprintf(err_str, ERROR_STRING_SIZE, "Partitions overlap or are not in " "ascending order!\n"); fdasd_error(anc, config_syntax_error, err_str); } if (i < (USABLE_PARTITIONS - 1) && anc->confdata[i + 1].start > 0 && stop >= anc->confdata[i + 1].start) { snprintf(err_str, ERROR_STRING_SIZE, "Partitions overlap or are not in " "ascending order!\n"); fdasd_error(anc, config_syntax_error, err_str); } part_info->used = 0x01; part_info->start_trk = start; part_info->end_trk = stop; part_info->len_trk = stop - start + 1; part_info->type = anc->confdata[i].type; /* update the current free space counter */ if (i == 0) anc->fspace_trk = start - FIRST_USABLE_TRK; if (i < USABLE_PARTITIONS - 1) { if (anc->confdata[i + 1].start != 0) part_info->fspace_trk = anc->confdata[i + 1].start - stop - 1; else part_info->fspace_trk = last_trk - stop; } else if (i == USABLE_PARTITIONS - 1) { part_info->fspace_trk = last_trk - stop; } part_info = part_info->next; } } /* * Verifies the specified block device. */ static void fdasd_verify_device(fdasd_anchor_t *anc, char *name) { char err_str[ERROR_STRING_SIZE]; struct stat dst; int count; if (stat(name, &dst) < 0) { snprintf(err_str, ERROR_STRING_SIZE, "Unable to get device status for device '%s'\n", name); fdasd_error(anc, device_verification_failed, err_str); } if (!S_ISBLK(dst.st_mode)) { snprintf(err_str, ERROR_STRING_SIZE, "Device '%s' (%d/%d) is not a block device\n", name, (unsigned short)major(dst.st_rdev), (unsigned short)minor(dst.st_rdev)); fdasd_error(anc, device_verification_failed, err_str); } if (!anc->force_virtual && minor(dst.st_rdev) & PARTN_MASK) { snprintf(err_str, ERROR_STRING_SIZE, "Partition '%s' (%d/%d) detected where device is " "required\n", name, (unsigned short)major(dst.st_rdev), (unsigned short)minor(dst.st_rdev)); fdasd_error(anc, device_verification_failed, err_str); } if (dasd_sys_raw_track_access(name)) { snprintf(err_str, ERROR_STRING_SIZE, "Device '%s' is in raw-track access mode\n", name); fdasd_error(anc, device_verification_failed, err_str); } count = u2s_get_host_access_count(name); if (anc->force_host) { if (count > 1) { snprintf(err_str, ERROR_STRING_SIZE, "Disk %s is online on operating system instances in %d different LPARs.\n" "Note: Your installation might include z/VM systems that are configured to\n" "automatically vary on disks, regardless of whether they are subsequently used.\n", name, count); fdasd_error(anc, device_verification_failed, err_str); } else if (count < 0) { snprintf(err_str, ERROR_STRING_SIZE, "Hosts access information not available for disk %s.\n", name); fdasd_error(anc, device_verification_failed, err_str); } } else if (count > 1) { printf("\nWARNING:\n" "Disk %s is online on operating system instances in %d different LPARs.\n" "Ensure that the disk is not being used by a system outside your LPAR.\n" "Note: Your installation might include z/VM systems that are configured to\n" "automatically vary on disks, regardless of whether they are subsequently used.\n\n", name, count); } if (anc->verbose) { printf("Verification successful for '%s' (%d/%d)\n", name, (unsigned short)major(dst.st_rdev), (unsigned short)minor(dst.st_rdev)); } } /* * Verifies the specified fdasd command line option * combinations. * * Note: * - 'version' and 'help' are priority options. * All other parameters are ignored in that case. * - 'silent' and 'verbose' are allways allowed in any * combination. * */ static void fdasd_verify_options(fdasd_anchor_t *anc) { /* Checked option combinations */ /* (inv = invalid / req = required / opt = optional) */ /* */ /* vols labe keep auto conf tabl */ /* er l _vol if e */ /* ser */ /* */ /* volser - inv INV inv inv inv */ /* label - inv REQ REQ inv */ /* keep_volser - REQ REQ inv */ /* auto opt opt - inv inv */ /* config opt opt - inv */ /* table - */ if (anc->print_volser && (options.volser || anc->keep_volser || anc->auto_partition || options.conffile || anc->print_table)) { fdasd_error(anc, parser_failed, "Option 'volser' cannot be used with other" " options.\n"); } if (options.volser) { if (!anc->auto_partition && !options.conffile) { fdasd_error(anc, parser_failed, "Option 'auto' or 'config' required when" " specifying 'label'\n"); } if (anc->keep_volser || anc->print_table) { fdasd_error(anc, parser_failed, "Option 'label' cannot be used with " "'keep_volser' and 'table'.\n"); } } if (anc->keep_volser) { if (!anc->auto_partition && !options.conffile) { fdasd_error(anc, parser_failed, "Option 'auto' or 'config' required when" " specifying 'keep_volser'\n"); } if (anc->print_table) { fdasd_error(anc, parser_failed, "Option 'keep_volser' cannot be used" " with 'table'.\n"); } } if (anc->auto_partition && (options.conffile || anc->print_table)) { fdasd_error(anc, parser_failed, "Option 'auto' cannot be used with " "'config' and 'table'.\n"); } if (options.conffile && anc->print_table) { fdasd_error(anc, parser_failed, "Option 'config' cannot be used with" " 'table'.\n"); } } /* * print mapping: partition number - data set name */ static void fdasd_show_mapping(fdasd_anchor_t *anc) { char str[20], *dev, dsname[45], *strp; partition_info_t *part_info; int i = 0, j = 0, dev_len; printf("\ndevice .........: %s\n", options.device); bzero(str, sizeof(str)); vtoc_volume_label_get_label(anc->vlabel, str); printf("volume label ...: %.4s\n", str); bzero(str, sizeof(str)); vtoc_volume_label_get_volser(anc->vlabel, str); printf("volume serial ..: %s\n\n", str); dev_len = strlen(options.device); dev = malloc(dev_len + 10); if (!dev) fdasd_error(anc, malloc_failed, "Show mapping: memory allocation failed."); strcpy(dev, options.device); strp = strstr(dev, DISC); if (strp == NULL) strp = strstr(dev, DEVICE); if (strp != NULL) strcpy(strp, PART); printf("WARNING: This mapping may be NOT up-to-date,\n" " if you have NOT saved your last changes!\n\n"); for (part_info = anc->first; part_info != NULL; part_info = part_info->next) { i++; if (part_info->used != 0x01) continue; bzero(dsname, sizeof(dsname)); strncpy(dsname, part_info->f1->DS1DSNAM, 44); vtoc_ebcdic_dec(dsname, dsname, 44); if (getdsn(anc, i - 1) < 0) sprintf(dsname, "new data set"); printf("%s%-2d - %-44s\n", dev, i, dsname); j++; } if (j == 0) printf("No partitions defined.\n"); free(dev); } /* * prints only the volume serial */ static void fdasd_print_volser(fdasd_anchor_t *anc) { char volser[VOLSER_LENGTH + 1]; bzero(volser, VOLSER_LENGTH); vtoc_ebcdic_dec(anc->vlabel->volid, volser, VOLSER_LENGTH); printf("%6.6s\n", volser); } /* * print partition table */ static void fdasd_list_partition_table(fdasd_anchor_t *anc) { int i = 0, dev_len = strlen(options.device); char str[20], *dev, *strp, *ch; partition_info_t *part_info; if (!anc->silent) { printf("\nDisk %s:\n" " cylinders ............: %d\n" " tracks per cylinder ..: %d\n" " blocks per track .....: %d\n" " bytes per block ......: %d\n", options.device, anc->formatted_cylinders, geo.heads, geo.sectors, anc->blksize); vtoc_volume_label_get_label(anc->vlabel, str); printf(" volume label .........: %s\n", str); vtoc_volume_label_get_volser(anc->vlabel, str); printf(" volume serial ........: %s\n", str); printf(" max partitions .......: %d\n\n", USABLE_PARTITIONS); } if (dev_len < 20) dev_len = 20; if (!anc->silent) { printf(" ------------------------------- tracks" " -------------------------------\n"); printf("%*s start end length Id System\n", dev_len + 1, "Device"); } dev = malloc(dev_len + 10); if (!dev) fdasd_error(anc, malloc_failed, "Print partition table: memory allocation failed."); strcpy(dev, options.device); strp = strstr(dev, DISC); if (strp == NULL) strp = strstr(dev, DEVICE); if (strp != NULL) strcpy(strp, PART); for (part_info = anc->first; part_info != NULL; part_info = part_info->next) { i++; if (part_info == anc->first && anc->fspace_trk > 0) { printf("%*s %9ld%9ld%9ld unused\n", dev_len, "", (unsigned long)FIRST_USABLE_TRK, (unsigned long)FIRST_USABLE_TRK + anc->fspace_trk - 1, anc->fspace_trk); } if (part_info->used != 0x01) continue; vtoc_ebcdic_dec(part_info->f1->DS1DSNAM, part_info->f1->DS1DSNAM, 44); ch = strstr(part_info->f1->DS1DSNAM, "PART"); if (ch != NULL) { strncpy(str, ch + 9, 6); str[6] = '\0'; } else { strcpy(str, "error"); } vtoc_ebcdic_enc(part_info->f1->DS1DSNAM, part_info->f1->DS1DSNAM, 44); printf("%*s%-2d %9ld%9ld%9ld %2x %6s\n", dev_len, dev, i, part_info->start_trk, part_info->end_trk, part_info->len_trk, i, fdasd_partition_type(str)); if (part_info->fspace_trk > 0) printf("%*s %9ld%9ld%9ld unused\n", dev_len, "", part_info->end_trk + 1, part_info->end_trk + part_info->fspace_trk, part_info->fspace_trk); } free(dev); } /* * List all supported partition types. */ static void fdasd_list_known_partitions(void) { unsigned int i; for (i = VALID_PARTITION_OFFSET; i < UTIL_ARRAY_SIZE(partition_types); i++) { printf("%3d %s\n", partition_types[i].type, partition_types[i].name); } } /* * get volser from vtoc */ static int fdasd_get_volser(fdasd_anchor_t *anc, char *volser) { volume_label_t vlabel; vtoc_read_volume_label(options.device, anc->label_pos, &vlabel); vtoc_volume_label_get_volser(&vlabel, volser); return 0; } /* * call IOCTL to re-read the partition table */ static void fdasd_reread_partition_table(fdasd_anchor_t *anc) { if (!anc->silent) printf("rereading partition table...\n"); if (dasd_reread_partition_table(options.device, 1) != 0) { fdasd_error(anc, unable_to_ioctl, "Error while rereading " "partition table.\nPlease reboot!"); } } /* * writes all changes to dasd */ static void fdasd_write_vtoc_labels(fdasd_anchor_t *anc) { char dsno[6], volser[VOLSER_LENGTH + 1], s2[45], *c1, *c2, *ch; partition_info_t *part_info; unsigned long blk, maxblk; format1_label_t emptyf1; char *dsname = NULL; cchhb_t f9addr; int i = 0, k = 0; if (!anc->silent) printf("writing VTOC...\n"); if (anc->verbose) printf("DSCBs: "); blk = (cchhb2blk(&anc->vlabel->vtoc, &geo) - 1) * anc->blksize; if (blk <= 0) fdasd_error(anc, vlabel_corrupted, ""); maxblk = blk + anc->blksize * 9; /* f4+f5+f7+3*f8+3*f9 */ /* write FMT4 DSCB */ vtoc_write_label(options.device, blk, NULL, anc->f4, NULL, NULL, NULL); if (anc->verbose) printf("f4 "); blk += anc->blksize; /* write FMT5 DSCB */ vtoc_write_label(options.device, blk, NULL, NULL, anc->f5, NULL, NULL); if (anc->verbose) printf("f5 "); blk += anc->blksize; /* write FMT7 DSCB */ if (anc->big_disk) { vtoc_write_label(options.device, blk, NULL, NULL, NULL, anc->f7, NULL); if (anc->verbose) printf("f7 "); blk += anc->blksize; } /* loop over all partitions (format 1 or format 8 DCB) */ for (part_info = anc->first; part_info != NULL; part_info = part_info->next) { if (part_info->used != 0x01) continue; i++; strncpy((char *)part_info->f1->DS1DSSN, anc->vlabel->volid, VOLSER_LENGTH); ch = part_info->f1->DS1DSNAM; vtoc_ebcdic_dec(ch, ch, 44); c1 = ch + 7; if (getdsn(anc, i - 1) > -1) { /* re-use the existing data set name */ c2 = strchr(c1, '.'); if (c2 != NULL) strncpy(s2, c2, 31); else fdasd_error(anc, dsname_corrupted, ""); strncpy(volser, anc->vlabel->volid, VOLSER_LENGTH); vtoc_ebcdic_dec(volser, volser, VOLSER_LENGTH); volser[VOLSER_LENGTH] = ' '; strncpy(c1, volser, VOLSER_LENGTH + 1); c1 = strchr(ch, ' '); strncpy(c1, s2, 31); } else { if (get_part_type_by_dsname(ch, &part_info->type)) part_info->type = PARTITION_NATIVE; /* create a new data set name */ while (getpos(anc, k) > -1) k++; setpos(anc, k, i - 1); strncpy(ch, "LINUX.V " " ", 44); strncpy(volser, anc->vlabel->volid, VOLSER_LENGTH); vtoc_ebcdic_dec(volser, volser, VOLSER_LENGTH); strncpy(c1, volser, VOLSER_LENGTH); c1 = strchr(ch, ' '); strncpy(c1, ".PART", 5); c1 += 5; sprintf(dsno, "%04d.", k + 1); strncpy(c1, dsno, 5); c1 += 5; get_part_dsname_by_type(part_info->type, &dsname); strncpy(c1, dsname, strlen(dsname)); /* We don't want \0 */ } vtoc_ebcdic_enc(ch, ch, 44); if (anc->verbose) printf("%2x ", part_info->f1->DS1FMTID); if (part_info->f1->DS1FMTID == 0xf8) { /* Now as we know where which label will be written, we * can add the address of the format 9 label to the * format 8 label. The f9 record will be written to the * block after the current blk. Remember: records are of * by one, so we have to add 2 and not just one. */ vtoc_set_cchhb(&f9addr, VTOC_START_CC, VTOC_START_HH, ((blk / anc->blksize) % geo.sectors) + 2); vtoc_update_format8_label(&f9addr, part_info->f1); vtoc_write_label(options.device, blk, part_info->f1, NULL, NULL, NULL, NULL); blk += anc->blksize; vtoc_write_label(options.device, blk, NULL, NULL, NULL, NULL, anc->f9); if (anc->verbose) printf("f9 "); blk += anc->blksize; } else { vtoc_write_label(options.device, blk, part_info->f1, NULL, NULL, NULL, NULL); blk += anc->blksize; } } /* write empty labels to the rest of the blocks */ bzero(&emptyf1, sizeof(emptyf1)); while (blk < maxblk) { vtoc_write_label(options.device, blk, &emptyf1, NULL, NULL, NULL, NULL); if (anc->verbose) printf("empty "); blk += anc->blksize; } if (anc->verbose) printf("\n"); } /* * writes all changes to dasd */ static void fdasd_write_labels(fdasd_anchor_t *anc) { if (anc->vlabel_changed) { if (!anc->silent) printf("writing volume label...\n"); vtoc_write_volume_label(options.device, anc->label_pos, anc->vlabel); } if (anc->vtoc_changed) fdasd_write_vtoc_labels(anc); if (anc->vtoc_changed || anc->vlabel_changed) fdasd_reread_partition_table(anc); } /* * re-creates the VTOC and deletes all partitions */ static void fdasd_recreate_vtoc_unconditional(fdasd_anchor_t *anc) { partition_info_t *part_info = anc->first; int i; vtoc_init_format4_label(anc->f4, geo.cylinders, anc->formatted_cylinders, geo.heads, geo.sectors, anc->blksize, anc->dev_type); vtoc_init_format5_label(anc->f5); vtoc_init_format7_label(anc->f7); vtoc_set_freespace(anc->f4, anc->f5, anc->f7, '+', anc->verbose, FIRST_USABLE_TRK, anc->formatted_cylinders * geo.heads - 1, anc->formatted_cylinders, geo.heads); while (part_info != NULL) { bzero(part_info->f1, sizeof(format1_label_t)); if (part_info->used == 0x01) { part_info->used = 0x00; part_info->start_trk = 0; part_info->end_trk = 0; part_info->len_trk = 0; part_info->fspace_trk = 0; } part_info = part_info->next; } anc->used_partitions = 0; anc->fspace_trk = anc->formatted_cylinders * geo.heads - FIRST_USABLE_TRK; for (i = 0; i < USABLE_PARTITIONS; i++) setpos(anc, i, -1); anc->vtoc_changed++; } /* * asks user for confirmation before recreating the vtoc */ static void fdasd_recreate_vtoc(fdasd_anchor_t *anc) { char str[INPUT_BUF_SIZE]; if (!anc->silent) { snprintf(str, INPUT_BUF_SIZE, "WARNING: All partitions on device '%s' will be " "deleted!\nDo you want to continue?", options.device); if (yes_no(str) != 0) return; printf("creating new VTOC... "); } fdasd_recreate_vtoc_unconditional(anc); if (!anc->silent) printf("ok\n"); } /* * re-create all VTOC labels, but use the partition information * from existing VTOC */ static void fdasd_reuse_vtoc(fdasd_anchor_t *anc) { partition_info_t *part_info = anc->first; char str[INPUT_BUF_SIZE]; format1_label_t f1; format4_label_t f4; format5_label_t f5; format7_label_t f7; if (!anc->silent) { snprintf(str, INPUT_BUF_SIZE, "WARNING: this will re-create your VTOC " "entries using the partition\n " "information of your existing VTOC. Continue?"); if (yes_no(str) != 0) return; } if (!anc->silent) printf("re-creating VTOC... "); vtoc_init_format4_label(&f4, geo.cylinders, anc->formatted_cylinders, geo.heads, geo.sectors, anc->blksize, anc->dev_type); /* reuse some FMT4 values */ f4.DS4HPCHR = anc->f4->DS4HPCHR; f4.DS4DSREC = anc->f4->DS4DSREC; /* re-initialize both free-space labels */ vtoc_init_format5_label(&f5); vtoc_init_format7_label(&f7); if (anc->fspace_trk > 0) { vtoc_set_freespace(&f4, &f5, &f7, '+', anc->verbose, FIRST_USABLE_TRK, FIRST_USABLE_TRK + anc->fspace_trk - 1, anc->formatted_cylinders, geo.heads); } while (part_info != NULL) { if (part_info->used != 0x01) { part_info = part_info->next; continue; } if (anc->formatted_cylinders > LV_COMPAT_CYL) vtoc_init_format8_label(anc->blksize, &part_info->f1->DS1EXT1, &f1); else vtoc_init_format1_label(anc->blksize, &part_info->f1->DS1EXT1, &f1); strncpy(f1.DS1DSNAM, part_info->f1->DS1DSNAM, 44); strncpy((char *)f1.DS1DSSN, (char *)part_info->f1->DS1DSSN, 6); f1.DS1CREDT = part_info->f1->DS1CREDT; memcpy(part_info->f1, &f1, sizeof(format1_label_t)); if (part_info->fspace_trk > 0) { vtoc_set_freespace(&f4, &f5, &f7, '+', anc->verbose, part_info->end_trk + 1, part_info->end_trk + part_info->fspace_trk, anc->formatted_cylinders, geo.heads); } part_info = part_info->next; } /* over-write old labels with new ones */ memcpy(anc->f4, &f4, sizeof(format4_label_t)); memcpy(anc->f5, &f5, sizeof(format5_label_t)); memcpy(anc->f7, &f7, sizeof(format7_label_t)); if (!anc->silent) printf("ok\n"); anc->vtoc_changed++; } /* * Changes the volume serial (menu option) */ static void fdasd_change_volser(fdasd_anchor_t *anc) { char volser[VOLSER_LENGTH + 1]; vtoc_volume_label_get_volser(anc->vlabel, volser); printf("Please specify new volume serial (6 characters).\n"); printf("current : %-6.6s\nnew [0X%04x]: ", volser, anc->devno); read_line(); fdasd_check_volser(line_ptr, anc->devno); printf("\nvolume identifier changed to '%-6s'\n", line_ptr); vtoc_volume_label_set_volser(anc->vlabel, line_ptr); vtoc_set_cchhb(&anc->vlabel->vtoc, VTOC_START_CC, VTOC_START_HH, 0x01); anc->vlabel_changed++; anc->vtoc_changed++; } /* * changes the partition type */ static void fdasd_change_part_type(fdasd_anchor_t *anc) { unsigned int part_id, part_type, i; partition_info_t *part_info; char *dsname = NULL; char str[20], *ch; fdasd_list_partition_table(anc); /* ask for partition number */ printf("\nchange partition type\n"); while (!isdigit(part_id = read_char("partition id (use 0 to exit): "))) printf("Invalid partition id '%c' detected.\n", part_id); part_id -= 48; printf("\n"); if (part_id == 0) return; if (part_id > anc->used_partitions) { printf("'%d' is not a valid partition id!\n", part_id); return; } part_info = anc->first; for (i = 1; i < part_id; i++) part_info = part_info->next; /* ask for partition type */ vtoc_ebcdic_dec(part_info->f1->DS1DSNAM, part_info->f1->DS1DSNAM, 44); ch = strstr(part_info->f1->DS1DSNAM, "PART") + 9; if (ch != NULL) { strncpy(str, ch, 6); str[6] = '\0'; } else { strcpy(str, "error"); } printf("current partition type is: %s\n\n", fdasd_partition_type(str)); fdasd_list_known_partitions(); printf("\n"); part_type = 0; while (part_type < 1 || part_type > UTIL_ARRAY_SIZE(partition_types) - 1) { do { part_type = read_char("new partition type: "); } while (!isdigit(part_type)); part_type -= 48; } if (get_part_dsname_by_type(part_type, &dsname)) printf("'%d' is not supported!\n", part_type); else snprintf(str, 7, "%-6s", dsname); ch = strstr(part_info->f1->DS1DSNAM, "PART") + 9; if (ch != NULL) strncpy(ch, str, 6); vtoc_ebcdic_enc(part_info->f1->DS1DSNAM, part_info->f1->DS1DSNAM, 44); anc->vtoc_changed++; } /* * initialize the VOL1 volume label */ static void fdasd_init_volume_label(fdasd_anchor_t *anc) { volume_label_t *vlabel = anc->vlabel; char volser[VOLSER_LENGTH + 1]; vtoc_volume_label_init(vlabel); vtoc_volume_label_set_key(vlabel, "VOL1"); vtoc_volume_label_set_label(vlabel, "VOL1"); if (anc->keep_volser) { if (fdasd_get_volser(anc, volser) == 0) vtoc_volume_label_set_volser(vlabel, volser); else fdasd_error(anc, volser_not_found, options.device); } else if (options.volser) { fdasd_check_volser(options.volser, anc->devno); vtoc_volume_label_set_volser(vlabel, options.volser); } else if (anc->auto_partition || options.conffile) { sprintf(volser, "0X%04x", anc->devno); vtoc_volume_label_set_volser(vlabel, volser); } else { printf("\nPlease specify volume serial (6 characters)" "[0X%04x]: ", anc->devno); read_line(); fdasd_check_volser(line_ptr, anc->devno); vtoc_volume_label_set_volser(vlabel, line_ptr); } vtoc_set_cchhb(&vlabel->vtoc, VTOC_START_CC, VTOC_START_HH, 0x01); anc->vlabel_changed++; } /* * sets some important partition data * (like used, start_trk, end_trk, len_trk) * by calculating these values with the * information provided in the labels */ static void fdasd_update_partition_info(fdasd_anchor_t *anc) { partition_info_t *prev_part_info = NULL, *part_info = anc->first; unsigned long max = anc->formatted_cylinders * geo.heads - 1; int i; anc->used_partitions = geo.sectors - 2 - anc->f4->DS4DSREC; for (i = 1; i <= USABLE_PARTITIONS; i++) { if (part_info->f1->DS1FMTID != 0xf1 && part_info->f1->DS1FMTID != 0xf8) { if (i == 1) /* there is no partition at all */ anc->fspace_trk = max - FIRST_USABLE_TRK + 1; else /* previous partition was the last one */ prev_part_info->fspace_trk = max - prev_part_info->end_trk; break; } /* this is a valid format 1 label */ part_info->used = 0x01; part_info->start_trk = cchh2trk(&part_info->f1->DS1EXT1.llimit, &geo); part_info->end_trk = cchh2trk(&part_info->f1->DS1EXT1.ulimit, &geo); part_info->len_trk = part_info->end_trk - part_info->start_trk + 1; if (i == 1) { /* first partition, there is at least one */ anc->fspace_trk = part_info->start_trk - FIRST_USABLE_TRK; } else { if (i == USABLE_PARTITIONS) /* last possible partition */ part_info->fspace_trk = max - part_info->end_trk; /* set free space values of previous partition */ prev_part_info->fspace_trk = part_info->start_trk - prev_part_info->end_trk - 1; } prev_part_info = part_info; part_info = part_info->next; } } /* * reorganizes all FMT1s, move all empty labels to the end */ static void fdasd_reorganize_FMT1s(fdasd_anchor_t *anc) { partition_info_t *part_info; format1_label_t *f1_label; int i, j; for (i = 1; i <= USABLE_PARTITIONS - 1; i++) { part_info = anc->first; for (j = 1; j <= USABLE_PARTITIONS - i; j++) { if (part_info->f1->DS1FMTID < part_info->next->f1->DS1FMTID) { f1_label = part_info->f1; part_info->f1 = part_info->next->f1; part_info->next->f1 = f1_label; } part_info = part_info->next; } } } /* * we have a invalid FMT4 DSCB and therefore we will re-create the VTOC */ static void fdasd_process_invalid_vtoc(fdasd_anchor_t *anc) { printf(" invalid\ncreating new VTOC...\n"); if (anc->hw_cylinders > LV_COMPAT_CYL) { printf("Warning: Device has more then %u cylinders!\n", LV_COMPAT_CYL); if (yes_no("Are you sure it was completely" " formatted with dasdfmt?") == 1) { if (!anc->silent) printf("exiting...\n"); fdasd_exit(anc, 0); } } anc->formatted_cylinders = anc->hw_cylinders; anc->fspace_trk = anc->formatted_cylinders * geo.heads - FIRST_USABLE_TRK; vtoc_init_format4_label(anc->f4, geo.cylinders, anc->formatted_cylinders, geo.heads, geo.sectors, anc->blksize, anc->dev_type); vtoc_init_format5_label(anc->f5); vtoc_init_format7_label(anc->f7); vtoc_set_freespace(anc->f4, anc->f5, anc->f7, '+', anc->verbose, FIRST_USABLE_TRK, anc->formatted_cylinders * geo.heads - 1, anc->formatted_cylinders, geo.heads); anc->vtoc_changed++; } /* * */ static void fdasd_process_valid_vtoc(fdasd_anchor_t *anc, unsigned long blk) { int f1_counter = 0, f7_counter = 0, f5_counter = 0; int i, part_no, f1_size = sizeof(format1_label_t); partition_info_t *part_info = anc->first; char part_no_str[5], *part_pos; format1_label_t f1_label; if (!anc->silent) printf(" ok\n"); if (anc->f4->DS4DEVCT.DS4DSCYL == LV_COMPAT_CYL && anc->f4->DS4DCYL > anc->f4->DS4DEVCT.DS4DSCYL) anc->formatted_cylinders = anc->f4->DS4DCYL; else anc->formatted_cylinders = anc->f4->DS4DEVCT.DS4DSCYL; anc->fspace_trk = anc->formatted_cylinders * geo.heads - FIRST_USABLE_TRK; /* skip f4 label, already read before */ blk += anc->blksize; if (anc->formatted_cylinders < anc->hw_cylinders) printf("WARNING: This device is not fully formatted! " "Only %u of %u cylinders are available.\n", anc->formatted_cylinders, anc->hw_cylinders); if (anc->verbose) printf("VTOC DSCBs : "); /* go through remaining labels, f4 label already done */ for (i = 1; i < geo.sectors; i++) { bzero(&f1_label, f1_size); vtoc_read_label(options.device, blk, &f1_label, NULL, NULL, NULL); switch (f1_label.DS1FMTID) { case 0xf1: case 0xf8: if (anc->verbose) printf("%s ", f1_label.DS1FMTID == 0xf1 ? "f1" : "f8"); if (part_info == NULL) break; memcpy(part_info->f1, &f1_label, f1_size); part_no = -1; vtoc_ebcdic_dec(part_info->f1->DS1DSNAM, part_info->f1->DS1DSNAM, 44); part_pos = strstr(part_info->f1->DS1DSNAM, "PART"); if (part_pos != NULL) { strncpy(part_no_str, part_pos + 4, 4); part_no_str[4] = '\0'; part_no = atoi(part_no_str) - 1; } vtoc_ebcdic_enc(part_info->f1->DS1DSNAM, part_info->f1->DS1DSNAM, 44); if (part_no < 0 || part_no >= USABLE_PARTITIONS) printf("WARNING: partition number (%i) found " "in data set name of an existing " "partition\ndoes not match range of " "possible partition numbers (1-%d)\n\n", part_no + 1, USABLE_PARTITIONS); else setpos(anc, part_no, f1_counter); part_info = part_info->next; f1_counter++; break; case 0xf5: if (anc->verbose) printf("f5 "); memcpy(anc->f5, &f1_label, f1_size); f5_counter++; break; case 0xf7: if (anc->verbose) printf("f7 "); if (f7_counter == 0) memcpy(anc->f7, &f1_label, f1_size); f7_counter++; break; case 0xf9: /* each format 8 lable has an associated format 9 lable, * but they are of no further use to us. */ if (anc->verbose) printf("f9 "); break; default: if (f1_label.DS1FMTID > 0) printf("'%d' is not supported!\n", f1_label.DS1FMTID); } blk += anc->blksize; } if (anc->verbose) printf("\n"); if (f5_counter == 0 || anc->big_disk) vtoc_init_format5_label(anc->f5); if (f7_counter == 0) vtoc_init_format7_label(anc->f7); fdasd_reorganize_FMT1s(anc); fdasd_update_partition_info(anc); } /* * we have a valid VTOC pointer, let's go and read the VTOC labels */ static int fdasd_valid_vtoc_pointer(fdasd_anchor_t *anc, unsigned long blk) { /* VOL1 label contains valid VTOC pointer */ if (!anc->silent) printf("reading vtoc ..........:"); vtoc_read_label(options.device, blk, NULL, anc->f4, NULL, NULL); if (anc->f4->DS4IDFMT != 0xf4) { if (anc->print_table) { printf("Your VTOC is corrupted!\n"); return -1; } fdasd_process_invalid_vtoc(anc); } else { fdasd_process_valid_vtoc(anc, blk); } return 0; } /* * */ static void fdasd_invalid_vtoc_pointer(fdasd_anchor_t *anc) { /* VOL1 label doesn't contain valid VTOC pointer */ if (yes_no("There is no VTOC yet, should I create one?") == 1) { if (!anc->silent) printf("exiting...\n"); fdasd_exit(anc, 0); } if (anc->hw_cylinders > LV_COMPAT_CYL) { printf("Warning: Device has more then %u cylinders!\n", LV_COMPAT_CYL); if (yes_no("Are you sure it was completely" " formatted with dasdfmt?") == 1) { if (!anc->silent) printf("exiting...\n"); fdasd_exit(anc, 0); } } anc->formatted_cylinders = anc->hw_cylinders; anc->fspace_trk = anc->formatted_cylinders * geo.heads - FIRST_USABLE_TRK; vtoc_init_format4_label(anc->f4, geo.cylinders, anc->formatted_cylinders, geo.heads, geo.sectors, anc->blksize, anc->dev_type); vtoc_init_format5_label(anc->f5); vtoc_init_format7_label(anc->f7); vtoc_set_freespace(anc->f4, anc->f5, anc->f7, '+', anc->verbose, FIRST_USABLE_TRK, anc->formatted_cylinders * geo.heads - 1, anc->formatted_cylinders, geo.heads); vtoc_set_cchhb(&anc->vlabel->vtoc, VTOC_START_CC, VTOC_START_HH, 0x01); anc->vtoc_changed++; anc->vlabel_changed++; } /* * check the dasd for a volume label */ static int fdasd_check_volume(fdasd_anchor_t *anc) { volume_label_t *vlabel = anc->vlabel; char inp_buf[INPUT_BUF_SIZE]; char str[LINE_LENGTH]; long long blk = -1; int rc = 1; if (!anc->silent) printf("reading volume label ..:"); vtoc_read_volume_label(options.device, anc->label_pos, vlabel); if (strncmp(vlabel->vollbl, vtoc_ebcdic_enc("VOL1", str, 4), 4) == 0) { /* found VOL1 volume label */ if (!anc->silent) printf(" VOL1\n"); blk = (cchhb2blk(&vlabel->vtoc, &geo) - 1) * anc->blksize; if (blk > 0) { rc = fdasd_valid_vtoc_pointer(anc, blk); if (anc->print_table && (rc < 0)) return -1; } else { if (anc->print_table) { printf("\nFound invalid VTOC pointer.\n"); return -1; } fdasd_invalid_vtoc_pointer(anc); } } else { /* didn't find VOL1 volume label */ if (anc->print_table || anc->print_volser) { printf("\nCannot show requested information because " "the disk label block is invalid\n"); return -1; } if (strncmp(vlabel->vollbl, vtoc_ebcdic_enc("LNX1", str, 4), 4) == 0) { if (!anc->silent) printf(" LNX1\n"); strcpy(inp_buf, "Overwrite inapplicable label?"); } else { if (!anc->silent) printf(" no known label\n"); if (!anc->auto_partition && !options.conffile) rc = yes_no("Should I create a new one?"); else rc = 0; } if (!anc->print_volser && !anc->print_table && rc == 1) { printf("Disc does not contain a VOL1 label, cannot " "create partitions.\nexiting...\n"); fdasd_exit(anc, -1); } if (anc->hw_cylinders > LV_COMPAT_CYL) { printf("Warning: Device has more than %u cylinders!\n", LV_COMPAT_CYL); if (!anc->auto_partition && !options.conffile && yes_no("Are you sure it was completely" " formatted with dasdfmt?") == 1) { if (!anc->silent) printf("exiting...\n"); fdasd_exit(anc, 0); } } anc->formatted_cylinders = anc->hw_cylinders; anc->fspace_trk = anc->formatted_cylinders * geo.heads - FIRST_USABLE_TRK; fdasd_init_volume_label(anc); vtoc_init_format4_label(anc->f4, geo.cylinders, anc->formatted_cylinders, geo.heads, geo.sectors, anc->blksize, anc->dev_type); vtoc_init_format5_label(anc->f5); vtoc_init_format7_label(anc->f7); vtoc_set_freespace(anc->f4, anc->f5, anc->f7, '+', anc->verbose, FIRST_USABLE_TRK, anc->formatted_cylinders * geo.heads - 1, anc->formatted_cylinders, geo.heads); anc->vtoc_changed++; } if (!anc->silent) printf("\n"); return 0; } /* * check disk access */ static void fdasd_check_disk_access(fdasd_anchor_t *anc) { char err_str[ERROR_STRING_SIZE]; format1_label_t f1; int fd, pos; bool ro; fd = open(options.device, O_RDONLY); if (fd == -1) { snprintf(err_str, ERROR_STRING_SIZE, "Could not open device '%s' " "in read-only mode!\n", options.device); fdasd_error(anc, unable_to_open_disk, err_str); } pos = anc->blksize * (2 * geo.heads - 1); /* last block in the second track */ if (lseek(fd, pos, SEEK_SET) == -1) { close(fd); snprintf(err_str, ERROR_STRING_SIZE, "Could not seek device '%s'.", options.device); fdasd_error(anc, unable_to_seek_disk, err_str); } if (read(fd, &f1, sizeof(format1_label_t)) != sizeof(format1_label_t)) { close(fd); snprintf(err_str, ERROR_STRING_SIZE, "Could not read from device '%s'.", options.device); fdasd_error(anc, unable_to_read_disk, err_str); } if (lseek(fd, pos, SEEK_SET) == -1) { close(fd); snprintf(err_str, ERROR_STRING_SIZE, "Could not seek device '%s'.", options.device); fdasd_error(anc, unable_to_seek_disk, err_str); } close(fd); if (dasd_is_ro(options.device, &ro) != 0) { snprintf(err_str, ERROR_STRING_SIZE, "Could not get read-only status for device '%s'.", options.device); fdasd_error(anc, unable_to_ioctl, err_str); } if (ro && !anc->print_volser && !anc->print_table) { printf("\nWARNING: Device '%s' is a read-only device!\n" "You will not be able to save any changes.\n\n", options.device); } } /* * The following two functions match those in the DASD ECKD device driver. * They are used to compute how many records of a given size can be stored * in one track. */ static unsigned int ceil_quot(unsigned int d1, unsigned int d2) { return (d1 + (d2 - 1)) / d2; } /* kl: key length, dl: data length */ static unsigned int recs_per_track(unsigned short dev_type, unsigned int kl, unsigned int dl) { int dn, kn; switch (dev_type) { case DASD_3380_TYPE: if (kl) return 1499 / (15 + 7 + ceil_quot(kl + 12, 32) + ceil_quot(dl + 12, 32)); else return 1499 / (15 + ceil_quot(dl + 12, 32)); case DASD_3390_TYPE: dn = ceil_quot(dl + 6, 232) + 1; if (kl) { kn = ceil_quot(kl + 6, 232) + 1; return 1729 / (10 + 9 + ceil_quot(kl + 6 * kn, 34) + 9 + ceil_quot(dl + 6 * dn, 34)); } else { return 1729 / (10 + 9 + ceil_quot(dl + 6 * dn, 34)); } case DASD_9345_TYPE: dn = ceil_quot(dl + 6, 232) + 1; if (kl) { kn = ceil_quot(kl + 6, 232) + 1; return 1420 / (18 + 7 + ceil_quot(kl + 6 * kn, 34) + ceil_quot(dl + 6 * dn, 34)); } else { return 1420 / (18 + 7 + ceil_quot(dl + 6 * dn, 34)); } } return 0; } /* * Verify that number of tracks (heads) per cylinder and number of * sectors per track match the expected values for a given device type * and block size. * Returns 1 for a valid match and 0 otherwise. */ static int fdasd_verify_geometry(unsigned short dev_type, int blksize, struct hd_geometry *geometry) { unsigned int expected_sectors; if (geometry->heads != 15) return 0; expected_sectors = recs_per_track(dev_type, 0, blksize); if (geometry->sectors == expected_sectors) return 1; return 0; } /* * reads dasd geometry data */ static void fdasd_get_geometry(fdasd_anchor_t *anc) { struct dasd_eckd_characteristics *characteristics; unsigned long long size_in_bytes; char err_str[ERROR_STRING_SIZE]; dasd_information2_t dasd_info; unsigned int blksize = 0; if (dasd_get_blocksize_in_bytes(options.device, &size_in_bytes) != 0) fdasd_error(anc, unable_to_ioctl, "Could not retrieve disk size."); /* * If anc->force_virtual is set, we do no real geometry detection. * anc->dev_type and anc->blksize have already been set via command * line parameter, and the rest of the geometry is now computed from * these values. */ if (anc->force_virtual) { geo.heads = 15; geo.sectors = recs_per_track(anc->dev_type, 0, anc->blksize); anc->hw_cylinders = size_in_bytes / (anc->blksize * geo.heads * geo.sectors); if (anc->hw_cylinders < LV_COMPAT_CYL) geo.cylinders = anc->hw_cylinders; else geo.cylinders = LV_COMPAT_CYL; geo.start = 0; anc->label_pos = 2 * anc->blksize; anc->devno = 0; if (anc->verbose) { printf("The force option is active. " "The following geometry will be used:\n" "device type %x, block size %d, cylinders %d," " heads %d, sectors %d\n", anc->dev_type, anc->blksize, anc->hw_cylinders, geo.heads, geo.sectors); } return; } if (dasd_get_geo(options.device, &geo) != 0) { fdasd_error(anc, unable_to_ioctl, "Could not retrieve disk geometry information."); } if (dasd_get_blocksize(options.device, &blksize) != 0) { fdasd_error(anc, unable_to_ioctl, "Could not retrieve blocksize information."); } /* get disk type */ if (dasd_get_info(options.device, &dasd_info) != 0) { if (anc->verbose) printf("BIODASDINFO ioctl failed," " use disk geometry only.\n"); /* verify that the geometry matches a 3390 DASD */ if (!fdasd_verify_geometry(DASD_3390_TYPE, blksize, &geo)) { fdasd_error(anc, wrong_disk_type, "Disk geometry does not match a DASD device" " of type 3390."); } anc->dev_type = DASD_3390_TYPE; anc->blksize = blksize; anc->hw_cylinders = size_in_bytes / (blksize * geo.heads * geo.sectors); /* The VOL1 label on a CDL formatted ECKD DASD is in block 2 * It will be verified later, if this position actually holds a * valid label record. */ anc->label_pos = 2 * blksize; /* A devno 0 is actually a valid devno, which could exist * in the system. Since we use this number only to create * a default volume serial, there is no serious conflict. */ anc->devno = 0; if (anc->verbose) { printf("The following device geometry will be used:\n" "device type %x, block size %d, cylinders %d," " heads %d, sectors %d\n", anc->dev_type, anc->blksize, anc->hw_cylinders, geo.heads, geo.sectors); } } else { characteristics = (struct dasd_eckd_characteristics *) &dasd_info.characteristics; if (characteristics->no_cyl == LV_COMPAT_CYL && characteristics->long_no_cyl) anc->hw_cylinders = characteristics->long_no_cyl; else anc->hw_cylinders = characteristics->no_cyl; if (strncmp(dasd_info.type, "ECKD", 4) != 0) { snprintf(err_str, ERROR_STRING_SIZE, "%s is not an ECKD disk! This disk type " "is not supported!", options.device); fdasd_error(anc, wrong_disk_type, err_str); } if (anc->verbose) printf("disk type check : ok\n"); if (dasd_info.FBA_layout != 0) { snprintf(err_str, ERROR_STRING_SIZE, "%s is not formatted with z/OS compatible " "disk layout!", options.device); fdasd_error(anc, wrong_disk_format, err_str); } if (anc->verbose) printf("disk layout check : ok\n"); if (dasd_info.open_count > 1) { if (anc->auto_partition) { snprintf(err_str, ERROR_STRING_SIZE, "DASD '%s' is in use. Unmount it first!", options.device); fdasd_error(anc, disk_in_use, err_str); } else { printf("\nWARNING: Your DASD '%s' is in use.\n" " If you proceed, you can " "heavily damage your system.\n" " If possible exit all" " applications using this disk\n" " and/or unmount it.\n\n", options.device); } } if (anc->verbose) printf("usage count check : ok\n"); anc->dev_type = dasd_info.dev_type; anc->blksize = blksize; anc->label_pos = dasd_info.label_block * blksize; anc->devno = dasd_info.devno; } } /* * asks for partition boundaries */ static unsigned long fdasd_read_int(unsigned long low, unsigned long dflt, unsigned long high, enum offset base, char *mesg, fdasd_anchor_t *anc) { unsigned int use_default = 1; unsigned long long trk = 0; char msg_txt[70]; switch (base) { case lower: sprintf(msg_txt, "%s ([%ld]-%ld): ", mesg, low, high); break; case upper: sprintf(msg_txt, "%s (%ld-[%ld]): ", mesg, low, high); break; default: sprintf(msg_txt, "%s (%ld-%ld): ", mesg, low, high); break; } while (1) { while (!isdigit(read_char(msg_txt)) && (*line_ptr != '-' && *line_ptr != '+' && *line_ptr != '\0')) continue; if ((*line_ptr == '+' || *line_ptr == '-') && base != lower) { if (*line_ptr == '+') ++line_ptr; trk = atoi(line_ptr); while (isdigit(*line_ptr)) { line_ptr++; use_default = 0; } switch (*line_ptr) { case 'c': case 'C': trk *= geo.heads; break; case 'k': case 'K': trk *= 1024; trk /= anc->blksize; trk /= geo.sectors; break; case 'm': case 'M': trk *= (1024 * 1024); trk /= anc->blksize; trk /= geo.sectors; break; case 'g': case 'G': trk *= (1024 * 1024 * 1024); trk /= anc->blksize; trk /= geo.sectors; break; case 0x0a: break; default: printf("WARNING: '%c' is not a " "valid appendix and probably " "not what you want!\n", *line_ptr); break; } trk += (low - 1); } else if (*line_ptr == '\0') { switch (base) { case lower: trk = low; break; case upper: trk = high; break; } } else { if (*line_ptr == '+' || *line_ptr == '-') { printf("\nWARNING: '%c' is not valid in\n" "this case and will be ignored!\n", *line_ptr); ++line_ptr; } trk = atoi(line_ptr); while (isdigit(*line_ptr)) { line_ptr++; use_default = 0; } if (*line_ptr != 0x0a) printf("\nWARNING: '%c' is not a valid " "appendix and probably not what " "you want!\n", *line_ptr); } if (use_default) printf("Using default value %lld\n", trk = dflt); else printf("You have selected track %lld\n", trk); if (trk >= low && trk <= high) break; else printf("Value out of range.\n"); } return trk; } /* * returns unused partition info pointer if there * is a free partition, otherwise NULL */ static partition_info_t *fdasd_get_empty_f1_label(fdasd_anchor_t *anc) { if (anc->used_partitions < USABLE_PARTITIONS) return anc->last; else return NULL; } /* * asks for and sets some important partition data */ static int fdasd_get_partition_data(fdasd_anchor_t *anc, extent_t *part_extent, partition_info_t *part_info) { unsigned long start, stop, limit; partition_info_t *part_tmp; cchh_t llimit, ulimit; u_int16_t hh, head; u_int32_t cc, cyl; u_int8_t b1, b2; char mesg[48]; start = FIRST_USABLE_TRK; cyl = get_usable_cylinders(anc); head = anc->f4->DS4DEVCT.DS4DSTRK; limit = (head * cyl - 1); sprintf(mesg, "First track (1 track = %d KByte)", geo.sectors * anc->blksize / 1024); /* find default start value */ for (part_tmp = anc->first; part_tmp->next != NULL; part_tmp = part_tmp->next) { if ((start >= part_tmp->start_trk) && (start <= part_tmp->end_trk)) start = part_tmp->end_trk + 1; } if (start > limit) { printf("Not that easy, no free tracks available.\n"); return -1; } /* read start value */ start = fdasd_read_int(start, start, limit, lower, mesg, anc); /* check start value from user */ for (part_tmp = anc->first; part_tmp->next != NULL; part_tmp = part_tmp->next) { if (start >= part_tmp->start_trk && start <= part_tmp->end_trk) { /* start is within another partition */ start = part_tmp->end_trk + 1; if (start > limit) { start = FIRST_USABLE_TRK; part_tmp = anc->first; } printf("value within another partition, " "using %ld instead\n", start); } if (start < part_tmp->start_trk) { limit = part_tmp->start_trk - 1; break; } } if (start == limit) { stop = start; } else { sprintf(mesg, "Last track or +size[c|k|m|g]"); stop = fdasd_read_int(start, limit, limit, upper, mesg, anc); } /* update partition info */ part_info->len_trk = stop - start + 1; part_info->start_trk = start; part_info->end_trk = stop; cc = start / geo.heads; hh = start - (cc * geo.heads); vtoc_set_cchh(&llimit, cc, hh); /* check for cylinder boundary */ if (hh == 0) b1 = 0x81; else b1 = 0x01; cc = stop / geo.heads; hh = stop - cc * geo.heads; vtoc_set_cchh(&ulimit, cc, hh); /* it is always the 1st extent */ b2 = 0x00; vtoc_set_extent(part_extent, b1, b2, &llimit, &ulimit); return 0; } /* * */ static void fdasd_enqueue_new_partition(fdasd_anchor_t *anc) { partition_info_t *part_tmp = anc->first, *part_info = anc->last; int i, j, k = 0; for (i = 1; i < USABLE_PARTITIONS; i++) { if ((part_tmp->end_trk == 0) || (part_info->start_trk < part_tmp->start_trk)) { break; } else { part_tmp = part_tmp->next; k++; } } if (anc->first == part_tmp) anc->first = part_info; if (part_info != part_tmp) { anc->last->prev->next = NULL; anc->last = anc->last->prev; part_info->next = part_tmp; part_info->prev = part_tmp->prev; part_tmp->prev = part_info; if (part_info->prev != NULL) part_info->prev->next = part_info; } part_info->used = 0x01; for (i = 0; i < USABLE_PARTITIONS; i++) { j = getpos(anc, i); if (j >= k) setpos(anc, i, j + 1); } /* update free-space counters */ if (anc->first == part_info) { /* partition is the first used partition */ if (part_info->start_trk == FIRST_USABLE_TRK) { /* partition starts right behind VTOC */ part_info->fspace_trk = anc->fspace_trk - part_info->len_trk; anc->fspace_trk = 0; } else { /* there is some space between VTOC and partition */ part_info->fspace_trk = anc->fspace_trk - part_info->len_trk - part_info->start_trk + FIRST_USABLE_TRK; anc->fspace_trk = part_info->start_trk - FIRST_USABLE_TRK; } } else { /* there are partitions in front of the new one */ if (part_info->start_trk == part_info->prev->end_trk + 1) { /* new partition is right behind the previous one */ part_info->fspace_trk = part_info->prev->fspace_trk - part_info->len_trk; part_info->prev->fspace_trk = 0; } else { /* there is some space between new and prev. part. */ part_info->fspace_trk = part_info->prev->fspace_trk - part_info->len_trk - part_info->start_trk + part_info->prev->end_trk + 1; part_info->prev->fspace_trk = part_info->start_trk - part_info->prev->end_trk - 1; } } } /* * */ static void fdasd_dequeue_old_partition(fdasd_anchor_t *anc, partition_info_t *part_info, int k) { int i, j; if (part_info != anc->first && part_info != anc->last) { /* dequeue any non-special element */ part_info->prev->next = part_info->next; part_info->next->prev = part_info->prev; } if (part_info == anc->first) { /* dequeue first element */ anc->first = part_info->next; part_info->next->prev = NULL; anc->fspace_trk += (part_info->len_trk + part_info->fspace_trk); } else { part_info->prev->fspace_trk += (part_info->len_trk + part_info->fspace_trk); } if (part_info != anc->last) { part_info->prev = anc->last; part_info->next = NULL; anc->last->next = part_info; anc->last = part_info; } for (i = 0; i < USABLE_PARTITIONS; i++) { j = getpos(anc, i); if (j >= k) setpos(anc, i, j - 1); } part_info->used = 0x00; part_info->len_trk = 0x0; part_info->start_trk = 0x0; part_info->end_trk = 0x0; part_info->fspace_trk = 0x0; bzero(part_info->f1, sizeof(format1_label_t)); } /* * adds a new partition to the 'partition table' */ static void fdasd_add_partition(fdasd_anchor_t *anc) { partition_info_t *part_info; unsigned long start, stop; extent_t ext; cchhb_t hf1; part_info = fdasd_get_empty_f1_label(anc); if (part_info == NULL) { printf("No more free partitions left,\n" "you have to delete one first!"); return; } if (fdasd_get_partition_data(anc, &ext, part_info) != 0) return; if (anc->formatted_cylinders > LV_COMPAT_CYL) vtoc_init_format8_label(anc->blksize, &ext, part_info->f1); else vtoc_init_format1_label(anc->blksize, &ext, part_info->f1); fdasd_enqueue_new_partition(anc); anc->used_partitions += 1; get_addr_of_highest_f1_f8_label(anc, &hf1); vtoc_update_format4_label(anc->f4, &hf1, anc->f4->DS4DSREC - 1); start = cchh2trk(&ext.llimit, &geo); stop = cchh2trk(&ext.ulimit, &geo); vtoc_set_freespace(anc->f4, anc->f5, anc->f7, '-', anc->verbose, start, stop, anc->formatted_cylinders, geo.heads); anc->vtoc_changed++; } /* * removes a partition from the 'partition table' */ static void fdasd_remove_partition(fdasd_anchor_t *anc) { partition_info_t *part_info = anc->first; unsigned long start, stop; unsigned int part_id, i; cchhb_t hf1; fdasd_list_partition_table(anc); while (!isdigit(part_id = read_char("\ndelete partition with id " "(use 0 to exit): "))) printf("Invalid partition id '%c' detected.\n", part_id); printf("\n"); part_id -= 48; if (part_id == 0) return; if (part_id > anc->used_partitions) { printf("'%d' is not a valid partition id!\n", part_id); return; } printf("deleting partition number '%d'...\n", part_id); setpos(anc, part_id - 1, -1); for (i = 1; i < part_id; i++) part_info = part_info->next; start = cchh2trk(&part_info->f1->DS1EXT1.llimit, &geo); stop = cchh2trk(&part_info->f1->DS1EXT1.ulimit, &geo); fdasd_dequeue_old_partition(anc, part_info, part_id - 1); anc->used_partitions -= 1; if (anc->used_partitions != 0) get_addr_of_highest_f1_f8_label(anc, &hf1); else bzero(&hf1, sizeof(struct cchhb)); vtoc_update_format4_label(anc->f4, &hf1, anc->f4->DS4DSREC + 1); vtoc_set_freespace(anc->f4, anc->f5, anc->f7, '+', anc->verbose, start, stop, anc->formatted_cylinders, geo.heads); anc->vtoc_changed++; } /* * writes a standard volume label and a standard VTOC with * only one partition to disc. With this function is it * possible to create one partition in non-interactive mode, * which can be used within shell scripts */ static void fdasd_auto_partition(fdasd_anchor_t *anc) { partition_info_t *part_info = anc->first; cchh_t llimit, ulimit; u_int16_t head; u_int32_t cyl; extent_t ext; cchhb_t hf1; if (!anc->silent) printf("auto-creating one partition for the whole disk...\n"); fdasd_init_volume_label(anc); if (anc->verbose) printf("initializing labels...\n"); vtoc_init_format4_label(anc->f4, geo.cylinders, anc->formatted_cylinders, geo.heads, geo.sectors, anc->blksize, anc->dev_type); vtoc_init_format5_label(anc->f5); vtoc_init_format7_label(anc->f7); cyl = get_usable_cylinders(anc); head = anc->f4->DS4DEVCT.DS4DSTRK; part_info->used = 0x01; part_info->fspace_trk = 0; part_info->len_trk = head * cyl - FIRST_USABLE_TRK; part_info->start_trk = FIRST_USABLE_TRK; part_info->end_trk = head * cyl - 1; vtoc_set_cchh(&llimit, 0, FIRST_USABLE_TRK); vtoc_set_cchh(&ulimit, cyl - 1, head - 1); vtoc_set_extent(&ext, 0x01, 0x00, &llimit, &ulimit); if (anc->formatted_cylinders > LV_COMPAT_CYL) vtoc_init_format8_label(anc->blksize, &ext, part_info->f1); else vtoc_init_format1_label(anc->blksize, &ext, part_info->f1); anc->fspace_trk = 0; anc->used_partitions = 1; get_addr_of_highest_f1_f8_label(anc, &hf1); vtoc_update_format4_label(anc->f4, &hf1, anc->f4->DS4DSREC - 1); anc->vtoc_changed++; fdasd_write_labels(anc); fdasd_exit(anc, 0); } /* * does the partitioning regarding to the config file */ static void fdasd_auto_partition_conffile(fdasd_anchor_t *anc) { partition_info_t *part_info = anc->first; unsigned long start, stop; cchh_t llimit, ulimit; char *dsname = NULL; extent_t ext; cchhb_t hf1; char *type; fdasd_init_volume_label(anc); if (anc->verbose) printf("initializing labels...\n"); vtoc_init_format4_label(anc->f4, geo.cylinders, anc->formatted_cylinders, geo.heads, geo.sectors, anc->blksize, anc->dev_type); vtoc_init_format5_label(anc->f5); vtoc_init_format7_label(anc->f7); if (anc->fspace_trk != 0) { start = FIRST_USABLE_TRK; stop = start + anc->fspace_trk - 1; vtoc_set_freespace(anc->f4, anc->f5, anc->f7, '+', anc->verbose, start, stop, anc->formatted_cylinders, geo.heads); } do { if (part_info->used != 0x01) continue; vtoc_set_cchh(&llimit, part_info->start_trk / geo.heads, part_info->start_trk % geo.heads); vtoc_set_cchh(&ulimit, part_info->end_trk / geo.heads, part_info->end_trk % geo.heads); vtoc_set_extent(&ext, (vtoc_get_head_from_cchh(&llimit) == 0 ? 0x81 : 0x01), 0x00, &llimit, &ulimit); if (anc->formatted_cylinders > LV_COMPAT_CYL) vtoc_init_format8_label(anc->blksize, &ext, part_info->f1); else vtoc_init_format1_label(anc->blksize, &ext, part_info->f1); anc->used_partitions += 1; get_addr_of_highest_f1_f8_label(anc, &hf1); vtoc_update_format4_label(anc->f4, &hf1, anc->f4->DS4DSREC - 1); /* update free space labels */ if (part_info->fspace_trk != 0) { start = part_info->end_trk + 1; stop = start + part_info->fspace_trk - 1; vtoc_set_freespace(anc->f4, anc->f5, anc->f7, '+', anc->verbose, start, stop, anc->formatted_cylinders, geo.heads); } /* write correct partition type */ vtoc_ebcdic_dec(part_info->f1->DS1DSNAM, part_info->f1->DS1DSNAM, 44); type = strstr(part_info->f1->DS1DSNAM, ".NEW"); get_part_dsname_by_type(part_info->type, &dsname); sprintf(type, ".%s", dsname); vtoc_ebcdic_enc(part_info->f1->DS1DSNAM, part_info->f1->DS1DSNAM, 44); } while ((part_info = part_info->next) != NULL); anc->vtoc_changed++; fdasd_write_labels(anc); fdasd_exit(anc, 0); } /* * quits fdasd without saving */ static void fdasd_quit(fdasd_anchor_t *anc) { char str[INPUT_BUF_SIZE]; if (anc->vtoc_changed || anc->vlabel_changed) { snprintf(str, INPUT_BUF_SIZE, "All changes will be lost! " "Do you really want to quit?"); if (yes_no(str) == 1) return; printf("exiting without saving...\n"); } else { if (!anc->silent) printf("exiting...\n"); } fdasd_exit(anc, 0); } /* * */ int main(int argc, char *argv[]) { fdasd_anchor_t anchor; int rc = 0; fdasd_initialize_anchor(&anchor); fdasd_parse_options(&anchor, &options, argc, argv); fdasd_verify_device(&anchor, options.device); fdasd_verify_options(&anchor); fdasd_get_geometry(&anchor); fdasd_check_disk_access(&anchor); /* check dasd for labels and vtoc */ rc = fdasd_check_volume(&anchor); if (anchor.formatted_cylinders * geo.heads > BIG_DISK_SIZE) anchor.big_disk++; if (anchor.auto_partition) { fdasd_recreate_vtoc_unconditional(&anchor); fdasd_auto_partition(&anchor); } if (options.conffile) { fdasd_recreate_vtoc_unconditional(&anchor); fdasd_parse_conffile(&anchor, &options); fdasd_check_conffile_input(&anchor, &options); fdasd_auto_partition_conffile(&anchor); } if (anchor.print_volser) { fdasd_print_volser(&anchor); fdasd_quit(&anchor); } if (anchor.print_table) { if (rc == 0) fdasd_list_partition_table(&anchor); fdasd_quit(&anchor); } fdasd_menu(); while (1) { putchar('\n'); switch (tolower(read_char("Command (m for help): "))) { case 'd': fdasd_remove_partition(&anchor); break; case 'n': fdasd_add_partition(&anchor); break; case 'v': fdasd_change_volser(&anchor); break; case 't': fdasd_change_part_type(&anchor); break; case 'p': fdasd_list_partition_table(&anchor); break; case 'l': printf("\n"); fdasd_list_known_partitions(); break; case 's': fdasd_show_mapping(&anchor); break; case 'u': anchor.option_reuse++; break; case 'r': anchor.option_recreate++; break; case 'm': fdasd_menu(); break; case 'q': fdasd_quit(&anchor); break; case 'w': fdasd_write_labels(&anchor); fdasd_exit(&anchor, 0); default: printf("please use one of the following commands:\n"); fdasd_menu(); } if (anchor.option_reuse) { fdasd_reuse_vtoc(&anchor); anchor.option_reuse = 0; } if (anchor.option_recreate) { fdasd_recreate_vtoc(&anchor); anchor.option_recreate = 0; } } return -1; } s390-tools-2.3.0/fdasd/fdasd.h000066400000000000000000000067421323410431100156640ustar00rootroot00000000000000/* * fdasd - Create or modify partitions on ECKD DASDs * * Copyright IBM Corp. 2001, 2017 * * s390-tools is free software; you can redistribute it and/or modify * it under the terms of the MIT license. See LICENSE for details. */ #ifndef FDASD_H #define FDASD_H /***************************************************************************** * SECTION: Definitions needed for DASD-API (see dasd.h) * *****************************************************************************/ #define DASD_PARTN_BITS 2 /***************************************************************************** * SECTION: FDASD internal types * *****************************************************************************/ #define PARTN_MASK ((1 << DASD_PARTN_BITS) - 1) #define USABLE_PARTITIONS ((1 << DASD_PARTN_BITS) - 1) #define DEFAULT_FDASD_CONF "/etc/fdasd.conf" /* default config file */ #define CONFIG_FILE_SIZE (USABLE_PARTITIONS * LINE_LENGTH) #define CONFIG_MAX 3 /* maximum number of parameters per config file entry */ #define FDASD_ERROR "fdasd error: " #define DEVICE "device" #define DISC "disc" #define PART "part" #define ALTERNATE_CYLINDERS_USED 0x10 /* partition types */ #define PARTITION_NEW 0 #define PARTITION_NATIVE 1 #define PARTITION_SWAP 2 #define PARTITION_RAID 3 #define PARTITION_LVM 4 #define PARTITION_GPFS 5 /* * PARTITION_NEW is the first item in our partition_types array and technically * maps to PARTITION_NATIVE. As PARTITION_NEW isn't a valid partition_type, it * can be ignored. Use this offset when iterate over the array. */ #define VALID_PARTITION_OFFSET 1 typedef struct partition_type { char *name; /* User-friendly Name */ char *dsname; /* Data Set Name */ int type; /* Numerical Representation */ } partition_type_t; struct fdasd_options { char *device; char *volser; char *conffile; }; static struct fdasd_options options = { NULL, /* device */ NULL, /* volser */ NULL, /* conffile */ }; typedef struct partition_info { u_int8_t used; unsigned long start_trk; unsigned long end_trk; unsigned long len_trk; unsigned long fspace_trk; format1_label_t *f1; int type; struct partition_info *next; struct partition_info *prev; } partition_info_t; typedef struct config_data { unsigned long start; unsigned long stop; int type; } config_data_t; typedef struct fdasd_anchor { int vlabel_changed; int vtoc_changed; int auto_partition; int print_table; int print_volser; int keep_volser; int force_virtual; int force_host; int big_disk; int silent; int verbose; int devno; int option_reuse; int option_recreate; int partno[USABLE_PARTITIONS]; u_int16_t dev_type; unsigned int used_partitions; unsigned long label_pos; unsigned int blksize; unsigned long fspace_trk; format4_label_t *f4; format5_label_t *f5; format7_label_t *f7; format9_label_t *f9; /* template for all f9 labels */ partition_info_t *first; partition_info_t *last; volume_label_t *vlabel; config_data_t confdata[USABLE_PARTITIONS]; u_int32_t hw_cylinders; u_int32_t formatted_cylinders; } fdasd_anchor_t; enum offset {lower, upper}; enum fdasd_failure { parser_failed, unable_to_open_disk, unable_to_seek_disk, unable_to_read_disk, read_only_disk, unable_to_ioctl, wrong_disk_type, wrong_disk_format, disk_in_use, config_syntax_error, vlabel_corrupted, dsname_corrupted, malloc_failed, device_verification_failed, volser_not_found }; #define ERROR_STRING_SIZE 1024 #define INPUT_BUF_SIZE 1024 #endif /* FDASD_H */ s390-tools-2.3.0/hmcdrvfs/000077500000000000000000000000001323410431100151545ustar00rootroot00000000000000s390-tools-2.3.0/hmcdrvfs/Makefile000066400000000000000000000024711323410431100166200ustar00rootroot00000000000000#!/usr/bin/make -f include ../common.mak ifeq (${HAVE_FUSE},0) all: $(SKIP) HAVE_FUSE=0 install: $(SKIP) HAVE_FUSE=0 else check_dep: $(call check_dep, \ "hmcdrvfs", \ "fuse.h", \ "fuse-devel or libfuse-dev", \ "HAVE_FUSE=0") ifneq ($(shell sh -c 'command -v pkg-config'),) FUSE_CFLAGS = $(shell pkg-config --silence-errors --cflags fuse) FUSE_LDLIBS = $(shell pkg-config --silence-errors --libs fuse) else FUSE_CFLAGS = -D_FILE_OFFSET_BITS=64 -I/usr/include/fuse FUSE_LDLIBS = -lfuse endif ALL_CFLAGS += -DFUSE_USE_VERSION=26 -D_LARGEFILE_SOURCE $(FUSE_CFLAGS) LDLIBS += $(FUSE_LDLIBS) -lpthread -lrt -ldl -lm OBJECTS = hmcdrvfs.o all: check_dep hmcdrvfs $(OBJECTS): Makefile hmcdrvfs: $(OBJECTS) install: all install-scripts $(INSTALL) -g $(GROUP) -o $(OWNER) -m 755 hmcdrvfs \ $(DESTDIR)$(USRBINDIR) $(INSTALL) -g $(GROUP) -o $(OWNER) -m 644 hmcdrvfs.1 \ $(DESTDIR)$(MANDIR)/man1 $(INSTALL) -g $(GROUP) -o $(OWNER) -m 644 lshmc.8 \ $(DESTDIR)$(MANDIR)/man8 install-scripts: lshmc @for i in $^; do \ cat $$i | \ sed -e 's/%S390_TOOLS_VERSION%/$(S390_TOOLS_RELEASE)/' \ >$(DESTDIR)$(USRSBINDIR)/$$i; \ chown $(OWNER).$(GROUP) $(DESTDIR)$(USRSBINDIR)/$$i; \ chmod 755 $(DESTDIR)$(USRSBINDIR)/$$i; \ done endif clean: rm -f hmcdrvfs *.o .PHONY: all install install-scripts clean check_dep s390-tools-2.3.0/hmcdrvfs/hmcdrvfs.1000066400000000000000000000202021323410431100170460ustar00rootroot00000000000000.\" Copyright IBM Corp. 2015, 2017 .\" s390-tools is free software; you can redistribute it and/or modify .\" it under the terms of the MIT license. See LICENSE for details. .\" .TH HMCDRVFS 1 "Mar 2015" "s390-tools" .\" disable hyphenation for words below .hw hmcdrv hmcdrvfs fuse DIAGNOSTICS .SH NAME hmcdrvfs \- mount a .SM FUSE file system for remote access to a .SM DVD in a .SM HMC DVD drive .SH SYNOPSIS mounting: .nf .RS \fBhmcdrvfs\fP MOUNTPOINT [OPTIONS] .RE .fi .PP unmounting: .nf .RS \fBfusermount\fP -u MOUNTPOINT .RE .fi .SH DESCRIPTION Use the \fBhmcdrvfs\fP command for read-only access to files on a .SM DVD in the .SM DVD drive of an .SM HMC\c \&. On the .SM HMC\c , the .SM DVD must be assigned to the .SM LPAR within which your Linux instance runs. For .SM z/VM guests, the .SM DVD must be assigned to the .SM LPAR where the .SM z/VM hypervisor runs. With the .SM DVD assigned to your Linux instance, this command creates a .SM FUSE.HMCDRVFS file system with the content of the .SM DVD at the specified mountpoint. .SH OPTIONS .SS "General mount options" .TP \fB-o\fP opt[,opt...] .SM FUSE or mount command options; for the .SM FUSE options see below, for mount options see \fBmount(8)\fP .TP \fB-h\fP, \fB--help\fP print usage information, then exit (see also DIAGNOSTICS) .TP \fB-v\fP, \fB--version\fP print version information, then exit .SS "Specific FUSE.HMCDRVFS options" .TP .BI "-o hmclang=" LANG specify the language setting on the .SM HMC\c ; for valid values, see \fBlocale(1)\fP; for more information, see DIAGNOSTICS and EXAMPLES .TP .BI "-o hmctz=" TZ specify the time zone setting on the .SM HMC\c ; for valid values, see \fBtzset(3)\fP; for more information, see DIAGNOSTICS and EXAMPLES .SS "Applicable FUSE options (version 2.6)" .TP \fB-d\fP, \fB-o debug\fP enable debug output (implies \fB-f\fP) .TP .B -f foreground operation .TP .B -s disable multi-threaded operation .TP .B -o allow_other allow access by other users .TP .B -o allow_root allow access by root .TP .B -o nonempty allow mounts over non-empty file/dir .TP .B -o default_permissions enable permission checking by kernel .TP .BI "-o fsname=" NAME set file system name .TP .BI "-o subtype=" TYPE set file system type .TP .BI "-o max_read=" N set maximum size of read requests .TP .B -o direct_io use direct I/O .TP .B -o kernel_cache cache files in kernel .TP \fB-o \fP[\fBno\fP]\fBauto_cache\fP enable caching based on modification times .TP .BI "-o umask=" M set file permissions (octal) .TP .BI "-o uid=" N set file owner .TP .BI "-o gid=" N set file group .TP .BI "-o entry_timeout=" T cache timeout for names (default: 1.0 second) .TP .BI "-o attr_timeout=" T cache timeout for attributes (default: 1.0 second) .TP .BI "-o ac_attr_timeout=" T auto cache timeout for attributes (default: \fBattr_timeout\fP) .TP .BI "-o max_readahead=" N set maximum readahead .TP .B -o async_read perform reads asynchronously (default) .TP .B -o sync_read perform reads synchronously .TP .B -o no_remote_lock disable remote file locking .TP .B -o intr allow requests to be interrupted .TP .BI "-o intr_signal=" NUM signal to send on interrupt .SH EXAMPLES To mount the .SM HMC drive .SM DVD at \fI/mnt/hmc\fP without any special options use: .PP .nf .RS .B $ hmcdrvfs /mnt/hmc .RE .fi .PP In case the kernel module \fIhmcdrv\fP was not loaded in advance use: .PP .nf .RS .B # modprobe hmcdrv .B # hmcdrvfs /mnt/hmc .RE .fi .PP To translate the UID and GID of files on the .SM HMC drive .SM DVD to your system users and groups along with overriding the permissions use for example: .PP .nf .RS .B $ hmcdrvfs /mnt/hmc -o uid=500 -o gid=1000 -o umask=0337 .RE .fi .PP To speed up transfer rates to frequently accessed directories use the cache timeout option: .PP .nf .RS .B $ hmcdrvfs /mnt/hmc -o entry_timeout=60 .RE .PP .fi If the .SM HMC is in a different timezone and is configured for a different language use, for example: .PP .nf .RS .B $ hmcdrvfs /mnt/hmc -o hmclang=de_DE -o hmctz=Europe/Berlin .RE .fi .PP or disregarding any daylight saving time, specifying hours west of Prime Meridian (UTC): .PP .nf .RS .B $ hmcdrvfs /mnt/hmc -o hmclang=de_DE -o hmctz="GMT-1" .RE .fi .PP To unmount the .SM HMC drive .SM DVD mounted on \fI/mnt/hmc\fP use: .PP .nf .RS .B $ fusermount -u /mnt/hmc .RE .fi .PP It is also possible to mount by using \fBfstab(5)\fP. Specify the mount point and associated mount options in \fI/etc/fstab\fP, for example: .PP .nf .RS hmcdrvfs /mnt/hmc fuse ro,noatime,allow_other,uid=500,gid=1000 .RE .fi .PP You can then mount the file system with this command: .PP .nf .RS .B # mount /mnt/hmc .RE .fi .SH FILES Some general options about mount policy can be set in the \fI/etc/\:fuse.conf\fP file. These options are: .TP .BI "mount_max=" NNN Set the maximum number of .SM FUSE mounts allowed to non-root users. The default is 1000. .TP .B user_allow_other Allow non-root users to specify the \fBallow_other\fP or \fBallow_root\fP mount options. .SH DIAGNOSTICS .IP 1. 3 The .SM FUSE.HMCDRVFS file system needs access to device node \fI/dev/\:hmcdrv\fP. This node is created automatically when the \fIhmcdrv\fP kernel module is loaded (see Linux kernel configuration option .SM CONFIG_HMC_DRV\c ). The user process that runs the \fBhmcdrvfs\fP command must have sufficient privileges to read from and write to node \fI/dev/\:hmcdrv\fP. Use the commands \fBchown(1)\fP, \fBchgrp(1)\fP and/or \fBchmod(1)\fP on node \fI/dev/\:hmcdrv\fP to ensure this condition. .IP 2. 3 In addition to the required permissions, there are some environmental requirements: .RS .IP - 2 In a .SM z/VM environment, the .SM z/VM guest virtual machine must have at least privilege class B. .IP - 2 For Linux in .SM LPAR mode, the .SM LPAR activation profile must allow issuing .SM SCLP requests. .IP - 2 On the .SM HMC\c , the .SM DVD must be assigned to the associated system image (use menu \fIAccess Removable Media\fP). .RE .IP 3. 3 The .SM FUSE.HMCDRVFS file system maintains a file attributes cache, with an aging timeout. This timeout is related to the \fBentry_timeout\fP and \fBattr_timeout\fP .SM FUSE options. Its value exceeds the greater of the two, \fBentry_timeout\fP and \fBattr_timeout\fP, by 30 - 60 seconds. This timeout affects the performance of the .SM FUSE.HMCDRVFS file system. .RE .IP 4. 3 Different language and time zone settings on the .SM HMC and your Linux instance can result in incorrect file modification information. .RS .IP - 2 Use the \fBhmclang\fP specific .SM FUSE.HMCDRVFS option if the language settings of the .SM HMC and your Linux instance do not match. Correctly setting this option prevents incorrect file modification dates in the file details. Omitting the \fBhmclang\fP option can result in incorrect dates of the form 01-01-YYYY, with misleading values for the day and month. .IP - 2 Use the \fBhmctz\fP specific .SM FUSE.HMCDRVFS option if the time zone settings of the .SM HMC and your Linux instance do not match. Correctly setting this option prevents incorrect file modification times in the file details. Omitting \fBhmctz\fP, an incorrect specification, or a missing time zone description file can result in modification times that differ up to 25 hours from the correct times. Specifications that cannot be interpreted result in GMT/UTC being set. If the .SM HMC is set to GMT/UTC, specify \fBhmctz=""\fP. .RE .IP 5. 3 The following generic mount options from \fBmount(8)\fP are ignored: .RS 3 .TP \fB-w\fP, \fB--rw\fP, \fB-o rw\fP mount the file system read/write .TP .B -o atime update inode access times on this file system .RE .IP 6. 3 The following .SM FUSE mount options from \fBmount.fuse(8)\fP are ignored: .RS 3 .TP .B -o hard_remove immediate removal .TP .B -o negative_timeout cache timeout for deleted names .TP .BI "-o max_write=" N set maximum size of write requests .TP .B -o atomic_o_trunc enable atomic open+truncate support .TP .B -o big_writes enable larger than 4kB writes .TP .BI "-o subdir=" DIR prepend this directory to all paths .TP .B -o use_ino let file system set inode numbers .TP .B -o readdir_ino try to fill in d_ino in readdir .RE .SH SEE ALSO .BR tzset(3), .BR locale(1), .BR fusermount(1), .BR fstab(5), .BR mount(8), .B mount.fuse(8) .BR lshmc(8), and Linux on System z: Device Drivers, Features and Commands s390-tools-2.3.0/hmcdrvfs/hmcdrvfs.c000066400000000000000000001074651323410431100171510ustar00rootroot00000000000000/* * HMC Drive FUSE Filesystem (FUSE.HMCDRVFS) * * Copyright IBM Corp. 2015, 2017 * * 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "lib/zt_common.h" #define HMCDRV_FUSE_LOGNAME "hmcdrvfs" /* log prefix */ #define HMCDRV_FUSE_LOGHEAD HMCDRV_FUSE_LOGNAME ": " /* log header */ #define HMCDRV_FUSE_FTPDEV "/dev/hmcdrv" /* DIAG/SCLP FTP device */ #define HMCDRV_FUSE_MAXPATH 192 /* max. path length (including EOS) */ #define HMCDRV_FUSE_MAXFTPLEN 3 /* max. length of FTP cmd (dir/nls/get) */ #define HMCDRV_FUSE_OFSPATH (HMCDRV_FUSE_MAXFTPLEN + 1) /* path in cmd */ #define HMCDRV_FUSE_MAXCMDLEN (HMCDRV_FUSE_MAXPATH + HMCDRV_FUSE_OFSPATH) /* internal increase (margin/offset) of cache timeout with respect to FUSE * options 'attr_timeout' and/or 'entry_timeout' (having default 1s) */ #define HMCDRV_FUSE_CACHE_TMOFS 30 /* size of file name/attributes cache (should be a prime number) */ #define HMCDRV_FUSE_CACHE_SIZE 2053 /* number of hash table lines inspected in a single garbage loop */ #define HMCDRV_FUSE_GARBAGE_MAX 128 /* max. size of FTP 'dir ' output chunk size */ #define HMCDRV_FUSE_DIRBUF_SIZE (64 * 1024) /* max. string length in FTP 'dir ' output buffer */ #define HMCDRV_FUSE_DIRBUF_LEN (HMCDRV_FUSE_DIRBUF_SIZE - 1) /* pointer to path (token) in FTP command string associated with file 'fp' */ #define HMCDRV_FUSE_PATH(fp) ((fp)->ftpcmd + HMCDRV_FUSE_OFSPATH) /* * DEBUG log macro (requires -DDEBUG, else empty) */ #ifdef DEBUG #define HMCDRV_FUSE_DBGLOG(fmt, ...) syslog(LOG_ERR, (fmt), ##__VA_ARGS__) #else #define HMCDRV_FUSE_DBGLOG(fmt, ...) do {} while (0) #endif /* DEBUG */ /* * log output to 'stderr' and optionally (if from a fork'ed process/thread) * to syslog */ #define HMCDRV_FUSE_LOG(sev, fmt, ...) \ hmcdrv_fuse_log((sev), HMCDRV_FUSE_LOGHEAD "%s: " fmt "\n", \ hmcdrv_fuse_logsevname[sev], ##__VA_ARGS__) #define HMCDRV_FUSE_STR(x) #x #define HMCDRV_FUSE_STRINGER(x) HMCDRV_FUSE_STR(x) #define HMCDRV_FUSE_RELEASE HMCDRV_FUSE_STRINGER(S390_TOOLS_RELEASE) #define HMCDRV_FUSE_OPT(t, p, v) { t, offsetof(struct hmcdrv_fuse_opt, p), v } /* * option key IDs (used on command line parsing) */ enum { HMCDRV_FUSE_OPTKEY_VERSION, /* "-v", "--version" */ HMCDRV_FUSE_OPTKEY_HELP, /* "-h", "--help" */ HMCDRV_FUSE_OPTKEY_RO, /* read-only, e.g. "-o ro" or "-r" */ HMCDRV_FUSE_OPTKEY_RW, /* option associated with -wr (ignored) */ HMCDRV_FUSE_OPTKEY_NATM, /* option "noatime" */ HMCDRV_FUSE_OPTKEY_CTMO, /* cache timeout (entry/attr_timeout) */ HMCDRV_FUSE_OPTKEY_NSUP, /* unsupported options, e.g. "subdir" */ }; /* * internal used FTP command IDs */ enum hmcdrv_fuse_cmdid { HMCDRV_FUSE_CMDID_NLS = 0, HMCDRV_FUSE_CMDID_DIR = 1, HMCDRV_FUSE_CMDID_GET = 2, HMCDRV_FUSE_CMDID_SIZE }; /* * options/parameters to lookup at start */ struct hmcdrv_fuse_opt { unsigned int ro:1; /* option(s) "ro", "-r", etc. */ unsigned int noatime:1; /* option "noatime" */ char *hmctz; /* HMC timezone option "-o hmctz=TZ" */ char *hmclang; /* HMC locale option "-o hmclang=LANG" */ }; /* * HMC drive FUSE context */ struct hmcdrv_fuse_context { struct hmcdrv_fuse_opt opt; /* some saved startup options */ int fd; /* file descriptor of FTP device HMCDRV_FUSE_FTPDEV */ char *path; /* mount point path (root) */ struct stat st; /* mount point stat attributes */ time_t ctmo; /* cache timeout (derived from entry/attr_timeout) */ pthread_t tid; /* cache aging thread ID */ pthread_mutex_t mutex; /* cache access mutex */ pid_t pid; /* PID of main() */ char *abmon[12]; /* abbreviated month name of HMC locale */ int ablen[12]; /* length of each abbreviated month name */ }; /* * SE/HMC drive FUSE file private data */ struct hmcdrv_fuse_file { struct hmcdrv_fuse_file *next; /* collision list (equal hash) */ struct stat st; /* stat structure of this file */ char *symlnk; /* pointer to path name of symlink target (S_IFLNK) */ time_t timeout; /* cache timeout for this file */ size_t cmdlen; /* length of FTP command + path */ char ftpcmd[0]; /* FTP command + path (max HMCDRV_FUSE_MAXCMDLEN) */ }; /* * all file attributes accumulated from interpreting tokens/fields of 'dir' * command listing */ struct hmcdrv_fuse_attr { struct stat *st; /* pointer to stat structure */ struct tm ftm; /* local time */ char *fname; /* (base-) name */ }; /* * prototype (forward) declaration for hmcdrv_cache_dir() */ static struct hmcdrv_fuse_file *hmcdrv_file_get(const char *path); /* * log severity strings */ static const char *const hmcdrv_fuse_logsevname[] = { "Emergency", /* (0) LOG_EMERG */ "Alert", /* (1) LOG_ALERT */ "Critical", /* (2) LOG_CRIT */ "Error", /* (3) LOG_ERR */ "Warning", /* (4) LOG_WARNING */ "Notice", /* (5) LOG_NOTICE */ "Info", /* (6) LOG_INFO */ "Debug" /* (7) DEBUG */ }; /* * file attributes cache */ static struct hmcdrv_fuse_file *hmcdrv_fuse_cache[HMCDRV_FUSE_CACHE_SIZE]; /* * context */ static struct hmcdrv_fuse_context hmcdrv_ctx = { .fd = -1, .ctmo = 1 + HMCDRV_FUSE_CACHE_TMOFS, .mutex = PTHREAD_MUTEX_INITIALIZER, }; /* * log output from hmcdrvfs daemon */ static void hmcdrv_fuse_log(int sev, const char *fmt, ...) { va_list argp; va_start(argp, fmt); vfprintf(stderr, fmt, argp); /* always to stderr */ va_end(argp); if (hmcdrv_ctx.pid != getpid()) { struct fuse_context *fctx = fuse_get_context(); if ((fctx != NULL) && (hmcdrv_ctx.pid != fctx->pid)) { va_start(argp, fmt); vsyslog(sev, fmt + (sizeof(HMCDRV_FUSE_LOGHEAD) - 1), argp); va_end(argp); } } } /* * calculate a hash value from a file path */ static unsigned int hmcdrv_hash_path(const char *path) { unsigned int hash = ~0; int len = strlen(path); while (len > 0) { hash += (unsigned) *path; ++path; --len; } hash %= HMCDRV_FUSE_CACHE_SIZE; return hash; } /* * restart of cache entry aging time */ static void hmcdrv_cache_trestart(struct hmcdrv_fuse_file *fp) { fp->timeout = time(NULL) + hmcdrv_ctx.ctmo; } /* * update or free symlink information */ static void hmcdrv_cache_symlink(struct hmcdrv_fuse_file *fp, const char *symlink) { if (fp->symlnk != NULL) { free(fp->symlnk); fp->symlnk = NULL; } if ((symlink != NULL) && (symlink[0] != '\0')) { fp->symlnk = malloc(HMCDRV_FUSE_MAXPATH); if (fp->symlnk != NULL) { strncpy(fp->symlnk, symlink, HMCDRV_FUSE_MAXPATH); fp->symlnk[HMCDRV_FUSE_MAXPATH - 1] = '\0'; } } } /* * refresh the attributes of file/directory 'path' * note: if there is no cache entry for this file, then create one */ static void hmcdrv_cache_refresh(const char *path, const struct stat *st, const char *symlink) { struct hmcdrv_fuse_file **pbase; /* storage location of fp */ struct hmcdrv_fuse_file *fp; unsigned int index; int pathlen = strlen(path); if ((pathlen == 0) || (pathlen >= HMCDRV_FUSE_MAXPATH)) { HMCDRV_FUSE_LOG(LOG_WARNING, "Invalid path length for '%s'", path); return; } index = hmcdrv_hash_path(path); pbase = &hmcdrv_fuse_cache[index]; fp = *pbase; while (fp != NULL) { if (strcmp(HMCDRV_FUSE_PATH(fp), path) == 0) { fp->st = *st; /* update file info */ hmcdrv_cache_symlink(fp, symlink); hmcdrv_cache_trestart(fp); /* restart aging */ return; } pbase = &fp->next; fp = fp->next; } /* entry does not exist so far - create new one */ fp = malloc((offsetof(struct hmcdrv_fuse_file, ftpcmd) + HMCDRV_FUSE_OFSPATH + 1) + pathlen); if (fp == NULL) { HMCDRV_FUSE_LOG(LOG_ERR, "Out of memory, %ld bytes", (offsetof(struct hmcdrv_fuse_file, ftpcmd) + HMCDRV_FUSE_OFSPATH + 1) + pathlen); } else { *pbase = fp; fp->cmdlen = pathlen + HMCDRV_FUSE_OFSPATH; fp->st = *st; memcpy(HMCDRV_FUSE_PATH(fp), path, pathlen + 1); fp->symlnk = NULL; hmcdrv_cache_symlink(fp, symlink); hmcdrv_cache_trestart(fp); fp->next = NULL; } } /* * search for a file/directory path in cache */ static struct hmcdrv_fuse_file *hmcdrv_cache_find(const char *path) { unsigned index = hmcdrv_hash_path(path); struct hmcdrv_fuse_file *fp = hmcdrv_fuse_cache[index]; while (fp != NULL) { if (strcmp(HMCDRV_FUSE_PATH(fp), path) == 0) { hmcdrv_cache_trestart(fp); return fp; } fp = fp->next; } return NULL; } /* * check for aging timeout of cache entry at index */ static void hmcdrv_cache_expire(unsigned index, time_t now) { struct hmcdrv_fuse_file **pbase; /* storage location of fp */ struct hmcdrv_fuse_file *fp, *next; pbase = &hmcdrv_fuse_cache[index]; fp = *pbase; /* iterate the collision list */ while (fp != NULL) { next = fp->next; if ((fp->timeout <= now) && (strcmp(HMCDRV_FUSE_PATH(fp), "/") != 0)) { hmcdrv_cache_symlink(fp, NULL); *pbase = next; free(fp); } else { pbase = &fp->next; } fp = next; } } /* * cache expiry handler thread */ static void *hmcdrv_cache_aging(void *UNUSED(arg)) { unsigned int cnt; time_t now; unsigned index = 0; while (1) { sleep(1); pthread_mutex_lock(&hmcdrv_ctx.mutex); now = time(NULL); /* each second scan a number of cache entries */ for (cnt = 0; cnt < HMCDRV_FUSE_GARBAGE_MAX; ++cnt) { hmcdrv_cache_expire(index, now); if (++index == HMCDRV_FUSE_CACHE_SIZE) index = 0; } pthread_mutex_unlock(&hmcdrv_ctx.mutex); } return NULL; } /* * convert a FTP command ID into a string * * Note: If a command string is shorter than HMCDRV_FUSE_MAXFTPLEN then * define it right-aligned (prefixed with spaces) in table 'cmdstr'. */ static void hmcdrv_ftp_str(enum hmcdrv_fuse_cmdid cmd, char *ftp) { static const char *cmdstr[HMCDRV_FUSE_CMDID_SIZE] = { #if (HMCDRV_FUSE_MAXFTPLEN != 3) #error The length of strings in cmdstr[] must match HMCDRV_FUSE_MAXFTPLEN #endif "nls", /* HMCDRV_FUSE_CMDID_NLS */ "dir", /* HMCDRV_FUSE_CMDID_DIR */ "get" /* HMCDRV_FUSE_CMDID_GET */ }; strcpy(ftp, cmdstr[cmd]); ftp[HMCDRV_FUSE_MAXFTPLEN] = ' '; /* overwrite '\0' from strcpy() */ } /* * FTP command execution via kernel device */ static ssize_t hmcdrv_ftp_transfer(struct hmcdrv_fuse_file *fp, char *buf, size_t len, off_t offset) { static off_t current_offset; static char last_ftpcmd[HMCDRV_FUSE_MAXCMDLEN]; ssize_t retlen; /* * First check if this is a sequential read from the same file. If * so skip repositioning the files seek pointer and emitting a new * command. */ if ((offset != current_offset) || (strncmp(fp->ftpcmd, last_ftpcmd, HMCDRV_FUSE_MAXCMDLEN) != 0)) { if ((lseek(hmcdrv_ctx.fd, offset, SEEK_END) < 0) || (write(hmcdrv_ctx.fd, fp->ftpcmd, fp->cmdlen) < 0)) { last_ftpcmd[0] = '\0'; return -errno; } current_offset = offset; } retlen = read(hmcdrv_ctx.fd, buf, len); if (retlen < 0) { last_ftpcmd[0] = '\0'; return -errno; } current_offset += retlen; strncpy(last_ftpcmd, fp->ftpcmd, HMCDRV_FUSE_MAXCMDLEN); last_ftpcmd[HMCDRV_FUSE_MAXCMDLEN - 1] = '\0'; return retlen; } /* * FTP command assembly and execution */ static ssize_t hmcdrv_ftp_cmd(struct hmcdrv_fuse_file *fp, enum hmcdrv_fuse_cmdid cmd, char *buf, size_t len, off_t offset) { if (fp == NULL) return -EBADF; hmcdrv_ftp_str(cmd, fp->ftpcmd); return hmcdrv_ftp_transfer(fp, buf, len, offset); } /* * returns a file path (from internal file structure) to a buffer, * with appending a slash (in case it is missing) */ static int hmcdrv_path_copy(struct hmcdrv_fuse_file *fp, char *dest) { char *src = HMCDRV_FUSE_PATH(fp); int len = 0; while ((len < (HMCDRV_FUSE_MAXPATH - 1)) && (*src != '\0')) { *dest = *src; ++len; ++dest; ++src; } if ((len > 0) && (*(dest - 1) != '/')) { *dest++ = '/'; ++len; } *dest = '\0'; return len; } /* * convert a string into into a unsigned number, * returning 0 on success or -errno on error */ static int hmcdrv_parse_uint(const char *s, unsigned int *pval) { errno = 0; *pval = strtoul(s, NULL, 10); return -errno; } /* * convert a string into into a signed number (with range check), * returning 0 on success or -errno on error */ static int hmcdrv_parse_int(const char *s, int vmin, int vmax, int *pval) { errno = 0; *pval = strtol(s, NULL, 10); if (errno == 0) { if ((*pval >= vmin) && (*pval <= vmax)) return 0; errno = ERANGE; } return -errno; } /* * convert an abbreviated month name (%b) into a number */ static int hmcdrv_parse_month(const char *s) { int i; for (i = 0; i < 12; ++i) { if (strncasecmp(hmcdrv_ctx.abmon[i], s, hmcdrv_ctx.ablen[i]) == 0) return i; } return -1; } /* * return the file mode from a 'ls -l' like mode string */ static mode_t hmcdrv_parse_mode(const char *s) { int i; mode_t type; mode_t mode = 0; switch (*s) { case 'd': /* directory */ type = S_IFDIR; break; case 'b': /* block device */ type = S_IFBLK; break; case 'c': /* character device */ type = S_IFCHR; break; case 'l': /* symbolic link */ type = S_IFLNK; break; case 'p': /* FIFO */ type = S_IFIFO; break; case 's': /* socket */ type = S_IFSOCK; break; case '-': type = S_IFREG; break; default: return 0; } for (i = 0; i < 9; ++i) { mode <<= 1; switch (*++s) { case 'r': case 'w': case 'x': mode |= 1; break; case '-': break; default: return 0; } } mode &= ~(S_IWUSR | S_IWGRP | S_IWOTH); return mode | type; } /* * convert a time string from format "%H:%M" into a 'struct tm' (without any * local time conversion) */ static int hmcdrv_parse_time(const char *s, struct tm *t) { char *endptr; int hour, minute; errno = 0; hour = strtol(s, &endptr, 10); if ((errno == 0) && (endptr != s) && (hour >= 0) && (hour < 24)) { if (*endptr == ':') { s = ++endptr; errno = 0; minute = strtol(s, &endptr, 10); if ((errno == 0) && (endptr != s) && (minute >= 0) && (minute < 60)) { t->tm_hour = hour; t->tm_min = minute; return 0; /* success */ } } } return -1; } /* * interpret token depending on current field number and * fill the attributes structure * * Return: 'field' incremented by 1 or INT_MAX on parse errors */ static int hmcdrv_parse_ntoken(int field, char *token, struct hmcdrv_fuse_attr *attr) { switch (field) { case 0: /* mode */ attr->st->st_mode = hmcdrv_parse_mode(token); if (attr->st->st_mode == 0) /* error ? */ field = INT_MAX - 1; /* stop */ break; case 1: /* number of hard links to this file */ errno = 0; attr->st->st_nlink = strtoul(token, NULL, 10); if (errno != 0) field = INT_MAX - 1; break; case 2: /* user ID */ if (hmcdrv_parse_uint(token, &attr->st->st_uid) != 0) { attr->st->st_uid = hmcdrv_ctx.st.st_uid; HMCDRV_FUSE_LOG(LOG_WARNING, "Could not parse UID (will use %u)", attr->st->st_uid); } break; case 3: /* group ID */ if (hmcdrv_parse_uint(token, &attr->st->st_gid) != 0) { attr->st->st_gid = hmcdrv_ctx.st.st_gid; HMCDRV_FUSE_LOG(LOG_WARNING, "Could not parse GID (will use %u)", attr->st->st_gid); } break; case 4: /* file size */ errno = 0; attr->st->st_size = strtol(token, NULL, 10); if (errno != 0) field = INT_MAX - 1; break; case 5: /* month of year [0,11] */ attr->ftm.tm_mon = hmcdrv_parse_month(token); break; case 6: /* day of month [1,31] */ if (hmcdrv_parse_int(token, 1, 31, &attr->ftm.tm_mday) != 0) { field = INT_MAX - 1; } else { /* * in case month is not in English language we've * got an error in attr->ftm.tm_mon */ if (attr->ftm.tm_mon < 0) { attr->ftm.tm_mday = 1; /* YYYY-01-01 */ attr->ftm.tm_mon = 0; } } break; case 7: /* %H:%M or %Y */ if (hmcdrv_parse_time(token, &attr->ftm) != 0) { if (hmcdrv_parse_int(token, 1900, INT_MAX, &attr->ftm.tm_year) != 0) field = INT_MAX - 1; else attr->ftm.tm_year -= 1900; } break; case 8: /* file name (rest of line) */ attr->fname = token; break; default: break; } return ++field; } /* * parse filename and attributes from 'dir' output (a single line) * * Note: Because HMC command 'dir' always uses the HMC server local time the * current timezone should be set correctly, based on startup option * "-o hmctz=TZ". * * Return: pointer to start of next line (skipping any whitespace) * or pointer to '\0' */ static char *hmcdrv_parse_line(char *line, char *namebuf, int bufsize, struct stat *st, char *symlink) { int field = 0; struct hmcdrv_fuse_attr attr = {.fname = NULL, .st = st}; memset(st, 0, sizeof(*st)); namebuf[0] = '\0'; /* set default values */ st->st_mtime = time(NULL); /* see next line: gmtime_r() */ gmtime_r(&st->st_mtime, &attr.ftm); /* tm_year default */ attr.ftm.tm_isdst = -1; /* mktime() should use "TZ" info */ attr.ftm.tm_hour = attr.ftm.tm_min = attr.ftm.tm_sec = 0; while ((*line != '\0') && isspace(*line)) ++line; /* skip leading whitespace (incl. newlines) */ while ((*line != '\0') && (*line != '\n') && (field < 9)) { while ((*line != '\0') && (*line != '\n') && isspace(*line)) ++line; /* skip spaces, but not newlines */ if ((*line != '\0') && (*line != '\n')) { field = hmcdrv_parse_ntoken(field, line, &attr); while ((*line != '\0') && (*line != '\n') && !isspace(*line)) ++line; /* search end of field */ } } /* while */ /* search end of line now (skips some fields in case of error) */ while ((*line != '\0') && (*line != '\n')) ++line; /* * If start of file name was found by parser, then indicate success * returning the file attributes. But do this only if the end of * line was really found, else this seems to be an incomplete line * in current chunk (fragment). */ if ((attr.fname != NULL) && (*line == '\n')) { char *arrow = NULL; /* " -> " pointer, when symlink */ *line = '\0'; /* temporary set EOS for strncpy() */ if (S_ISLNK(st->st_mode)) { arrow = strstr(attr.fname, " -> "); if (arrow != NULL) *arrow = '\0'; } strncpy(namebuf, attr.fname, bufsize); namebuf[bufsize - 1] = '\0'; /* safety */ if (arrow == NULL) { symlink[0] = '\0'; } else { strncpy(symlink, arrow + 4, HMCDRV_FUSE_MAXPATH); *arrow = ' '; /* restore */ } *line = '\n'; /* restore */ st->st_mtime = mktime(&attr.ftm); /* UTC from local time */ if (st->st_mtime == (time_t) -1) st->st_mtime = 0; st->st_atime = st->st_ctime = st->st_mtime; /* In contrast to FUSE documentation the members below must * be set in stat structure. For example 'du' uses them, * because it shows the disk usage from used blocks and not * the file size (unless option '--apparent-size' is given). */ st->st_blksize = 2048U; /* nearly all CD/DVDs have this */ st->st_blocks = (st->st_size + 511U) / 512U; } while ((*line != '\0') && isspace(*line)) ++line; /* skip whitespace after EOL, incl. newlines */ return line; } /* * performs a FTP 'dir', then scans/parses the output, fills the cache and * (optional) calls the FUSE filler function for every file in 'dir' listing */ static int hmcdrv_cache_dir(const char *dir, fuse_fill_dir_t filler, void *buf) { char path[HMCDRV_FUSE_MAXPATH]; /* constructed path of a file */ char symlink[HMCDRV_FUSE_MAXPATH]; /* target of symlink (S_IFLNK) */ struct hmcdrv_fuse_file *fpdir; /* directory file pointer */ struct stat st; /* attributes of file */ char *dirbuf; /* 'dir' listing (chunk) buffer */ char *next; /* 'next line' pointer in 'dir' buffer */ char *line; /* current line start position in 'dir' buffer */ int dirlen; /* number of characters in 'dir' buffer */ int fraglen; /* fragment length in 'dir' buffer */ char *fname; /* buffer pointer for basename of file */ int len; /* maximum length of file basename (buffer space) */ off_t offset; /* device 'dir' position */ char *tzenv = NULL; /* "TZ" environment variable */ fpdir = hmcdrv_file_get(dir); if (fpdir == NULL) { HMCDRV_FUSE_LOG(LOG_ERR, "Could not find directory '%s' in cache", dir); return -ENOENT; } dirbuf = malloc(HMCDRV_FUSE_DIRBUF_SIZE); if (dirbuf == NULL) { HMCDRV_FUSE_LOG(LOG_ERR, "Out of memory, %d bytes", HMCDRV_FUSE_DIRBUF_SIZE); return -ENOMEM; } /* set HMC timezone (optional), else host timezone remains active - * possibly different from HMC timezone and so resulting in wrong * file modification times (cf. mktime() in hmcdrv_parse_line()) */ if (hmcdrv_ctx.opt.hmctz != NULL) { /* "-o hmctz=TZ" present ? */ tzenv = getenv("TZ"); if (tzenv != NULL) tzenv = strdup(tzenv); if (setenv("TZ", hmcdrv_ctx.opt.hmctz, 1) != 0) { HMCDRV_FUSE_LOG(LOG_ERR, "Could not set HMC timezone '%s': %s", hmcdrv_ctx.opt.hmctz, strerror(errno)); if (tzenv != NULL) { setenv("TZ", tzenv, 1); free(tzenv); } free(dirbuf); return -ENOMEM; } tzset(); } len = hmcdrv_path_copy(fpdir, path); /* get directory path */ fname = path + len; /* start of basename in path buffer */ len = HMCDRV_FUSE_MAXPATH - len; /* space for basename */ HMCDRV_FUSE_DBGLOG("scanning '%s'...", path); /* do the 'dir' chunks and parse all */ dirlen = hmcdrv_ftp_cmd(fpdir, HMCDRV_FUSE_CMDID_DIR, dirbuf, HMCDRV_FUSE_DIRBUF_LEN, 0); next = dirbuf; /* first line at start of buffer */ offset = dirlen; while (dirlen > 0) { next[dirlen] = '\0'; /* force end of string */ /* parse all files in this 'dir' listing chunk */ do { line = next; /* parse next line and retrieve the file name */ next = hmcdrv_parse_line(line, fname, len, &st, symlink); /* If parsing of 'dir' line was successful, then * store file attributes and name in cache. Else * skip this line because of incorrect syntax - * possibly it is a fragment (at end of buffer) or * this line does not hold any valid file info. */ if (*fname != '\0') { hmcdrv_cache_refresh(path, &st, symlink); if ((filler != NULL) && (filler(buf, fname, &st, 0) != 0)) filler = NULL; /* stop filling */ #ifdef DEBUG strftime(symlink, sizeof(symlink), "%c", gmtime(&st.st_mtime)); HMCDRV_FUSE_DBGLOG(" * %s (size=%zu, mode=%o, uid=%u, gid=%u, time=%s)", fname, st.st_size, st.st_mode, st.st_uid, st.st_gid, symlink); #endif } } while (*next != '\0'); /* Try to read more bytes from the FTP device. But regard * remaining chars from the last line, in case it could not * completely parsed (in other words the line was a * fragment, and has to be moved to start of buffer). */ if (*fname == '\0') { /* no valid filename? */ fraglen = next - line; if (fraglen > 0) memcpy(dirbuf, line, fraglen); } else { fraglen = 0; } dirlen = hmcdrv_ftp_cmd(fpdir, HMCDRV_FUSE_CMDID_DIR, dirbuf + fraglen, HMCDRV_FUSE_DIRBUF_LEN - fraglen, offset); if (dirlen > 0) { /* on success continue */ next = dirbuf; offset += dirlen; dirlen += fraglen; } } free(dirbuf); /* restore old host timezone */ if (hmcdrv_ctx.opt.hmctz != NULL) { if (tzenv != NULL) { putenv(tzenv); free(tzenv); } else { unsetenv("TZ"); } tzset(); } return dirlen; } /* * cache all files located in parent directory of 'dir', * so also caching the attributes of 'dir' again * * Return: 0 on success, else a negative error code (-ENOENT if 'dir' is "/") */ static int hmcdrv_cache_parent(const char *dir) { char *tmp, *parent; if (strcmp(dir, "/") == 0) /* root has no parent */ return -ENOENT; tmp = strdup(dir); /* need a copy because of dirname() */ if (tmp == NULL) { HMCDRV_FUSE_LOG(LOG_ERR, "Out of memory, %zd bytes", strlen(dir) + 1); return -ENOMEM; } parent = dirname(tmp); HMCDRV_FUSE_DBGLOG("re-scanning parent '%s'...", parent); hmcdrv_cache_dir(parent, NULL, NULL); free(tmp); return 0; } /* * return the file attributes data pointer (or NULL on error) */ static struct hmcdrv_fuse_file *hmcdrv_file_get(const char *path) { struct hmcdrv_fuse_file *fp = hmcdrv_cache_find(path); if (fp != NULL) /* path found in cache ? */ return fp; /* in very, very rare cases we must scan the parent again, * recursive up to the root directory */ if (hmcdrv_cache_parent(path) != 0) return NULL; /* because the mutex is taken by the caller, we MUST find this file * now in cache (if not so then the HMC drive DVD may have changed * without unmounting FUSE.HMCDRVFS) */ return hmcdrv_cache_find(path); } /* * obtain file status information (attributes) on FUSE.HMCDRVFS filesystem * * Note: The most important function which FUSE calls (very often). */ static int hmcdrv_fuse_getattr(const char *path, struct stat *stbuf) { struct hmcdrv_fuse_file *fp; int rc = 0; pthread_mutex_lock(&hmcdrv_ctx.mutex); fp = hmcdrv_file_get(path); if (fp == NULL) rc = -ENOENT; else *stbuf = fp->st; pthread_mutex_unlock(&hmcdrv_ctx.mutex); return rc; } /* * get symlink target path */ static int hmcdrv_fuse_readlink(const char *path, char *buf, size_t size) { struct hmcdrv_fuse_file *fp; int rc = 0; pthread_mutex_lock(&hmcdrv_ctx.mutex); fp = hmcdrv_file_get(path); if (fp == NULL) { rc = -ENOENT; } else { if (fp->symlnk == NULL) { rc = -ENOMEM; } else { if (!S_ISLNK(fp->st.st_mode)) { rc = -EINVAL; } else { strncpy(buf, fp->symlnk, size); buf[size - 1] = '\0'; } } } pthread_mutex_unlock(&hmcdrv_ctx.mutex); return rc; } /* * open a directory on FUSE.HMCDRVFS filesystem */ static int hmcdrv_fuse_opendir(const char *UNUSED(path), struct fuse_file_info *fi) { if ((fi->flags & O_ACCMODE) != O_RDONLY) return -EACCES; /* all files are read-only */ return 0; } /* * read a directory on FUSE.HMCDRVFS filesystem */ static int hmcdrv_fuse_readdir(const char *path, void *buf, fuse_fill_dir_t filler, off_t UNUSED(offset), struct fuse_file_info *UNUSED(fi)) { int ret; filler(buf, ".", NULL, 0); filler(buf, "..", NULL, 0); pthread_mutex_lock(&hmcdrv_ctx.mutex); ret = hmcdrv_cache_dir(path, filler, buf); pthread_mutex_unlock(&hmcdrv_ctx.mutex); return ret; } /* * open a file on FUSE.HMCDRVFS filesystem */ static int hmcdrv_fuse_open(const char *UNUSED(path), struct fuse_file_info *fi) { if ((fi->flags & O_ACCMODE) != O_RDONLY) return -EACCES; /* all files are read-only */ return 0; } /* * read a file on FUSE.HMCDRVFS filesystem */ static int hmcdrv_fuse_read(const char *path, char *buf, size_t size, off_t offset, struct fuse_file_info *UNUSED(fi)) { struct hmcdrv_fuse_file *fp; int rc; pthread_mutex_lock(&hmcdrv_ctx.mutex); fp = hmcdrv_file_get(path); if (fp == NULL) rc = -ENOENT; else rc = hmcdrv_ftp_cmd(fp, HMCDRV_FUSE_CMDID_GET, buf, size, offset); pthread_mutex_unlock(&hmcdrv_ctx.mutex); return rc; } /* * initialize FUSE.HMCDRVFS filesystem * * Note: calls fuse_exit() on errors * * Return: value to be passed in the private_data field of fuse_context to * all file operations and as a parameter to the destroy() method */ static void *hmcdrv_fuse_init(struct fuse_conn_info *UNUSED(conn)) { pthread_mutexattr_t attr; memset(hmcdrv_fuse_cache, 0, sizeof(hmcdrv_fuse_cache)); openlog(HMCDRV_FUSE_LOGNAME, LOG_PID, LOG_DAEMON); if (pthread_mutexattr_init(&attr) != 0) goto err_out; pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE); hmcdrv_ctx.fd = open(HMCDRV_FUSE_FTPDEV, O_RDWR); if (hmcdrv_ctx.fd < 0) goto err_dev; if (pthread_mutex_init(&hmcdrv_ctx.mutex, &attr) != 0) goto err_mutex; hmcdrv_cache_refresh("/", &hmcdrv_ctx.st, NULL); /* never expires */ if (pthread_create(&hmcdrv_ctx.tid, NULL, hmcdrv_cache_aging, NULL) == 0) { pthread_mutexattr_destroy(&attr); return &hmcdrv_ctx.tid; } err_mutex: close(hmcdrv_ctx.fd); err_dev: pthread_mutexattr_destroy(&attr); err_out: HMCDRV_FUSE_LOG(LOG_ERR, "Initialization failed: %s", strerror(errno)); closelog(); fuse_exit(fuse_get_context()->fuse); return NULL; } /* * clean up FUSE.HMCDRVFS filesystem */ static void hmcdrv_fuse_exit(void *arg) { struct hmcdrv_fuse_file *fp, *next; int i; if (arg != NULL) pthread_cancel(*(pthread_t *) arg); pthread_mutex_lock(&hmcdrv_ctx.mutex); for (i = 0; i < HMCDRV_FUSE_CACHE_SIZE; ++i) { fp = hmcdrv_fuse_cache[i]; while (fp != NULL) { next = fp->next; hmcdrv_cache_symlink(fp, NULL); free(fp); fp = next; } } memset(hmcdrv_fuse_cache, 0, sizeof(hmcdrv_fuse_cache)); pthread_mutex_destroy(&hmcdrv_ctx.mutex); closelog(); if (hmcdrv_ctx.fd >= 0) close(hmcdrv_ctx.fd); } /* * FUSE.HMCDRVFS internal main function */ static int hmcdrv_fuse_main(struct fuse_args *args) { static struct fuse_operations hmcdrv_fuse_op = { .init = hmcdrv_fuse_init, .destroy = hmcdrv_fuse_exit, .getattr = hmcdrv_fuse_getattr, .opendir = hmcdrv_fuse_opendir, .readdir = hmcdrv_fuse_readdir, .open = hmcdrv_fuse_open, .read = hmcdrv_fuse_read, .readlink = hmcdrv_fuse_readlink }; #if FUSE_VERSION >= 26 return fuse_main(args->argc, args->argv, &hmcdrv_fuse_op, NULL); #else return fuse_main(args->argc, args->argv, &hmcdrv_fuse_op); #endif } /* * usage output */ static void hmcdrv_fuse_usage(const char *progname) { fprintf(stderr, "Usage: %s MOUNTPOINT [OPTIONS]\n\n" "Use the %s command to read files from a HMC drive DVD.\n" "\n" "General options:\n" " -o opt,[opt...] Mount options\n" " -h --help Print help, then exit\n" " -v --version Print version, then exit\n" "\n" "Specific options:\n" " -o hmclang=LANG HMC speaks language LANG (see locale(1))\n" " -o hmctz=TZ HMC is in timezone TZ (see tzset(3))\n" "\n" "Attention:\n" " The following general and FUSE specific mount options will\n" " be ignored (most do not make sense on a read-only media):\n" " --rw, -w, -o rw, -o atime, -o max_write=N, -o big_writes,\n" " -o atomic_o_trunc, -o hard_remove, -o negative_timeout=T,\n" " -o use_ino, -o readdir_ino, -o subdir=DIR\n" "\n", progname, progname); } /* * process the "-o hmclang=LANG" command line option (if there is one) and * prepare abbreviated month names (%b) in context structure * * Return: 0 on success, -1 on error */ static int hmcdrv_optproc_lang(void) { static const nl_item nl_abmon[12] = { ABMON_1, ABMON_2, ABMON_3, ABMON_4, ABMON_5, ABMON_6, ABMON_7, ABMON_8, ABMON_9, ABMON_10, ABMON_11, ABMON_12}; unsigned int i; char *lc = NULL; /* current LC_TIME locale */ if (hmcdrv_ctx.opt.hmclang != NULL) { HMCDRV_FUSE_DBGLOG("option \"-o hmclang=%s\" detected", hmcdrv_ctx.opt.hmclang); lc = setlocale(LC_TIME, NULL); if (setlocale(LC_TIME, hmcdrv_ctx.opt.hmclang) == NULL) { setlocale(LC_TIME, lc); return -1; } } for (i = 0; i < (sizeof(nl_abmon) / sizeof(nl_abmon[0])); ++i) { hmcdrv_ctx.abmon[i] = nl_langinfo(nl_abmon[i]); hmcdrv_ctx.ablen[i] = strlen(hmcdrv_ctx.abmon[i]); } if (lc) setlocale(LC_TIME, lc); /* restore LC_TIME locale */ return 0; } /* * option parsing function */ static int hmcdrv_fuse_optproc(void *data, const char *arg, int key, struct fuse_args *outargs) { double tmo; /* timeout T in "-o entry/attr_timeout=T" */ mode_t mask; /* umask() of caller */ (void)outargs; switch (key) { case HMCDRV_FUSE_OPTKEY_NSUP: HMCDRV_FUSE_LOG(LOG_WARNING, "FUSE option \"%s\" ignored (unsupported)", arg); return 0; /* remove this option(s) */ case HMCDRV_FUSE_OPTKEY_RW: /* write options (unsupported) */ HMCDRV_FUSE_LOG(LOG_WARNING, "FUSE option \"%s\" ignored (r/o filesystem)", arg); return 0; /* remove this option(s) */ case HMCDRV_FUSE_OPTKEY_RO: /* "-o ro" or "-r" */ ((struct hmcdrv_fuse_opt *)data)->ro = 1; return 1; case HMCDRV_FUSE_OPTKEY_NATM: /* "-o noatime" */ ((struct hmcdrv_fuse_opt *)data)->noatime = 1; return 1; case HMCDRV_FUSE_OPTKEY_CTMO: /* "-o entry/attr_timeout=T" */ if (sscanf(arg, "%*[^=]=%lf", &tmo) == 1) { if (tmo < 1.0) tmo = 1.0; if (hmcdrv_ctx.ctmo < (HMCDRV_FUSE_CACHE_TMOFS + (time_t)tmo)) hmcdrv_ctx.ctmo = HMCDRV_FUSE_CACHE_TMOFS + (time_t)tmo; HMCDRV_FUSE_DBGLOG("option \"%s\" detected (cache timeout now is %" PRIdMAX " sec.)", arg, (intmax_t) hmcdrv_ctx.ctmo); } return 1; case HMCDRV_FUSE_OPTKEY_VERSION: fprintf(stderr, HMCDRV_FUSE_LOGHEAD "HMC drive DVD file system, version %s\n" "Copyright IBM Corp. 2015, 2017\n", HMCDRV_FUSE_RELEASE); exit(EXIT_SUCCESS); case HMCDRV_FUSE_OPTKEY_HELP: hmcdrv_fuse_usage(outargs->argv[0]); fuse_opt_add_arg(outargs, "-ho"); hmcdrv_fuse_main(outargs); exit(EXIT_SUCCESS); case FUSE_OPT_KEY_NONOPT: /* normally the mount point */ mask = umask(0); umask(mask); hmcdrv_ctx.st.st_uid = getgid(); hmcdrv_ctx.st.st_gid = getuid(); hmcdrv_ctx.st.st_ino = 0; hmcdrv_ctx.st.st_size = 0; /* unknown */ hmcdrv_ctx.st.st_blksize = 2048U; /* DVD sector size */ hmcdrv_ctx.st.st_nlink = 2; /* minimum */ hmcdrv_ctx.st.st_mtime = time(NULL); hmcdrv_ctx.st.st_atime = hmcdrv_ctx.st.st_mtime; hmcdrv_ctx.st.st_ctime = hmcdrv_ctx.st.st_mtime; hmcdrv_ctx.st.st_mode = (S_IFDIR | S_IXUSR | S_IRUSR | S_IXGRP | S_IRGRP | S_IXOTH | S_IROTH) & ~(mask | (S_IWUSR | S_IWGRP | S_IWOTH)); HMCDRV_FUSE_DBGLOG("mount point is %s (uid = %u, gid = %u)", arg, hmcdrv_ctx.st.st_uid, hmcdrv_ctx.st.st_gid); return 1; default: HMCDRV_FUSE_DBGLOG("option \"%s\" passed to FUSE", arg); return 1; /* pass all other options to fuse_main() */ } } /* * FUSE.HMCDRVFS entry function */ int main(int argc, char *argv[]) { static struct fuse_opt lookup_opt[] = { HMCDRV_FUSE_OPT("hmctz=%s", hmctz, 0), HMCDRV_FUSE_OPT("hmclang=%s", hmclang, 0), FUSE_OPT_KEY("ro", HMCDRV_FUSE_OPTKEY_RO), FUSE_OPT_KEY("-r", HMCDRV_FUSE_OPTKEY_RO), FUSE_OPT_KEY("--read-only", HMCDRV_FUSE_OPTKEY_RO), FUSE_OPT_KEY("noatime", HMCDRV_FUSE_OPTKEY_NATM), FUSE_OPT_KEY("entry_timeout=%lf", HMCDRV_FUSE_OPTKEY_CTMO), FUSE_OPT_KEY("attr_timeout=%lf", HMCDRV_FUSE_OPTKEY_CTMO), /* unsupported (ignored) */ FUSE_OPT_KEY("subdir=%s", HMCDRV_FUSE_OPTKEY_NSUP), FUSE_OPT_KEY("use_ino", HMCDRV_FUSE_OPTKEY_NSUP), FUSE_OPT_KEY("readdir_ino", HMCDRV_FUSE_OPTKEY_NSUP), /* to be ignored on read-only filesystem */ FUSE_OPT_KEY("rw", HMCDRV_FUSE_OPTKEY_RW), FUSE_OPT_KEY("-w", HMCDRV_FUSE_OPTKEY_RW), FUSE_OPT_KEY("--rw", HMCDRV_FUSE_OPTKEY_RW), FUSE_OPT_KEY("atime", HMCDRV_FUSE_OPTKEY_RW), FUSE_OPT_KEY("max_write=%u", HMCDRV_FUSE_OPTKEY_RW), FUSE_OPT_KEY("big_writes", HMCDRV_FUSE_OPTKEY_RW), FUSE_OPT_KEY("hard_remove", HMCDRV_FUSE_OPTKEY_RW), FUSE_OPT_KEY("atomic_o_trunc", HMCDRV_FUSE_OPTKEY_RW), FUSE_OPT_KEY("negative_timeout=%lf", HMCDRV_FUSE_OPTKEY_RW), /* options, that exit immediately */ FUSE_OPT_KEY("-v", HMCDRV_FUSE_OPTKEY_VERSION), FUSE_OPT_KEY("--version", HMCDRV_FUSE_OPTKEY_VERSION), FUSE_OPT_KEY("-h", HMCDRV_FUSE_OPTKEY_HELP), FUSE_OPT_KEY("--help", HMCDRV_FUSE_OPTKEY_HELP), FUSE_OPT_END }; int ret; struct fuse_args args = FUSE_ARGS_INIT(argc, argv); memset(&hmcdrv_ctx.opt, 0, sizeof(hmcdrv_ctx.opt)); hmcdrv_ctx.pid = getpid(); fuse_opt_parse(&args, &hmcdrv_ctx.opt, lookup_opt, hmcdrv_fuse_optproc); /* the following options are required on a read-only media */ if (!hmcdrv_ctx.opt.ro) fuse_opt_add_arg(&args, "-oro"); if (!hmcdrv_ctx.opt.noatime) fuse_opt_add_arg(&args, "-onoatime"); if (hmcdrv_optproc_lang() != 0) { HMCDRV_FUSE_LOG(LOG_ERR, "Unknown HMC language in '-o hmclang=%s'", hmcdrv_ctx.opt.hmclang); exit(EXIT_FAILURE); } /* notice that there is no way to check the timezone parameter for * correct syntax, not in glibc as well as POSIX 1003.1 */ if (hmcdrv_ctx.opt.hmctz != NULL) HMCDRV_FUSE_DBGLOG("option \"-o hmctz=%s\" detected", hmcdrv_ctx.opt.hmctz); ret = hmcdrv_fuse_main(&args); fuse_opt_free_args(&args); return ret; } s390-tools-2.3.0/hmcdrvfs/lshmc000066400000000000000000000035151323410431100162110ustar00rootroot00000000000000#!/bin/bash # # lshmc - Print files from a HMC drive DVD # # Copyright IBM Corp. 2015, 2017 # # s390-tools is free software; you can redistribute it and/or modify # it under the terms of the MIT license. See LICENSE for details. # TOOL=$(basename $0) FTPDEV="/dev/hmcdrv" FTPCMD="dir" #------------------------------------------------------------------------------ # Print usage #------------------------------------------------------------------------------ function PrintUsage() { cat <<-EOD Usage: $(basename $0) [OPTIONS] [FILE] List information about the FILE(s) residing on a HMC drive DVD. Use OPTIONS described below or present simple wildcards on behalf of FILE. -h, --help Print this help, then exit. -v, --version Print version information, then exit. -s, --short Print only files, in a short listing format. EOD } #------------------------------------------------------------------------------ # Print version #------------------------------------------------------------------------------ function PrintVersion() { cat <<-EOD $TOOL: version %S390_TOOLS_VERSION% Copyright IBM Corp. 2015, 2017 EOD } FILES="" while [ $# -gt 0 ]; do case $1 in --help|-h) PrintUsage exit 0 ;; --version|-v) PrintVersion exit 0 ;; --short|-s) FTPCMD="nls" ;; -*) echo "$TOOL: Invalid option $1" echo "Try '$TOOL --help' for more information." exit 1 ;; *) FILES="$FILES $1" ;; esac shift done if [ ! -c "$FTPDEV" ]; then echo "$TOOL: Device \"$FTPDEV\" does not exist (modprobe hmcdrv ?)" exit 1 fi if [ ${#FILES} -eq 0 ]; then FILES="/" fi # open device $FTPDEV and assign the file descriptor to variable fd exec {fd}<>${FTPDEV} # echo the FTP command into device file echo "$FTPCMD $FILES" >&${fd} # and output the response cat <&${fd} STATUS="$?" # close file descriptor $fd exec {fd}>&- exit $STATUS s390-tools-2.3.0/hmcdrvfs/lshmc.8000066400000000000000000000042251323410431100163560ustar00rootroot00000000000000.\" Copyright IBM Corp. 2015, 2017 .\" s390-tools is free software; you can redistribute it and/or modify .\" it under the terms of the MIT license. See LICENSE for details. .\" .TH LSHMC 8 "Mar 2015" "s390-tools" .\" disable hyphenation for words below .hw hmcdrv lshmc .\" save horizontal spacing to registers .nr default-word-spacing \n[.ss] .nr default-sentence-spacing \n[.sss] .\" define a macro for default horizontal spacing .de ssd .ss \n[default-word-spacing] \n[default-sentence-spacing] .. .SH NAME lshmc \- list .SM HMC drive .SM DVD contents .SH SYNOPSIS .TP \fBlshmc\fP [OPTIONS] [FILE] .SH DESCRIPTION List files on a .SM DVD in the .SM DVD drive of the Hardware Management Console .ss 0 ( .SM HMC ) .ssd \&. By default, the command lists all files in the root directory of the .SM DVD\c \&. Use FILE to list a different set of files. FILE can specify the path, relative to the .SM DVD root directory, for a directory and file and can contain the * and ? wildcard characters. .SH OPTIONS .TP .B "-h, --help" Print help text, then exit. .TP .B "-v, --version" Print version information, then exit. .TP .B "-s, --short" Print only regular files (no directories, symbolic links and special files), in a short listing format. .SH EXAMPLES To list the files in a .SM HMC drive .SM DVD root directory use: .PP .nf .RS .B # lshmc .RE .fi .PP In case the kernel module \fIhmcdrv\fP was not loaded in advance use: .PP .nf .RS .B # modprobe hmcdrv .B # lshmc .RE .fi .PP To list all HTML files in subdirectory \fIwww\fP use: .PP .nf .RS .B # lshmc /www/*.html .RE .fi .PP .SH DIAGNOSTICS The \fBlshmc\fP command needs access to device node \fI/dev/\:hmcdrv\fP. This node is created automatically when the \fIhmcdrv\fP kernel module is loaded (see Linux kernel configuration option .SM CONFIG_HMC_DRV\c ). The user process that runs the \fBlshmc\fP command must have sufficient privileges to read from and write to node \fI/dev/\:hmcdrv\fP. Use the commands \fBchown(1)\fP, \fBchgrp(1)\fP and/or \fBchmod(1)\fP on node \fI/dev/\:hmcdrv\fP to ensure this condition. .SH SEE ALSO .B ls(1), chown(1), chgrp(1), chmod(1) and Linux on System z: Device Drivers, Features and Commands s390-tools-2.3.0/hyptop/000077500000000000000000000000001323410431100146635ustar00rootroot00000000000000s390-tools-2.3.0/hyptop/Makefile000066400000000000000000000015341323410431100163260ustar00rootroot00000000000000include ../common.mak ifeq (${HAVE_NCURSES},0) all: $(SKIP) HAVE_NCURSES=0 install: $(SKIP) HAVE_NCURSES=0 else check_dep: $(call check_dep, \ "hyptop", \ "ncurses.h", \ "ncurses-devel or libncurses-dev", \ "HAVE_NCURSES=0") LDLIBS += -lncurses all: check_dep hyptop OBJECTS = hyptop.o opts.o helper.o \ sd_core.o sd_sys_items.o sd_cpu_items.o \ tbox.o table.o table_col_unit.o \ dg_debugfs.o dg_debugfs_lpar.o dg_debugfs_vm.o dg_debugfs_vmd0c.o \ win_sys_list.o win_sys.o win_fields.o \ win_cpu_types.o win_help.o nav_desc.o hyptop: $(OBJECTS) $(rootdir)/libutil/libutil.a install: all $(INSTALL) -g $(GROUP) -o $(OWNER) -m 755 hyptop \ $(DESTDIR)$(USRSBINDIR) $(INSTALL) -g $(GROUP) -o $(OWNER) -m 644 hyptop.8 \ $(DESTDIR)$(MANDIR)/man8 endif clean: rm -f *.o *~ hyptop core .PHONY: all install clean check_dep s390-tools-2.3.0/hyptop/dg_debugfs.c000066400000000000000000000035261323410431100171260ustar00rootroot00000000000000/* * hyptop - Show hypervisor performance data on System z * * Common functions for debugfs data gatherer * * Copyright IBM Corp. 2010, 2017 * * 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 #include #include #include #include #include "dg_debugfs.h" #include "helper.h" #include "hyptop.h" #define HYPFS_SUBDIR "/s390_hypfs/" static char *l_debugfs_dir; static void l_check_rc(int rc, int exit_on_err) { if (!exit_on_err) return; if (rc == -EACCES) ERR_EXIT("Permission denied, check \"%s/s390_hypfs/\"\n", l_debugfs_dir); if (rc != -ENOENT) ERR_EXIT("Could not initialize data gatherer (%s)\n", strerror(-rc)); } static void l_check_rc_final(int rc, int exit_on_err) { if (!exit_on_err) return; l_check_rc(rc, exit_on_err); ERR_EXIT("Could not initialize data gatherer (%s)\n", strerror(-rc)); } /* * Initialize debugfs data gatherer backend */ int dg_debugfs_init(int exit_on_err) { int rc; l_debugfs_dir = ht_mount_point_get("debugfs"); if (!l_debugfs_dir) { if (!exit_on_err) return -ENODEV; ERR_EXIT("Debugfs is not mounted, try \"mount none -t debugfs " "/sys/kernel/debug\"\n"); } rc = dg_debugfs_vm_init(); if (rc == 0) return 0; else l_check_rc(rc, exit_on_err); rc = dg_debugfs_lpar_init(); if (rc == 0) return 0; else l_check_rc_final(rc, exit_on_err); return rc; } /* * Open a debugfs file */ int dg_debugfs_open(const char *file) { char *path; int fh; path = ht_alloc(strlen(l_debugfs_dir) + strlen(HYPFS_SUBDIR) + strlen(file) + 1); path[0] = 0; strcat(path, l_debugfs_dir); strcat(path, HYPFS_SUBDIR); strcat(path, file); fh = open(path, O_RDONLY); ht_free(path); if (fh == -1) return -errno; else return fh; } s390-tools-2.3.0/hyptop/dg_debugfs.h000066400000000000000000000013541323410431100171300ustar00rootroot00000000000000/* * hyptop - Show hypervisor performance data on System z * * Common functions for debugfs data gatherer * * Copyright IBM Corp. 2010, 2017 * * s390-tools is free software; you can redistribute it and/or modify * it under the terms of the MIT license. See LICENSE for details. */ #ifndef DG_DEBUGFS_H #define DG_DEBUGFS_H #include "sd.h" #define DBFS_WAIT_TIME_US 10000 extern int dg_debugfs_init(int exit_on_err); extern int dg_debugfs_vm_init(void); extern int dg_debugfs_lpar_init(void); extern int dg_debugfs_open(const char *file); /* * z/VM diag 0C prototypes */ int dg_debugfs_vmd0c_init(void); void dg_debugfs_vmd0c_sys_cpu_fill(struct sd_sys *sys, u64 online_time, unsigned int cpu_cnt); #endif /* DG_DEBUGFS_H */ s390-tools-2.3.0/hyptop/dg_debugfs_lpar.c000066400000000000000000000173241323410431100201450ustar00rootroot00000000000000/* * hyptop - Show hypervisor performance data on System z * * Hyptop LPAR data gatherer that operates on debugfs * * Copyright IBM Corp. 2010, 2017 * * 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 #include #include #include #include "dg_debugfs.h" #include "helper.h" #include "hyptop.h" #include "sd.h" #define MTID_MASK 0x1f #define LPAR_NAME_LEN 8 #define TMP_SIZE 64 #define LPAR_PHYS_FLG 0x80 #define CPU_TYPE_LEN 16 #define DEBUGFS_FILE "diag_204" static u64 l_update_time_us; static long l_204_buf_size; /* * Diag data structure definition */ struct l_x_info_blk_hdr { u8 npar; u8 flags; u8 reserved1[6]; u64 curtod1; u64 curtod2; u8 reserved[40]; } __attribute__ ((packed)); struct l_x_sys_hdr { u8 reserved1; u8 cpus; u8 rcpus; u8 reserved2[5]; char sys_name[LPAR_NAME_LEN]; u8 reserved3[33]; u8 mtid; u8 reserved4[46]; } __attribute__ ((packed)); static inline void l_sys_hdr__sys_name(struct l_x_sys_hdr *hdr, char *name) { memcpy(name, hdr->sys_name, LPAR_NAME_LEN); ht_ebcdic_to_ascii(name, LPAR_NAME_LEN); name[LPAR_NAME_LEN] = 0; ht_strstrip(name); } struct l_x_cpu_info { u16 cpu_addr; u8 reserved1[2]; u8 ctidx; u8 reserved2[3]; u64 acc_time; u64 lp_time; u8 reserved3[6]; u8 reserved4[2]; u64 online_time; u8 reserved5[24]; u64 mt_idle_time; u8 reserved6[24]; } __attribute__ ((packed)); static int l_thread_cnt(struct l_x_sys_hdr *hdr) { return (hdr->mtid & MTID_MASK) + 1; } static void l_idx2name(int index, char *name) { switch (index) { case 0: strcpy(name, SD_CPU_TYPE_STR_CP); break; case 3: strcpy(name, SD_CPU_TYPE_STR_IFL); break; default: strcpy(name, SD_CPU_TYPE_STR_UN); } } struct l_x_phys_hdr { u8 reserved1[1]; u8 cpus; u8 reserved2[94]; } __attribute__ ((packed)); struct l_x_phys_cpu { u16 cpu_addr; u8 reserved1[2]; u8 ctidx; u8 reserved2[3]; u64 mgm_time; u8 reserved3[80]; } __attribute__ ((packed)); /* * Fill CPU with data */ static void l_sd_cpu_fill(struct sd_cpu *cpu, struct l_x_cpu_info *cpu_info, int threads) { sd_cpu_cpu_time_us_set(cpu, cpu_info->lp_time); if (threads > 1) sd_cpu_thread_time_us_set(cpu, cpu_info->lp_time * threads - cpu_info->mt_idle_time); else sd_cpu_thread_time_us_set(cpu, cpu_info->lp_time); sd_cpu_mgm_time_us_set(cpu, G0(cpu_info->acc_time - cpu_info->lp_time)); sd_cpu_online_time_us_set(cpu, cpu_info->online_time); sd_cpu_state_set(cpu, SD_CPU_STATE_UNKNOWN); } /* * Fill system with data */ static void *l_sd_sys_fill(struct sd_sys *lpar, struct l_x_sys_hdr *sys_hdr) { struct l_x_cpu_info *cpu_info; int i; cpu_info = (struct l_x_cpu_info *) (sys_hdr + 1); for (i = 0; i < sys_hdr->rcpus; i++) { char cpu_type[CPU_TYPE_LEN + 1]; struct sd_cpu *cpu; char cpu_id[10]; sprintf(cpu_id, "%i", cpu_info->cpu_addr); cpu = sd_cpu_get(lpar, cpu_id); if (!cpu) { l_idx2name(cpu_info->ctidx, cpu_type); cpu = sd_cpu_new(lpar, cpu_id, cpu_type, 1); } l_sd_cpu_fill(cpu, cpu_info, lpar->threads_per_core); sd_cpu_commit(cpu); cpu_info++; } return cpu_info; } /* * Fill one physical CPU with data */ static void l_sd_cpu_phys_fill(struct sd_sys *sys, struct l_x_phys_cpu *cpu_info) { char cpu_type[CPU_TYPE_LEN + 1]; char cpu_id[TMP_SIZE]; struct sd_cpu *cpu; snprintf(cpu_id, TMP_SIZE, "%i", cpu_info->cpu_addr); cpu = sd_cpu_get(sys, cpu_id); if (!cpu) { l_idx2name(cpu_info->ctidx, cpu_type); cpu = sd_cpu_new(sys, cpu_id, cpu_type, 1); sd_cpu_state_set(cpu, SD_CPU_STATE_UNKNOWN); } sd_cpu_mgm_time_us_set(cpu, cpu_info->mgm_time); sd_cpu_real_type_set(cpu, cpu_type); sd_cpu_commit(cpu); } /* * Fill all physical CPUs with data */ static void l_sd_sys_root_cpu_phys_fill(struct sd_sys *sys, struct l_x_phys_hdr *phys_hdr) { struct l_x_phys_cpu *cpu_info; int i; cpu_info = (struct l_x_phys_cpu *) (phys_hdr + 1); for (i = 0; i < phys_hdr->cpus; i++) { l_sd_cpu_phys_fill(sys, cpu_info); cpu_info++; } } /* * Header for debugfs file "diag_204" */ struct l_debugfs_d204_hdr { u64 len; u16 version; u8 reserved[54]; } __attribute__ ((packed)); struct l_debugfs_d204 { struct l_debugfs_d204_hdr h; char buf[]; } __attribute__ ((packed)); /* * Read debugfs file */ static void l_read_debugfs(struct l_debugfs_d204_hdr **hdr, struct l_x_info_blk_hdr **data) { long real_buf_size; ssize_t rc; void *buf; int fh; do { fh = dg_debugfs_open(DEBUGFS_FILE); *hdr = buf = ht_alloc(l_204_buf_size); rc = read(fh, buf, l_204_buf_size); if (rc == -1) ERR_EXIT_ERRNO("Reading hypervisor data failed"); close(fh); real_buf_size = (*hdr)->len + sizeof(struct l_debugfs_d204_hdr); if (rc == real_buf_size) break; l_204_buf_size = real_buf_size; ht_free(buf); } while (1); *data = buf + sizeof(struct l_debugfs_d204_hdr); } /* * Fill System Data */ static void l_sd_sys_root_fill(struct sd_sys *sys) { struct l_x_info_blk_hdr *time_hdr; struct l_debugfs_d204_hdr *hdr; struct l_x_sys_hdr *sys_hdr; struct sd_sys *lpar; char lpar_id[10]; int i; do { l_read_debugfs(&hdr, &time_hdr); if (l_update_time_us != ht_ext_tod_2_us(&time_hdr->curtod1)) { l_update_time_us = ht_ext_tod_2_us(&time_hdr->curtod1); break; } /* * Got old snapshot from kernel. Wait some time until * new snapshot is available. */ ht_free(hdr); usleep(DBFS_WAIT_TIME_US); } while (1); sys_hdr = ((void *) time_hdr) + sizeof(struct l_x_info_blk_hdr); for (i = 0; i < time_hdr->npar; i++) { l_sys_hdr__sys_name(sys_hdr, lpar_id); lpar = sd_sys_get(sys, lpar_id); if (!lpar) lpar = sd_sys_new(sys, lpar_id); lpar->threads_per_core = l_thread_cnt(sys_hdr); sys_hdr = l_sd_sys_fill(lpar, sys_hdr); sd_sys_commit(lpar); } if (time_hdr->flags & LPAR_PHYS_FLG) l_sd_sys_root_cpu_phys_fill(sys, (void *) sys_hdr); ht_free(hdr); sd_sys_commit(sys); } /* * Update system data */ static void l_sd_update(void) { struct sd_sys *root = sd_sys_root_get(); sd_sys_update_start(root); l_sd_sys_root_fill(root); sd_sys_update_end(root, l_update_time_us); } /* * Supported system items */ static struct sd_sys_item *l_sys_item_vec[] = { &sd_sys_item_core_cnt, &sd_sys_item_thread_cnt, &sd_sys_item_core_diff, &sd_sys_item_thread_diff, &sd_sys_item_mgm_diff, &sd_sys_item_core, &sd_sys_item_thread, &sd_sys_item_mgm, &sd_sys_item_online, NULL, }; /* * Default system items */ static struct sd_sys_item *l_sys_item_enable_vec[] = { &sd_sys_item_core_cnt, &sd_sys_item_core_diff, &sd_sys_item_mgm_diff, &sd_sys_item_core, &sd_sys_item_mgm, &sd_sys_item_online, NULL, }; /* * Supported CPU items */ static struct sd_cpu_item *l_cpu_item_vec[] = { &sd_cpu_item_type, &sd_cpu_item_core_diff, &sd_cpu_item_thread_diff, &sd_cpu_item_mgm_diff, &sd_cpu_item_core, &sd_cpu_item_thread, &sd_cpu_item_mgm, &sd_cpu_item_online, NULL, }; /* * Default CPU items */ static struct sd_cpu_item *l_cpu_item_enable_vec[] = { &sd_cpu_item_type, &sd_cpu_item_core_diff, &sd_cpu_item_mgm_diff, NULL, }; /* * Supported CPU types */ static struct sd_cpu_type *l_cpu_type_vec[] = { &sd_cpu_type_ifl, &sd_cpu_type_cp, &sd_cpu_type_un, NULL, }; /* * Define data gatherer structure */ static struct sd_dg l_sd_dg = { .update_sys = l_sd_update, .cpu_type_vec = l_cpu_type_vec, .sys_item_vec = l_sys_item_vec, .sys_item_enable_vec = l_sys_item_enable_vec, .cpu_item_vec = l_cpu_item_vec, .cpu_item_enable_vec = l_cpu_item_enable_vec, }; /* * Initialize LPAR debugfs data gatherer */ int dg_debugfs_lpar_init(void) { int fh; l_204_buf_size = sizeof(struct l_debugfs_d204_hdr); fh = dg_debugfs_open(DEBUGFS_FILE); if (fh < 0) return fh; else close(fh); sd_dg_register(&l_sd_dg, 1); return 0; } s390-tools-2.3.0/hyptop/dg_debugfs_vm.c000066400000000000000000000170771323410431100176360ustar00rootroot00000000000000/* * hyptop - Show hypervisor performance data on System z * * Hyptop z/VM data gatherer that operates on debugfs * * Copyright IBM Corp. 2010, 2017 * * 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 #include #include #include "dg_debugfs.h" #include "helper.h" #include "hyptop.h" #include "sd.h" #define VM_CPU_TYPE "UN" #define VM_CPU_ID "ALL" #define NAME_LEN 8 #define DEBUGFS_FILE "diag_2fc" #define VM_CPU_ID_OPERATING "0" #define VM_CPU_ID_STOPPED "1" static u64 l_update_time_us; static long l_2fc_buf_size; static int l_use_debugfs_vmd0c; static char l_guest_name[64]; /* * Diag 2fc data structure definition */ struct l_diag2fc_data { u32 version; u32 flags; u64 used_cpu; u64 el_time; u64 mem_min_kb; u64 mem_max_kb; u64 mem_share_kb; u64 mem_used_kb; u32 pcpus; u32 lcpus; u32 vcpus; u32 ocpus; u32 cpu_max; u32 cpu_shares; u32 cpu_use_samp; u32 cpu_delay_samp; u32 page_wait_samp; u32 idle_samp; u32 other_samp; u32 total_samp; char guest_name[NAME_LEN]; }; /* * Header for debugfs file "diag_2fc" */ struct l_debugfs_d2fc_hdr { u64 len; u16 version; char tod_ext[16]; u64 count; char reserved[30]; } __attribute__ ((packed)); struct l_debugfs_d2fc { struct l_debugfs_d2fc_hdr h; char diag2fc_buf[]; } __attribute__ ((packed)); /* * Get local guest name */ static void l_guest_name_init(void) { int level, found = 0; char line[1024]; FILE *fh; fh = fopen("/proc/sysinfo", "r"); if (!fh) ERR_EXIT_ERRNO("Could not open '/proc/sysinfo'"); while (fgets(line, sizeof(line), fh)) { if (sscanf(line, "VM%02d Name: %s", &level, l_guest_name) == 2) found = 1; } if (!found) ERR_EXIT("Could find guest name in '/proc/sysinfo'"); fclose(fh); } /* * Get existing or create new CPU */ static struct sd_cpu *l_cpu_alloc(struct sd_sys *guest, const char *id, int cnt) { struct sd_cpu *cpu = sd_cpu_get(guest, id); return cpu ? cpu : sd_cpu_new(guest, id, SD_CPU_TYPE_STR_UN, cnt); } /* * Get number of operating CPUs */ static int l_ocpus(struct l_diag2fc_data *data) { /* * For guests with ABSOLUTE or limit SHARE, ocpus and cpu_max is zero. * In this case we return vcpus. */ return (data->cpu_max == 0) ? data->vcpus : data->ocpus; } /* * Fill operating CPUs with data */ static void l_cpu_oper_fill(struct sd_sys *guest, struct l_diag2fc_data *data) { struct sd_cpu *cpu; cpu = l_cpu_alloc(guest, VM_CPU_ID_OPERATING, l_ocpus(data)); sd_cpu_state_set(cpu, SD_CPU_STATE_OPERATING); sd_cpu_cpu_time_us_set(cpu, data->used_cpu); sd_cpu_online_time_us_set(cpu, data->el_time); sd_cpu_cnt(cpu) = l_ocpus(data); sd_cpu_commit(cpu); } /* * Fill stopped CPUs with data */ static void l_cpu_stop_fill(struct sd_sys *guest, struct l_diag2fc_data *data) { int cnt = data->vcpus - l_ocpus(data); struct sd_cpu *cpu; cpu = l_cpu_alloc(guest, VM_CPU_ID_STOPPED, cnt); sd_cpu_state_set(cpu, SD_CPU_STATE_STOPPED); sd_cpu_cpu_time_us_set(cpu, 0); sd_cpu_online_time_us_set(cpu, 0); sd_cpu_cnt(cpu) = cnt; sd_cpu_commit(cpu); } /* * Fill CPUs will data */ static void l_cpu_fill(struct sd_sys *guest, struct l_diag2fc_data *data) { if (l_ocpus(data) > 0) l_cpu_oper_fill(guest, data); if (data->vcpus - l_ocpus(data) > 0) l_cpu_stop_fill(guest, data); } /* * Fill "guest" with data */ static void l_sd_sys_fill(struct sd_sys *guest, struct l_diag2fc_data *data) { if (l_use_debugfs_vmd0c && (strcmp(guest->id, l_guest_name) == 0)) dg_debugfs_vmd0c_sys_cpu_fill(guest, data->el_time, data->vcpus); else l_cpu_fill(guest, data); sd_sys_weight_cur_set(guest, data->cpu_shares); sd_sys_weight_max_set(guest, data->cpu_max); sd_sys_mem_min_kib_set(guest, data->mem_min_kb); sd_sys_mem_max_kib_set(guest, data->mem_max_kb); sd_sys_mem_use_kib_set(guest, data->mem_used_kb); sd_sys_update_time_us_set(guest, l_update_time_us); sd_sys_commit(guest); } /* * Read debugfs file */ static void l_read_debugfs(struct l_debugfs_d2fc_hdr **hdr, struct l_diag2fc_data **data) { long real_buf_size; ssize_t rc; void *buf; int fh; do { fh = dg_debugfs_open(DEBUGFS_FILE); *hdr = buf = ht_alloc(l_2fc_buf_size); rc = read(fh, buf, l_2fc_buf_size); if (rc == -1) ERR_EXIT_ERRNO("Reading hypervisor data failed"); close(fh); real_buf_size = (*hdr)->len + sizeof(struct l_debugfs_d2fc_hdr); if (rc == real_buf_size) break; l_2fc_buf_size = real_buf_size; ht_free(buf); } while (1); *data = buf + sizeof(struct l_debugfs_d2fc_hdr); } /* * Fill System Data */ static void l_sd_sys_root_fill(struct sd_sys *sys) { struct l_diag2fc_data *d2fc_data; struct l_debugfs_d2fc_hdr *hdr; struct sd_cpu *cpu; unsigned int i; do { l_read_debugfs(&hdr, &d2fc_data); if (l_update_time_us != ht_ext_tod_2_us(&hdr->tod_ext)) { l_update_time_us = ht_ext_tod_2_us(&hdr->tod_ext); break; } /* * Got old snapshot from kernel. Wait some time until * new snapshot is available. */ ht_free(hdr); usleep(DBFS_WAIT_TIME_US); } while (1); cpu = sd_cpu_get(sys, VM_CPU_ID); if (!cpu) cpu = sd_cpu_new(sys, VM_CPU_ID, SD_CPU_TYPE_STR_UN, d2fc_data[0].lcpus); sd_cpu_state_set(cpu, SD_CPU_STATE_UNKNOWN); sd_cpu_cnt(cpu) = d2fc_data[0].lcpus; sd_cpu_commit(cpu); for (i = 0; i < hdr->count; i++) { struct l_diag2fc_data *data = &d2fc_data[i]; char guest_name[NAME_LEN + 1]; struct sd_sys *guest; guest_name[NAME_LEN] = 0; memcpy(guest_name, data->guest_name, NAME_LEN); ht_ebcdic_to_ascii(guest_name, NAME_LEN); ht_strstrip(guest_name); guest = sd_sys_get(sys, guest_name); if (!guest) guest = sd_sys_new(sys, guest_name); l_sd_sys_fill(guest, data); } ht_free(hdr); sd_sys_commit(sys); } /* * Update system data */ static void l_sd_update(void) { struct sd_sys *root = sd_sys_root_get(); sd_sys_update_start(root); l_sd_sys_root_fill(root); sd_sys_update_end(root, l_update_time_us); } /* * Supported system items */ static struct sd_sys_item *l_sys_item_vec[] = { &sd_sys_item_cpu_cnt, &sd_sys_item_cpu_oper_cnt, &sd_sys_item_cpu_diff, &sd_sys_item_mgm_diff, &sd_sys_item_cpu, &sd_sys_item_mgm, &sd_sys_item_online, &sd_sys_item_mem_use, &sd_sys_item_mem_max, &sd_sys_item_weight_cur, &sd_sys_item_weight_max, NULL, }; /* * Default system items */ static struct sd_sys_item *l_sys_item_enable_vec[] = { &sd_sys_item_cpu_cnt, &sd_sys_item_cpu_diff, &sd_sys_item_cpu, &sd_sys_item_online, &sd_sys_item_mem_max, &sd_sys_item_mem_use, &sd_sys_item_weight_cur, NULL, }; /* * Supported CPU items */ static struct sd_cpu_item *l_cpu_item_vec[] = { &sd_cpu_item_cpu_diff, &sd_cpu_item_mgm_diff, &sd_cpu_item_cpu, &sd_cpu_item_mgm, &sd_cpu_item_online, NULL, }; /* * Default CPU items */ static struct sd_cpu_item *l_cpu_item_enable_vec[] = { &sd_cpu_item_cpu_diff, NULL, }; /* * Supported CPU types */ static struct sd_cpu_type *l_cpu_type_vec[] = { &sd_cpu_type_un, NULL, }; /* * Define data gatherer structure */ static struct sd_dg dg_debugfs_vm_dg = { .update_sys = l_sd_update, .cpu_type_vec = l_cpu_type_vec, .sys_item_vec = l_sys_item_vec, .sys_item_enable_vec = l_sys_item_enable_vec, .cpu_item_vec = l_cpu_item_vec, .cpu_item_enable_vec = l_cpu_item_enable_vec, }; /* * Initialize z/VM debugfs data gatherer */ int dg_debugfs_vm_init(void) { int fh; fh = dg_debugfs_vmd0c_init(); if (fh == 0) l_use_debugfs_vmd0c = 1; fh = dg_debugfs_open(DEBUGFS_FILE); if (fh < 0) return fh; else close(fh); l_2fc_buf_size = sizeof(struct l_debugfs_d2fc_hdr); l_guest_name_init(); sd_dg_register(&dg_debugfs_vm_dg, 0); return 0; } s390-tools-2.3.0/hyptop/dg_debugfs_vmd0c.c000066400000000000000000000071451323410431100202200ustar00rootroot00000000000000/* * hyptop - Show hypervisor performance data on System z * * Hyptop z/VM data gatherer for diag 0c that operates on debugfs * * Copyright IBM Corp. 2010, 2017 * * 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 #include #include #include #include "dg_debugfs.h" #include "helper.h" #include "hyptop.h" #include "sd.h" #define DEBUGFS_FILE "diag_0c" static long l_0c_buf_size; /* * Diag 0c entry structure definition */ struct hypfs_diag0c_entry { char date[8]; /* MM/DD/YY in EBCDIC */ char time[8]; /* HH:MM:SS in EBCDIC */ __u64 virtcpu; /* Virtual time consumed by the virt CPU (us) */ __u64 totalproc; /* Total of virtual and simulation time (us) */ __u32 cpu; /* Linux logical CPU number */ __u32 reserved; /* Align to 8 byte */ }; /* * Header for debugfs file "diag_0c" */ struct hypfs_diag0c_hdr { __u64 len; /* Length of diag0c buffer without header */ __u16 version; /* Version of header */ char reserved1[6]; /* Reserved */ char tod_ext[16]; /* TOD clock for diag0c */ __u64 count; /* Number of entries (CPUs) in diag0c array */ char reserved2[24]; /* Reserved */ }; struct hypfs_diag0c_data { struct hypfs_diag0c_hdr hdr; /* 64 byte header */ struct hypfs_diag0c_entry entry[]; /* diag0c entry array */ }; /* * Fill one CPU with data */ static void l_sd_cpu_fill(struct sd_sys *sys, unsigned int cpu_nr, u64 online_time, u64 cpu_time, u64 mgm_time, enum sd_cpu_state state) { struct sd_cpu *cpu; char cpu_id[16]; sprintf(cpu_id, "%d", cpu_nr); cpu = sd_cpu_get(sys, cpu_id); if (!cpu) cpu = sd_cpu_new(sys, cpu_id, SD_CPU_TYPE_STR_UN, 1); sd_cpu_cpu_time_us_set(cpu, cpu_time); sd_cpu_mgm_time_us_set(cpu, mgm_time); sd_cpu_online_time_us_set(cpu, online_time); sd_cpu_state_set(cpu, state); sd_cpu_cnt(cpu) = 1; sd_cpu_commit(cpu); } /* * Read debugfs file */ static void l_read_debugfs(struct hypfs_diag0c_hdr **hdr, struct hypfs_diag0c_entry **entry) { long real_buf_size; ssize_t rc; void *buf; int fh; do { fh = dg_debugfs_open(DEBUGFS_FILE); if (fh < 0) ERR_EXIT_ERRNO("Could not open file: %s", DEBUGFS_FILE); *hdr = buf = ht_alloc(l_0c_buf_size); rc = read(fh, buf, l_0c_buf_size); if (rc == -1) ERR_EXIT_ERRNO("Reading hypervisor data failed"); close(fh); real_buf_size = (*hdr)->len + sizeof(struct hypfs_diag0c_hdr); if (rc == real_buf_size) break; l_0c_buf_size = real_buf_size; ht_free(buf); } while (1); *entry = buf + sizeof(struct hypfs_diag0c_hdr); } /* * Fill System Data */ void dg_debugfs_vmd0c_sys_cpu_fill(struct sd_sys *sys, u64 online_time, unsigned int cpu_cnt) { unsigned int i, cpu_online_vec[cpu_cnt]; struct hypfs_diag0c_entry *d0c_entry; struct hypfs_diag0c_hdr *hdr; u64 mgm_time; memset(cpu_online_vec, 0, sizeof(cpu_online_vec)); l_read_debugfs(&hdr, &d0c_entry); /* First fill online CPUs */ for (i = 0; i < hdr->count; i++) { mgm_time = G0(d0c_entry[i].totalproc - d0c_entry[i].virtcpu); l_sd_cpu_fill(sys, d0c_entry[i].cpu, online_time, d0c_entry[i].virtcpu, mgm_time, SD_CPU_STATE_OPERATING); cpu_online_vec[d0c_entry[i].cpu] = 1; } /* Then fill offline CPUs */ for (i = 0; i < cpu_cnt; i++) { if (cpu_online_vec[i]) continue; l_sd_cpu_fill(sys, i, 0, 0, 0, SD_CPU_STATE_STOPPED); } } /* * Initialize z/VM debugfs data gatherer */ int dg_debugfs_vmd0c_init(void) { int fh; fh = dg_debugfs_open(DEBUGFS_FILE); if (fh < 0) return -1; else close(fh); l_0c_buf_size = sizeof(struct hypfs_diag0c_hdr); return 0; } s390-tools-2.3.0/hyptop/helper.c000066400000000000000000000157721323410431100163220ustar00rootroot00000000000000/* * hyptop - Show hypervisor performance data on System z * * Helper functions * * Copyright IBM Corp. 2010, 2017 * * 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "helper.h" #include "hyptop.h" #include "sd.h" /* * Globals */ static iconv_t l_iconv_ebcdic_ascii; static int l_underline_cnt; static int l_reverse_cnt; static int l_bold_cnt; /* * Print time of day */ void ht_print_time(void) { char time_str[40]; struct timeval tv; struct tm *tm; gettimeofday(&tv, NULL); tm = localtime(&tv.tv_sec); strftime(time_str, sizeof(time_str), "%H:%M:%S", tm); hyptop_printf("%s", time_str); } /* * Alloc uninitialized memory and exit on failure */ void *ht_alloc(size_t size) { void *ptr; ptr = malloc(size); if (!ptr) ERR_EXIT("Out of memory (%zu Kb)", size / 1024); return ptr; } /* * Alloc memory initialized with "0" and exit on failure */ void *ht_zalloc(size_t size) { void *ptr; ptr = calloc(1, size); if (!ptr) ERR_EXIT("Out of memory (%zu Kb)", size / 1024); return ptr; } /* * Realloc memory and exit on failure */ void *ht_realloc(void *old_ptr, size_t size) { void *ptr; assert(size != 0); if (old_ptr) ptr = realloc(old_ptr, size); else ptr = calloc(1, size); if (!ptr) ERR_EXIT("Out of memory (%lu Kb)", (unsigned long) size / 1024); return ptr; } /* * Convert EBCDIC string to ASCII */ void ht_ebcdic_to_ascii(char *inout, size_t len) { iconv(l_iconv_ebcdic_ascii, &inout, &len, &inout, &len); } /* * Get mount point for file system tye "fs_type" */ char *ht_mount_point_get(const char *fs_type) { struct mntent *mntbuf; FILE *mounts; mounts = setmntent(_PATH_MOUNTED, "r"); if (!mounts) ERR_EXIT_ERRNO("Could not find \"%s\" mount point", fs_type); while ((mntbuf = getmntent(mounts)) != NULL) { if (strcmp(mntbuf->mnt_type, fs_type) == 0) { endmntent(mounts); return ht_strdup(mntbuf->mnt_dir); } } endmntent(mounts); return NULL; } /* * Remove all trailing blanks and reture pointer to first non blank character */ char *ht_strstrip(char *s) { size_t size; char *end; size = strlen(s); if (!size) return s; end = s + size - 1; while (end >= s && isspace(*end)) end--; *(end + 1) = '\0'; while (*s && isspace(*s)) s++; return s; } /* * Return copy of string */ char *ht_strdup(const char *str) { char *rc; rc = ht_alloc(strlen(str) + 1); strcpy(rc, str); return rc; } /* * Print help icon in current line */ void ht_print_help_icon(void) { hyptop_print_seek_back(6); ht_underline_on(); hyptop_printf("?"); ht_underline_off(); hyptop_printf("=help"); } /* * Print headline */ void ht_print_head(const char *sys) { struct sd_cpu_type *cpu_type; int i; ht_print_time(); hyptop_printf(" "); if (sys) { ht_bold_on(); hyptop_printf("%s", sys); ht_bold_off(); hyptop_printf(" "); } hyptop_printf("cpu-"); ht_underline_on(); hyptop_printf("t"); ht_underline_off(); hyptop_printf(": "); sd_cpu_type_iterate(cpu_type, i) { if (!sd_cpu_type_selected(cpu_type)) continue; hyptop_printf("%s(%i) ", sd_cpu_type_id(cpu_type), sd_cpu_type_cpu_cnt(cpu_type)); } ht_print_help_icon(); hyptop_print_nl(); } /* * Curses attribute functions */ static void ht_attr_on(int attr) { if (g.o.batch_mode_specified) return; attron(attr); } static void ht_attr_off(int attr) { if (g.o.batch_mode_specified) return; attroff(attr); } void ht_bold_on(void) { if (l_bold_cnt == 0) ht_attr_on(A_BOLD); l_bold_cnt++; } void ht_bold_off(void) { l_bold_cnt--; if (l_bold_cnt == 0) ht_attr_off(A_BOLD); } void ht_underline_on(void) { if (l_underline_cnt == 0) ht_attr_on(A_UNDERLINE); l_underline_cnt++; } void ht_underline_off(void) { l_underline_cnt--; if (l_underline_cnt == 0) ht_attr_off(A_UNDERLINE); } void ht_reverse_on(void) { if (l_reverse_cnt == 0) ht_attr_on(A_REVERSE); l_reverse_cnt++; } void ht_reverse_off(void) { l_reverse_cnt--; if (l_reverse_cnt == 0) ht_attr_off(A_REVERSE); } /* * Print scroll bar */ void ht_print_scroll_bar(int row_cnt, int row_start, int rows_add_top, int rows_add_bottom, int can_scroll_up, int can_scroll_down, int with_border) { int row_cnt_displ, bar_len, start, i; double scale1, scale2; row_cnt_displ = MIN(row_cnt, g.c.row_cnt - rows_add_top - rows_add_bottom); if (row_cnt_displ <= 0) return; /* scale1: Scaling factor virtual screen to physical screen */ scale1 = ((double) row_cnt_displ) / ((double) row_cnt); /* scale2: Scaling factor physical screen to scroll bar size */ scale2 = ((double) row_cnt_displ - 2) / row_cnt_displ; bar_len = MAX(((double) row_cnt_displ * scale1 * scale2 + 0.5), 1); /* start: Start row in scroll bar */ start = ((double) row_start) * scale1 * scale2 + 0.5; if (row_cnt_displ - 2 - start < bar_len) start = row_cnt_displ - 2 - bar_len; ht_reverse_on(); if (with_border) { ht_underline_on(); hyptop_printf_pos(rows_add_top - 1, g.c.col_cnt - 1, " "); ht_underline_off(); hyptop_printf_pos(row_cnt_displ + rows_add_top, g.c.col_cnt - 1, " "); } ht_underline_on(); if (can_scroll_up) { ht_bold_on(); hyptop_printf_pos(rows_add_top, g.c.col_cnt - 1, "^"); ht_bold_off(); } else { hyptop_printf_pos(rows_add_top, g.c.col_cnt - 1, "^"); } ht_underline_off(); if (row_cnt_displ == 1) goto out; ht_underline_on(); if (can_scroll_down) { ht_bold_on(); hyptop_printf_pos(row_cnt_displ - 1 + rows_add_top, g.c.col_cnt - 1, "v"); ht_bold_off(); } else { hyptop_printf_pos(row_cnt_displ - 1 + rows_add_top, g.c.col_cnt - 1, "v"); } ht_underline_off(); if (row_cnt_displ == 2) goto out; for (i = 0; i < row_cnt_displ - 2; i++) hyptop_printf_pos(i + rows_add_top + 1, g.c.col_cnt - 1, " "); ht_underline_on(); hyptop_printf_pos(i + rows_add_top, g.c.col_cnt - 1, " "); ht_underline_off(); ht_bold_on(); for (i = 0; i < bar_len; i++) { if (i + start == row_cnt_displ - 3) ht_underline_on(); hyptop_printf_pos(i + start + 1 + rows_add_top, g.c.col_cnt - 1, "#"); if (i + start == row_cnt_displ - 3) ht_underline_off(); } ht_bold_off(); out: ht_reverse_off(); } /* * Convert string to uppercase */ void ht_str_to_upper(char *str) { while (*str) { *str = toupper(*str); str++; } } /* * Convert ext TOD to microseconds */ u64 ht_ext_tod_2_us(void *tod_ext) { char *tod_ptr = tod_ext; u64 us, *tod1, *tod2; tod1 = (u64 *) tod_ptr; tod2 = (u64 *) &tod_ptr[8]; us = *tod1 << 8; us |= *tod2 >> 58; us = us >> 12; return us; } /* * Initialize helper module */ void hyptop_helper_init(void) { l_iconv_ebcdic_ascii = iconv_open("ISO-8859-1", "EBCDIC-US"); if (l_iconv_ebcdic_ascii == (iconv_t) -1) ERR_EXIT("Could not initialize iconv\n"); } s390-tools-2.3.0/hyptop/helper.h000066400000000000000000000040701323410431100163140ustar00rootroot00000000000000/* * hyptop - Show hypervisor performance data on System z * * Helper functions * * Copyright IBM Corp. 2010, 2017 * * s390-tools is free software; you can redistribute it and/or modify * it under the terms of the MIT license. See LICENSE for details. */ #ifndef HELPER_H #define HELPER_H #include #include #include #include "lib/util_base.h" #include "lib/zt_common.h" #define G0(x) MAX(0, (s64) (x)) /* * Helper Prototypes */ extern void hyptop_helper_init(void); extern char *ht_strstrip(char *str); extern char *ht_strdup(const char *str); extern void ht_print_head(const char *sys); extern void ht_print_help_icon(void); extern void ht_ebcdic_to_ascii(char *inout, size_t len); extern char *ht_mount_point_get(const char *fs_type); extern u64 ht_ext_tod_2_us(void *tod_ext); extern void ht_print_time(void); /* * Memory alloc functions */ extern void *ht_zalloc(size_t size); extern void *ht_alloc(size_t size); extern void *ht_realloc(void *ptr, size_t size); static inline void ht_free(void *ptr) { free(ptr); } /* * Curses extensions */ #define KEY_RETURN 0012 #define KEY_ESCAPE 0033 void ht_bold_on(void); void ht_bold_off(void); void ht_reverse_on(void); void ht_reverse_off(void); void ht_underline_on(void); void ht_underline_off(void); void ht_str_to_upper(char *str); void ht_print_scroll_bar(int row_cnt, int row_start, int row_bar_start, int row_bar_bottom, int can_scroll_up, int can_scroll_down, int with_boder); /* * Error Macros */ #define ERR_MSG(x...) \ do { \ hyptop_text_mode(); \ fflush(stdout); \ fprintf(stderr, "%s: ", g.prog_name);\ fprintf(stderr, x); \ } while (0) #define ERR_EXIT(x...) \ do { \ hyptop_text_mode(); \ fflush(stdout); \ fprintf(stderr, "%s: ", g.prog_name); \ fprintf(stderr, x); \ hyptop_exit(1); \ exit(1); \ } while (0) #define ERR_EXIT_ERRNO(x...) \ do { \ fflush(stdout); \ fprintf(stderr, "%s: ", g.prog_name); \ fprintf(stderr, x); \ fprintf(stderr, " (%s)", strerror(errno)); \ fprintf(stderr, "\n"); \ hyptop_exit(1); \ } while (0) #endif /* HELPER_H */ s390-tools-2.3.0/hyptop/hyptop.8000066400000000000000000000177431323410431100163130ustar00rootroot00000000000000.\" Copyright 2017 IBM Corp. .\" s390-tools is free software; you can redistribute it and/or modify .\" it under the terms of the MIT license. See LICENSE for details. .\" .TH HYPTOP 8 "Nov 2009" "s390-tools" .SH NAME hyptop \- Show hypervisor performance data on System z .SH SYNOPSIS .B hyptop [OPTIONS] .SH DESCRIPTION .B hyptop provides a dynamic real-time view of a hypervisor environment on System z. It works with either the z/VM or the LPAR hypervisor. Depending on the available data it shows for example CPU and memory information about running LPARs or z/VM guests. hyptop provides two windows: .IP " -" sys_list: Shows a list of systems that the hypervisor is currently running .IP " -" sys: Shows one system in more detail. .PP System names in hyptop are either LPAR names as shown on the SE or HMC, or z/VM guest IDs that identify z/VM guest virtual machines. .PP You can run hyptop in interactive mode (default) or in batch mode with the "\-b" option. For how to use the interactive mode, see the online help (enter "?" after hyptop is started). .SH OPTIONS .TP .BR "\-h" " or " "\-\-help" Print usage information, then exit. .TP .BR "\-v" " or " "\-\-version" Print version information, then exit. .TP .BR "\-w " " or " "\-\-window=" Select current window. Use the options "--sys", "--fields", and "--sort" to modify the current window. The last window specified with the "--window" option will be used as start window. The default window is "sys_list". .TP .BR "\-s ,..." " or " "\-\-sys=,..." Select systems for current window. If this option is specified, only the selected systems are shown for the window. For window "sys" only one system can be specified. .TP .BR "\-f [:],..." " or " "\-\-fields=[:],..." Select fields and units in the current window. "F_LETTER" is the field letter that identifies uniquely a field (for example "c" for CPU time). "UNIT" is the used entity for displaying data for the field (for example "us" for microseconds). See FIELDS and UNITS below for definitions. If the "--fields" option is specified, only the selected fields are shown. .TP .BR "\-S " " or " "\-\-sort=" Select sort field for current window. To reverse the sort order, specify the option twice. See FIELDS below for definitions. .TP .BR "\-t ,..." " or " "\-\-cpu_types=,..." Select CPU types that are used for CPU time calculations. See CPU TYPES below for definitions. .TP .BR "\-b" " or " "\-\-batch_mode" Use batch mode (no curses). This can be useful for sending output from hyptop to another program, a file, or a line mode terminal. In this mode no user input is accepted. .TP .BR "\-d " " or " "\-\-delay=" Specifies the delay between screen updates. .TP .BR "\-n " " or " "\-\-iterations=" Specifies the maximum number of iterations before ending. .SH PREREQUISITES The following things are required to run hyptop: .IP " -" The Linux kernel must have the required support to provide the performance data. .IP " -" debugfs has to be mounted. .IP " -" The hyptop user must have read permission for the required debugfs files. .IP " -" You can always monitor the guest operating system where hyptop is running. To monitor any other operating system instances running on the same hypervisor as hyptop, you will need additional permissions. For z/VM, the guest virtual machine must have privilege class B. For LPAR, on the HMC or SE security menu of the LPAR activation profile, select the Global performance data control checkbox. .PP To mount debugfs, you can use this command: # mount none -t debugfs /sys/kernel/debug To make this persistent, add the following to "/etc/fstab": none /sys/kernel/debug debugfs defaults 0 0 .SH FIELDS The supported fields depend on the available data on the hypervisor. This is different between LPAR and z/VM. It might also depend on machine type, z/VM version and kernel version. Each field has a unique field letter that can be used to select the field in interactive mode or through the "--fields" command line option. The following fields are available under LPAR: In "sys_list" and "sys" window: 'c' - Core dispatch time per second 'e' - Thread time per second 'm' - Management time per second 'C' - Total core dispatch time 'E' - Total thread time 'M' - Total management time 'o' - Online time In "sys_list" window: '#' - Number of cores (sum of initial and reserved) 'T' - Number of threads (sum of initial and reserved) In "sys" window: 'p' - CPU type 'v' - Visualization of core dispatch time per second The following fields are available under z/VM: In "sys_list" and "sys" window: 'c' - CPU time per second 'm' - Management time per second (*) 'C' - Total CPU time 'M' - Total management time (*) 'o' - Online time In "sys_list" window: '#' - Number of CPUs 'O' - Number of operating CPUs 'u' - Used memory 'a' - Maximum memory 'r' - Current weight 'x' - Maximum weight In "sys" window: 'v' - Visualization of CPU time per second (*) Only available for the local guest virtual machine Only available if the system has the required support .SH UNITS Depending on the field type the values can be displayed in different units. The following units are supported: Time: 'us' - Microseconds (10^-6 seconds) 'ms' - Millisconds (10^-3 seconds) '%' - Hundreds of a second (10^-2 seconds) or percent 's' - Seconds 'm' - Minutes 'hm' - Hours & Minutes 'dhm' - Days & Hours & Minutes Memory: 'kib' - Kibibytes (1.024 bytes) 'mib' - Mebibytes (1.048.576 bytes) 'gib' - Gibibytes (1.073.741.824 bytes) Miscellaneous: 'str' - String '#' - Count/Number 'vis' - Visualization .SH CPU TYPES Depending on the hypervisor different CPU types are supported. These CPU types can be selected either interactively or with the "--cpu_types" command line option. The calculation of the CPU data only uses CPUs of the specified types. On LPAR the following CPU types are supported: 'IFL' - Integrated Facility for Linux 'CP' - CP processor type 'UN' - Unspecified processor type (other than CP or IFL) NOTE: It is possible that on older machines also IFLs are shown as CPs. On z/VM currently only the processor type 'UN' is available. .SH CPU DATA For Linux on z/VM, no performance data is available for individual CPUs on remote guests. Therefore, the hyptop "sys" window shows identical values for each of the operating CPUs. For the CPU time fields, these values represent the total across all operating CPUs divided by the number of operating CPUs. For the online time field, the value is the time during which at least one CPU has been operational. Operating CPUs are shown with CPU identifier "0" and stopped CPUs with "1". .SH EXAMPLES To start hyptop with the "sys_list" window in interactive mode, enter: .br # hyptop .br To start hyptop with the "sys_list" window in batch mode, enter: .br # hyptop -b .br To start hyptop with the "sys_list" window in interactive mode with the fields CPU time (in milliseconds) and online time (unit default) and sort the output according to online time, enter: .br # hyptop -f c:ms,o -S o .br To start hyptop with the "sys" window with system "MYLPAR" with the fields CPU time (unit milliseconds) and online time (unit default) and sort the output reverse according the online time, enter: .br # hyptop -w sys -s MYLPAR -f c:ms,o -S o -S o .br To start hyptop with the "sys_list" window in batch mode with update delay 5 seconds and 10 iterations, enter: .br # hyptop -b -d 5 -n 10 .br To start hyptop with the "sys_list" window and use only CPU types IFL and CP for CPU time calculation, enter: .br # hyptop -t ifl,cp .SH ENVIRONMENT .TP .B TERM The TERM environment variable specifies your terminal type. To run \fBhyptop\fP in interactive mode the TERM environment variable has to be set. The interactive mode is not available for terminals that have TERM=dumb (e.g. line mode terminals). s390-tools-2.3.0/hyptop/hyptop.c000066400000000000000000000150411323410431100163530ustar00rootroot00000000000000/* * hyptop - Show hypervisor performance data on System z * * Main & init functions * * Copyright IBM Corp. 2010, 2017 * * 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 #include #include #include #include #include #include #include #include "dg_debugfs.h" #include "helper.h" #include "hyptop.h" #include "opts.h" #include "sd.h" #include "win_cpu_types.h" #ifdef WITH_HYPFS #include "dg_hypfs.h" #endif /* * Globals for the whole program */ struct hyptop_globals g; /* * Get current terminal size and tell curses about it */ static void l_term_size_get(void) { struct winsize ws; g.c.col_cnt = 80; g.c.row_cnt = 24; if (ioctl(1, TIOCGWINSZ, &ws) != -1) { if ((ws.ws_col != 0) && (ws.ws_row != 0)) { g.c.col_cnt = ws.ws_col; g.c.row_cnt = ws.ws_row; } } resizeterm(g.c.row_cnt, g.c.col_cnt); } /* * Process input */ static enum hyptop_win_action l_process_input(struct hyptop_win *win) { int c; /* Skip all resize events */ while ((c = wgetch(stdscr)) == KEY_RESIZE) {} return win->process_input(win, c); } /* * Process input with timeout */ static enum hyptop_win_action l_process_input_timeout(time_t time_s, long time_us) { struct timeval tv; fd_set fds; int rc; while (1) { FD_ZERO(&fds); FD_SET(0, &fds); tv.tv_sec = time_s; tv.tv_usec = time_us; rc = select(1, &fds, NULL, NULL, &tv); switch (rc) { case 0: /* Timeout */ return WIN_KEEP; case 1: /* Input */ if (l_process_input(g.w.cur) == WIN_SWITCH) return WIN_SWITCH; continue; case -1: if (errno != EINTR) ERR_EXIT_ERRNO("Select call failed"); /* Signal: Resize */ hyptop_update_term(); continue; default: assert(0); } } } /* * Sleep */ static enum hyptop_win_action l_sleep(time_t time_s, long time_us) { struct timespec ts; ts.tv_sec = time_s; ts.tv_nsec = time_us * 1000; nanosleep(&ts, NULL); return WIN_KEEP; } /* * External process input with timeout funciton */ enum hyptop_win_action hyptop_process_input_timeout(void) { enum hyptop_win_action rc; if (g.o.batch_mode_specified) { opts_iterations_next(); rc = l_sleep(g.o.delay_s, g.o.delay_us); } else { rc = l_process_input_timeout(g.o.delay_s, g.o.delay_us); opts_iterations_next(); } return rc; } /* * External process input funciton */ enum hyptop_win_action hyptop_process_input(void) { return l_process_input_timeout(-1U, 0); } /* * Signal handler for exiting hyptop */ static void l_sig_exit(int sig) { (void) sig; hyptop_exit(0); } /* * Install signal handler */ static void l_sig_handler_init(void) { struct sigaction sigact; /* Ignore signals SIGUSR1 and SIGUSR2 */ if (sigemptyset(&sigact.sa_mask) < 0) goto fail; sigact.sa_flags = 0; sigact.sa_handler = SIG_IGN; if (sigaction(SIGUSR1, &sigact, NULL) < 0) goto fail; if (sigaction(SIGUSR2, &sigact, NULL) < 0) goto fail; /* Exit on SIGINT, SIGTERM, SIGHUP, ... */ if (sigemptyset(&sigact.sa_mask) < 0) goto fail; sigact.sa_handler = l_sig_exit; if (sigaction(SIGINT, &sigact, NULL) < 0) goto fail; if (sigaction(SIGTERM, &sigact, NULL) < 0) goto fail; if (sigaction(SIGHUP, &sigact, NULL) < 0) goto fail; if (sigaction(SIGQUIT, &sigact, NULL) < 0) goto fail; if (sigaction(SIGALRM, &sigact, NULL) < 0) goto fail; if (sigaction(SIGPIPE, &sigact, NULL) < 0) goto fail; return; fail: ERR_EXIT_ERRNO("Could not initialize signal handler"); } /* * Start curses */ static int l_initscr(void) { if (!initscr()) return ERR; g.c.initialized = 1; atexit(hyptop_text_mode); return 0; } /* * Check if terminal is able to run hyptop in curses mode */ static void l_term_check(void) { char *term_str = getenv("TERM"); if (!term_str) ERR_EXIT("Please set TERM environment variable or " "try \"--batch_mode\"\n"); /* S390 line mode terminals normally have TERM=dumb */ if (strcmp(term_str, "dumb") == 0) ERR_EXIT("Terminal of type \"dumb\" is not supported," " try \"--batch_mode\"\n"); } /* * Init curses */ static void l_term_init(void) { if (g.o.batch_mode_specified) return; l_term_check(); if (l_initscr() == ERR) goto fail; if (noecho() == ERR) goto fail; if (nodelay(stdscr, TRUE) == ERR) goto fail; if (cbreak() == ERR) /* Line buffering disabled. pass on everything */ goto fail; if (keypad(stdscr, TRUE) == ERR) goto fail; curs_set(0); /* prevent cursor from blinking */ l_term_size_get(); l_sig_handler_init(); return; fail: ERR_EXIT("Could not initialize curses, try \"--batch_mode\"\n"); } /* * Initialize data gatherer */ #ifdef WITH_HYPFS static void l_dg_init(void) { if (dg_debugfs_init(0) == 0) return; if (dg_hypfs_init() == 0) return; ERR_EXIT("Could not initialize data gatherer\n"); } #else static void l_dg_init(void) { dg_debugfs_init(1); } #endif /* * Windows event loop */ static void l_event_loop(void) { while (1) g.w.cur->run(g.w.cur); } /* * Clear terminal and write new window content to it */ static void l_update_term_curses(void) { /* Init screen */ l_term_size_get(); curs_set(0); /* pervent cursor from blinking */ move(0, 0); erase(); hyptop_printf_init(); /* Write window to screen */ g.w.cur->update_term(g.w.cur); refresh(); } /* * Write window content in line mode */ static void l_update_term_batch(void) { g.w.cur->update_term(g.w.cur); printf("\n"); } /* * Update terminal with new window content */ void hyptop_update_term(void) { if (g.o.batch_mode_specified) l_update_term_batch(); else l_update_term_curses(); } /* * Switch to new window "win" */ enum hyptop_win_action win_switch(struct hyptop_win *win) { assert(g.w.prev_cnt < sizeof(g.w.prev) / sizeof(void *)); g.w.prev[g.w.prev_cnt] = g.w.cur; g.w.prev_cnt++; g.w.cur = win; return WIN_SWITCH; } /* * Switch back to previous window */ enum hyptop_win_action win_back(void) { g.w.prev_cnt--; g.w.cur = g.w.prev[g.w.prev_cnt]; return WIN_SWITCH; } /* * Switch to text mode */ void hyptop_text_mode(void) { if (!g.c.initialized) return; g.c.initialized = 0; clear(); refresh(); endwin(); } /* * Exit hyptop */ void __noreturn hyptop_exit(int rc) { hyptop_text_mode(); exit(rc); } /* * Initialize all modules and start first window */ int main(int argc, char *argv[]) { opts_parse(argc, argv); hyptop_helper_init(); sd_init(); l_dg_init(); opt_verify_systems(); l_term_init(); win_sys_list_init(); win_sys_init(); g.win_cpu_types = win_cpu_types_new(); l_event_loop(); return 0; } s390-tools-2.3.0/hyptop/hyptop.h000066400000000000000000000103451323410431100163620ustar00rootroot00000000000000/* * hyptop - Show hypervisor performance data on System z * * Command line options, window definition, print functions, etc. * * Copyright IBM Corp. 2010, 2017 * * s390-tools is free software; you can redistribute it and/or modify * it under the terms of the MIT license. See LICENSE for details. */ #ifndef HYPTOP_H #define HYPTOP_H #include #include #include #include #include "helper.h" #include "nav_desc.h" #include "table.h" #define HYPTOP_OPT_DEFAULT_DELAY 2 #define HYPTOP_MAX_WIN_DEPTH 4 #define HYPTOP_MAX_LINE 512 #define PROG_NAME "hyptop" /* * Options info */ struct hyptop_str_vec_opt { unsigned int specified; char **vec; unsigned int cnt; }; struct hyptop_col_vec_opt { unsigned int specified; struct table_col_spec **vec; unsigned int cnt; }; struct hyptop_win_opts { struct hyptop_str_vec_opt sys; struct hyptop_col_vec_opt fields; unsigned int sort_field_specified; char sort_field; }; struct hyptop_opts { unsigned int win_specified; unsigned int batch_mode_specified; unsigned int iterations_specified; unsigned int iterations; unsigned int iterations_act; struct hyptop_win *cur_win; struct hyptop_str_vec_opt cpu_types; int delay_s; int delay_us; }; /* * Curses info */ struct hyptop_curses { int row_cnt; int col_cnt; char line[HYPTOP_MAX_LINE]; int x; int y; int initialized; }; /* * Window info */ struct hyptop_win_info { struct hyptop_win *cur; struct hyptop_win *prev[HYPTOP_MAX_WIN_DEPTH]; unsigned int prev_cnt; }; /* * Globals definition */ struct hyptop_globals { struct hyptop_opts o; struct hyptop_curses c; struct hyptop_win_info w; const char *prog_name; struct hyptop_win *win_cpu_types; }; extern struct hyptop_globals g; /* * Print functions */ #define hyptop_printf_pos(y, x, p...) \ do { \ if (g.o.batch_mode_specified) \ printf(p); \ else { \ int len; \ len = snprintf(g.c.line, sizeof(g.c.line) - 1, p); \ len = MIN(len, (g.c.col_cnt - (x))); \ if (len > 0) { \ mvaddnstr((y), (x), g.c.line, len); \ } \ } \ } while (0) #define hyptop_printf(p...) \ do { \ if (g.o.batch_mode_specified) \ printf(p); \ else { \ int len; \ len = snprintf(g.c.line, sizeof(g.c.line) - 1, p); \ len = MIN(len, (g.c.col_cnt - g.c.x)); \ if (len > 0) { \ mvaddnstr(g.c.y, g.c.x, g.c.line, len); \ g.c.x += len; \ } \ } \ } while (0) static inline void hyptop_printf_init(void) { g.c.x = 0; g.c.y = 0; } static inline void hyptop_print_seek_back(int i) { unsigned int cnt = MAX(g.c.col_cnt - g.c.x - i, 0); if (g.o.batch_mode_specified) return; if (cnt) { memset(g.c.line, ' ', cnt); assert(cnt < sizeof(g.c.line)); g.c.line[cnt] = 0; addstr(g.c.line); } g.c.x = g.c.col_cnt - i; } static inline void hyptop_print_nl(void) { unsigned int cnt = MAX(g.c.col_cnt - g.c.x, 0); if (g.o.batch_mode_specified) { printf("\n"); return; } if (cnt) { memset(g.c.line, ' ', g.c.col_cnt - g.c.x); assert(cnt < sizeof(g.c.line)); g.c.line[cnt] = 0; addstr(g.c.line); } g.c.x = 0; g.c.y++; } /* * hyptop windows */ enum hyptop_win_action { WIN_SWITCH, WIN_KEEP, }; extern enum hyptop_win_action hyptop_process_input_timeout(void); extern enum hyptop_win_action hyptop_process_input(void); extern enum hyptop_win_action win_switch(struct hyptop_win *w); extern enum hyptop_win_action win_back(void); struct hyptop_win; struct hyptop_win { enum hyptop_win_action (*process_input)(struct hyptop_win *w, int c); void (*update_term)(struct hyptop_win *w); void (*run)(struct hyptop_win *w); const char *id; const char *desc; struct nav_desc **desc_normal_vec; struct nav_desc **desc_select_vec; struct nav_desc **desc_general_vec; struct hyptop_win_opts opts; }; /* * Window sys_list */ extern struct hyptop_win win_sys_list; extern void win_sys_list_init(void); /* * Window sys */ extern struct hyptop_win win_sys; extern void win_sys_set(const char *sys_id); extern void win_sys_init(void); /* * Window cpu_types */ extern void win_cpu_types_init(void); /* * Misc functions */ extern void hyptop_update_term(void); extern void __noreturn hyptop_exit(int rc); extern void hyptop_text_mode(void); #endif /* HYPTOP_H */ s390-tools-2.3.0/hyptop/nav_desc.c000066400000000000000000000125441323410431100166170ustar00rootroot00000000000000/* * hyptop - Show hypervisor performance data on System z * * Description of navigation keys * * Copyright IBM Corp. 2010, 2017 * * 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 #include #include #include "nav_desc.h" #include "tbox.h" #define L_KEY_LEN 14 #define L_KEY_FMT "%-14s" /* Select mode */ struct nav_desc nav_desc_select_mode_enter = { .desc = "Enter select mode", .keys = {"RIGHT", "l", NULL}, }; struct nav_desc nav_desc_select_mode_leave = { .desc = "Leave select mode", .keys = {"LEFT", "h", NULL}, }; /* "sys" Window */ struct nav_desc nav_desc_win_enter_sys = { .desc = "Go to the \"sys\" window for selected system", .keys = {"RIGHT", "l", NULL}, }; struct nav_desc nav_desc_win_leave_sys = { .desc = "Go to the previous window", .keys = {"LEFT", "h", "q", NULL}, }; struct nav_desc nav_desc_win_leave_sys_fast = { .desc = "Go to the previous window", .keys = {"q", NULL}, }; /* "fields" window */ struct nav_desc nav_desc_win_enter_fields = { .desc = "Go to the \"fields\" window", .keys = {"f", NULL}, } ; struct nav_desc nav_desc_win_leave_fields = { .desc = "Go to the previous window", .keys = {"LEFT", "ENTER", "h", "f", "q", NULL}, }; struct nav_desc nav_desc_win_leave_fields_fast = { .desc = "Go to the previous window", .keys = {"f", "q", NULL}, }; /* "cpu_types" window */ struct nav_desc nav_desc_win_enter_cpu_types = { .desc = "Go to the \"cpu_types\" window", .keys = {"t", NULL}, }; struct nav_desc nav_desc_win_leave_cpu_types = { .desc = "Go to the previous window", .keys = {"LEFT", "ENTER", "h", "t", "q", NULL}, }; struct nav_desc nav_desc_win_leave_cpu_types_fast = { .desc = "Go to the previous window", .keys = {"t", "q", NULL}, }; /* Marks */ struct nav_desc nav_desc_marks_clear = { .desc = "Clear all marked rows", .keys = {"SPACE", NULL}, }; struct nav_desc nav_desc_mark_toggle = { .desc = "Toggle mark for selected row", .keys = {"SPACE", NULL}, }; struct nav_desc nav_desc_mark_toggle_view = { .desc = "Toggle view for marked rows", .keys = {".", NULL}, }; /* Units */ struct nav_desc nav_desc_col_unit_increase = { .desc = "Increase unit type of selected column", .keys = {"+", NULL}, }; struct nav_desc nav_desc_col_unit_decrease = { .desc = "Decrease unit type of selected column", .keys = {"-", NULL}, }; struct nav_desc nav_desc_row_unit_increase = { .desc = "Increase unit type of selected row", .keys = {"+", NULL}, }; struct nav_desc nav_desc_row_unit_decrease = { .desc = "Decrease unit type of selected row", .keys = {"-", NULL}, }; /* Select columns */ struct nav_desc nav_desc_select_col_next = { .desc = "Select next column", .keys = {">", NULL}, }; struct nav_desc nav_desc_select_col_prev = { .desc = "Select previous column", .keys = {"<", NULL}, }; struct nav_desc nav_desc_select_col_hotkey = { .desc = "Select column with hotkey", .keys = {"", NULL}, }; /* Quit */ struct nav_desc nav_desc_quit = { .desc = "Quit program", .keys = {"q", NULL}, }; /* Select rows */ struct nav_desc nav_desc_toggle_mark_hotkey = { .desc = "Toggle mark for row with hotkey", .keys = {"", NULL}, }; /* Navigation */ struct nav_desc nav_desc_scroll_up_line = { .desc = "Scroll up one line", .keys = {"UP", "k", NULL}, }; struct nav_desc nav_desc_scroll_down_line = { .desc = "Scroll down one line", .keys = {"DOWN", "j", NULL}, }; struct nav_desc nav_desc_scroll_up_page = { .desc = "Scroll up one page", .keys = {"PGUP", NULL}, }; struct nav_desc nav_desc_scroll_down_page = { .desc = "Scroll down one page", .keys = {"PGDOWN", NULL}, }; struct nav_desc nav_desc_scroll_up_head = { .desc = "Scroll up to head of window", .keys = {"g", NULL}, }; struct nav_desc nav_desc_scroll_down_tail = { .desc = "Scroll down to tail of window", .keys = {"G", NULL}, }; /* * Add navigation descriptons to text box */ static void l_nav_desc_add(struct tbox *tb, struct nav_desc *desc) { char keys_str[L_KEY_LEN + 1]; unsigned int i, first; char *key; first = 1; keys_str[0] = 0; for (i = 0; (key = desc->keys[i]); i++) { /* * If we have used the whole space for the keys, * we write the line and begin a new one */ if (strlen(desc->keys[i]) + strlen(keys_str) + 1 > L_KEY_LEN) { tbox_printf(tb, " " L_KEY_FMT ": %s", keys_str, desc->desc); keys_str[0] = 0; first = 1; } if (!first) strcat(keys_str, ","); else first = 0; strcat(keys_str, "'"); strcat(keys_str, desc->keys[i]); strcat(keys_str, "'"); assert(strlen(keys_str) <= L_KEY_LEN); } tbox_printf(tb, " " L_KEY_FMT ": %s", keys_str, desc->desc); } /* * Add navigation descriptions for "normal", "select" and "general" to text box */ void nav_desc_add(struct tbox *tb, struct nav_desc **desc_normal, struct nav_desc **desc_select, struct nav_desc **desc_general) { unsigned int i; tbox_printf(tb, "\\BSupported keys in this window\\B"); tbox_printf(tb, " "); tbox_printf(tb, "NORMAL MODE:"); for (i = 0; (desc_normal[i]); i++) l_nav_desc_add(tb, desc_normal[i]); tbox_printf(tb, " "); tbox_printf(tb, "SELECT MODE:"); for (i = 0; (desc_select[i]); i++) l_nav_desc_add(tb, desc_select[i]); tbox_printf(tb, " "); tbox_printf(tb, "GENERAL:"); for (i = 0; (desc_general[i]); i++) l_nav_desc_add(tb, desc_general[i]); tbox_printf(tb, " "); } s390-tools-2.3.0/hyptop/nav_desc.h000066400000000000000000000037171323410431100166260ustar00rootroot00000000000000/* * hyptop - Show hypervisor performance data on System z * * Description of navigation keys * * Copyright IBM Corp. 2010, 2017 * * s390-tools is free software; you can redistribute it and/or modify * it under the terms of the MIT license. See LICENSE for details. */ #ifndef NAV_DESC_H #define NAV_DESC_H #include "tbox.h" struct nav_desc { char *desc; char *keys[]; }; void nav_desc_add(struct tbox *tb, struct nav_desc **desc_normal, struct nav_desc **desc_select, struct nav_desc **desc_general); extern struct nav_desc nav_desc_quit; extern struct nav_desc nav_desc_select_mode_enter; extern struct nav_desc nav_desc_select_mode_leave; extern struct nav_desc nav_desc_win_enter_sys; extern struct nav_desc nav_desc_win_leave_sys; extern struct nav_desc nav_desc_win_leave_sys_fast; extern struct nav_desc nav_desc_win_enter_fields; extern struct nav_desc nav_desc_win_leave_fields; extern struct nav_desc nav_desc_win_leave_fields_fast; extern struct nav_desc nav_desc_win_enter_cpu_types; extern struct nav_desc nav_desc_win_leave_cpu_types; extern struct nav_desc nav_desc_win_leave_cpu_types_fast; extern struct nav_desc nav_desc_marks_clear; extern struct nav_desc nav_desc_mark_toggle; extern struct nav_desc nav_desc_mark_toggle_view; extern struct nav_desc nav_desc_col_unit_increase; extern struct nav_desc nav_desc_col_unit_decrease; extern struct nav_desc nav_desc_row_unit_increase; extern struct nav_desc nav_desc_row_unit_decrease; extern struct nav_desc nav_desc_select_col_next; extern struct nav_desc nav_desc_select_col_prev; extern struct nav_desc nav_desc_select_col_hotkey; extern struct nav_desc nav_desc_toggle_mark_hotkey; extern struct nav_desc nav_desc_scroll_up_line; extern struct nav_desc nav_desc_scroll_down_line; extern struct nav_desc nav_desc_scroll_up_page; extern struct nav_desc nav_desc_scroll_down_page; extern struct nav_desc nav_desc_scroll_up_head; extern struct nav_desc nav_desc_scroll_down_tail; #endif /* NAV_DESC_H */ s390-tools-2.3.0/hyptop/opts.c000066400000000000000000000216331323410431100160210ustar00rootroot00000000000000/* * hyptop - Show hypervisor performance data on System z * * Command line parsing * * Copyright IBM Corp. 2010, 2017 * * 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 #include #include "lib/zt_common.h" #include "getopt.h" #include "helper.h" #include "hyptop.h" #include "opts.h" #include "sd.h" static const char l_copyright_str[] = "Copyright IBM Corp. 2010, 2017"; /* * Help text for tool */ static char HELP_TEXT[] = "Usage: hyptop [OPTIONS]\n" "\n" "Show hypervisor performance data on System z.\n" "\n" "-h, --help Print this help, then exit\n" "-v, --version Print version information, then exit\n" "-w, --window WIN_NAME Current window (\"sys\" or \"sys_list\")\n" "-s, --sys SYSTEM[,..] Systems for current window\n" "-f, --fields LETTER[:UNIT][,..] Fields and units for current window\n" "-S, --sort LETTER Sort field for current window\n" "-t, --cpu_types TYPE[,..] CPU types used for time calculations\n" "-b, --batch_mode Use batch mode (no curses)\n" "-d, --delay SECONDS Delay time between screen updates\n" "-n, --iterations NUMBER Number of iterations before ending\n"; /* * Initialize default settings */ static void l_init_defaults(void) { g.prog_name = PROG_NAME; g.o.delay_s = HYPTOP_OPT_DEFAULT_DELAY; g.w.cur = &win_sys_list; g.o.cur_win = &win_sys_list; } /* * Print "help" hint */ static void l_std_usage_exit(void) { fprintf(stderr, "Try '%s --help' for more information.\n", g.prog_name); hyptop_exit(1); } /* * Print help text */ static void l_usage(void) { printf("%s", HELP_TEXT); } /* * Print version information */ static void l_print_version(void) { printf("%s: Hypervisor Top version %s\n", g.prog_name, RELEASE_STRING); printf("%s\n", l_copyright_str); } /* * Check if string is a number */ static int l_number_check(const char *str) { const char *ptr = str; while (*ptr) { if (!isdigit(*ptr)) ERR_EXIT("The argument \"%s\" is not an integer\n", str); ptr++; } return 1; } /* * Set delay option */ static void l_delay_set(char *delay_string) { int secs; l_number_check(delay_string); if (sscanf(delay_string, "%i", &secs) != 1) ERR_EXIT("The delay value \"%s\" is invalid\n", delay_string); g.o.delay_s = secs; g.o.delay_us = 0; } /* * Get number of occurrences of character 'c' in "str" */ static int l_get_char_cnt(char *str, char c) { unsigned int i; int cnt = 0; for (i = 0; str[i] != 0; i++) { if (str[i] == c) cnt++; } return cnt; } /* * Return copy of string with removed trailing and leading blanks */ static char *l_trim_str_new(char *str) { char *rc; int i; for (i = 0; *(str + i) == ' '; i++) {} rc = ht_strdup(str + i); ht_strstrip(rc); if (strlen(rc) == 0) ERR_EXIT("The argument \"%s\" is invalid\n", str); return rc; } /* * Get column specification for string */ static struct table_col_spec *l_get_col_spec(char *str) { struct table_col_spec *col_spec; unsigned int i; char *key_str; col_spec = ht_zalloc(sizeof(*col_spec)); for (i = strlen(str); i > 0; i--) { if (str[i] == ':') { col_spec->unit_str = l_trim_str_new(&str[i + 1]); str[i] = 0; } } key_str = l_trim_str_new(str); if (strlen(key_str) > 1) ERR_EXIT("The field key \"%s\" is invalid\n", key_str); col_spec->hotkey = key_str[0]; ht_free(key_str); return col_spec; } /* * Set the "--fields" option */ static void l_fields_set(char *str) { struct hyptop_col_vec_opt *opt = &g.o.cur_win->opts.fields; unsigned int i, j; opt->cnt = l_get_char_cnt(str, ',') + 1; opt->vec = ht_zalloc(sizeof(void *) * (opt->cnt + 1)); j = 0; for (i = strlen(str); i > 0; i--) { if (str[i] != ',') continue; opt->vec[j] = l_get_col_spec(&str[i + 1]); str[i] = 0; j++; } opt->vec[j] = l_get_col_spec(str); opt->specified = 1; } /* * Set the "--sort_field" option */ static void l_sort_field_set(char *str) { if (strlen(str) > 1) ERR_EXIT("The sort field \"%s\" is invalid\n", str); if (g.o.cur_win->opts.sort_field_specified && g.o.cur_win->opts.sort_field != str[0]) g.o.cur_win->opts.sort_field_specified = 0; g.o.cur_win->opts.sort_field_specified++; g.o.cur_win->opts.sort_field = str[0]; } /* * Setup a string vector out of a comma separated list in "str" */ static void l_str_vec_set(char *str, struct hyptop_str_vec_opt *opt) { unsigned int i, j; opt->cnt = l_get_char_cnt(str, ',') + 1; opt->vec = ht_zalloc(sizeof(void *) * (opt->cnt + 1)); j = 0; for (i = strlen(str); i > 0; i--) { if (str[i] != ',') continue; opt->vec[j] = l_trim_str_new(&str[i + 1]); str[i] = 0; j++; } opt->vec[j] = l_trim_str_new(str); opt->specified = 1; } /* * Set the "--sys" option */ static void l_sys_set(char *str) { l_str_vec_set(str, &g.o.cur_win->opts.sys); } /* * Set the "--cpu_types" option */ static void l_cpu_types_set(char *str) { l_str_vec_set(str, &g.o.cpu_types); } /* * Set the "--window" option */ static void l_window_set(const char *str) { g.o.win_specified = 1; if (strcmp(str, win_sys_list.id) == 0) g.o.cur_win = &win_sys_list; else if (strcmp(str, win_sys.id) == 0) g.o.cur_win = &win_sys; else ERR_EXIT("The window \"%s\" is unknown\n", str); } /* * Set the "--iterations" option */ static void l_iterations_set(const char *str) { l_number_check(str); g.o.iterations_specified = 1; g.o.iterations = atoi(str); } /* * Set the "--batch_mode" option */ static void l_batch_mode_set(void) { g.o.batch_mode_specified = 1; } /* * Make option consisteny checks at end of command line parsing */ static void l_parse_finish(void) { if (g.o.iterations_specified && g.o.iterations == 0) hyptop_exit(0); if (g.o.cur_win != &win_sys) return; if (!win_sys.opts.sys.specified) ERR_EXIT("Specify a system for window \"sys\"\n"); if (win_sys.opts.sys.cnt != 1) ERR_EXIT("More than one system for window \"sys\" has been " "specified\n"); win_switch(&win_sys); } /* * Main command line parsing function */ void opts_parse(int argc, char *argv[]) { int opt, index; static struct option long_options[] = { { "version", no_argument, NULL, 'v'}, { "help", no_argument, NULL, 'h'}, { "batch_mode", no_argument, NULL, 'b'}, { "delay", required_argument, NULL, 'd'}, { "window", required_argument, NULL, 'w'}, { "sys", required_argument, NULL, 's'}, { "iterations", required_argument, NULL, 'n'}, { "fields", required_argument, NULL, 'f'}, { "sort_field", required_argument, NULL, 'S'}, { "cpu_types", required_argument, NULL, 't'}, { NULL, 0, NULL, 0 } }; static const char option_string[] = "vhbd:w:s:n:f:t:S:"; l_init_defaults(); while (1) { opt = getopt_long(argc, argv, option_string, long_options, &index); if (opt == -1) break; switch (opt) { case 'v': l_print_version(); hyptop_exit(0); case 'h': l_usage(); hyptop_exit(0); case 'b': l_batch_mode_set(); break; case 'd': l_delay_set(optarg); break; case 'w': l_window_set(optarg); break; case 's': l_sys_set(optarg); break; case 'n': l_iterations_set(optarg); break; case 't': l_cpu_types_set(optarg); break; case 'f': l_fields_set(optarg); break; case 'S': l_sort_field_set(optarg); break; default: l_std_usage_exit(); } } if (optind != argc) ERR_EXIT("Invalid positional parameter \"%s\" specified\n", argv[optind]); l_parse_finish(); } /* * Has "sys_name" been specified on command line? */ int opts_sys_specified(struct hyptop_win *win, const char* sys_name) { unsigned int i; if (!win->opts.sys.specified) return 1; for (i = 0; i < win->opts.sys.cnt; i++) { if (strcmp(win->opts.sys.vec[i], sys_name) == 0) return 1; } return 0; } /* * Verify that all specified systems are available for window */ static void l_verify_systems(struct hyptop_win *win) { char *sys_name; unsigned int i; for (i = 0; i < win->opts.sys.cnt; i++) { if (sd_sys_get(sd_sys_root_get(), win->opts.sys.vec[i])) continue; sys_name = ht_strdup(win->opts.sys.vec[i]); ht_str_to_upper(win->opts.sys.vec[i]); if (sd_sys_get(sd_sys_root_get(), win->opts.sys.vec[i])) { ht_free(sys_name); continue; } ERR_EXIT("System \"%s\" is not available\n", sys_name); } } /* * Verify that all specified systems are available for all windows */ void opt_verify_systems(void) { l_verify_systems(&win_sys_list); l_verify_systems(&win_sys); if (g.o.cur_win == &win_sys) win_sys_set(win_sys.opts.sys.vec[0]); } /* * Increase iterations count and exit if necessary */ void opts_iterations_next(void) { if (g.o.iterations_specified) { g.o.iterations_act++; if (g.o.iterations_act >= g.o.iterations) hyptop_exit(0); } if (g.o.batch_mode_specified) printf("---------------------------------------------------" "----------------------------\n"); } s390-tools-2.3.0/hyptop/opts.h000066400000000000000000000010411323410431100160150ustar00rootroot00000000000000/* * hyptop - Show hypervisor performance data on System z * * Command line parsing * * Copyright IBM Corp. 2010, 2017 * * s390-tools is free software; you can redistribute it and/or modify * it under the terms of the MIT license. See LICENSE for details. */ #ifndef OPTS_H #define OPTS_H #include "hyptop.h" extern void opts_parse(int argc, char *argv[]); extern void opts_iterations_next(void); extern int opts_sys_specified(struct hyptop_win *win, const char* sys_name); extern void opt_verify_systems(void); #endif /* OPTS_H */ s390-tools-2.3.0/hyptop/sd.h000066400000000000000000000274271323410431100154560ustar00rootroot00000000000000/* * hyptop - Show hypervisor performance data on System z * * System data module: Provide database for system data (e.g. CPU and memory) * * Copyright IBM Corp. 2010, 2017 * * s390-tools is free software; you can redistribute it and/or modify * it under the terms of the MIT license. See LICENSE for details. */ #ifndef SD_H #define SD_H #include "lib/util_list.h" #include "helper.h" #include "table.h" #define SD_DG_INIT_INTERVAL_MS 200 #define SD_SYS_ID_SIZE 9 /* * CPU info */ struct sd_cpu_info { u64 cpu_time_us; u64 thread_time_us; u64 mgm_time_us_set; u64 mgm_time_us; u64 wait_time_us; s64 steal_time_us; u64 online_time_us; }; /* * Memory Info */ struct sd_mem { u64 min_kib; u64 max_kib; u64 use_kib; }; /* * Weight */ struct sd_weight { u16 cur; u16 min; u16 max; }; /* * System Name */ struct sd_sys_name { char os[9]; }; struct sd_sys; /* * SD info */ struct sd_info { u8 active; struct sd_sys *parent; }; struct sd_cpu; /* * SD System (can be e.g. CEC, VM or guest/LPAR) */ struct sd_sys { struct util_list_node list; struct sd_info i; u64 update_time_us; u32 child_cnt; u32 child_cnt_active; struct util_list child_list; u32 cpu_cnt; u32 cpu_cnt_active; struct util_list cpu_list; u32 threads_per_core; char id[SD_SYS_ID_SIZE]; struct sd_sys_name name; struct sd_mem mem; struct sd_weight weight; }; #define sd_sys_id(sys) ((sys)->id) #define sd_sys_name_os(sys) ((sys)->name.os) void sd_sys_update_start(struct sd_sys *sys); void sd_sys_update_end(struct sd_sys *sys, u64 update_time_us); struct sd_sys *sd_sys_root_get(void); struct sd_sys *sd_sys_get(struct sd_sys *parent, const char *id); struct sd_sys *sd_sys_new(struct sd_sys *parent, const char *id); static inline void sd_sys_weight_cur_set(struct sd_sys *sys, u64 value) { sys->weight.cur = value; } static inline void sd_sys_weight_min_set(struct sd_sys *sys, u64 value) { sys->weight.min = value; } static inline void sd_sys_weight_max_set(struct sd_sys *sys, u64 value) { sys->weight.max = value; } static inline void sd_sys_mem_use_kib_set(struct sd_sys *sys, u64 value) { sys->mem.use_kib = value; } static inline void sd_sys_mem_min_kib_set(struct sd_sys *sys, u64 value) { sys->mem.min_kib = value; } static inline void sd_sys_mem_max_kib_set(struct sd_sys *sys, u64 value) { sys->mem.max_kib = value; } static inline void sd_sys_update_time_us_set(struct sd_sys *sys, u64 value) { sys->update_time_us = value; } /* * CPU type */ #define CPU_TYPE_ID_LEN 16 #define CPU_TYPE_DESC_LEN 64 #define SD_CPU_TYPE_STR_IFL "IFL" #define SD_CPU_TYPE_STR_CP "CP" #define SD_CPU_TYPE_STR_UN "UN" struct sd_cpu_type { char id[CPU_TYPE_ID_LEN]; char desc[CPU_TYPE_DESC_LEN]; u32 idx; int cpu_cnt; char hotkey; }; #define sd_cpu_type_id(type) (type->id) #define sd_cpu_type_desc(type) (type->desc) int sd_cpu_type_selected(struct sd_cpu_type *cpu_type); void sd_cpu_type_select_toggle(struct sd_cpu_type *cpu_type); void sd_cpu_type_select(struct sd_cpu_type *cpu_type); void sd_cpu_type_select_all(void); void sd_cpu_type_select_none(void); struct sd_cpu_type *sd_cpu_type_by_id(const char *id); static inline int sd_cpu_type_cpu_cnt(struct sd_cpu_type *type) { return type->cpu_cnt; } static inline void sd_sys_commit(struct sd_sys *sys) { struct sd_sys *parent = sys->i.parent; sys->i.active = 1; if (parent) parent->child_cnt_active++; } extern struct sd_cpu_type sd_cpu_type_ifl; extern struct sd_cpu_type sd_cpu_type_cp; extern struct sd_cpu_type sd_cpu_type_un; /* * SD CPU */ enum sd_cpu_state { SD_CPU_STATE_UNKNOWN = 0, SD_CPU_STATE_OPERATING = 1, SD_CPU_STATE_STOPPED = 2, SD_CPU_STATE_DECONFIG = 3, }; struct sd_cpu { struct util_list_node list; struct sd_info i; char id[9]; struct sd_cpu_type *type; char real_type[CPU_TYPE_ID_LEN]; struct sd_cpu_info d1; struct sd_cpu_info d2; struct sd_cpu_info *d_cur; struct sd_cpu_info *d_prev; u16 cnt; enum sd_cpu_state state; }; static inline char *sd_cpu_state_str(enum sd_cpu_state state) { static char *state_str[] = {"UK", "OP", "ST", "DC"}; return state_str[(int) state]; } #define sd_cpu_has_diff(cpu) (cpu->d_prev != NULL) #define sd_cpu_diff(cpu, member) (cpu->d_cur->member - cpu->d_prev->member) #define sd_cpu_id(cpu) (cpu->id) #define sd_cpu_cnt(cpu) (cpu->cnt) #define sd_cpu_type_str(cpu) (cpu->type->id) #define sd_cpu_state(cpu) (cpu->state) struct sd_cpu *sd_cpu_get(struct sd_sys *sys, const char *cpu_id); struct sd_cpu *sd_cpu_new(struct sd_sys *parent, const char *id, const char *type, int cnt); static inline void sd_cpu_state_set(struct sd_cpu *cpu, enum sd_cpu_state state) { cpu->state = state; } static inline void sd_cpu_real_type_set(struct sd_cpu *cpu, const char *type) { strncpy(cpu->real_type, type, sizeof(cpu->real_type)); } static inline void sd_cpu_cpu_time_us_set(struct sd_cpu *cpu, u64 value) { cpu->d_cur->cpu_time_us = value; } static inline void sd_cpu_thread_time_us_set(struct sd_cpu *cpu, u64 value) { cpu->d_cur->thread_time_us = value; } static inline void sd_cpu_mgm_time_us_set(struct sd_cpu *cpu, u64 value) { cpu->d_cur->mgm_time_us_set = 1; cpu->d_cur->mgm_time_us = value; } static inline void sd_cpu_wait_time_us_set(struct sd_cpu *cpu, u64 value) { cpu->d_cur->wait_time_us = value; } static inline void sd_cpu_steal_time_us_set(struct sd_cpu *cpu, s64 value) { cpu->d_cur->steal_time_us = value; } static inline void sd_cpu_online_time_us_set(struct sd_cpu *cpu, u64 value) { cpu->d_cur->online_time_us = value; } static inline void sd_cpu_commit(struct sd_cpu *cpu) { struct sd_sys *parent = cpu->i.parent; cpu->i.active = 1; if (parent) parent->cpu_cnt_active++; } /* * Item types */ enum sd_item_type { SD_TYPE_U16, SD_TYPE_U32, SD_TYPE_U64, SD_TYPE_S64, SD_TYPE_STR, }; /* * CPU item */ struct sd_cpu_item { struct table_col table_col; enum sd_item_type type; int offset; char *desc; int (*fn_set)(struct sd_cpu_item *, struct sd_cpu *); u64 (*fn_u64)(struct sd_cpu_item *, struct sd_cpu *); s64 (*fn_s64)(struct sd_cpu_item *, struct sd_cpu *); char *(*fn_str)(struct sd_cpu_item *, struct sd_cpu *); }; #define sd_cpu_item_type(x) ((x)->type) #define sd_cpu_item_table_col(item) (&(item)->table_col) extern int sd_cpu_item_available(struct sd_cpu_item *item); extern int sd_cpu_item_cnt(void); /* * Item access functions */ static inline u64 sd_cpu_item_set(struct sd_cpu_item *item, struct sd_cpu *cpu) { return (item->fn_set == NULL) ? 1 : item->fn_set(item, cpu); } static inline u64 sd_cpu_item_u64(struct sd_cpu_item *item, struct sd_cpu *cpu) { return item->fn_u64(item, cpu); } static inline u64 sd_cpu_item_s64(struct sd_cpu_item *item, struct sd_cpu *cpu) { return item->fn_s64(item, cpu); } static inline char *sd_cpu_item_str(struct sd_cpu_item *item, struct sd_cpu *cpu) { if (item->fn_str) return item->fn_str(item, cpu); else return ((char *) cpu) + item->offset; } /* * Predefined CPU items */ extern struct sd_cpu_item sd_cpu_item_type; extern struct sd_cpu_item sd_cpu_item_state; extern struct sd_cpu_item sd_cpu_item_cpu_diff; extern struct sd_cpu_item sd_cpu_item_core_diff; extern struct sd_cpu_item sd_cpu_item_thread_diff; extern struct sd_cpu_item sd_cpu_item_mgm_diff; extern struct sd_cpu_item sd_cpu_item_wait_diff; extern struct sd_cpu_item sd_cpu_item_steal_diff; extern struct sd_cpu_item sd_cpu_item_cpu; extern struct sd_cpu_item sd_cpu_item_core; extern struct sd_cpu_item sd_cpu_item_thread; extern struct sd_cpu_item sd_cpu_item_mgm; extern struct sd_cpu_item sd_cpu_item_wait; extern struct sd_cpu_item sd_cpu_item_steal; extern struct sd_cpu_item sd_cpu_item_online; /* * System item */ struct sd_sys_item { struct table_col table_col; enum sd_item_type type; int offset; char *desc; int info; int (*fn_set)(struct sd_sys_item *, struct sd_sys *); u64 (*fn_u64)(struct sd_sys_item *, struct sd_sys *); s64 (*fn_s64)(struct sd_sys_item *, struct sd_sys *); }; #define sd_sys_item_table_col(item) (&item->table_col) #define sd_sys_item_type(item) (item->type) extern int sd_sys_item_available(struct sd_sys_item *item); extern int sd_sys_item_cnt(void); /* * Item access functions */ static inline int sd_sys_item_set(struct sd_sys *sys, struct sd_sys_item *item) { return (item->fn_set == NULL) ? 1 : item->fn_set(item, sys); } static inline u64 sd_sys_item_u64(struct sd_sys *sys, struct sd_sys_item *item) { return item->fn_u64(item, sys); } static inline s64 sd_sys_item_s64(struct sd_sys *sys, struct sd_sys_item *item) { return item->fn_s64(item, sys); } static inline char *sd_sys_item_str(struct sd_sys *sys, struct sd_sys_item *item) { return ((char *) sys) + item->offset; } /* * Predefined System items */ extern struct sd_sys_item sd_sys_item_cpu_cnt; extern struct sd_sys_item sd_sys_item_core_cnt; extern struct sd_sys_item sd_sys_item_thread_cnt; extern struct sd_sys_item sd_sys_item_cpu_oper_cnt; extern struct sd_sys_item sd_sys_item_cpu_deconf_cnt; extern struct sd_sys_item sd_sys_item_cpu_stop_cnt; extern struct sd_sys_item sd_sys_item_cpu_diff; extern struct sd_sys_item sd_sys_item_core_diff; extern struct sd_sys_item sd_sys_item_thread_diff; extern struct sd_sys_item sd_sys_item_mgm_diff; extern struct sd_sys_item sd_sys_item_wait_diff; extern struct sd_sys_item sd_sys_item_steal_diff; extern struct sd_sys_item sd_sys_item_cpu; extern struct sd_sys_item sd_sys_item_core; extern struct sd_sys_item sd_sys_item_thread; extern struct sd_sys_item sd_sys_item_mgm; extern struct sd_sys_item sd_sys_item_wait; extern struct sd_sys_item sd_sys_item_steal; extern struct sd_sys_item sd_sys_item_online; extern struct sd_sys_item sd_sys_item_mem_max; extern struct sd_sys_item sd_sys_item_mem_min; extern struct sd_sys_item sd_sys_item_mem_use; extern struct sd_sys_item sd_sys_item_weight_cur; extern struct sd_sys_item sd_sys_item_weight_min; extern struct sd_sys_item sd_sys_item_weight_max; extern struct sd_sys_item sd_sys_item_os_name; extern struct sd_sys_item sd_sys_item_samples_total; extern struct sd_sys_item sd_sys_item_samples_cpu_using; /* * Data gatherer backend */ struct sd_dg { void (*update_sys)(void); struct sd_cpu_type **cpu_type_vec; struct sd_sys_item **sys_item_vec; struct sd_sys_item **sys_item_enable_vec; struct sd_cpu_item **cpu_item_vec; struct sd_cpu_item **cpu_item_enable_vec; }; void sd_dg_register(struct sd_dg *, int); int sd_dg_has_core_data(void); /* * Iterators */ #define sd_sys_iterate(parent, sys) \ util_list_iterate(&parent->child_list, sys) #define sd_cpu_iterate(parent, cpu) \ util_list_iterate(&parent->cpu_list, cpu) #define sd_sys_item_iterate(ptr, i) \ for (i = 0; (ptr = sd.dg->sys_item_vec[i]); i++) #define sd_sys_item_enable_iterate(ptr, i) \ for (i = 0; (ptr = sd.dg->sys_item_enable_vec[i]); i++) #define sd_cpu_item_iterate(ptr, i) \ for (i = 0; (ptr = sd.dg->cpu_item_vec[i]); i++) #define sd_cpu_item_enable_iterate(ptr, i) \ for (i = 0; (ptr = sd.dg->cpu_item_enable_vec[i]); i++) #define sd_cpu_type_iterate(ptr, i) \ for (i = 0; (ptr = sd.dg->cpu_type_vec[i]); i++) /* * Offset macros */ #define SD_SYSTEM_OFFSET(x) \ ((unsigned long)(void *)&(((struct sd_sys *) NULL)->x)) #define SD_CPU_INFO_OFFSET(x) \ ((unsigned long)(void *)&(((struct sd_cpu_info *) NULL)->x)) static inline int l_cpu_info_set(struct sd_cpu_info *info, unsigned long offset) { return (int)*(u64 *)(((char *) info) + offset - 8); } static inline u64 l_cpu_info_u64(struct sd_cpu_info *info, unsigned long offset) { return *(u64 *)(((char *) info) + offset); } static inline s64 l_cpu_info_s64(struct sd_cpu_info *info, unsigned long offset) { return *(s64 *)(((char *) info) + offset); } /* * Misc */ void sd_update(void); extern void sd_init(void); static inline u64 l_sub_64(u64 x, u64 y) { return x < y ? 0 : x - y; } struct sd_globals { struct sd_dg *dg; }; extern struct sd_globals sd; #endif /* SD_H */ s390-tools-2.3.0/hyptop/sd_core.c000066400000000000000000000176231323410431100164560ustar00rootroot00000000000000/* * hyptop - Show hypervisor performance data on System z * * System data module: Provide backend independent database for system data * (e.g. for CPU and memory data) * * Copyright IBM Corp. 2010, 2017 * * 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 #include #include "helper.h" #include "hyptop.h" #include "opts.h" #include "sd.h" /* * Internal globals for system data */ static u32 l_cpu_type_selected_mask; static int l_cpu_type_cnt; static int l_sys_item_cnt; static int l_cpu_item_cnt; static int l_has_core_data; static struct sd_sys *l_root_sys; /* * External globals for system data */ struct sd_globals sd; /* * Get root system */ struct sd_sys *sd_sys_root_get(void) { return l_root_sys; } /* * Get CPU type by it's ID */ struct sd_cpu_type *sd_cpu_type_by_id(const char *id) { struct sd_cpu_type *type; unsigned int i; sd_cpu_type_iterate(type, i) { if (strcasecmp(id, type->id) == 0) return type; } return NULL; } /* * Is CPU type selected? */ int sd_cpu_type_selected(struct sd_cpu_type *cpu_type) { return l_cpu_type_selected_mask & cpu_type->idx; } /* * Toggle selection of CPU type */ void sd_cpu_type_select_toggle(struct sd_cpu_type *cpu_type) { if (l_cpu_type_selected_mask & cpu_type->idx) l_cpu_type_selected_mask &= ~cpu_type->idx; else l_cpu_type_selected_mask |= cpu_type->idx; } /* * Select exactly specified CPU type */ void sd_cpu_type_select(struct sd_cpu_type *cpu_type) { l_cpu_type_selected_mask = cpu_type->idx; } /* * Select all available CPU types */ void sd_cpu_type_select_all(void) { l_cpu_type_selected_mask = (u32)-1; } /* * Deselect all CPU types */ void sd_cpu_type_select_none(void) { l_cpu_type_selected_mask = 0; } /* * Setup CPU types specified on command line */ static void l_opts_cpu_types_init(void) { struct sd_cpu_type *type; unsigned int i; if (!g.o.cpu_types.specified) return; sd_cpu_type_select_none(); for (i = 0; i < g.o.cpu_types.cnt; i++) { type = sd_cpu_type_by_id(g.o.cpu_types.vec[i]); if (!type) ERR_EXIT("Invalid CPU type \"%s\"\n", g.o.cpu_types.vec[i]); sd_cpu_type_select_toggle(type); } } /* * Init CPU count for all CPU types */ static void l_cpu_types_init(void) { struct sd_sys *sys = sd_sys_root_get(); struct sd_cpu_type *cpu_type; unsigned int i; sd_cpu_type_iterate(cpu_type, i) { sd_cpu_type_select(cpu_type); cpu_type->cpu_cnt = sd_sys_item_u64(sys, &sd_sys_item_cpu_cnt); } sd_cpu_type_select_all(); l_opts_cpu_types_init(); } /* * Update system data using the data gatherer */ void sd_update(void) { sd.dg->update_sys(); } /* * Register a data gatherer */ void sd_dg_register(struct sd_dg *dg, int has_core_data) { struct timespec ts = {0, SD_DG_INIT_INTERVAL_MS * 1000000}; struct sd_sys_item *sys_item; struct sd_cpu_item *cpu_item; unsigned int i; l_has_core_data = has_core_data; sd.dg = dg; for (i = 0; dg->cpu_type_vec[i]; i++) dg->cpu_type_vec[i]->idx = (1UL << i); l_cpu_type_cnt = i; sd_sys_item_iterate(sys_item, i) l_sys_item_cnt++; sd_cpu_item_iterate(cpu_item, i) l_cpu_item_cnt++; sd_update(); nanosleep(&ts, NULL); sd_update(); l_cpu_types_init(); } /* * Does backend has core data? */ int sd_dg_has_core_data(void) { return l_has_core_data; } /* * Get CPU from sys by ID */ struct sd_cpu *sd_cpu_get(struct sd_sys *sys, const char* id) { struct sd_cpu *cpu; util_list_iterate(&sys->cpu_list, cpu) { if (strcmp(cpu->id, id) == 0) return cpu; } return NULL; } /* * Get CPU type by ID */ static struct sd_cpu_type *l_cpu_type_by_id(const char *id) { struct sd_cpu_type **cpu_type_vec = sd.dg->cpu_type_vec; int i; for (i = 0; i < l_cpu_type_cnt; i++) { if (strcmp(cpu_type_vec[i]->id, id) == 0) return cpu_type_vec[i]; } return NULL; } /* * Allocate and initialize new CPU */ struct sd_cpu *sd_cpu_new(struct sd_sys *parent, const char *id, const char *type, int cnt) { struct sd_cpu *cpu; cpu = ht_zalloc(sizeof(*cpu)); cpu->i.parent = parent; strncpy(cpu->id, id, sizeof(cpu->id)); cpu->type = l_cpu_type_by_id(type); cpu->d_cur = &cpu->d1; cpu->cnt = cnt; util_list_add_tail(&parent->cpu_list, cpu); return cpu; } /* * Get system by ID */ struct sd_sys *sd_sys_get(struct sd_sys *parent, const char* id) { struct sd_sys *sys; util_list_iterate(&parent->child_list, sys) { if (strcmp(sys->id, id) == 0) return sys; } return NULL; } /* * Allocate and initialize new system */ struct sd_sys *sd_sys_new(struct sd_sys *parent, const char *id) { struct sd_sys *sys_new; sys_new = ht_zalloc(sizeof(*sys_new)); strncpy(sys_new->id, id, sizeof(sys_new->id)); util_list_init(&sys_new->child_list, struct sd_sys, list); util_list_init(&sys_new->cpu_list, struct sd_cpu, list); if (parent) { sys_new->i.parent = parent; parent->child_cnt++; util_list_add_tail(&parent->child_list, sys_new); } sys_new->threads_per_core = 1; return sys_new; } /* * Free system */ static void sd_sys_free(struct sd_sys *sys) { ht_free(sys); } /* * Free CPU */ static void sd_cpu_free(struct sd_cpu *cpu) { ht_free(cpu); } /* * Start update cycle for CPU */ static void l_cpu_update_start(struct sd_cpu *cpu) { struct sd_cpu_info *tmp; cpu->i.active = 0; if (!cpu->d_prev) { cpu->d_prev = &cpu->d1; cpu->d_cur = &cpu->d2; } else { tmp = cpu->d_prev; cpu->d_prev = cpu->d_cur; cpu->d_cur = tmp; } } /* * Start update cycle for system */ void sd_sys_update_start(struct sd_sys *sys) { struct sd_sys *child; struct sd_cpu *cpu; sys->i.active = 0; sys->child_cnt_active = 0; sys->cpu_cnt_active = 0; util_list_iterate(&sys->cpu_list, cpu) l_cpu_update_start(cpu); util_list_iterate(&sys->child_list, child) sd_sys_update_start(child); } /* * End update cycle for CPUs of a system */ static void l_cpu_update_end(struct sd_sys *sys) { struct sd_cpu *cpu, *tmp; /* Has system not lost any CPU? */ if (sys->cpu_cnt_active == sys->cpu_cnt) return; util_list_iterate_safe(&sys->cpu_list, cpu, tmp) { if (!cpu->i.active) { /* CPU has not been updated, remove it */ util_list_remove(&sys->cpu_list, cpu); sd_cpu_free(cpu); continue; } } sys->cpu_cnt = sys->cpu_cnt_active; } /* * End update cycle for system */ static void l_sys_update_end(struct sd_sys *sys) { struct sd_sys *child, *tmp; l_cpu_update_end(sys); util_list_iterate_safe(&sys->child_list, child, tmp) { if (!child->i.active) { /* child has not been updated, remove it */ util_list_remove(&sys->child_list, child); sd_sys_free(child); continue; } /* Recursively update child */ l_sys_update_end(child); } sys->child_cnt = sys->child_cnt_active; } /* * End update cycle for system */ void sd_sys_update_end(struct sd_sys *sys, u64 update_time_us) { sys->update_time_us = update_time_us; l_sys_update_end(sys); } /* * Is system item available? */ int sd_sys_item_available(struct sd_sys_item *item) { struct sd_sys_item *ptr; unsigned int i; sd_sys_item_iterate(ptr, i) { if (item == ptr) return 1; } return 0; } /* * Number of system items */ int sd_sys_item_cnt(void) { return l_sys_item_cnt; } /* * Is CPU item avaiable? */ int sd_cpu_item_available(struct sd_cpu_item *item) { struct sd_cpu_item *ptr; unsigned int i; sd_cpu_item_iterate(ptr, i) { if (item == ptr) return 1; } return 0; } /* * Number of CPU items */ int sd_cpu_item_cnt(void) { return l_cpu_item_cnt; } /* * Init system data module */ void sd_init(void) { l_root_sys = sd_sys_new(NULL, "root"); } /* * CPU Types */ struct sd_cpu_type sd_cpu_type_ifl = { .id = SD_CPU_TYPE_STR_IFL, .desc = "Integrated Facility for Linux", .hotkey = 'i', }; struct sd_cpu_type sd_cpu_type_cp = { .id = SD_CPU_TYPE_STR_CP, .desc = "Central processor", .hotkey = 'p', }; struct sd_cpu_type sd_cpu_type_un = { .id = SD_CPU_TYPE_STR_UN, .desc = "Unspecified processor type", .hotkey = 'u', }; s390-tools-2.3.0/hyptop/sd_cpu_items.c000066400000000000000000000134331323410431100175110ustar00rootroot00000000000000/* * hyptop - Show hypervisor performance data on System z * * Provide CPU Items * * Copyright IBM Corp. 2010, 2017 * * 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 "sd.h" /* * Return CPU type of "cpu" */ static char *l_cpu_type(struct sd_cpu_item *item, struct sd_cpu *cpu) { (void) item; return sd_cpu_type_str(cpu); } /* * Return CPU state of "cpu" */ static char *l_cpu_state(struct sd_cpu_item *item, struct sd_cpu *cpu) { (void) item; return sd_cpu_state_str(sd_cpu_state(cpu)); } /* * value = (value_current - value_prev) / online_time_diff */ static double l_cpu_diff(struct sd_cpu_item *item, struct sd_cpu *cpu, int sign) { u64 online_time_diff_us; double factor, diff_us; if (sd_cpu_state(cpu) == SD_CPU_STATE_STOPPED) return 0; if (!cpu->d_prev || !cpu->d_cur) return 0; if (!sd_cpu_type_selected(cpu->type)) return 0; online_time_diff_us = l_sub_64(cpu->d_cur->online_time_us, cpu->d_prev->online_time_us); if (online_time_diff_us == 0) return 0; factor = ((double) online_time_diff_us) / 1000000 * cpu->cnt; if (sign) diff_us = l_cpu_info_s64(cpu->d_cur, item->offset) - l_cpu_info_s64(cpu->d_prev, item->offset); else diff_us = l_sub_64(l_cpu_info_u64(cpu->d_cur, item->offset), l_cpu_info_u64(cpu->d_prev, item->offset)); diff_us /= factor; return diff_us; } /* * unsigned value = (value_current - value_prev) / online_time_diff */ static u64 l_cpu_diff_u64(struct sd_cpu_item *item, struct sd_cpu *cpu) { return l_cpu_diff(item, cpu, 0); } /* * signed value = (value_current - value_prev) / online_time_diff */ static s64 l_cpu_diff_s64(struct sd_cpu_item *item, struct sd_cpu *cpu) { return l_cpu_diff(item, cpu, 1); } /* * Return true, if CPU item has been set */ static inline int l_cpu_item_set(struct sd_cpu_item *item, struct sd_cpu *cpu) { return l_cpu_info_set(cpu->d_cur, item->offset); } /* * Return cpu item value */ static u64 l_cpu_item_64(struct sd_cpu_item *item, struct sd_cpu *cpu) { if (!cpu->d_cur) return 0; if (!sd_cpu_type_selected(cpu->type)) return 0; if (item->table_col.agg == TABLE_COL_AGG_MAX) return l_cpu_info_u64(cpu->d_cur, item->offset); else return l_cpu_info_u64(cpu->d_cur, item->offset) / cpu->cnt; } /* * CPU item definitions */ struct sd_cpu_item sd_cpu_item_type = { .table_col = TABLE_COL_STR('p', "type"), .type = SD_TYPE_STR, .desc = "CPU type", .fn_str = l_cpu_type, }; struct sd_cpu_item sd_cpu_item_state = { .table_col = TABLE_COL_STR('a', "stat"), .type = SD_TYPE_STR, .desc = "CPU state", .fn_str = l_cpu_state, }; struct sd_cpu_item sd_cpu_item_core_diff = { .table_col = TABLE_COL_TIME_DIFF_SUM(table_col_unit_perc, 'c', "core"), .type = SD_TYPE_U64, .offset = SD_CPU_INFO_OFFSET(cpu_time_us), .desc = "Core dispatch time per second", .fn_u64 = l_cpu_diff_u64, }; struct sd_cpu_item sd_cpu_item_cpu_diff = { .table_col = TABLE_COL_TIME_DIFF_SUM(table_col_unit_perc, 'c', "cpu"), .type = SD_TYPE_U64, .offset = SD_CPU_INFO_OFFSET(cpu_time_us), .desc = "CPU time per second", .fn_u64 = l_cpu_diff_u64, }; struct sd_cpu_item sd_cpu_item_thread_diff = { .table_col = TABLE_COL_TIME_DIFF_SUM(table_col_unit_perc, 'e', "the"), .type = SD_TYPE_U64, .offset = SD_CPU_INFO_OFFSET(thread_time_us), .desc = "Thread time per second", .fn_u64 = l_cpu_diff_u64, }; struct sd_cpu_item sd_cpu_item_mgm_diff = { .table_col = TABLE_COL_TIME_DIFF_SUM(table_col_unit_perc, 'm', "mgm"), .type = SD_TYPE_U64, .offset = SD_CPU_INFO_OFFSET(mgm_time_us), .desc = "Management time per second", .fn_set = l_cpu_item_set, .fn_u64 = l_cpu_diff_u64, }; struct sd_cpu_item sd_cpu_item_wait_diff = { .table_col = TABLE_COL_TIME_DIFF_SUM(table_col_unit_perc, 'w', "wait"), .type = SD_TYPE_U64, .offset = SD_CPU_INFO_OFFSET(wait_time_us), .desc = "Wait time per second", .fn_u64 = l_cpu_diff_u64, }; struct sd_cpu_item sd_cpu_item_steal_diff = { .table_col = TABLE_COL_STIME_DIFF_SUM(table_col_unit_perc, 's', "steal"), .type = SD_TYPE_S64, .offset = SD_CPU_INFO_OFFSET(steal_time_us), .desc = "Steal time per second", .fn_s64 = l_cpu_diff_s64, }; struct sd_cpu_item sd_cpu_item_core = { .table_col = TABLE_COL_TIME_SUM(table_col_unit_hm, 'C', "core+"), .type = SD_TYPE_U64, .offset = SD_CPU_INFO_OFFSET(cpu_time_us), .desc = "Total core dispatch time", .fn_u64 = l_cpu_item_64, }; struct sd_cpu_item sd_cpu_item_cpu = { .table_col = TABLE_COL_TIME_SUM(table_col_unit_hm, 'C', "cpu+"), .type = SD_TYPE_U64, .offset = SD_CPU_INFO_OFFSET(cpu_time_us), .desc = "Total CPU time", .fn_u64 = l_cpu_item_64, }; struct sd_cpu_item sd_cpu_item_thread = { .table_col = TABLE_COL_TIME_SUM(table_col_unit_hm, 'E', "the+"), .type = SD_TYPE_U64, .offset = SD_CPU_INFO_OFFSET(thread_time_us), .desc = "Total thread time", .fn_u64 = l_cpu_item_64, }; struct sd_cpu_item sd_cpu_item_mgm = { .table_col = TABLE_COL_TIME_SUM(table_col_unit_hm, 'M', "mgm+"), .type = SD_TYPE_U64, .offset = SD_CPU_INFO_OFFSET(mgm_time_us), .desc = "Total management time", .fn_set = l_cpu_item_set, .fn_u64 = l_cpu_item_64, }; struct sd_cpu_item sd_cpu_item_wait = { .table_col = TABLE_COL_TIME_SUM(table_col_unit_hm, 'W', "wait+"), .type = SD_TYPE_U64, .offset = SD_CPU_INFO_OFFSET(wait_time_us), .desc = "Total wait time", .fn_u64 = l_cpu_item_64, }; struct sd_cpu_item sd_cpu_item_steal = { .table_col = TABLE_COL_STIME_SUM(table_col_unit_hm, 'T', "steal+"), .type = SD_TYPE_U64, .offset = SD_CPU_INFO_OFFSET(steal_time_us), .desc = "Total steal time", .fn_u64 = l_cpu_item_64, }; struct sd_cpu_item sd_cpu_item_online = { .table_col = TABLE_COL_TIME_MAX(table_col_unit_dhm, 'o', "online"), .type = SD_TYPE_U64, .offset = SD_CPU_INFO_OFFSET(online_time_us), .desc = "Online time", .fn_u64 = l_cpu_item_64, }; s390-tools-2.3.0/hyptop/sd_sys_items.c000066400000000000000000000235651323410431100175470ustar00rootroot00000000000000/* * hyptop - Show hypervisor performance data on System z * * Provide System Items * * Copyright IBM Corp. 2010, 2017 * * 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 "sd.h" /* * Count CPUs of system according to active CPU types and requested CPU state */ static u64 l_sys_cpu_cnt_gen(struct sd_sys *sys, enum sd_cpu_state state, int all) { struct sd_cpu *cpu; u32 cnt = 0; sd_cpu_iterate(sys, cpu) { if (!sd_cpu_type_selected(cpu->type)) continue; if (all || sd_cpu_state(cpu) == state) cnt += cpu->cnt; } return cnt; } /* * Count all CPUs of system */ static u64 l_sys_cpu_cnt(struct sd_sys_item *item, struct sd_sys *sys) { (void) item; return l_sys_cpu_cnt_gen(sys, SD_CPU_STATE_UNKNOWN, 1); } /* * Count all threads of system */ static u64 l_sys_thread_cnt(struct sd_sys_item *item, struct sd_sys *sys) { (void) item; return l_sys_cpu_cnt_gen(sys, SD_CPU_STATE_UNKNOWN, 1) * sys->threads_per_core; } /* * Count CPUs of system with state stopped */ static u64 l_sys_cpu_st_cnt(struct sd_sys_item *item, struct sd_sys *sys) { (void) item; return l_sys_cpu_cnt_gen(sys, SD_CPU_STATE_STOPPED, 0); } /* * Count CPUs of system with state operating */ static u64 l_sys_cpu_op_cnt(struct sd_sys_item *item, struct sd_sys *sys) { (void) item; return l_sys_cpu_cnt_gen(sys, SD_CPU_STATE_OPERATING, 0); } /* * Count CPUs of system with state deconfigured */ static u64 l_sys_cpu_dc_cnt(struct sd_sys_item *item, struct sd_sys *sys) { (void) item; return l_sys_cpu_cnt_gen(sys, SD_CPU_STATE_DECONFIG, 0); } /* * Check if CPU info is set */ static int l_sys_cpu_info_set(struct sd_sys_item *item, struct sd_sys *sys) { struct sd_cpu *cpu; sd_cpu_iterate(sys, cpu) { if (!sd_cpu_type_selected(cpu->type)) continue; if (!l_cpu_info_set(cpu->d_cur, item->offset)) return 0; } return 1; } /* * Get u64 system item value from "sys" */ static u64 l_sys_item_u64(struct sd_sys_item *item, struct sd_sys *sys) { switch (item->type) { case SD_TYPE_U16: return *(u16 *)(((char *) sys) + item->offset); case SD_TYPE_U32: return *(u32 *)(((char *) sys) + item->offset); case SD_TYPE_U64: return *(u64 *)(((char *) sys) + item->offset); case SD_TYPE_S64: case SD_TYPE_STR: break; } assert(0); return 0; } /* * Calculate system item out of sum of CPU info */ static u64 l_sys_cpu_info_sum_u64(struct sd_sys_item *item, struct sd_sys *sys) { struct sd_cpu *cpu; u64 rc = 0; sd_cpu_iterate(sys, cpu) { if (!sd_cpu_type_selected(cpu->type)) continue; rc += l_cpu_info_u64(cpu->d_cur, item->offset); } return rc; } /* * Calculate system item out of MAX of CPU info */ static u64 l_sys_cpu_info_max_u64(struct sd_sys_item *item, struct sd_sys *sys) { struct sd_cpu *cpu; u64 rc = 0; sd_cpu_iterate(sys, cpu) { if (!sd_cpu_type_selected(cpu->type)) continue; rc = MAX(rc, l_cpu_info_u64(cpu->d_cur, item->offset)); } return rc; } /* * value = (value_current - value_prev) / online_time_diff */ static double l_cpu_info_diff_u64(struct sd_sys_item *item, struct sd_cpu *cpu, int sign) { u64 online_time_diff_us; double factor, diff_us; if (!sd_cpu_type_selected(cpu->type)) return 0; if (sd_cpu_state(cpu) == SD_CPU_STATE_STOPPED) return 0; online_time_diff_us = l_sub_64(cpu->d_cur->online_time_us, cpu->d_prev->online_time_us); if (online_time_diff_us == 0) return 0; if (sign) { diff_us = l_cpu_info_s64(cpu->d_cur, item->offset) - l_cpu_info_s64(cpu->d_prev, item->offset); } else { diff_us = l_sub_64(l_cpu_info_u64(cpu->d_cur, item->offset), l_cpu_info_u64(cpu->d_prev, item->offset)); } factor = ((double) online_time_diff_us) / 1000000; diff_us /= factor; return diff_us; } /* * SUM over all CPUs: value = (value_current - value_prev) / online_time_diff */ static u64 l_sys_cpu_info_diff_u64(struct sd_sys_item *item, struct sd_sys *sys) { struct sd_cpu *cpu; u64 rc = 0; sd_cpu_iterate(sys, cpu) { if (!cpu->d_prev || !cpu->d_cur) return 0; rc += l_cpu_info_diff_u64(item, cpu, 0); } return rc; } /* * SUM over all CPUs: value = (value_current - value_prev) / online_time_diff */ static s64 l_sys_cpu_info_diff_s64(struct sd_sys_item *item, struct sd_sys *sys) { struct sd_cpu *cpu; s64 rc = 0; sd_cpu_iterate(sys, cpu) { if (!cpu->d_prev || !cpu->d_cur) return 0; rc += l_cpu_info_diff_u64(item, cpu, 1); } return rc; } /* * System item definitions */ struct sd_sys_item sd_sys_item_core_cnt = { .table_col = TABLE_COL_CNT_SUM('#', "#core"), .type = SD_TYPE_U32, .desc = "Number of cores", .fn_u64 = l_sys_cpu_cnt, }; struct sd_sys_item sd_sys_item_cpu_cnt = { .table_col = TABLE_COL_CNT_SUM('#', "#cpu"), .type = SD_TYPE_U32, .desc = "Number of CPUs", .fn_u64 = l_sys_cpu_cnt, }; struct sd_sys_item sd_sys_item_thread_cnt = { .table_col = TABLE_COL_CNT_SUM('T', "#the"), .type = SD_TYPE_U32, .desc = "Number of threads", .fn_u64 = l_sys_thread_cnt, }; struct sd_sys_item sd_sys_item_cpu_oper_cnt = { .table_col = TABLE_COL_CNT_SUM('O', "#cpuop"), .type = SD_TYPE_U32, .desc = "Number of operating CPUs", .fn_u64 = l_sys_cpu_op_cnt, }; struct sd_sys_item sd_sys_item_cpu_stop_cnt = { .table_col = TABLE_COL_CNT_SUM('S', "#cpust"), .type = SD_TYPE_U32, .desc = "Number of stopped CPUs", .fn_u64 = l_sys_cpu_st_cnt, }; struct sd_sys_item sd_sys_item_cpu_deconf_cnt = { .table_col = TABLE_COL_CNT_SUM('D', "#cpudc"), .type = SD_TYPE_U32, .desc = "Number of deconfigured CPUs", .fn_u64 = l_sys_cpu_dc_cnt, }; struct sd_sys_item sd_sys_item_core_diff = { .table_col = TABLE_COL_TIME_DIFF_SUM(table_col_unit_perc, 'c', "core"), .offset = SD_CPU_INFO_OFFSET(cpu_time_us), .type = SD_TYPE_U64, .desc = "Core dispatch time per second", .fn_u64 = l_sys_cpu_info_diff_u64, }; struct sd_sys_item sd_sys_item_cpu_diff = { .table_col = TABLE_COL_TIME_DIFF_SUM(table_col_unit_perc, 'c', "cpu"), .offset = SD_CPU_INFO_OFFSET(cpu_time_us), .type = SD_TYPE_U64, .desc = "CPU time per second", .fn_u64 = l_sys_cpu_info_diff_u64, }; struct sd_sys_item sd_sys_item_thread_diff = { .table_col = TABLE_COL_TIME_DIFF_SUM(table_col_unit_perc, 'e', "the"), .offset = SD_CPU_INFO_OFFSET(thread_time_us), .type = SD_TYPE_U64, .desc = "Thread time per second", .fn_u64 = l_sys_cpu_info_diff_u64, }; struct sd_sys_item sd_sys_item_mgm_diff = { .table_col = TABLE_COL_TIME_DIFF_SUM(table_col_unit_perc, 'm', "mgm"), .offset = SD_CPU_INFO_OFFSET(mgm_time_us), .type = SD_TYPE_U64, .desc = "Management time per second", .fn_set = l_sys_cpu_info_set, .fn_u64 = l_sys_cpu_info_diff_u64, }; struct sd_sys_item sd_sys_item_wait_diff = { .table_col = TABLE_COL_TIME_DIFF_SUM(table_col_unit_perc, 'w', "wait"), .offset = SD_CPU_INFO_OFFSET(wait_time_us), .type = SD_TYPE_U64, .desc = "Wait time per second", .fn_u64 = l_sys_cpu_info_diff_u64, }; struct sd_sys_item sd_sys_item_steal_diff = { .table_col = TABLE_COL_STIME_DIFF_SUM(table_col_unit_perc, 's', "steal"), .offset = SD_CPU_INFO_OFFSET(steal_time_us), .type = SD_TYPE_S64, .desc = "Steal time per second", .fn_s64 = l_sys_cpu_info_diff_s64, }; struct sd_sys_item sd_sys_item_core = { .table_col = TABLE_COL_TIME_SUM(table_col_unit_hm, 'C', "core+"), .offset = SD_CPU_INFO_OFFSET(cpu_time_us), .type = SD_TYPE_U64, .desc = "Total core dispatch time", .fn_u64 = l_sys_cpu_info_sum_u64, }; struct sd_sys_item sd_sys_item_cpu = { .table_col = TABLE_COL_TIME_SUM(table_col_unit_hm, 'C', "cpu+"), .offset = SD_CPU_INFO_OFFSET(cpu_time_us), .type = SD_TYPE_U64, .desc = "Total CPU time", .fn_u64 = l_sys_cpu_info_sum_u64, }; struct sd_sys_item sd_sys_item_thread = { .table_col = TABLE_COL_TIME_SUM(table_col_unit_hm, 'E', "the+"), .offset = SD_CPU_INFO_OFFSET(thread_time_us), .type = SD_TYPE_U64, .desc = "Total thread time", .fn_u64 = l_sys_cpu_info_sum_u64, }; struct sd_sys_item sd_sys_item_wait = { .table_col = TABLE_COL_TIME_SUM(table_col_unit_hm, 'W', "wait+"), .offset = SD_CPU_INFO_OFFSET(wait_time_us), .type = SD_TYPE_U64, .desc = "Total wait time", .fn_u64 = l_sys_cpu_info_sum_u64, }; struct sd_sys_item sd_sys_item_mgm = { .table_col = TABLE_COL_TIME_SUM(table_col_unit_hm, 'M', "mgm+"), .offset = SD_CPU_INFO_OFFSET(mgm_time_us), .type = SD_TYPE_U64, .desc = "Total management time", .fn_set = l_sys_cpu_info_set, .fn_u64 = l_sys_cpu_info_sum_u64, }; struct sd_sys_item sd_sys_item_steal = { .table_col = TABLE_COL_STIME_SUM(table_col_unit_hm, 'T', "steal+"), .offset = SD_CPU_INFO_OFFSET(steal_time_us), .type = SD_TYPE_U64, .desc = "Total steal time", .fn_u64 = l_sys_cpu_info_sum_u64, }; struct sd_sys_item sd_sys_item_online = { .table_col = TABLE_COL_TIME_MAX(table_col_unit_dhm, 'o', "online"), .offset = SD_CPU_INFO_OFFSET(online_time_us), .type = SD_TYPE_U64, .desc = "Online time", .fn_u64 = l_sys_cpu_info_max_u64, }; struct sd_sys_item sd_sys_item_mem_max = { .table_col = TABLE_COL_MEM_SUM(table_col_unit_gib, 'a', "memmax"), .offset = SD_SYSTEM_OFFSET(mem.max_kib), .type = SD_TYPE_U64, .desc = "Maximum memory", .fn_u64 = l_sys_item_u64, }; struct sd_sys_item sd_sys_item_mem_use = { .table_col = TABLE_COL_MEM_SUM(table_col_unit_gib, 'u', "memuse"), .offset = SD_SYSTEM_OFFSET(mem.use_kib), .type = SD_TYPE_U64, .desc = "Used memory", .fn_u64 = l_sys_item_u64, }; struct sd_sys_item sd_sys_item_weight_cur = { .table_col = TABLE_COL_CNT_MAX('r', "wcur"), .offset = SD_SYSTEM_OFFSET(weight.cur), .type = SD_TYPE_U16, .desc = "Current weight", .fn_u64 = l_sys_item_u64, }; struct sd_sys_item sd_sys_item_weight_min = { .table_col = TABLE_COL_CNT_MAX('n', "wmin"), .offset = SD_SYSTEM_OFFSET(weight.min), .type = SD_TYPE_U16, .desc = "Minimum weight", .fn_u64 = l_sys_item_u64, }; struct sd_sys_item sd_sys_item_weight_max = { .table_col = TABLE_COL_CNT_MAX('x', "wmax"), .offset = SD_SYSTEM_OFFSET(weight.max), .type = SD_TYPE_U16, .desc = "Maximum weight", .fn_u64 = l_sys_item_u64, }; s390-tools-2.3.0/hyptop/table.c000066400000000000000000000605201323410431100161210ustar00rootroot00000000000000/* * hyptop - Show hypervisor performance data on System z * * Table module: Provide line mode and curses base table * * Copyright IBM Corp. 2010, 2017 * * 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 #include #include #include #include #include #include "helper.h" #include "hyptop.h" #include "table.h" #define L_ROWS_EXTRA 2 /* head + last */ #define table_col_iterate(t, col, i) \ for (i = 0, col = t->col_vec[0]; col != NULL; col = t->col_vec[++i]) /* * Is row marked? */ static int l_row_is_marked(struct table *t, struct table_row *row) { struct table_mark_key *key; util_list_iterate(&t->mark_key_list, key) { if (strcmp(row->entries[0].str, key->str) == 0) return 1; } return 0; } /* * Add mark key to table */ static void l_mark_key_add(struct table *t, char *str) { struct table_mark_key *key; key = ht_zalloc(sizeof(*key)); strncpy(key->str, str, sizeof(key->str)); util_list_add_tail(&t->mark_key_list, key); } /* * Remove mark key from table */ static void l_mark_key_remove(struct table *t, char *str) { struct table_mark_key *key, *tmp; util_list_iterate_safe(&t->mark_key_list, key, tmp) { if (strcmp(str, key->str) == 0) { util_list_remove(&t->mark_key_list, key); ht_free(key); return; } } } /* * Delete all mark keys from table */ void table_row_mark_del_all(struct table *t) { struct table_mark_key *key, *tmp; struct table_row *row; util_list_iterate(&t->row_list, row) row->marked = 0; util_list_iterate_safe(&t->mark_key_list, key, tmp) { util_list_remove(&t->mark_key_list, key); ht_free(key); } } /* * Toggle mark for "row" */ void table_row_mark_toggle(struct table *t, struct table_row *row) { if (row->marked) { l_mark_key_remove(t, row->entries[0].str); row->marked = 0; t->row_cnt_marked--; if (t->row_cnt_marked == 0) t->mode_hide_unmarked = 0; } else { l_mark_key_add(t, row->entries[0].str); row->marked = 1; t->row_cnt_marked++; } } /* * Toggle mark by key */ void table_row_mark_toggle_by_key(struct table *t, const char *str) { struct table_row *row; util_list_iterate(&t->row_list, row) { if (strcmp(str, row->entries[0].str) == 0) table_row_mark_toggle(t, row); } } /* * Is column selected? */ static int l_col_selected(struct table *t, struct table_col *col) { return t->col_selected == col; } /* * Get number of rows for table */ static int l_row_cnt(struct table *t) { return t->mode_hide_unmarked ? t->row_cnt_marked : t->row_cnt; } /* * Get number of data rows that we can display on screen */ static int l_row_cnt_displ(struct table *t) { return g.c.row_cnt - t->row_cnt_extra; } /* * Alloc a new row for table */ struct table_row *table_row_alloc(struct table *t) { struct table_row *table_row; table_row = ht_zalloc(sizeof(*table_row)); table_row->entries = ht_zalloc(sizeof(*table_row->entries) * t->col_cnt); return table_row; } /* * Free table row */ static void table_row_free(struct table_row *table_row) { ht_free(table_row->entries); ht_free(table_row); } /* * Allocate and initialize a new table */ struct table *table_new(int extra_rows, int sorted, int first_bold, int with_units) { struct table *t = ht_zalloc(sizeof(*t)); util_list_init(&t->row_list, struct table_row, list); util_list_init(&t->mark_key_list, struct table_mark_key, list); t->row_cnt_marked = 0; if (with_units) t->row_cnt_extra = extra_rows + L_ROWS_EXTRA + 1; else t->row_cnt_extra = extra_rows + L_ROWS_EXTRA; t->attr_with_units = with_units; t->attr_sorted_table = sorted; t->attr_first_bold = first_bold; return t; } /* * Initialize headline for one column */ static void l_col_headline_init(struct table *t, struct table_col *col) { char *ptr; strcpy(col->p->head_first, col->head); ptr = strchr(col->p->head_first, tolower(col->hotkey)); assert(ptr != NULL); *ptr = 0; col->p->head_char[0] = col->hotkey; strcpy(col->p->head_last, ++ptr); if (!t->attr_sorted_table) { ht_str_to_upper(col->p->head_first); ht_str_to_upper(col->p->head_last); col->p->head_char[0] = toupper(col->p->head_char[0]); } } /* * Initialize the max width values for a column */ static void l_col_max_width_init(struct table *t, struct table_col *col) { /* Units are displayed with brackets, therefore (+2) */ if (t->attr_with_units) col->p->max_width = MAX(strlen(col->head), strlen(col->unit->str) + 2); else col->p->max_width = strlen(col->head); } /* * Add a new column to table */ void table_col_add(struct table *t, struct table_col *col) { col->p = ht_zalloc(sizeof(*col->p)); col->p->col_nr = t->col_cnt; col->p->enabled = 1; t->col_cnt++; t->col_vec = ht_realloc(t->col_vec, sizeof(void *) * (t->col_cnt + 1)); t->col_vec[t->col_cnt - 1] = col; t->col_vec[t->col_cnt] = NULL; if (!t->col_selected && t->attr_sorted_table) t->col_selected = col; if (t->row_last) table_row_free(t->row_last); t->row_last = table_row_alloc(t); l_col_headline_init(t, col); l_col_max_width_init(t, col); } /* * Initialize last row */ static void l_row_last_init(struct table *t) { memset(t->row_last->entries, 0, t->col_cnt * sizeof(struct table_entry)); } /* * Delete all rows of a table */ void table_row_del_all(struct table *t) { struct table_row *row, *tmp; util_list_iterate_safe(&t->row_list, row, tmp) { util_list_remove(&t->row_list, row); table_row_free(row); } l_row_last_init(t); t->row_cnt_marked = 0; t->ready = 0; t->row_cnt = 0; } /* * Reset table */ void table_reset(struct table *t) { table_row_mark_del_all(t); table_row_del_all(t); t->mode_sort_inverse = 0; t->mode_select = 0; } /* * Return true, if "e1" is less than "e2" */ static int l_entry_less_than(enum table_col_type type, struct table_entry *e1, struct table_entry *e2) { switch (type) { case TABLE_COL_TYPE_U64: return (e1->d.u64.v1 < e2->d.u64.v1); case TABLE_COL_TYPE_S64: return (e1->d.s64.v1 < e2->d.s64.v1); case TABLE_COL_TYPE_STR: return (strcmp(e1->str, e2->str) > 0); } return 0; /* Keep gcc quite */ } /* * Return true, if "row1" is less than "row2" */ static int l_row_less_than(struct table *t, struct table_row *row1, struct table_row *row2) { struct table_col *col = t->col_selected; struct table_entry *e1 = &row1->entries[col->p->col_nr]; struct table_entry *e2 = &row2->entries[col->p->col_nr]; if ((t->mode_sort_inverse && !col->p->rsort) || (!t->mode_sort_inverse && col->p->rsort)) return !l_entry_less_than(col->type, e1, e2); else return l_entry_less_than(col->type, e1, e2); } /* * Calculate: e1 = e1 + e2 */ static void l_entry_sum(enum table_col_type type, struct table_entry *e1, struct table_entry *e2) { switch (type) { case TABLE_COL_TYPE_U64: e1->d.u64.v1 += e2->d.u64.v1; return; case TABLE_COL_TYPE_S64: e1->d.s64.v1 += e2->d.s64.v1; return; default: assert(0); return; } } /* * Calculate: e1 = MAX(e1, e2) */ static void l_entry_max(enum table_col_type type, struct table_entry *e1, struct table_entry *e2) { switch (type) { case TABLE_COL_TYPE_U64: e1->d.u64.v1 = MAX(e1->d.u64.v1, e2->d.u64.v1); return; case TABLE_COL_TYPE_S64: e1->d.s64.v1 = MAX(e1->d.s64.v1, e2->d.s64.v1); return; default: assert(0); return; } } /* * Aggregate "row" to "last row" */ static void l_row_last_agg(struct table *t, struct table_row *table_row) { struct table_col *col; int col_nr; table_col_iterate(t, col, col_nr) { struct table_entry *e_last = &t->row_last->entries[col_nr]; struct table_entry *e_new = &table_row->entries[col_nr]; if (!e_new->set) continue; switch (col->agg) { case TABLE_COL_AGG_SUM: l_entry_sum(col->type, e_last, e_new); break; case TABLE_COL_AGG_MAX: l_entry_max(col->type, e_last, e_new); break; case TABLE_COL_AGG_NONE: break; } e_last->set = 1; } } /* * Format row: Invoke unit callback and adjust max width of column */ static void l_row_format(struct table *t, struct table_row *row) { unsigned int len, col_nr; struct table_col *col; table_col_iterate(t, col, col_nr) { struct table_entry *e = &row->entries[col_nr]; if (col->agg == TABLE_COL_AGG_NONE && row == t->row_last) len = 0; else len = col->unit->fn(col, e); assert(len < TABLE_STR_MAX); if (len > col->p->max_width) col->p->max_width = len; } } /* * Calculate last row */ static void l_row_last_calc(struct table *t) { struct table_row *row; l_row_last_init(t); util_list_iterate(&t->row_list, row) { if (t->mode_hide_unmarked && !row->marked) continue; l_row_last_agg(t, row); } l_row_format(t, t->row_last); } /* * Finish table after all rows have been added */ void table_finish(struct table *t) { l_row_last_calc(t); t->ready = 1; } /* * Add new row to table */ void table_row_add(struct table *t, struct table_row *row) { struct table_row *tmp; l_row_format(t, row); if (util_list_is_empty(&t->row_list) || !t->attr_sorted_table) { util_list_add_tail(&t->row_list, row); } else { util_list_iterate(&t->row_list, tmp) { if (l_row_less_than(t, tmp, row)) break; } if (tmp) util_list_add_prev(&t->row_list, row, tmp); else util_list_add_tail(&t->row_list, row); } if (l_row_is_marked(t, row)) { row->marked = 1; t->row_cnt_marked++; } t->row_cnt++; } /* * Rebuild table: Reformat all rows and adjust max width values */ void table_rebuild(struct table *t) { struct table_col *col; struct table_row *row; unsigned int i; table_col_iterate(t, col, i) l_col_max_width_init(t, col); util_list_iterate(&t->row_list, row) l_row_format(t, row); l_row_format(t, t->row_last); } /* * Compare callback for linked list sorting (ordering: large to small) */ static int l_row_cmp_fn(void *a, void *b, void *data) { return l_row_less_than(data, a, b) ? 1 : -1; } /* * Sort table (ordering: large to small) */ static void l_table_sort(struct table *t) { util_list_sort(&t->row_list, l_row_cmp_fn, t); } /* * Adjust table values for select mode (e.g. for window resize or scrolling) */ static void l_adjust_values_select_mode(struct table *t) { int row_cnt_displ = l_row_cnt_displ(t); int row_cnt = l_row_cnt(t); /* We went out of range with row selection */ if (t->row_nr_select >= row_cnt) t->row_nr_select = row_cnt - 1; /* Is selected row within visible area? */ if (t->row_nr_select < t->row_nr_begin) { /* Selected row is above area: Scroll up */ t->row_nr_begin = t->row_nr_select; } else if (t->row_nr_select - t->row_nr_begin >= row_cnt_displ) { /* Selected row is below area: Scroll down */ t->row_nr_begin = MAX(t->row_nr_select - row_cnt_displ + 1, 0); } } /* * Adjust table values (e.g. for window resize or scrolling) */ static void l_adjust_values(struct table *t) { int row_cnt_displ = l_row_cnt_displ(t); int row_cnt = l_row_cnt(t); if (t->mode_select) l_adjust_values_select_mode(t); /* If we do not use the whole screen, scroll up */ if (row_cnt - t->row_nr_begin < row_cnt_displ) t->row_nr_begin = MAX(row_cnt - row_cnt_displ, 0); } /* * Number of rows to be scrolled for page scroll */ static int l_scroll_page_row_cnt(struct table *t) { /* We have two rows overlap for scrolling pages */ return l_row_cnt_displ(t) - 2; } /* * Scroll table down */ void table_scroll_down(struct table *t, enum table_scroll_unit scroll_unit) { switch (scroll_unit) { case TABLE_SCROLL_LINE: t->row_nr_begin++; break; case TABLE_SCROLL_PAGE: t->row_nr_begin += l_scroll_page_row_cnt(t); break; case TABLE_SCROLL_LAST: t->row_nr_begin = t->row_cnt; break; } } /* * Scroll table up */ void table_scroll_up(struct table *t, enum table_scroll_unit scroll_unit) { switch (scroll_unit) { case TABLE_SCROLL_LINE: t->row_nr_begin = MAX(t->row_nr_begin - 1, 0); break; case TABLE_SCROLL_PAGE: t->row_nr_begin = MAX(t->row_nr_begin - l_scroll_page_row_cnt(t), 0); break; case TABLE_SCROLL_LAST: t->row_nr_begin = 0; break; } } /* * Return selected row */ static struct table_row *l_selected_row(struct table *t) { struct table_row *row; int row_nr = 0; util_list_iterate(&t->row_list, row) { if (t->mode_hide_unmarked && !row->marked) continue; if (row_nr == t->row_nr_select) return row; row_nr++; } return NULL; } /* * Toggle mark for selected row */ static void l_row_select_mark_toggle(struct table *t) { struct table_row *row; row = l_selected_row(t); table_row_mark_toggle(t, row); l_row_last_calc(t); } /* * Switch select mode off */ static void l_select_mode_off(struct table *t) { t->mode_select = 0; } /* * Switch select mode on */ static void l_select_mode_on(struct table *t) { t->mode_select = 1; t->row_nr_select = t->row_nr_begin; } /* * Get key for selected row */ void table_row_select_key_get(struct table *t, char str[TABLE_STR_MAX]) { struct table_row *row; row = l_selected_row(t); strncpy(str, row->entries[0].str, TABLE_STR_MAX); } /* * Select row one page down */ void table_row_select_down(struct table *t, enum table_scroll_unit scroll_unit) { switch (scroll_unit) { case TABLE_SCROLL_LINE: t->row_nr_select++; break; case TABLE_SCROLL_PAGE: t->row_nr_select += g.c.row_cnt - t->row_cnt_extra; break; case TABLE_SCROLL_LAST: t->row_nr_select = t->row_cnt; break; } } /* * Select row one page up */ void table_row_select_up(struct table *t, enum table_scroll_unit scroll_unit) { switch (scroll_unit) { case TABLE_SCROLL_LINE: t->row_nr_select = MAX(t->row_nr_select - 1, 0); break; case TABLE_SCROLL_PAGE: t->row_nr_select = MAX(t->row_nr_begin - (g.c.row_cnt - t->row_cnt_extra), 0); break; case TABLE_SCROLL_LAST: t->row_nr_select = 0; break; } } /* * Toggle "hide unmarked" mode */ static int l_mode_hide_unmarked_toggle(struct table *t) { if (t->row_cnt_marked == 0) return -ENODEV; t->mode_hide_unmarked = t->mode_hide_unmarked ? 0 : 1; t->row_nr_select = 0; l_row_last_calc(t); return 0; } /* * Is it possible to scroll down the table? */ static int l_can_scroll_down(struct table *t) { int row_cnt = t->mode_hide_unmarked ? t->row_cnt_marked : t->row_cnt; int row_cnt_real = g.c.row_cnt - t->row_cnt_extra; return (row_cnt - t->row_nr_begin > row_cnt_real); } /* * Is it possible to scroll up the table? */ static int l_can_scroll_up(struct table *t) { return (t->row_nr_begin > 0); } /* * Update the status field */ static void l_status_update(struct table *t) { struct table_entry *e_status = &t->row_last->entries[0]; if (g.o.batch_mode_specified) return; if (l_can_scroll_down(t) && l_can_scroll_up(t)) strcpy(e_status->str, "|"); else if (l_can_scroll_up(t)) strcpy(e_status->str, "^"); else if (l_can_scroll_down(t)) strcpy(e_status->str, "V"); else strcpy(e_status->str, "="); if (t->attr_sorted_table) { strcat(e_status->str, ":"); if (t->mode_sort_inverse) strcat(e_status->str, "^"); else strcat(e_status->str, "V"); } strcat(e_status->str, ":"); if (t->mode_select) strcat(e_status->str, "S"); else strcat(e_status->str, "N"); } /* * Print string with alignment */ static void l_str_print(struct table_col *col, const char *str) { char unit[10]; if (col->align == TABLE_COL_ALIGN_LEFT) sprintf(unit, "%%-%ds", col->p->max_width); else sprintf(unit, "%%%ds", col->p->max_width); hyptop_printf(unit, str); } /* * Print string for "col" */ static void l_col_print(struct table *t, struct table_col *col, const char *str) { if (l_col_selected(t, col)) ht_underline_on(); if (col->p->col_nr == 0 && t->attr_first_bold) ht_bold_on(); l_str_print(col, str); if (l_col_selected(t, col)) ht_underline_off(); if (col->p->col_nr == 0 && t->attr_first_bold) ht_bold_off(); } /* * Print status field */ static void l_status_print(struct table *t, const char *str) { ht_bold_on(); l_str_print(t->col_vec[0], str); ht_bold_off(); } /* * Print headline of column */ static void l_col_headline_print(struct table *t, struct table_col *col) { unsigned int len = strlen(col->head); char blank_str[TABLE_STR_MAX]; (void) t; memset(blank_str, ' ', col->p->max_width - len); blank_str[col->p->max_width - len] = 0; if (l_col_selected(t, col)) ht_bold_on(); if (col->align == TABLE_COL_ALIGN_RIGHT) hyptop_printf("%s", blank_str); hyptop_printf("%s", col->p->head_first); if (t->attr_sorted_table) ht_underline_on(); hyptop_printf("%s", col->p->head_char); if (t->attr_sorted_table) ht_underline_off(); hyptop_printf("%s", col->p->head_last); if (col->align == TABLE_COL_ALIGN_LEFT) hyptop_printf("%s", blank_str); if (l_col_selected(t, col)) ht_bold_off(); } /* * Print headline for table */ static void l_headline_print(struct table *t) { struct table_col *col; int col_nr, first = 1; ht_reverse_on(); /* Print all column headlines */ table_col_iterate(t, col, col_nr) { if (!col->p->enabled) continue; if (first) first = 0; else hyptop_printf(" "); l_col_headline_print(t, col); } /* This creates a black bar to the end of the line */ hyptop_print_seek_back(0); ht_reverse_off(); hyptop_print_nl(); } /* * Print unit line for table */ static void l_unitline_print(struct table *t) { struct table_col *col; int col_nr, first = 1; char unit_str[20]; if (!t->attr_with_units) return; ht_reverse_on(); /* Print all column units */ table_col_iterate(t, col, col_nr) { if (!col->p->enabled) continue; if (first) first = 0; else hyptop_printf(" "); if (l_col_selected(t, col)) ht_bold_on(); snprintf(unit_str, sizeof(unit_str), "(%s)", col->unit->str); l_str_print(col, unit_str); if (l_col_selected(t, col)) ht_bold_off(); } /* This creates a black bar to the end of the line */ hyptop_print_seek_back(0); ht_reverse_off(); hyptop_print_nl(); } /* * Print one table row */ static void l_row_print(struct table *t, struct table_row *row) { struct table_col *col; int first = 1, col_nr; table_col_iterate(t, col, col_nr) { struct table_entry *e = &row->entries[col_nr]; if (!col->p->enabled) continue; if (!first) hyptop_printf(" "); else first = 0; if (row == t->row_last && col_nr == 0) l_status_print(t, e->str); else l_col_print(t, col, e->str); } } /* * Print table under curses */ static void l_table_print_curses(struct table *t) { struct table_row *row; int row_nr = 0; if (!t->ready) return; l_adjust_values(t); l_status_update(t); l_headline_print(t); l_unitline_print(t); util_list_iterate(&t->row_list, row) { if (t->mode_hide_unmarked && !row->marked) continue; if (row_nr < t->row_nr_begin) { row_nr++; continue; } if (row_nr - t->row_nr_begin >= g.c.row_cnt - t->row_cnt_extra) break; if (t->mode_select && row_nr == t->row_nr_select) ht_reverse_on(); if (row->marked) ht_bold_on(); l_row_print(t, row); if (t->mode_select && row_nr == t->row_nr_select) { #ifdef WITH_SCROLL_BAR hyptop_print_seek_back(1); #else hyptop_print_seek_back(0); #endif ht_reverse_off(); } if (row->marked) ht_bold_off(); hyptop_print_nl(); row_nr++; } ht_reverse_on(); l_row_print(t, t->row_last); hyptop_print_seek_back(0); ht_reverse_off(); #ifdef WITH_SCROLL_BAR if (t->mode_hide_unmarked) ht_print_scroll_bar(t->row_cnt_marked, t->row_nr_begin, t->row_cnt_extra - 1, 1, l_can_scroll_up(t), l_can_scroll_down(t), 1); else ht_print_scroll_bar(t->row_cnt, t->row_nr_begin, t->row_cnt_extra - 1, 1, l_can_scroll_up(t), l_can_scroll_down(t), 1); #endif } /* * Print table under batch mode */ static void l_table_print_all(struct table *t) { struct table_row *row; l_headline_print(t); l_unitline_print(t); util_list_iterate(&t->row_list, row) { l_row_print(t, row); hyptop_print_nl(); } l_row_print(t, t->row_last); } /* * Print table to screen */ void table_print(struct table *t) { if (g.o.batch_mode_specified) l_table_print_all(t); else l_table_print_curses(t); } /* * Return column by hotkey */ static struct table_col *l_col_by_hotkey(struct table *t, char hotkey) { struct table_col *col; int col_nr; table_col_iterate(t, col, col_nr) { if (col->hotkey == hotkey) return col; } return NULL; } /* * Select next unit for column with "hotkey" */ void table_col_unit_next(struct table *t, char hotkey) { struct table_col *col; int i; col = l_col_by_hotkey(t, hotkey); if (!col || !col->unit_fam) assert(0); for (i = 0; col->unit_fam[i] != NULL; i++) { if (col->unit != col->unit_fam[i]) continue; if (col->unit_fam[i + 1] == NULL) col->unit = col->unit_fam[0]; else col->unit = col->unit_fam[i + 1]; return; } assert(0); } /* * Select previous unit for column with "hotkey" */ void table_col_unit_prev(struct table *t, char hotkey) { struct table_col *col; int i; col = l_col_by_hotkey(t, hotkey); if (!col || !col->unit_fam) assert(0); for (i = 0; col->unit_fam[i] != NULL; i++) { if (col->unit != col->unit_fam[i]) continue; if (i == 0) { int j; for (j = 0; col->unit_fam[j] != NULL; j++) {} col->unit = col->unit_fam[j - 1]; } else { col->unit = col->unit_fam[i - 1]; } return; } assert(0); } /* * Set unit for column */ int table_col_unit_set(struct table *t, char hotkey, const char *str) { struct table_col *col; int i; col = l_col_by_hotkey(t, hotkey); if (!col) return -ENODEV; for (i = 0; col->unit_fam[i] != NULL; i++) { if (strcasecmp(col->unit_fam[i]->str, str) == 0) { col->unit = col->unit_fam[i]; return 0; } } return -EINVAL; } /* * Select column by hotkey */ int table_col_select(struct table *t, char hotkey) { struct table_col *col; if (!t->attr_sorted_table) assert(0); col = l_col_by_hotkey(t, hotkey); if (!col || !col->p->enabled) return -ENODEV; if (t->col_selected == col) { t->mode_sort_inverse = t->mode_sort_inverse ? 0 : 1; } else { t->mode_sort_inverse = 0; t->col_selected = col; } table_rebuild(t); l_table_sort(t); return 0; } /* * Select next column */ void table_col_select_next(struct table *t) { int i; for (i = t->col_selected->p->col_nr + 1; i < t->col_cnt; i++) { if (t->col_vec[i]->p->enabled) goto found; } return; found: t->col_selected = t->col_vec[i]; l_table_sort(t); } /* * Select previous column */ void table_col_select_prev(struct table *t) { int i; for (i = t->col_selected->p->col_nr - 1; i >= 0; i--) { if (t->col_vec[i]->p->enabled) goto found; } return; found: t->col_selected = t->col_vec[i]; l_table_sort(t); } /* * Toggle enabled status for column */ void table_col_enable_toggle(struct table *t, char hotkey) { struct table_col *col; col = l_col_by_hotkey(t, hotkey); if (!col || col->p->col_nr == 0) return; col->p->enabled = col->p->enabled ? 0 : 1; if (col == t->col_selected) t->col_selected = t->col_vec[0]; } /* * Process input for table */ void table_process_input(struct table *t, int c) { switch (c) { case '<': if (t->attr_sorted_table) table_col_select_prev(t); break; case '>': if (t->attr_sorted_table) table_col_select_next(t); break; case '.': if (l_mode_hide_unmarked_toggle(t) == 0) l_select_mode_off(t); break; case '+': if (!t->attr_with_units) break; table_col_unit_next(t, t->col_selected->hotkey); table_rebuild(t); break; case '-': if (!t->attr_with_units) break; table_col_unit_prev(t, t->col_selected->hotkey); table_rebuild(t); break; case 'G': if (t->mode_select) table_row_select_down(t, TABLE_SCROLL_LAST); else table_scroll_down(t, TABLE_SCROLL_LAST); break; case 'g': if (t->mode_select) table_row_select_up(t, TABLE_SCROLL_LAST); else table_scroll_up(t, TABLE_SCROLL_LAST); break; case KEY_NPAGE: if (t->mode_select) table_row_select_down(t, TABLE_SCROLL_PAGE); else table_scroll_down(t, TABLE_SCROLL_PAGE); break; case KEY_PPAGE: if (t->mode_select) table_row_select_up(t, TABLE_SCROLL_PAGE); else table_scroll_up(t, TABLE_SCROLL_PAGE); break; case 'j': case KEY_DOWN: if (t->mode_select) table_row_select_down(t, TABLE_SCROLL_LINE); else table_scroll_down(t, TABLE_SCROLL_LINE); break; case 'k': case KEY_UP: if (t->mode_select) table_row_select_up(t, TABLE_SCROLL_LINE); else table_scroll_up(t, TABLE_SCROLL_LINE); break; case ' ': if (t->mode_select) { l_row_select_mark_toggle(t); } else { table_row_mark_del_all(t); t->mode_hide_unmarked = 0; } break; case 'l': case KEY_RIGHT: if (!t->mode_select) l_select_mode_on(t); break; case 'h': case KEY_LEFT: if (t->mode_select) l_select_mode_off(t); break; default: if (t->attr_sorted_table) table_col_select(t, c); } } s390-tools-2.3.0/hyptop/table.h000066400000000000000000000233661323410431100161350ustar00rootroot00000000000000/* * hyptop - Show hypervisor performance data on System z * * Table module: Provide line mode and curses base table * * Copyright IBM Corp. 2010, 2017 * * s390-tools is free software; you can redistribute it and/or modify * it under the terms of the MIT license. See LICENSE for details. */ #ifndef TABLE_H #define TABLE_H #include #include #include "lib/util_list.h" #include "helper.h" #define TABLE_STR_MAX 64 #define TABLE_HEADING_SIZE 20 struct table_col; struct table_entry; /* * Table Column Unit */ struct table_col_unit { int (*fn)(struct table_col*, struct table_entry *); const char *str; char *desc; char hotkey; }; /* Predefined units */ extern struct table_col_unit table_col_unit_str; extern struct table_col_unit table_col_unit_cnt; extern struct table_col_unit table_col_unit_kib; extern struct table_col_unit table_col_unit_mib; extern struct table_col_unit table_col_unit_gib; extern struct table_col_unit table_col_unit_us; extern struct table_col_unit table_col_unit_ms; extern struct table_col_unit table_col_unit_s; extern struct table_col_unit table_col_unit_hm; extern struct table_col_unit table_col_unit_dhm; extern struct table_col_unit table_col_unit_perc; extern struct table_col_unit table_col_unit_vis; /* Predefined families */ extern struct table_col_unit *table_col_unit_fam_str[]; extern struct table_col_unit *table_col_unit_fam_cnt[]; extern struct table_col_unit *table_col_unit_fam_mem[]; extern struct table_col_unit *table_col_unit_fam_time[]; extern struct table_col_unit *table_col_unit_fam_time_diff[]; extern struct table_col_unit *table_col_unit_fam_vis[]; /* * Table Column Type */ enum table_col_type { TABLE_COL_TYPE_U64, TABLE_COL_TYPE_S64, TABLE_COL_TYPE_STR, }; /* * Table Column Alignment */ enum table_col_align { TABLE_COL_ALIGN_LEFT, TABLE_COL_ALIGN_RIGHT, }; /* * Table Column Aggregation */ enum table_col_agg { TABLE_COL_AGG_SUM, TABLE_COL_AGG_MAX, TABLE_COL_AGG_NONE, }; static inline const char *table_col_agg_str(enum table_col_agg agg) { switch (agg) { case TABLE_COL_AGG_SUM: return "sum"; case TABLE_COL_AGG_MAX: return "max"; case TABLE_COL_AGG_NONE: return "none"; } return NULL; } /* * Table Column */ struct table_col_priv { unsigned int max_width; int col_nr; int enabled; char head_first[TABLE_HEADING_SIZE]; char head_char[2]; char head_last[TABLE_HEADING_SIZE]; int rsort; }; /* * Table Column Specification */ struct table_col_spec { char hotkey; char *unit_str; }; /* * Table Column */ struct table_col { enum table_col_type type; struct table_col_unit *unit; struct table_col_unit **unit_fam; enum table_col_align align; enum table_col_agg agg; char hotkey; char head[TABLE_HEADING_SIZE]; struct table_col_priv *p; }; static inline int table_col_enabled(struct table_col *col) { return col->p->enabled; } /* * Table Column Constructor Macros */ #define TABLE_COL_STR(l, h) \ { \ .type = TABLE_COL_TYPE_STR, \ .unit = &table_col_unit_str, \ .unit_fam = table_col_unit_fam_str, \ .align = TABLE_COL_ALIGN_RIGHT, \ .agg = TABLE_COL_AGG_NONE, \ .hotkey = l, \ .head = h, \ } #define TABLE_COL_STR_LEFT(l, h) \ { \ .type = TABLE_COL_TYPE_STR, \ .unit = &table_col_unit_str, \ .unit_fam = table_col_unit_fam_str, \ .align = TABLE_COL_ALIGN_LEFT, \ .agg = TABLE_COL_AGG_NONE, \ .hotkey = l, \ .head = h, \ } #define TABLE_COL_CNT_SUM(l, h) \ { \ .type = TABLE_COL_TYPE_U64, \ .unit = &table_col_unit_cnt, \ .unit_fam = table_col_unit_fam_cnt, \ .align = TABLE_COL_ALIGN_RIGHT, \ .agg = TABLE_COL_AGG_SUM, \ .hotkey = l, \ .head = h, \ } #define TABLE_COL_CNT_NONE(l, h) \ { \ .type = TABLE_COL_TYPE_U64, \ .unit = &table_col_unit_cnt, \ .unit_fam = table_col_unit_fam_cnt, \ .align = TABLE_COL_ALIGN_RIGHT, \ .agg = TABLE_COL_AGG_NONE, \ .hotkey = l, \ .head = h, \ } #define TABLE_COL_CNT_MAX(l, h) \ { \ .type = TABLE_COL_TYPE_U64, \ .unit = &table_col_unit_cnt, \ .unit_fam = table_col_unit_fam_cnt, \ .align = TABLE_COL_ALIGN_RIGHT, \ .agg = TABLE_COL_AGG_MAX, \ .hotkey = l, \ .head = h, \ } #define TABLE_COL_MEM_SUM(f, l, h) \ { \ .type = TABLE_COL_TYPE_U64, \ .unit = &f, \ .unit_fam = table_col_unit_fam_mem, \ .align = TABLE_COL_ALIGN_RIGHT, \ .agg = TABLE_COL_AGG_SUM, \ .hotkey = l, \ .head = h, \ } #define TABLE_COL_TIME_SUM(f, l, h) \ { \ .type = TABLE_COL_TYPE_U64, \ .unit = &f, \ .unit_fam = table_col_unit_fam_time, \ .align = TABLE_COL_ALIGN_RIGHT, \ .agg = TABLE_COL_AGG_SUM, \ .hotkey = l, \ .head = h, \ } #define TABLE_COL_TIME_DIFF_SUM(f, l, h) \ { \ .type = TABLE_COL_TYPE_U64, \ .unit = &f, \ .unit_fam = table_col_unit_fam_time_diff, \ .align = TABLE_COL_ALIGN_RIGHT, \ .agg = TABLE_COL_AGG_SUM, \ .hotkey = l, \ .head = h, \ } #define TABLE_COL_STIME_SUM(f, l, h) \ { \ .type = TABLE_COL_TYPE_S64, \ .unit = &f, \ .unit_fam = table_col_unit_fam_time, \ .align = TABLE_COL_ALIGN_RIGHT, \ .agg = TABLE_COL_AGG_SUM, \ .hotkey = l, \ .head = h, \ } #define TABLE_COL_STIME_DIFF_SUM(f, l, h) \ { \ .type = TABLE_COL_TYPE_S64, \ .unit = &f, \ .unit_fam = table_col_unit_fam_time_diff, \ .align = TABLE_COL_ALIGN_RIGHT, \ .agg = TABLE_COL_AGG_SUM, \ .hotkey = l, \ .head = h, \ } #define TABLE_COL_TIME_MAX(f, l, h) \ { \ .type = TABLE_COL_TYPE_U64, \ .unit = &f, \ .unit_fam = table_col_unit_fam_time, \ .align = TABLE_COL_ALIGN_RIGHT, \ .agg = TABLE_COL_AGG_MAX, \ .hotkey = l, \ .head = h, \ } /* * Set reverse sort property for column */ static inline void table_col_rsort(struct table_col *col) { col->p->rsort = 1; } /* * Column member access macros */ #define table_col_hotkey(col) ((col)->hotkey) #define table_col_head(col) ((col)->head) #define table_col_unit_str(col) ((col)->unit->str) /* * Table Entry */ struct table_entry { union { struct { u64 v1; u64 v2; } u64; struct { s64 v1; s64 v2; } s64; } d; int set; char str[TABLE_STR_MAX]; }; /* * Table Row */ struct table_row { struct util_list_node list; struct table_entry *entries; int marked; }; /* * Table Mark Key */ struct table_mark_key { struct util_list_node list; char str[TABLE_STR_MAX]; }; /* * Table */ struct table { struct util_list row_list; int col_cnt; struct table_col **col_vec; struct table_col *col_selected; struct table_row *row_last; int row_cnt; int row_cnt_marked; int row_cnt_extra; int row_nr_begin; int row_nr_select; int ready; struct util_list mark_key_list; int attr_sorted_table; int attr_first_bold; int attr_with_units; int mode_sort_inverse; int mode_select; int mode_hide_unmarked; }; /* * Return if we are in select mode */ static inline int table_mode_select(struct table *t) { return t->mode_select; } /* * Table croll units */ enum table_scroll_unit { TABLE_SCROLL_LINE, TABLE_SCROLL_PAGE, TABLE_SCROLL_LAST, }; /* * Prototypes */ extern struct table *table_new(int extra_rows, int sorted, int first_bold, int with_units); extern void table_reset(struct table *t); extern void table_rebuild(struct table *t); extern void table_finish(struct table *t); extern void table_print(struct table *t); extern void table_process_input(struct table *t, int c); extern void table_col_unit_next(struct table *t, char hotkey); extern void table_col_unit_prev(struct table *t, char hotkey); extern int table_col_unit_set(struct table *t, char hotkey, const char *unit); extern void table_col_add(struct table *t, struct table_col *col); extern int table_col_select(struct table *t, char hotkey); extern void table_col_select_next(struct table *t); extern void table_col_select_prev(struct table *t); extern void table_col_enable_toggle(struct table *t, char hotkey); extern void table_row_del_all(struct table *t); extern void table_row_add(struct table *t, struct table_row *row); extern void table_row_mark(struct table *t, struct table_row *row); extern void table_row_mark_del_all(struct table *t); extern void table_row_mark_toggle(struct table *t, struct table_row *row); extern void table_row_mark_toggle_by_key(struct table *t, const char *mark_key); extern void table_row_select_down(struct table *t, enum table_scroll_unit unit); extern void table_row_select_up(struct table *t, enum table_scroll_unit unit); extern void table_row_select_key_get(struct table *t, char str[TABLE_STR_MAX]); extern struct table_row *table_row_alloc(struct table *t); extern void table_scroll_down(struct table *t, enum table_scroll_unit unit); extern void table_scroll_up(struct table *t, enum table_scroll_unit unit); /* * Entry add functions */ static inline void table_row_entry_u64_add(struct table_row *table_row, struct table_col *table_col, u64 value) { table_row->entries[table_col->p->col_nr].d.u64.v1 = value; table_row->entries[table_col->p->col_nr].set = 1; } static inline void table_row_entry_s64_add(struct table_row *table_row, struct table_col *table_col, s64 value) { table_row->entries[table_col->p->col_nr].d.s64.v1 = value; table_row->entries[table_col->p->col_nr].set = 1; } static inline void table_row_entry_u64_add_pair(struct table_row *table_row, struct table_col *table_col, u64 value1, u64 value2) { table_row->entries[table_col->p->col_nr].d.u64.v1 = value1; table_row->entries[table_col->p->col_nr].d.u64.v2 = value2; table_row->entries[table_col->p->col_nr].set = 1; } static inline void table_row_entry_str_add(struct table_row *table_row, struct table_col *table_col, const char *str) { assert(strlen(str) < TABLE_STR_MAX); strcpy(table_row->entries[table_col->p->col_nr].str, str); table_row->entries[table_col->p->col_nr].set = 1; } /* * Interate over all mark keys */ #define table_iterate_mark_keys(t, key) \ util_list_iterate(&t->mark_key_list, key) #endif /* TABLE_H */ s390-tools-2.3.0/hyptop/table_col_unit.c000066400000000000000000000170741323410431100200230ustar00rootroot00000000000000/* * hyptop - Show hypervisor performance data on System z * * Table unit module: Provide different units for data * * Copyright IBM Corp. 2010, 2017 * * 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 #include "table.h" #define L_VISUAL_ROW_CNT 45 #define L_COL_FMT_STR_0 "%.0lf" #define L_COL_FMT_STR_2 "%.2lf" #define L_COL_NOT_SET_STR "-" /* * Helper: Divide value and format it */ static int l_unit_raw_div(struct table_col *col, struct table_entry *e, unsigned int divisor, const char *fmt_str) { double v1; if (!e->set) return snprintf(e->str, sizeof(e->str), L_COL_NOT_SET_STR); switch (col->type) { case TABLE_COL_TYPE_U64: v1 = ((double) e->d.u64.v1) / divisor; break; case TABLE_COL_TYPE_S64: v1 = ((double) e->d.s64.v1) / divisor; break; default: assert(0); } return snprintf(e->str, sizeof(e->str), fmt_str, v1); } /* * Helper: Format value as is */ static int l_unit_raw(struct table_col *col, struct table_entry *e) { if (!e->set) return snprintf(e->str, sizeof(e->str), L_COL_NOT_SET_STR); switch (col->type) { case TABLE_COL_TYPE_U64: return snprintf(e->str, sizeof(e->str), "%llu", e->d.u64.v1); case TABLE_COL_TYPE_S64: return snprintf(e->str, sizeof(e->str), "%lld", e->d.s64.v1); default: assert(0); return 0; } } /* * Format: String */ static int l_str(struct table_col *col, struct table_entry *e) { (void) col; return strlen(e->str); } struct table_col_unit table_col_unit_str = { .fn = l_str, .hotkey = 'S', .str = "str", .desc = "String", }; /* * Format: Count */ static int l_unit_cnt(struct table_col *col, struct table_entry *e) { return l_unit_raw(col, e); } struct table_col_unit table_col_unit_cnt = { .fn = l_unit_cnt, .hotkey = '#', .str = "#", .desc = "Count", }; /* * Format: Kibibytes */ static int l_unit_kib(struct table_col *col, struct table_entry *e) { return l_unit_raw(col, e); } struct table_col_unit table_col_unit_kib = { .fn = l_unit_kib, .hotkey = 'k', .str = "KiB", .desc = "Kibibyte (1.024 bytes)", }; /* * Format: Mebibytes */ static int l_unit_mib(struct table_col *col, struct table_entry *e) { return l_unit_raw_div(col, e, 1024, L_COL_FMT_STR_2); } struct table_col_unit table_col_unit_mib = { .fn = l_unit_mib, .hotkey = 'M', .str = "MiB", .desc = "Mebibyte (1.048.576 bytes)", }; /* * Format: Gibibytes */ static int l_unit_gib(struct table_col *col, struct table_entry *e) { return l_unit_raw_div(col, e, 1024 * 1024, L_COL_FMT_STR_2); } struct table_col_unit table_col_unit_gib = { .fn = l_unit_gib, .hotkey = 'g', .str = "GiB", .desc = "Gibibyte (1.073.741.824 bytes)", }; /* * Format: Microseconds */ static int l_unit_us(struct table_col *col, struct table_entry *e) { return l_unit_raw(col, e); } struct table_col_unit table_col_unit_us = { .fn = l_unit_us, .hotkey = 'u', .str = "us", }; /* * Format: Milliseconds */ static int l_unit_ms(struct table_col *col, struct table_entry *e) { return l_unit_raw_div(col, e, 1000, L_COL_FMT_STR_2); } struct table_col_unit table_col_unit_ms = { .fn = l_unit_ms, .hotkey = 'm', .str = "ms", }; /* * Format: Percent (Hundreds) */ static int l_unit_perc(struct table_col *col, struct table_entry *e) { return l_unit_raw_div(col, e, 10000, L_COL_FMT_STR_2); } struct table_col_unit table_col_unit_perc = { .fn = l_unit_perc, .hotkey = '%', .str = "%", .desc = "Percent", }; /* * Format: Seconds */ static int l_unit_s(struct table_col *col, struct table_entry *e) { return l_unit_raw_div(col, e, 1000000, L_COL_FMT_STR_2); } struct table_col_unit table_col_unit_s = { .fn = l_unit_s, .hotkey = 's', .str = "s", .desc = "Seconds", }; /* * Format: Minutes */ static int l_unit_m(struct table_col *col, struct table_entry *e) { return l_unit_raw_div(col, e, 1000000 * 60, L_COL_FMT_STR_0); } static struct table_col_unit table_col_unit_m = { .fn = l_unit_m, .hotkey = 'm', .str = "m", .desc = "Minutes", }; /* * Format: Hours:Minutes */ static int l_unit_hm_u64(char *str, u64 v1, int negative) { u64 time_tmp, time_h, time_m; time_tmp = v1 / (1000000 * 60); time_h = time_tmp / 60; time_m = time_tmp - time_h * 60; if (negative) return sprintf(str, "-%llu:%02llu", time_h, time_m); else return sprintf(str, "%llu:%02llu", time_h, time_m); } static int l_unit_hm(struct table_col *col, struct table_entry *e) { if (!e->set) return snprintf(e->str, sizeof(e->str), L_COL_NOT_SET_STR); switch (col->type) { case TABLE_COL_TYPE_U64: return l_unit_hm_u64(e->str, e->d.u64.v1, 0); case TABLE_COL_TYPE_S64: if (e->d.s64.v1 < 0) return l_unit_hm_u64(e->str, -e->d.s64.v1, 1); else return l_unit_hm_u64(e->str, e->d.s64.v1, 0); default: assert(0); return 0; } } struct table_col_unit table_col_unit_hm = { .fn = l_unit_hm, .hotkey = 'H', .str = "hm", .desc = "Hours:Minutes", }; /* * Format: Days:Hours:Minutes */ static int l_unit_dhm_u64(char *str, u64 v1, int negative) { u64 time_tmp, time_d, time_h, time_m; time_tmp = v1 / (1000000 * 60); time_d = time_tmp / (60 * 24); time_h = time_tmp / 60 - time_d * 24; time_m = time_tmp - time_h * 60 - time_d * 60 * 24; if (negative) return sprintf(str, "-%llu:%02llu:%02llu", time_d, time_h, time_m); else return sprintf(str, "%llu:%02llu:%02llu", time_d, time_h, time_m); } static int l_unit_dhm(struct table_col *col, struct table_entry *e) { if (!e->set) return snprintf(e->str, sizeof(e->str), L_COL_NOT_SET_STR); switch (col->type) { case TABLE_COL_TYPE_U64: return l_unit_dhm_u64(e->str, e->d.u64.v1, 0); case TABLE_COL_TYPE_S64: if (e->d.s64.v1 < 0) return l_unit_dhm_u64(e->str, -e->d.s64.v1, 1); else return l_unit_dhm_u64(e->str, e->d.s64.v1, 0); default: assert(0); return 0; } } struct table_col_unit table_col_unit_dhm = { .fn = l_unit_dhm, .hotkey = 'D', .str = "dhm", .desc = "Days:Hours:Minutes", }; /* * Format: Visualization with bar chart */ static int l_unit_vis(struct table_col *col, struct table_entry *e) { double val1_perc, val2_perc; int val1_nr, val2_nr; int i; assert(col->type == TABLE_COL_TYPE_U64); sprintf(e->str, "|"); val1_perc = e->d.u64.v1; val1_perc /= 1000000; val2_perc = e->d.u64.v2; val2_perc /= 1000000; val1_nr = (val1_perc * L_VISUAL_ROW_CNT) + 0.5; val2_nr = (val2_perc * L_VISUAL_ROW_CNT) + 0.5; if (val1_nr > L_VISUAL_ROW_CNT) val1_nr = L_VISUAL_ROW_CNT; if (val1_nr + val2_nr > L_VISUAL_ROW_CNT) val2_nr = L_VISUAL_ROW_CNT - val1_nr; for (i = 0; i < val1_nr; i++) strcat(e->str, "#"); for (i = 0; i < val2_nr; i++) strcat(e->str, "-"); for (i = 0; i < L_VISUAL_ROW_CNT - val1_nr - val2_nr; i++) strcat(e->str, " "); strcat(e->str, "|"); return strlen(e->str); } struct table_col_unit table_col_unit_vis = { .fn = l_unit_vis, .hotkey = 'v', .str = "vis", .desc = "Visualization with bar chart", }; /* * Families */ struct table_col_unit *table_col_unit_fam_str[] = { &table_col_unit_str, NULL, }; struct table_col_unit *table_col_unit_fam_cnt[] = { &table_col_unit_cnt, NULL, }; struct table_col_unit *table_col_unit_fam_mem[] = { &table_col_unit_kib, &table_col_unit_mib, &table_col_unit_gib, NULL, }; struct table_col_unit *table_col_unit_fam_time_diff[] = { &table_col_unit_us, &table_col_unit_ms, &table_col_unit_perc, &table_col_unit_s, NULL, }; struct table_col_unit *table_col_unit_fam_time[] = { &table_col_unit_us, &table_col_unit_ms, &table_col_unit_s, &table_col_unit_m, &table_col_unit_hm, &table_col_unit_dhm, NULL, }; struct table_col_unit *table_col_unit_fam_vis[] = { &table_col_unit_vis, NULL, }; s390-tools-2.3.0/hyptop/tbox.c000066400000000000000000000101541323410431100160040ustar00rootroot00000000000000/* * hyptop - Show hypervisor performance data on System z * * Text box: Provide scrollable text window under curses. * * Copyright IBM Corp. 2010, 2017 * * 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 #include #include "helper.h" #include "hyptop.h" #include "tbox.h" /* * Delete one line */ static void l_line_free(struct tbox_line *line) { ht_free(line->str); ht_free(line); } /* * Delete all lines */ void tbox_line_del_all(struct tbox *tb) { struct tbox_line *line, *tmp; util_list_iterate_safe(&tb->line_list, line, tmp) { util_list_remove(&tb->line_list, line); l_line_free(line); } tb->tbox_ready = 0; tb->line_cnt = 0; } /* * Finish text box after all lines have been added */ void tbox_finish(struct tbox *tb) { tb->tbox_ready = 1; } /* * Add one line to text box */ void tbox_line_add(struct tbox *tb, const char *str) { struct tbox_line *line; if (strlen(str) > TBOX_MAX_STR) assert(0); line = ht_zalloc(sizeof(*line)); line->str = ht_strdup(str); util_list_add_tail(&tb->line_list, line); tb->last_line = line; tb->line_cnt++; } /* * Adjust values, if we scrolled out of range */ static void l_adjust_values(struct tbox *tb) { if (tb->line_cnt - tb->line_start < g.c.row_cnt) tb->line_start = MAX(tb->line_cnt - g.c.row_cnt, 0); } /* * Scroll text box down */ void tbox_scroll_down(struct tbox *tb, enum tbox_scroll_unit unit) { switch (unit) { case TBOX_SCROLL_LINE: tb->line_start++; break; case TBOX_SCROLL_PAGE: tb->line_start += (g.c.row_cnt - 2); break; case TBOX_SCROLL_LAST: tb->line_start = tb->line_cnt; break; } } /* * Scroll text box up */ void tbox_scroll_up(struct tbox *tb, enum tbox_scroll_unit unit) { switch (unit) { case TBOX_SCROLL_LINE: tb->line_start = MAX(tb->line_start - 1, 0); break; case TBOX_SCROLL_PAGE: tb->line_start = MAX(tb->line_start - (g.c.row_cnt - 2), 0); break; case TBOX_SCROLL_LAST: tb->line_start = 0; break; } } /* * Resize text box */ void tbox_term_resize(struct tbox *tb) { l_adjust_values(tb); } /* * Toggle bold curses format attribute */ static void l_bold_toggle(void) { static int bold_on; if (bold_on) { ht_bold_off(); bold_on = 0; } else { ht_bold_on(); bold_on = 1; } } /* * Toggle underline curses format attribute */ static void l_underline_toggle(void) { static int underline_on; if (underline_on) { ht_underline_off(); underline_on = 0; } else { ht_underline_on(); underline_on = 1; } } /* * Print one line with attributes (bold and underline) */ static void l_print_line(const char *line) { char line_cpy[TBOX_MAX_STR + 1]; char *ptr_old, *ptr; strncpy(line_cpy, line, sizeof(line_cpy)); ptr_old = ptr = line_cpy; do { ptr = strchr(ptr, '\\'); if (ptr) { *ptr = 0; hyptop_printf("%s", ptr_old); switch (ptr[1]) { case 'B': l_bold_toggle(); break; case 'U': l_underline_toggle(); break; } ptr += 2; ptr_old = ptr; } else { hyptop_printf("%s", ptr_old); return; } } while (*ptr); } #ifdef WITH_SCROLL_BAR static int l_can_scroll_down(struct tbox *tb) { return (tb->line_cnt - tb->line_start > g.c.row_cnt); } static int l_can_scroll_up(struct tbox *tb) { return (tb->line_start > 0); } #endif /* * Print text box to screen */ void tbox_print(struct tbox *tb) { int line_nr = 0, first = 1; struct tbox_line *line; if (!tb->tbox_ready) return; l_adjust_values(tb); util_list_iterate(&tb->line_list, line) { if (line_nr < tb->line_start) { line_nr++; continue; } /* Have we printed the whole visible screen ? */ if (line_nr - tb->line_start >= g.c.row_cnt) break; if (first) first = 0; else hyptop_print_nl(); l_print_line(line->str); line_nr++; } #ifdef WITH_SCROLL_BAR ht_print_scroll_bar(tb->line_cnt, tb->line_start, 0, 0, l_can_scroll_up(tb), l_can_scroll_down(tb), 0); #endif } /* * Create new text box */ struct tbox *tbox_new(void) { struct tbox *tb; tb = ht_zalloc(sizeof(*tb)); util_list_init(&tb->line_list, struct tbox_line, list); return tb; } s390-tools-2.3.0/hyptop/tbox.h000066400000000000000000000022211323410431100160050ustar00rootroot00000000000000/* * hyptop - Show hypervisor performance data on System z * * Text box: Provide scrollable text window under curses. * * Copyright IBM Corp. 2010, 2017 * * s390-tools is free software; you can redistribute it and/or modify * it under the terms of the MIT license. See LICENSE for details. */ #ifndef TBOX_H #define TBOX_H #include "lib/util_list.h" #define TBOX_MAX_STR 120 struct tbox_line { struct util_list_node list; char *str; }; struct tbox { struct util_list line_list; int line_cnt; int line_start; int tbox_ready; struct tbox_line *last_line; }; enum tbox_scroll_unit { TBOX_SCROLL_LINE, TBOX_SCROLL_PAGE, TBOX_SCROLL_LAST, }; struct tbox *tbox_new(void); void tbox_line_del_all(struct tbox *tb); void tbox_line_add(struct tbox *tb, const char *str); void tbox_finish(struct tbox *tb); void tbox_scroll_down(struct tbox *tb, enum tbox_scroll_unit); void tbox_scroll_up(struct tbox *tb, enum tbox_scroll_unit); void tbox_term_resize(struct tbox *tb); void tbox_print(struct tbox *tb); #define tbox_printf(tb, x...) \ { \ char line[TBOX_MAX_STR + 1]; \ sprintf(line, x); \ tbox_line_add(tb, line); \ } #endif /* TBOX_H */ s390-tools-2.3.0/hyptop/win_cpu_types.c000066400000000000000000000145261323410431100177270ustar00rootroot00000000000000/* * hyptop - Show hypervisor performance data on System z * * Window "cpu_types": Select CPU types used for CPU data calculation. * * Copyright IBM Corp. 2010, 2017 * * 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 "helper.h" #include "hyptop.h" #include "nav_desc.h" #include "sd.h" #include "table.h" #include "win_cpu_types.h" /* * Globals for cpu_types window */ static struct table_col l_col_key = TABLE_COL_STR_LEFT('k', "k"); static struct table_col l_col_select = TABLE_COL_STR_LEFT('s', "s"); static struct table_col l_col_id = TABLE_COL_STR_LEFT('i', "id"); static struct table_col l_col_desc = TABLE_COL_STR_LEFT('d', "description"); /* * Online help text for cpu_types window */ static const char l_help_str[] = "In the \"cpu_types\" window you can select the CPU types that are used for\n" "calculating CPU data. Toggle the selection of types either by pressing the\n" "corresponding hotkey or by selecting them in select mode using the SPACE bar.\n" "\n" "The table of the \"cpu_types\" window has the following columns:\n" " - K : Hotkey of CPU type\n" " - S : Shows if CPU type is selected\n" " - ID : Name of CPU type\n" " - DESC: Description of CPU type\n"; /* * Description of Navigation Keys (used for help window) */ static struct nav_desc *l_nav_desc_normal_vec[] = { &nav_desc_select_mode_enter, &nav_desc_marks_clear, &nav_desc_win_leave_cpu_types, NULL, }; static struct nav_desc *l_nav_desc_select_vec[] = { &nav_desc_select_mode_leave, &nav_desc_mark_toggle, &nav_desc_win_leave_cpu_types_fast, NULL, }; static struct nav_desc *l_nav_desc_general_vec[] = { &nav_desc_toggle_mark_hotkey, &nav_desc_scroll_up_line, &nav_desc_scroll_down_line, &nav_desc_scroll_up_page, &nav_desc_scroll_down_page, &nav_desc_scroll_up_head, &nav_desc_scroll_down_tail, &nav_desc_mark_toggle_view, NULL, }; /* * Add a CPU type to the table */ static void l_add_cpu_type(struct win_cpu_types *win_cpu_types, struct sd_cpu_type *cpu_type) { char char_str[2], select_str[2]; struct table_row *table_row; if (sd_cpu_type_selected(cpu_type)) sprintf(select_str, "*"); else sprintf(select_str, " "); sprintf(char_str, "%c", cpu_type->hotkey); table_row = table_row_alloc(win_cpu_types->t); table_row_entry_str_add(table_row, &l_col_select, select_str); table_row_entry_str_add(table_row, &l_col_key, char_str); table_row_entry_str_add(table_row, &l_col_id, cpu_type->id); table_row_entry_str_add(table_row, &l_col_desc, cpu_type->desc); table_row_add(win_cpu_types->t, table_row); if (sd_cpu_type_selected(cpu_type)) table_row_mark_toggle(win_cpu_types->t, table_row); } /* * Fill all available CPU types into table */ static void l_table_create(struct win_cpu_types *win_cpu_types) { struct sd_cpu_type *cpu_type; unsigned int i; table_row_del_all(win_cpu_types->t); table_row_mark_del_all(win_cpu_types->t); sd_cpu_type_iterate(cpu_type, i) l_add_cpu_type(win_cpu_types, cpu_type); table_finish(win_cpu_types->t); } /* * Toggle the cpu type specified by "key" in the system data module */ static void l_toggle_cpu_type(char key) { struct sd_cpu_type *cpu_type; unsigned int i; sd_cpu_type_iterate(cpu_type, i) { if (key == cpu_type->hotkey) { sd_cpu_type_select_toggle(cpu_type); return; } } } /* * Process input for selection with SPACE key */ static void l_process_input_select_space(struct win_cpu_types *win_cpu_types) { char cpu_type_key[TABLE_STR_MAX]; if (table_mode_select(win_cpu_types->t)) { table_row_select_key_get(win_cpu_types->t, cpu_type_key); l_toggle_cpu_type(cpu_type_key[0]); } else { struct table_mark_key *key; table_iterate_mark_keys(win_cpu_types->t, key) l_toggle_cpu_type(key->str[0]); } } /* * Process input for selection with hotkey */ static void l_process_input_select_key(struct win_cpu_types *win_cpu_types, int c) { char cpu_type_key[TABLE_STR_MAX]; sprintf(cpu_type_key, "%c", c); table_row_mark_toggle_by_key(win_cpu_types->t, cpu_type_key); l_toggle_cpu_type(cpu_type_key[0]); } /* * Process input and switch window if necessary */ static enum hyptop_win_action l_process_input(struct hyptop_win *win, int c) { struct win_cpu_types *win_cpu_types = (struct win_cpu_types *) win; switch (c) { case 't': case 'q': return win_back(); case KEY_RETURN: case KEY_ENTER: case 'h': case KEY_LEFT: if (!table_mode_select(win_cpu_types->t)) return win_back(); break; case '?': return win_switch(win_cpu_types->win_help); case ' ': l_process_input_select_space(win_cpu_types); break; case ERR: return WIN_KEEP; default: l_process_input_select_key(win_cpu_types, c); break; } table_process_input(win_cpu_types->t, c); hyptop_update_term(); return WIN_KEEP; } /* * Event loop: We stay in hyptop_process_input() until fields menu * is left. */ static void l_run(struct hyptop_win *win) { struct win_cpu_types *win_cpu_types = (struct win_cpu_types *) win; table_reset(win_cpu_types->t); while (1) { hyptop_update_term(); if (hyptop_process_input() == WIN_SWITCH) return; } } /* * Create table and print it to screen */ static void l_update_term(struct hyptop_win *win) { struct win_cpu_types *win_cpu_types = (struct win_cpu_types *) win; l_table_create(win_cpu_types); hyptop_printf("Select Processor Types"); ht_print_help_icon(); hyptop_print_nl(); table_print(win_cpu_types->t); } /* * Create new cpu_types window */ struct hyptop_win *win_cpu_types_new(void) { struct win_cpu_types *win_cpu_types; win_cpu_types = ht_zalloc(sizeof(*win_cpu_types)); win_cpu_types->win.process_input = l_process_input; win_cpu_types->win.update_term = l_update_term; win_cpu_types->win.run = l_run; win_cpu_types->win.desc = l_help_str; win_cpu_types->win.desc_normal_vec = l_nav_desc_normal_vec; win_cpu_types->win.desc_select_vec = l_nav_desc_select_vec; win_cpu_types->win.desc_general_vec = l_nav_desc_general_vec; win_cpu_types->win.id = "cpu_types"; win_cpu_types->t = table_new(1, 0, 0, 0); table_col_add(win_cpu_types->t, &l_col_key); table_col_add(win_cpu_types->t, &l_col_select); table_col_add(win_cpu_types->t, &l_col_id); table_col_add(win_cpu_types->t, &l_col_desc); win_cpu_types->win_help = win_help_new((struct hyptop_win *) win_cpu_types); l_table_create(win_cpu_types); return (struct hyptop_win *) win_cpu_types; } s390-tools-2.3.0/hyptop/win_cpu_types.h000066400000000000000000000011621323410431100177240ustar00rootroot00000000000000/* * hyptop - Show hypervisor performance data on System z * * Window "cpu_types": Select CPU types used for CPU data calculation. * * Copyright IBM Corp. 2010, 2017 * * s390-tools is free software; you can redistribute it and/or modify * it under the terms of the MIT license. See LICENSE for details. */ #ifndef WIN_CPU_TYPES_H #define WIN_CPU_TYPES_H #include "hyptop.h" #include "table.h" #include "win_help.h" struct win_cpu_types { struct hyptop_win win; struct table *t; int in_select; struct hyptop_win *win_help; }; extern struct hyptop_win *win_cpu_types_new(void); #endif /* WIN_CPU_TYPES_H */ s390-tools-2.3.0/hyptop/win_fields.c000066400000000000000000000164071323410431100171620ustar00rootroot00000000000000/* * hyptop - Show hypervisor performance data on System z * * Window "fields": Select fields dialog. * * Copyright IBM Corp. 2010, 2017 * * 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 "helper.h" #include "hyptop.h" #include "table.h" #include "win_fields.h" /* * Globals for fields window */ static struct table_col l_col_select = TABLE_COL_STR_LEFT('s', "s"); static struct table_col l_col_id = TABLE_COL_STR_LEFT('i', "id"); static struct table_col l_col_key = TABLE_COL_STR_LEFT('k', "k"); static struct table_col l_col_unit = TABLE_COL_STR_LEFT('u', "unit"); static struct table_col l_col_agg = TABLE_COL_STR_LEFT('a', "agg"); static struct table_col l_col_desc = TABLE_COL_STR_LEFT('d', "description"); /* * Online help text for fields window */ static const char l_help_str[] = "In the \"fields\" window you can select fields and units. Toggle the selection\n" "of fields either by pressing the corresponding hotkey or by selecting them\n" "in select mode using the SPACE bar. The units can be changed by selecting a\n" "field in select mode and by pressing '+' or '-'.\n" "\n" "The table of the \"fields\" window has the following columns:\n" " - K : Hotkey of field\n" " - S : Shows if field is selected\n" " - ID : Name of field\n" " - UNIT: Current unit used for field\n" " - AGG : Aggregation used for last line of table\n" " - DESC: Description of field\n"; /* * Description of Navigation Keys (used for help window) */ static struct nav_desc *l_nav_desc_normal_vec[] = { &nav_desc_select_mode_enter, &nav_desc_marks_clear, &nav_desc_win_leave_fields, NULL, }; static struct nav_desc *l_nav_desc_select_vec[] = { &nav_desc_select_mode_leave, &nav_desc_mark_toggle, &nav_desc_row_unit_increase, &nav_desc_row_unit_decrease, &nav_desc_win_leave_fields_fast, NULL, }; static struct nav_desc *l_nav_desc_general_vec[] = { &nav_desc_toggle_mark_hotkey, &nav_desc_scroll_up_line, &nav_desc_scroll_down_line, &nav_desc_scroll_up_page, &nav_desc_scroll_down_page, &nav_desc_scroll_up_head, &nav_desc_scroll_down_tail, &nav_desc_mark_toggle_view, NULL, }; /* * Add a field that is the column of the reference table to the table */ static void l_add_field(struct win_fields *win_fields, struct table_col *col, const char *desc) { char char_str[2], select_str[2]; struct table_row *table_row; if (table_col_enabled(col)) sprintf(select_str, "*"); else sprintf(select_str, " "); sprintf(char_str, "%c", table_col_hotkey(col)); table_row = table_row_alloc(win_fields->t); table_row_entry_str_add(table_row, &l_col_select, select_str); table_row_entry_str_add(table_row, &l_col_key, char_str); table_row_entry_str_add(table_row, &l_col_id, table_col_head(col)); table_row_entry_str_add(table_row, &l_col_unit, table_col_unit_str(col)); table_row_entry_str_add(table_row, &l_col_agg, table_col_agg_str(col->agg)); table_row_entry_str_add(table_row, &l_col_desc, desc); table_row_add(win_fields->t, table_row); if (table_col_enabled(col)) table_row_mark_toggle(win_fields->t, table_row); } /* * Fill all field information into table */ static void l_table_create(struct win_fields *win_fields) { unsigned int i; table_row_del_all(win_fields->t); table_row_mark_del_all(win_fields->t); for (i = 0; win_fields->col_vec[i]; i++) { l_add_field(win_fields, win_fields->col_vec[i], win_fields->col_desc_vec[i]); } table_finish(win_fields->t); } /* * Process input for selection with SPACE key */ static void l_process_input_select_space(struct win_fields *win_fields) { char field_key[TABLE_STR_MAX]; if (table_mode_select(win_fields->t)) { table_row_select_key_get(win_fields->t, field_key); table_col_enable_toggle(win_fields->table, field_key[0]); } else { struct table_mark_key *key; /* switch off all fields in reference table */ table_iterate_mark_keys(win_fields->t, key) table_col_enable_toggle(win_fields->table, key->str[0]); } } /* * Process input for selection with hotkey */ static void l_process_input_select_key(struct win_fields *win_fields, int c) { char field_key[TABLE_STR_MAX]; sprintf(field_key, "%c", c); table_row_mark_toggle_by_key(win_fields->t, field_key); table_col_enable_toggle(win_fields->table, field_key[0]); } /* * Process input for unit selection */ static void l_process_input_units(struct win_fields *win_fields, int c) { char field_key[TABLE_STR_MAX]; if (!table_mode_select(win_fields->t)) return; table_row_select_key_get(win_fields->t, field_key); if (c == '+') table_col_unit_next(win_fields->table, field_key[0]); else table_col_unit_prev(win_fields->table, field_key[0]); } /* * Process input and switch window if necessary */ static enum hyptop_win_action l_process_input(struct hyptop_win *win, int c) { struct win_fields *win_fields = (struct win_fields *) win; switch (c) { case 'f': case 'q': return win_back(); case KEY_RETURN: case KEY_ENTER: case 'h': case KEY_LEFT: if (!table_mode_select(win_fields->t)) return win_back(); break; case '?': return win_switch(win_fields->win_help); case ' ': l_process_input_select_space(win_fields); break; case '+': case '-': l_process_input_units(win_fields, c); break; case ERR: return WIN_KEEP; default: l_process_input_select_key(win_fields, c); break; } table_process_input(win_fields->t, c); hyptop_update_term(); return WIN_KEEP; } /* * Event loop: We stay in hyptop_process_input() until fields menu * is left. */ static void l_run(struct hyptop_win *win) { struct win_fields *win_fields = (struct win_fields *) win; table_reset(win_fields->t); while (1) { hyptop_update_term(); if (hyptop_process_input() == WIN_SWITCH) return; } } /* * Create table and print it to screen */ static void l_update_term(struct hyptop_win *win) { struct win_fields *win_fields = (struct win_fields *) win; l_table_create(win_fields); hyptop_printf("Select Fields and Units"); ht_print_help_icon(); hyptop_print_nl(); table_print(win_fields->t); } /* * Create new fields window * * - t...........: Reference table * - col_vec.....: Table column vector for fields * - col_desc_vec: Vector with descriptions for fields */ struct hyptop_win *win_fields_new(struct table *t, struct table_col **col_vec, char **col_desc_vec) { struct win_fields *win_fields; win_fields = ht_zalloc(sizeof(*win_fields)); win_fields->win.process_input = l_process_input; win_fields->win.update_term = l_update_term; win_fields->win.run = l_run; win_fields->win.desc = l_help_str; win_fields->win.desc_normal_vec = l_nav_desc_normal_vec; win_fields->win.desc_select_vec = l_nav_desc_select_vec; win_fields->win.desc_general_vec = l_nav_desc_general_vec; win_fields->win.id = "fields"; win_fields->t = table_new(1, 0, 0, 0); table_col_add(win_fields->t, &l_col_key); table_col_add(win_fields->t, &l_col_select); table_col_add(win_fields->t, &l_col_id); table_col_add(win_fields->t, &l_col_unit); table_col_add(win_fields->t, &l_col_agg); table_col_add(win_fields->t, &l_col_desc); win_fields->col_desc_vec = col_desc_vec; win_fields->col_vec = col_vec; win_fields->table = t; win_fields->win_help = win_help_new((struct hyptop_win *) win_fields); l_table_create(win_fields); return (struct hyptop_win *) win_fields; } s390-tools-2.3.0/hyptop/win_fields.h000066400000000000000000000013461323410431100171630ustar00rootroot00000000000000/* * hyptop - Show hypervisor performance data on System z * * Window "fields": Select fields dialog. * * Copyright IBM Corp. 2010, 2017 * * s390-tools is free software; you can redistribute it and/or modify * it under the terms of the MIT license. See LICENSE for details. */ #ifndef WIN_FIELDS_H #define WIN_FIELDS_H #include "hyptop.h" #include "table.h" #include "win_help.h" struct win_fields { struct hyptop_win win; struct table *t; struct table *table; struct table_col **col_vec; char **col_desc_vec; int mode_unit_change; int in_select; struct hyptop_win *win_help; }; struct hyptop_win *win_fields_new(struct table *t, struct table_col **col_vec, char **col_desc_vec); #endif /* WIN_FIELDS_H */ s390-tools-2.3.0/hyptop/win_help.c000066400000000000000000000051161323410431100166370ustar00rootroot00000000000000/* * hyptop - Show hypervisor performance data on System z * * Window "help": Show online help text. * * Copyright IBM Corp. 2010, 2017 * * 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 "helper.h" #include "hyptop.h" #include "sd.h" #include "table.h" #include "win_help.h" /* * Print help text to screen */ static void l_update_term(struct hyptop_win *win) { struct win_help *win_help = (struct win_help *) win; tbox_print(win_help->tb); } /* * Process input and switch window if necessary */ static enum hyptop_win_action l_process_input(struct hyptop_win *win, int c) { struct win_help *win_help = (struct win_help *) win; switch (c) { case 'h': case KEY_RETURN: case KEY_ENTER: case KEY_LEFT: case '?': case 'q': return win_back(); case 'G': tbox_scroll_down(win_help->tb, TBOX_SCROLL_LAST); break; case 'g': tbox_scroll_up(win_help->tb, TBOX_SCROLL_LAST); break; case KEY_NPAGE: tbox_scroll_down(win_help->tb, TBOX_SCROLL_PAGE); break; case KEY_PPAGE: tbox_scroll_up(win_help->tb, TBOX_SCROLL_PAGE); break; case 'k': case KEY_UP: tbox_scroll_up(win_help->tb, TBOX_SCROLL_LINE); break; case 'j': case KEY_DOWN: tbox_scroll_down(win_help->tb, TBOX_SCROLL_LINE); break; case ERR: return WIN_KEEP; default: break; } hyptop_update_term(); return WIN_KEEP; } /* * Event loop: wait for input and print help text */ static void l_run(struct hyptop_win *win) { (void) win; while (1) { hyptop_update_term(); if (hyptop_process_input() == WIN_SWITCH) return; } } /* * Add text to text box */ static void l_add_text(struct tbox *tb, const char *str) { char *line, *line_end, *str_cpy; str_cpy = line_end = ht_strdup(str); for (line = str_cpy; line_end != NULL; line = line_end + 1) { line_end = strchr(line, '\n'); if (line_end) *line_end = 0; tbox_line_add(tb, line); } ht_free(str_cpy); } /* * Create new help window for "win" and init window description */ struct hyptop_win *win_help_new(struct hyptop_win *win) { struct win_help *win_help; win_help = ht_zalloc(sizeof(*win_help)); win_help->tb = tbox_new(); tbox_printf(win_help->tb, "\\BWindow: %s\\B", win->id); tbox_printf(win_help->tb, " "); l_add_text(win_help->tb, win->desc); nav_desc_add(win_help->tb, win->desc_normal_vec, win->desc_select_vec, win->desc_general_vec); tbox_finish(win_help->tb); win_help->win.process_input = l_process_input; win_help->win.update_term = l_update_term; win_help->win.run = l_run; return (struct hyptop_win *) win_help; } s390-tools-2.3.0/hyptop/win_help.h000066400000000000000000000007771323410431100166540ustar00rootroot00000000000000/* * hyptop - Show hypervisor performance data on System z * * Window "help": Show online help text. * * Copyright IBM Corp. 2010, 2017 * * s390-tools is free software; you can redistribute it and/or modify * it under the terms of the MIT license. See LICENSE for details. */ #ifndef WIN_HELP_H #define WIN_HELP_H #include "hyptop.h" #include "tbox.h" struct win_help { struct hyptop_win win; struct tbox *tb; }; struct hyptop_win *win_help_new(struct hyptop_win *win); #endif /* WIN_HELP_H */ s390-tools-2.3.0/hyptop/win_sys.c000066400000000000000000000221351323410431100165250ustar00rootroot00000000000000/* * hyptop - Show hypervisor performance data on System z * * Window "sys": Shows one system in more detail. * * Copyright IBM Corp. 2010, 2017 * * 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 #include "helper.h" #include "hyptop.h" #include "opts.h" #include "sd.h" #include "table.h" #include "win_fields.h" #include "win_help.h" /* * Globals for sys_list window */ static char l_sys_id[SD_SYS_ID_SIZE]; /* System to show */ static struct table_col l_cpu_col; /* CPU column */ static struct table_col l_vis_col; /* Visual column */ static struct table *l_t; /* Table */ static int l_initialized; /* Win initialized ? */ static struct hyptop_win *l_win_fields; /* Fields window */ static struct hyptop_win *l_win_help; /* Help window */ /* CPU column */ static struct table_col l_cpu_col = { .type = TABLE_COL_TYPE_U64, .unit = &table_col_unit_cnt, .unit_fam = table_col_unit_fam_cnt, .align = TABLE_COL_ALIGN_LEFT, .agg = TABLE_COL_AGG_NONE, .hotkey = 'i', .head = "cpuid", }; /* Visual column */ static struct table_col l_vis_col = { .type = TABLE_COL_TYPE_U64, .unit = &table_col_unit_vis, .unit_fam = table_col_unit_fam_vis, .align = TABLE_COL_ALIGN_LEFT, .agg = TABLE_COL_AGG_NONE, .hotkey = 'v', .head = "visual", }; /* * Online help text for sys window */ static const char l_help_str[] = "The \"sys\" window displays CPU information about one selected system.\n" "Under z/VM you can only see aggregated CPU information and not information\n" "about single CPUs.\n" "\n" "Select a column by pressing the hotkey of the column. This key is underlined\n" "in the heading. The table is sorted according to the values in the selected\n" "column. If you press the hotkey again, the sort order is reversed.\n" "Alternatively you can select columns with the '<' and '>' keys.\n"; /* * Description of Navigation Keys (used for help window) */ static struct nav_desc *l_nav_desc_normal_vec[] = { &nav_desc_select_mode_enter, &nav_desc_marks_clear, &nav_desc_win_leave_sys, NULL, }; static struct nav_desc *l_nav_desc_select_vec[] = { &nav_desc_select_mode_leave, &nav_desc_mark_toggle, &nav_desc_win_leave_sys_fast, NULL, }; static struct nav_desc *l_nav_desc_general_vec[] = { &nav_desc_win_enter_fields, &nav_desc_win_enter_cpu_types, &nav_desc_col_unit_increase, &nav_desc_col_unit_decrease, &nav_desc_select_col_next, &nav_desc_select_col_prev, &nav_desc_select_col_hotkey, &nav_desc_scroll_up_line, &nav_desc_scroll_down_line, &nav_desc_scroll_up_page, &nav_desc_scroll_down_page, &nav_desc_scroll_up_head, &nav_desc_scroll_down_tail, &nav_desc_mark_toggle_view, NULL, }; /* * Add CPU item to table row */ static void l_cpu_item_add(struct table_row *table_row, struct sd_cpu *cpu, struct sd_cpu_item *item) { switch (sd_cpu_item_type(item)) { case SD_TYPE_U16: case SD_TYPE_U32: assert(0); break; case SD_TYPE_U64: table_row_entry_u64_add(table_row, sd_cpu_item_table_col(item), sd_cpu_item_u64(item, cpu)); break; case SD_TYPE_S64: table_row_entry_s64_add(table_row, sd_cpu_item_table_col(item), sd_cpu_item_s64(item, cpu)); break; case SD_TYPE_STR: table_row_entry_str_add(table_row, sd_cpu_item_table_col(item), sd_cpu_item_str(item, cpu)); break; } } /* * Add visualization of CPU time to table row */ static void l_cpu_add_visual(struct table_row *table_row, struct sd_cpu *cpu) { s64 steal_us; u64 cpu_us; cpu_us = sd_cpu_item_u64(&sd_cpu_item_cpu_diff, cpu); steal_us = sd_cpu_item_s64(&sd_cpu_item_steal_diff, cpu); steal_us = MAX(steal_us, 0); table_row_entry_u64_add_pair(table_row, &l_vis_col, cpu_us, steal_us); } /* * Add CPU to table */ static void l_cpu_add(struct sd_cpu *cpu) { struct table_row *table_row; struct sd_cpu_item *item; unsigned int cpu_id; unsigned int i; table_row = table_row_alloc(l_t); cpu_id = atoi(sd_cpu_id(cpu)); table_row_entry_u64_add(table_row, &l_cpu_col, cpu_id); sd_cpu_item_iterate(item, i) { if (!sd_cpu_item_set(item, cpu)) continue; l_cpu_item_add(table_row, cpu, item); } l_cpu_add_visual(table_row, cpu); table_row_add(l_t, table_row); } /* * Fill system CPU data into table */ static int l_table_create(void) { struct sd_sys *parent; struct sd_cpu *cpu; int i; parent = sd_sys_get(sd_sys_root_get(), l_sys_id); if (!parent) return -ENODEV; table_row_del_all(l_t); sd_cpu_iterate(parent, cpu) { for (i = 0; i < cpu->cnt; i++) l_cpu_add(cpu); } table_finish(l_t); return 0; } /* * Print table to screen */ static void l_table_update_term(struct hyptop_win *win) { (void) win; ht_print_head(l_sys_id); table_print(l_t); } /* * Process input and switch window if necessary */ static enum hyptop_win_action l_process_input(struct hyptop_win *win, int c) { (void) win; switch (c) { case 't': return win_switch(g.win_cpu_types); case '?': return win_switch(l_win_help); case 'f': return win_switch(l_win_fields); case 'q': return win_back(); case 'h': case KEY_LEFT: if (!(table_mode_select(l_t))) return win_back(); break; case ERR: return WIN_KEEP; } table_process_input(l_t, c); hyptop_update_term(); return WIN_KEEP; } /* * Enable field and set unit */ static void l_field_set(struct table_col_spec *col_spec) { table_col_enable_toggle(l_t, col_spec->hotkey); if (!col_spec->unit_str) return; if (table_col_unit_set(l_t, col_spec->hotkey, col_spec->unit_str)) ERR_EXIT("Invalid unit \"%s\" for field \"%c\"\n", col_spec->unit_str, col_spec->hotkey); } /* * Enable field defined in "col_spec" */ static void l_field_enable(struct table_col_spec *col_spec) { struct sd_cpu_item *item; struct table_col *col; unsigned int i; if (table_col_hotkey(&l_vis_col) == col_spec->hotkey) { l_field_set(col_spec); return; } sd_cpu_item_iterate(item, i) { col = sd_cpu_item_table_col(item); if (table_col_hotkey(col) != col_spec->hotkey) continue; l_field_set(col_spec); return; } ERR_EXIT("Unknown field \"%c\"\n", col_spec->hotkey); } /* * Enable fields defined on command line */ static void l_fields_enable_cmdline(void) { unsigned int i; table_col_enable_toggle(l_t, table_col_hotkey(&l_vis_col)); for (i = 0; i < win_sys.opts.fields.cnt; i++) l_field_enable(win_sys.opts.fields.vec[i]); } /* * Enable fields like defined in data gatherer */ static void l_fields_enable_default(void) { struct sd_cpu_item *item; struct table_col *col; unsigned int i; sd_cpu_item_enable_iterate(item, i) { col = sd_cpu_item_table_col(item); table_col_enable_toggle(l_t, table_col_hotkey(col)); } } /* * Event loop: Make regular updates of table */ static void l_run(struct hyptop_win *win) { enum hyptop_win_action action; (void) win; /* Reformat table when entering window */ table_rebuild(l_t); while (1) { if (l_table_create()) { if (g.o.batch_mode_specified) ERR_EXIT("System \"%s\" not available.\n", l_sys_id); win_back(); return; } hyptop_update_term(); action = hyptop_process_input_timeout(); if (action == WIN_SWITCH) return; /* No updates in select mode */ if (!table_mode_select(l_t)) sd_update(); } } /* * Define system for window */ void win_sys_set(const char *sys_id) { if (l_initialized) table_reset(l_t); strncpy(l_sys_id, sys_id, sizeof(l_sys_id)); } /* * Initialize window */ void win_sys_init(void) { struct table_col **col_vec; struct sd_cpu_item *item; struct table_col *col; char **col_desc_vec, *vis_str; unsigned int i, item_cnt; if (sd_dg_has_core_data()) { strcpy(l_cpu_col.head, "coreid"); vis_str = "Visualization of core dispatch time per second"; } else { vis_str = "Visualization of CPU time per second"; } /* Alloc table and add columns */ l_t = table_new(1, 1, 1, 1); table_col_add(l_t, &l_cpu_col); table_col_rsort(&l_cpu_col); item_cnt = sd_cpu_item_cnt() + 2; col_vec = ht_zalloc(sizeof(void *) * item_cnt); col_desc_vec = ht_zalloc(sizeof(void *) * item_cnt); sd_cpu_item_iterate(item, i) { col = sd_cpu_item_table_col(item); table_col_add(l_t, col); table_col_enable_toggle(l_t, table_col_hotkey(col)); col_vec[i] = col; col_desc_vec[i] = item->desc; } col_vec[i] = &l_vis_col; col_desc_vec[i] = vis_str; table_col_add(l_t, &l_vis_col); /* Enable fields */ if (win_sys.opts.fields.specified) l_fields_enable_cmdline(); else l_fields_enable_default(); /* Select sort field */ if (win_sys.opts.sort_field_specified) { for (i = 0; i < win_sys.opts.sort_field_specified; i++) { if (table_col_select(l_t, win_sys.opts.sort_field)) ERR_EXIT("Sort field \"%c\" is not available\n", win_sys.opts.sort_field); } } /* Initialize help and fields window */ l_win_fields = win_fields_new(l_t, col_vec, col_desc_vec); l_win_help = win_help_new(&win_sys); l_initialized = 1; } /* * hyptop window structure definition */ struct hyptop_win win_sys = { .process_input = l_process_input, .update_term = l_table_update_term, .run = l_run, .id = "sys", .desc = l_help_str, .desc_normal_vec = l_nav_desc_normal_vec, .desc_select_vec = l_nav_desc_select_vec, .desc_general_vec = l_nav_desc_general_vec, }; s390-tools-2.3.0/hyptop/win_sys_list.c000066400000000000000000000245221323410431100175620ustar00rootroot00000000000000/* * hyptop - Show hypervisor performance data on System z * * Window "sys_list": * Shows a list of systems that the hypervisor is currently running. * * Copyright IBM Corp. 2010, 2017 * * 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 "helper.h" #include "hyptop.h" #include "nav_desc.h" #include "opts.h" #include "sd.h" #include "table.h" #include "win_fields.h" #include "win_help.h" /* * Globals for sys_list window */ static struct table *l_t; /* Table */ static struct hyptop_win *l_win_fields; /* Fields Window */ static struct hyptop_win *l_win_help; /* Herp Window */ /* System column */ static struct table_col l_col_sys = TABLE_COL_STR_LEFT('y', "system"); /* * Online help text for sys_list window */ static const char l_help_str[] = "The following windows can be accessed:\n" "\n" " +-----------+ RIGHT +----------+\n" " | | <----------------------> | |\n" " | | LEFT | |\n" " | | | |\n" " | sys_list | 't' +-----------+ | | 't' +-----------+\n" " | | <-------> | cpu_types | | sys | <-------> | cpu_types |\n" " | (start) | 't',LEFT +-----------+ | | 't',LEFT +-----------+\n" " | | | |\n" " | | 'f' +--------+ | | 'f' +--------+\n" " | | <-------> | fields | | | <-------> | fields |\n" " | | 'f',LEFT +--------+ | | 'f',LEFT +--------+\n" " +-----------+ +----------+\n" "\n" " * sys_list: Start window that shows a list of systems that the hypervisor\n" " is currently running.\n" " * sys: Shows one system in more detail.\n" " * cpu_types: Select CPU types that are used for calculating CPU data.\n" " * fields: Select fields and units for windows sys_list or sys.\n" "\n" "\\BNavigation\\B\n" "\n" "To navigate between the windows, use the arrow keys or 'hjkl'. The windows\n" "have two modes, \"normal mode\" and \"select mode\". When you start the " "program,\n" "the window is in normal mode where data is updated at regular intervals. Use\n" "the RIGHT arrow key to enter the select mode. In select mode you can select\n" "rows with with UP and DOWN arrow keys and mark them with the SPACE bar. From\n" "the \"sys_list\" window you can access the \"sys\" window in select mode\n" "with the arrow key RIGHT. Leave the select mode with the arrow key LEFT.\n" "If you are in normal mode, the arrow key LEFT goes to the previous window.\n" "You can scroll all windows using the arrow keys UP, DOWN, PAGEUP and\n" "PAGEDOWN. You can jump to the end of a window with 'G' and to the beginning\n" "with 'g'.\n" "\n" "Select a column by pressing the hotkey of the column. This key is underlined\n" "in the heading. The table is sorted according to the values in the selected\n" "column. If you press the hotkey again, the sort order is reversed.\n" "Alternatively you can select columns with the '<' and '>' keys.\n" "\n" "\\BTable layout\\B\n" "\n" "At the top left of the table the current time is shown. Then the CPU types\n" "with the physical CPU numbers that are used for CPU time calculation are\n" "displayed. The second row shows the units that are currently used for\n" "formatting the data. The last row shows the status display (see description\n" "below) and the aggregation of the the data columns. The last row aggregates\n" "all rows, not only the visible ones. If only the marked rows are shown\n" "(with '.') then only these rows are aggregated.\n" "\n" "\\BStatus display\\B\n" "\n" "At the left bottom of the screen a status display is shown.\n" "Example: \"V:V:N\"\n\n" "The first character shows, if the window can be scrolled:\n" " 'V': Window can be scrolled down\n" " '|': Window can be scrolled up/down\n" " '^': Window can be scrolled up\n" " '=': Window cannot be scrolled\n" "The second character shows the sort order for sorted tables:\n" " 'V': Higher values first\n" " '^': Lower values first\n" "The third character shows the current mode:\n" " 'N': Normal mode\n" " 'S': Select mode\n"; /* * Description of Navigation Keys (used for help window) */ static struct nav_desc *l_nav_desc_normal_vec[] = { &nav_desc_select_mode_enter, &nav_desc_marks_clear, NULL, }; static struct nav_desc *l_nav_desc_select_vec[] = { &nav_desc_select_mode_leave, &nav_desc_win_enter_sys, &nav_desc_mark_toggle, NULL, }; static struct nav_desc *l_nav_desc_general_vec[] = { &nav_desc_win_enter_fields, &nav_desc_win_enter_cpu_types, &nav_desc_col_unit_increase, &nav_desc_col_unit_decrease, &nav_desc_select_col_next, &nav_desc_select_col_prev, &nav_desc_select_col_hotkey, &nav_desc_scroll_up_line, &nav_desc_scroll_down_line, &nav_desc_scroll_up_page, &nav_desc_scroll_down_page, &nav_desc_scroll_up_head, &nav_desc_scroll_down_tail, &nav_desc_mark_toggle_view, &nav_desc_quit, NULL, }; /* * Add system item to table row */ static void l_sys_item_add(struct table_row *table_row, struct sd_sys *sys, struct sd_sys_item *item) { switch (sd_sys_item_type(item)) { case SD_TYPE_U64: case SD_TYPE_U32: case SD_TYPE_U16: table_row_entry_u64_add(table_row, sd_sys_item_table_col(item), sd_sys_item_u64(sys, item)); break; case SD_TYPE_S64: table_row_entry_s64_add(table_row, sd_sys_item_table_col(item), sd_sys_item_s64(sys, item)); break; case SD_TYPE_STR: table_row_entry_str_add(table_row, sd_sys_item_table_col(item), sd_sys_item_str(sys, item)); break; } } /* * Add system to table */ static void l_sys_add(struct sd_sys *sys) { struct table_row *table_row; struct sd_sys_item *item; unsigned int i; table_row = table_row_alloc(l_t); table_row_entry_str_add(table_row, &l_col_sys, sd_sys_id(sys)); sd_sys_item_iterate(item, i) { if (!sd_sys_item_set(sys, item)) continue; l_sys_item_add(table_row, sys, item); } table_row_add(l_t, table_row); } /* * Fill system data into table */ static void l_table_create(void) { struct sd_sys *parent, *guest; table_row_del_all(l_t); parent = sd_sys_root_get(); sd_sys_iterate(parent, guest) { if (!opts_sys_specified(&win_sys_list, sd_sys_id(guest))) continue; l_sys_add(guest); } table_finish(l_t); } /* * Print table to screen */ static void l_table_update_term(struct hyptop_win *win) { (void) win; ht_print_head(NULL); table_print(l_t); } /* * Process input and switch window if necessary */ static enum hyptop_win_action l_process_input(struct hyptop_win *win, int c) { char selected_sys[TABLE_STR_MAX]; (void) win; switch (c) { case 'f': return win_switch(l_win_fields); case 't': return win_switch(g.win_cpu_types); case '?': return win_switch(l_win_help); case 'q': hyptop_exit(0); case 'l': case KEY_RIGHT: if (!table_mode_select(l_t)) break; table_row_select_key_get(l_t, selected_sys); win_sys_set(selected_sys); return win_switch(&win_sys); case ERR: break; } table_process_input(l_t, c); hyptop_update_term(); return WIN_KEEP; } /* * Enable field and set unit */ static void l_field_set(struct table_col_spec *col_spec) { table_col_enable_toggle(l_t, col_spec->hotkey); if (!col_spec->unit_str) return; if (table_col_unit_set(l_t, col_spec->hotkey, col_spec->unit_str)) ERR_EXIT("Invalid unit \"%s\" for field \"%c\"\n", col_spec->unit_str, col_spec->hotkey); } /* * Enable field defined in "col_spec" */ static void l_field_enable(struct table_col_spec *col_spec) { struct sd_sys_item *item; struct table_col *col; unsigned int i; sd_sys_item_iterate(item, i) { col = sd_sys_item_table_col(item); if (table_col_hotkey(col) != col_spec->hotkey) continue; l_field_set(col_spec); return; } ERR_EXIT("Unknown field \"%c\"\n", col_spec->hotkey); } /* * Enable fields defined on command line */ static void l_fields_enable_cmdline(void) { unsigned int i; for (i = 0; i < win_sys_list.opts.fields.cnt; i++) l_field_enable(win_sys_list.opts.fields.vec[i]); } /* * Enable fields like defined in data gatherer */ static void l_fields_enable_default(void) { struct sd_sys_item *item; struct table_col *col; unsigned int i; sd_sys_item_enable_iterate(item, i) { col = sd_sys_item_table_col(item); table_col_enable_toggle(l_t, table_col_hotkey(col)); } } /* * Event loop: Make regular updates of table */ static void l_run(struct hyptop_win *win) { enum hyptop_win_action action; (void) win; /* Reformat table when entering window */ table_rebuild(l_t); while (1) { l_table_create(); hyptop_update_term(); action = hyptop_process_input_timeout(); if (action == WIN_SWITCH) return; /* No updates in select mode */ if (!table_mode_select(l_t)) sd_update(); } } /* * Initialize window */ void win_sys_list_init(void) { struct table_col **col_vec; struct sd_sys_item *item; struct table_col *col; char **col_desc_vec; unsigned int i; int item_cnt; /* Alloc table and add columns */ l_t = table_new(1, 1, 1, 1); table_col_add(l_t, &l_col_sys); item_cnt = sd_sys_item_cnt() + 1; col_vec = ht_zalloc(sizeof(void *) * item_cnt); col_desc_vec = ht_zalloc(sizeof(void *) * item_cnt); sd_sys_item_iterate(item, i) { col = sd_sys_item_table_col(item); table_col_add(l_t, col); table_col_enable_toggle(l_t, table_col_hotkey(col)); col_vec[i] = col; col_desc_vec[i] = item->desc; } /* Enable fields */ if (win_sys_list.opts.fields.specified) l_fields_enable_cmdline(); else l_fields_enable_default(); /* Select sort field */ if (win_sys_list.opts.sort_field_specified) { for (i = 0; i < win_sys_list.opts.sort_field_specified; i++) { if (table_col_select(l_t, win_sys_list.opts.sort_field)) ERR_EXIT("Sort field \"%c\" is not available\n", win_sys_list.opts.sort_field); } } else { table_col_select(l_t, sd_sys_item_cpu_diff.table_col.hotkey); } /* Initialize help and fields window */ l_win_help = win_help_new(&win_sys_list); l_win_fields = win_fields_new(l_t, col_vec, col_desc_vec); } /* * hyptop window structure definition */ struct hyptop_win win_sys_list = { .process_input = l_process_input, .update_term = l_table_update_term, .run = l_run, .id = "sys_list", .desc = l_help_str, .desc_normal_vec = l_nav_desc_normal_vec, .desc_select_vec = l_nav_desc_select_vec, .desc_general_vec = l_nav_desc_general_vec, }; s390-tools-2.3.0/include/000077500000000000000000000000001323410431100147635ustar00rootroot00000000000000s390-tools-2.3.0/include/lib/000077500000000000000000000000001323410431100155315ustar00rootroot00000000000000s390-tools-2.3.0/include/lib/ccw.h000066400000000000000000000020421323410431100164540ustar00rootroot00000000000000/* * ccw - Channel Command Word library (traditional I/O) * * Copyright 2017 IBM Corp. * * s390-tools is free software; you can redistribute it and/or modify * it under the terms of the MIT license. See LICENSE for details. */ #ifndef LIB_CCW_H #define LIB_CCW_H #include #include "lib/zt_common.h" /** * ccw_devid - CCW device ID */ struct ccw_devid { /** Channel Subsystem ID */ unsigned int cssid:8; /** Subchannel set ID */ unsigned int ssid:8; /** Device number */ unsigned int devno:16; } __packed; /** * Initialize ccw_devid structure * * @param[in,out] devid Pointer to ccw_devid structure to be initialized * @param[in] cssid Channel Subsystem ID * @param[in] ssid Subchannel set ID * @param[in] devno Device number */ static inline void ccw_devid_init(struct ccw_devid *devid, unsigned int cssid, unsigned int ssid, unsigned int devno) { devid->cssid = cssid; devid->ssid = ssid; devid->devno = devno; } bool ccw_parse_str(struct ccw_devid *devid, const char *id); #endif s390-tools-2.3.0/include/lib/dasd_base.h000066400000000000000000000156521323410431100176200ustar00rootroot00000000000000/* * dasd_base - Library for DASD related functions * * DASD related helper functions for accessing device information * * Copyright IBM Corp. 2013, 2017 * Copyright Red Hat Inc. 2017 * * s390-tools is free software; you can redistribute it and/or modify * it under the terms of the MIT license. See LICENSE for details. */ #ifndef LIB_DASD_BASE_H #define LIB_DASD_BASE_H #ifdef __linux__ #include #endif #include #include #include typedef struct dasd_information2_t { unsigned int devno; /* S/390 devno */ unsigned int real_devno; /* for aliases */ unsigned int schid; /* S/390 subchannel identifier */ unsigned int cu_type : 16; /* from SenseID */ unsigned int cu_model : 8; /* from SenseID */ unsigned int dev_type : 16; /* from SenseID */ unsigned int dev_model : 8; /* from SenseID */ unsigned int open_count; unsigned int req_queue_len; unsigned int chanq_len; /* length of chanq */ char type[4]; /* from discipline.name, 'none' for unknown */ unsigned int status; /* current device level */ unsigned int label_block; /* where to find the VOLSER */ unsigned int FBA_layout; /* fixed block size (like AIXVOL) */ unsigned int characteristics_size; unsigned int confdata_size; unsigned char characteristics[64];/*from read_device_characteristics */ unsigned char configuration_data[256];/*from read_configuration_data */ unsigned int format; /* format info like formatted/cdl/ldl/... */ unsigned int features; /* dasd features like 'ro',... */ unsigned int reserved0; /* reserved for further use ,... */ unsigned int reserved1; /* reserved for further use ,... */ unsigned int reserved2; /* reserved for further use ,... */ unsigned int reserved3; /* reserved for further use ,... */ unsigned int reserved4; /* reserved for further use ,... */ unsigned int reserved5; /* reserved for further use ,... */ unsigned int reserved6; /* reserved for further use ,... */ unsigned int reserved7; /* reserved for further use ,... */ } dasd_information2_t; struct dasd_eckd_characteristics { unsigned short cu_type; struct { unsigned char support:2; unsigned char async:1; unsigned char reserved:1; unsigned char cache_info:1; unsigned char model:3; } __attribute__ ((packed)) cu_model; unsigned short dev_type; unsigned char dev_model; struct { unsigned char mult_burst:1; unsigned char RT_in_LR:1; unsigned char reserved1:1; unsigned char RD_IN_LR:1; unsigned char reserved2:4; unsigned char reserved3:8; unsigned char defect_wr:1; unsigned char XRC_supported:1; unsigned char reserved4:1; unsigned char striping:1; unsigned char reserved5:4; unsigned char cfw:1; unsigned char reserved6:2; unsigned char cache:1; unsigned char dual_copy:1; unsigned char dfw:1; unsigned char reset_alleg:1; unsigned char sense_down:1; } __attribute__ ((packed)) facilities; unsigned char dev_class; unsigned char unit_type; unsigned short no_cyl; unsigned short trk_per_cyl; unsigned char sec_per_trk; unsigned char byte_per_track[3]; unsigned short home_bytes; unsigned char formula; union { struct { unsigned char f1; unsigned short f2; unsigned short f3; } __attribute__ ((packed)) f_0x01; struct { unsigned char f1; unsigned char f2; unsigned char f3; unsigned char f4; unsigned char f5; } __attribute__ ((packed)) f_0x02; } __attribute__ ((packed)) factors; unsigned short first_alt_trk; unsigned short no_alt_trk; unsigned short first_dia_trk; unsigned short no_dia_trk; unsigned short first_sup_trk; unsigned short no_sup_trk; unsigned char MDR_ID; unsigned char OBR_ID; unsigned char director; unsigned char rd_trk_set; unsigned short max_rec_zero; unsigned char reserved1; unsigned char RWANY_in_LR; unsigned char factor6; unsigned char factor7; unsigned char factor8; unsigned char reserved2[3]; unsigned char reserved3[6]; unsigned int long_no_cyl; } __attribute__ ((packed)); /* * struct format_data_t * represents all data necessary to format a dasd */ typedef struct format_data_t { unsigned int start_unit; /* from track */ unsigned int stop_unit; /* to track */ unsigned int blksize; /* sectorsize */ unsigned int intensity; } format_data_t; /* * struct format_check_t * represents all data necessary to evaluate the format of * different tracks of a dasd */ typedef struct format_check_t { /* Input */ struct format_data_t expect; /* Output */ unsigned int result; /* Error indication (DASD_FMT_ERR_*) */ unsigned int unit; /* Track that is in error */ unsigned int rec; /* Record that is in error */ unsigned int num_records; /* Records in the track in error */ unsigned int blksize; /* Block-size of first record in error */ unsigned int key_length; /* Key length of first record in error */ } format_check_t; #ifndef __linux__ /* definition from hdreg.h */ struct hd_geometry { unsigned char heads; unsigned char sectors; unsigned short cylinders; unsigned long start; }; #endif #define DASD_IOCTL_LETTER 'D' /* Disable the volume (for Linux) */ #define BIODASDDISABLE _IO(DASD_IOCTL_LETTER, 0) /* Enable the volume (for Linux) */ #define BIODASDENABLE _IO(DASD_IOCTL_LETTER, 1) /* Get information on a dasd device (enhanced) */ #define BIODASDINFO2 _IOR(DASD_IOCTL_LETTER, 3, dasd_information2_t) /* #define BIODASDFORMAT _IOW(IOCTL_LETTER,0,format_data_t) , deprecated */ #define BIODASDFMT _IOW(DASD_IOCTL_LETTER, 1, format_data_t) /* Check device format according to format_data_t */ #define BIODASDCHECKFMT _IOWR(DASD_IOCTL_LETTER, 2, format_check_t) /******************************************************************************** * SECTION: Further IOCTL Definitions (see fs.h and hdreq.h) *******************************************************************************/ /* get read-only status (0 = read_write) */ #define BLKROGET _IO(0x12, 94) /* re-read partition table */ #define BLKRRPART _IO(0x12, 95) /* get block device sector size */ #define BLKSSZGET _IO(0x12, 104) /* return device size in bytes (u64 *arg) */ #define BLKGETSIZE64 _IOR(0x12, 114, size_t) #ifndef __linux__ /* from */ #define HDIO_GETGEO 0x0301 #endif int dasd_check_format(const char *device, format_check_t *p); int dasd_format_disk(int fd, format_data_t *p); int dasd_disk_disable(const char *device, int *fd); int dasd_disk_enable(int fd); int dasd_get_blocksize(const char *device, unsigned int *blksize); int dasd_get_blocksize_in_bytes(const char *device, unsigned long long *blksize); int dasd_get_geo(const char *device, struct hd_geometry *geo); int dasd_get_info(const char *device, dasd_information2_t *info); int dasd_is_ro(const char *device, bool *ro); int dasd_reread_partition_table(const char *device, int ntries); #endif /* LIB_DASD_BASE_H */ s390-tools-2.3.0/include/lib/dasd_sys.h000066400000000000000000000007631323410431100175210ustar00rootroot00000000000000/* * dasd - Library for DASD related functions * * DASD related helper functions for accessing device information via sysfs * * Copyright IBM Corp. 2016, 2017 * * s390-tools is free software; you can redistribute it and/or modify * it under the terms of the MIT license. See LICENSE for details. */ #ifndef LIB_DASD_SYS_H #define LIB_DASD_SYS_H #include #include "u2s.h" int dasd_sys_raw_track_access(char *); int dasd_reset_chpid(char *, char *); #endif /* LIB_DASD_SYS_H */ s390-tools-2.3.0/include/lib/libzds.h000066400000000000000000000551731323410431100172040ustar00rootroot00000000000000/** * \file libzds.h * This is the main header file for the internal library libzds. * Please note that this library should currently only be used * by programs in the s390-tools package. It is not yet meant * for external use as interfaces and definitions may change * without further notice. * * Copyright IBM Corp. 2013, 2017 * * s390-tools is free software; you can redistribute it and/or modify * it under the terms of the MIT license. See LICENSE for details. */ /** * @mainpage * The libzds is a s390-tools internal library for use with DASD * devices in raw_track_access mode. * * The regular operation mode of the DASD device driver allows only to * access ECKD DASDs that were formatted with a specific record * layout. The raw access mode of the DASD device driver allows to * access any kind of ECKD DASD, but requires the correct use of * the DIRECT_IO interface and leaves the interpretation of the data * format on these devices to the user. * * This library supports the use of raw DASD devices by providing * functions that * @li access the device with DIRECT_IO and the correct buffer alignment * @li provide access to label and VTOC data on the device * @li provide access to simple z/OS data set formats * (physical sequential (PS) and partitioned data sets (PDS)) * * * @section interface_groups Library Interface * * @subsection interface_structures Data Structures * * The data structures provided by this library can be divided into * two types: Structures that represent external hardware and software * interfaces, and structures that are defined by libzds itself: * * @ref external_interfaces * * @ref libzds_data * * * @subsection interface_functions Functions * * The functions provided by this library are divided into 5 categories: * base, low, mid, and highlevel functions and helper functions. * * The lower the level, the less dependent are the functions on the data * that is stored on the DASDs. The higher the level the more abstract * are the implemented concepts. * * The base level functions are needed to setup the internal data * structures which the other functions work on. Otherwise, he use of higher * level functions does not require the use of low level functions. * For example: To simply read data from a data set, you just need the * base and high level functions and can ignore the low and mid level * functions. * * \ref libzds_functions_base * * \ref libzds_functions_low * * \ref libzds_functions_mid * * \ref libzds_functions_high * * \ref libzds_functions_helper * * * @section naming_scheme Naming Scheme * * All interface functions start with lzds_ for libzds (could be changed later * but libzds_ is quite long). Next is the entity the function works on, * for example * @li @c lzds_zdsroot_... * @li @c lzds_dasd_... * * So if you know what entity you want to work on, you know where to look. * * Then follows the operation (add, get, read, alloc, ...). * There are several verbs that can mean 'access data'. * As a guideline we define the following meaning for use in this library: * @li read: Will result in data being read from a device * @li get: Get a value from one of the internal structures * @li extract: Use the internal data to create higher level data, * e.g. use the VTOC information of a DASD to create data * set structures * @li alloc: Create and return a libzds data structure * * @note For every alloc function there shall be a matching free function to * release the memory. However, often the structure is created in the * context of a another structure, but when it is freed, that is done in * its own context. For example: * lzds_zdsroot_alloc_dasditerator is matched by lzds_dasditerator_free * * Finally the object of the operation, what you want to get or achieve, * e.g lzds_ds_get_is_PDS * * For the parmeter list, the general rule is: * The subject comes first, the object last, further parameters in between. */ #ifndef LIB_LIBZDS_H /** * @brief Watchdog for libzds.h inclusion. */ #define LIB_LIBZDS_H #include "vtoc.h" /** * \defgroup external_interfaces External constants and structures. * @{ * @brief These constants and structures are related to * hardware and software interfaces that are specified outside of * libzds. * * @li For a description of ECKD data formats see * 'IBM 3990/9390 Storage Control Reference', Document Number GA32-0274-05 * @li For a description of VTOC entries see * 'z/OS DFSMSdfp Advanced Services', Document Number SC26-7400-11 * @li For a description of physical sequential and partitioned data sets see * 'z/OS DFSMS Using Data Sets', Document Number SC26-7410-11 * @li For a description of the Linux on System z DASD device driver see * 'Device Drivers, Features, and Commands (kernel 3.7)', * Document Number SC33-8411-18 */ /** * @brief The size of one raw track when read via the DASD device driver * with raw_track_access. * * When reading from a DASD in raw_track_access mode, you need to * align your I/O to multiples of this size. */ #define RAWTRACKSIZE 65536 /** * @brief Maximum size of one record on a track * * This is the maximum size of a single record on a track. If a track contains * multiple records, the additional overhead will cause the sum of these * multiple records to be smaller than the biggest single record, so MAXRECSIZE * is also the upper limit for user data that a single track can hold. */ #define MAXRECSIZE 56664 /** * @brief Maximum number of extents a data set can hold. * * We do not handle extended format data sets so we can have a total of 16 * extents per dataset (3 in the f1 and 13 in the f3 label). */ #define MAXEXTENTS 16 /** * @brief Maximum size of a data set name string (including one byte for * 0-termination) */ #define MAXDSNAMELENGTH 45 /** * @brief Maximum size of a partitioned data set member name string * (including one byte for 0-termination) */ #define MEMBERNAMELENGTH 9 /** * @brief The maximum number of volumes (devices) that a multi volume data * set can span. */ #define MAXVOLUMESPERDS 59 /** * @brief Eight bytes of 0xFF are used in several cases to designate the end of data. * */ #define ENDTOKEN 0xFFFFFFFFFFFFFFFFULL /** * @brief This structure represents the count field in an ECKD record. */ struct eckd_count { /** @brief record ID * * In general the record ID is defined as just a 5 byte field. * The interpretation of these 5 bytes as a struct cchhb_t is a common * convention, which we assume here as well. */ cchhb_t recid; /** @brief key length */ unsigned char kl; /** @brief data length */ unsigned short dl; } __attribute__ ((packed)); /** * @brief A generic structure to describe a data set control block (DSCB) * * The elements of the VTOC are called DSCBs. All DSCBs have in common that * they have a size of 140 bytes, and byte 44 is the identifier that * determines the type of DSCB. */ struct dscb { /** @brief key area * * This part of the DSCB is usually stored in the key part of an * ECKD record in the VTOC. The contents depends on the format. */ char key[44]; /** @brief Format identifier * * This identifier determines the data layout of the rest of the * DSCB. In a format-x DSCB this field is called DSxFMTID. * The identifiers for format-1 to format-9 are the respective * EBCDIC characters '1' to '9' (0xf1 to 0xf9). * An empty DSCB record (format-0 DSCB) contains 140 zeros, so * here the format id is 0x00. */ char fmtid; /** @brief The residual data part of the DSCB * * The contents depends on the format. */ char data[95]; } __attribute__ ((packed)); /** * @brief This structure represents a segment descriptor word (SDW), * record descriptor word (RDW) or block descriptor word (BDW). * * These are used to describe data set blocks and records. * (see z/OS DFSMS Using Data Sets) */ struct segment_header { /** @brief Is this an empty segment (valid for SDW) */ unsigned short nullsegment:1; /** @brief Length of the segment */ unsigned short length:15; /** @brief reserved */ unsigned char reserved1:6; /** @brief Segment control code (valid for SDW) * * 0: logical record consists of just this segment, * 1: first segment in the logical record, * 2: last segment in the logical record, * 3: intermediate in the logical record */ unsigned char position:2; /** @brief reserved */ unsigned char reserved3:8; } __attribute__ ((packed)); /** * @brief The key length of a PDS directory member record. */ #define PDS_DIR_KL 8 /** * @brief The data length of a PDS directory member record. */ #define PDS_DIR_DL 256 /** * @brief This structure represents and entry in the PDS directory and * describes a member of the data set. * * This structure represents only the fixed part of the member * entry. The variable size of the user data part is determined * by the entry user_data_count. * (see z/OS DFSMS Using Data Sets) */ struct pds_member_entry { /** @brief Member name */ char name[8]; /** @brief Start track of the member (relative to start of the PDS) */ unsigned short track; /** @brief Start record of the member */ unsigned char record; /** @brief Is this entry an alias for another entry? */ unsigned char is_alias:1; /** @brief How many TTRN note lists are contained in the user data. */ unsigned char ttrn_count:2; /** @brief The user_data_count value counts 'half words' i.e. shorts! */ unsigned char user_data_count:5; } __attribute__ ((packed)); /** @} */ /* end of group hardware */ /** * @defgroup libzds_data libzds data structures * @{ * @brief These are the data structures used by the libzds API. * * For users of libzds these are opaque data structures and they have * no dependency on the implementation details of these structures. * All libzds interface functions work on pointers to these structures, * so programs that use the library do not need to know them either. * This prevents users from accessing the data in unsupported ways * allows us to change the implementation without changing the * interface. */ /** * @struct zdsroot * @brief The root of all device and data set information. * * Note that data sets do not belong to DASDs, as they * may span over more than one DASD. */ struct zdsroot; /** * @struct dasd * @brief Represents one physical device, may have a vtoc */ struct dasd; /** * @struct dasditerator * @brief Allows to iterate over all dasds in the zdsroot */ struct dasditerator; /** * @struct dasdhandle * @brief Represents the state of a DASD device while it is in use. * * For applications that need to read data directly from a DASD device. * The idea is to have an abstract handle for a DASD that is in * use, similar to a FILE pointer */ struct dasdhandle; /** * @struct raw_vtoc * @brief The VTOC is a directory of data sets on one dasd */ struct raw_vtoc; /** * @struct dscbiterator * @brief allows to iterate over all DSCBs in a vtoc */ struct dscbiterator; /** * @struct dataset * @brief The whole of one data set * * May refer to one or more dataset parts * and may have a list of partitioned dataset members. */ struct dataset; /** * @struct dsiterator * @brief Allows to iterate over all data sets in the zdsroot */ struct dsiterator; /** * @struct pdsmember * @brief If a data set is a partitioned data set (PDS) it * may have zero or more PDS members */ struct pdsmember; /** * @struct memberiterator * @brief Allows to iterate over all members in the dataset */ struct memberiterator; /** * @struct dshandle * @brief Represents the state of a data set while it is in use * * This state includes the I/O buffers used for reading, * the position within the data set, options used for processing the data, * etc. The idea is to have an abstract handle for a data set that is in * use, similar to a FILE pointer. */ struct dshandle; /** * @struct error_log * @brief A stack of error messages that are related to the last error */ struct errorlog; /** @} */ /* end of group libzds_data */ /** * @defgroup libzds_functions_base Base functions * @{ * @brief These functions are basic setup functions which need to * be used before any of the low, mid or high level functions * can be used. These functions concern the allocation and * initialization of the zdsroot and the basic handling of devices. */ /** * @brief Allocate a new zdsroot structure. */ int lzds_zdsroot_alloc(struct zdsroot **root); /** * @brief Free the memory of the given zdsroot structure. */ void lzds_zdsroot_free(struct zdsroot *root); /** * @brief Add a DASD device to the zdsroot. */ int lzds_zdsroot_add_device(struct zdsroot *root, const char *devnode, struct dasd **dasd); /** * @brief Get the errorlog. */ void lzds_dasd_get_errorlog(struct dasd *dasd, struct errorlog **log); /** * @brief Allocate index that allows to iterate through all DASDs * stored in the root. */ int lzds_zdsroot_alloc_dasditerator(struct zdsroot *root, struct dasditerator **it); /** * @brief Free the dasditerator structure. */ void lzds_dasditerator_free(struct dasditerator *it); /** * @brief Get the next dasd structure. */ int lzds_dasditerator_get_next_dasd(struct dasditerator *it, struct dasd **dasd); /** * @brief Return the device node name that was used for this dasd. */ void lzds_dasd_get_device(struct dasd *dasd, char **device); /** * @brief Get the dasd structure that belongs to the given device. */ int lzds_zdsroot_get_dasd_by_node_name(struct zdsroot *root, const char *device, struct dasd **dasd); /** * @brief Get the errorlog. */ void lzds_zdsroot_get_errorlog(struct zdsroot *root, struct errorlog **log); int lzds_errorlog_fprint(struct errorlog *log, FILE *stream); /** @} */ /* end of group libzds_functions_base */ /** * @defgroup libzds_functions_low Low level interface functions. * @{ * @brief Very basic functions, should all work on every kind DASD. * * These functions give access to the data on a DASD on a very * low abstraction level. They do not dependent on the data on the * device itself. * */ /** * @brief Based on the dasd device geometry, compute a track number from a * given cchh_t (cylinder, head) address value. */ void lzds_dasd_cchh2trk(struct dasd *dasd, cchh_t *p, unsigned int *track); /** * @brief Get the number of cylinders that this DASD has. */ void lzds_dasd_get_cylinders(struct dasd *dasd, unsigned int *cylinders); /** * @brief Get the number of heads, that a cylinder of this DASD has. */ void lzds_dasd_get_heads(struct dasd *dasd, unsigned int *heads); /** * @brief Allocate a new dasd context structure for given data set. */ int lzds_dasd_alloc_dasdhandle(struct dasd *dasd, struct dasdhandle **dasdh); /** * @brief Free memory that was allocated for a dasdhandle. */ void lzds_dasdhandle_free(struct dasdhandle *dasdh); /** * @brief This makes the dasd context ready for read operations. */ int lzds_dasdhandle_open(struct dasdhandle *dasdh); /** * @brief This closes the file descriptor connected with the dasdhandle. */ int lzds_dasdhandle_close(struct dasdhandle *dasdh); /** * @brief Read raw tracks from the dasdhandle. */ int lzds_dasdhandle_read_tracks_to_buffer(struct dasdhandle *dasdh, unsigned int starttrck, unsigned int endtrck, char *trackdata); /** @} */ /* end of group libzds_functions_low */ /** * @defgroup libzds_functions_mid Mid level interface functions * @{ * @brief Functions that give access to low level structures like VTOC * records. * * These functions give access to and rely on the meta data stored on * the DASD, in particular the VTOC. * These functions take the structure of the data on the * device into account, so they may fail if this data is not * correct (e.g. if the VTOC is broken). * * @todo The interface of the mid level functions is not properly structured yet. * */ /** * @brief Read the volume label from device. The data as stored as * part of the struct dasd. */ int lzds_dasd_read_vlabel(struct dasd *dasd); /** * @brief Get the previously read volume label data.. */ int lzds_dasd_get_vlabel(struct dasd *dasd, struct volume_label **vlabel); /** * @brief Read the vtoc data from device. The data as stored as part * of the struct dasd. */ int lzds_dasd_read_rawvtoc(struct dasd *dasd); /** * @brief Get the previously read raw_vtoc data. */ int lzds_dasd_get_rawvtoc(struct dasd *dasd, struct raw_vtoc **vtoc); /** * @brief Allocate index that allows to iterate through all DSCB * records stored in the raw_vtoc. */ int lzds_raw_vtoc_alloc_dscbiterator(struct raw_vtoc *rawvtoc, struct dscbiterator **it); /** * @brief Free the iterators memory */ void lzds_dscbiterator_free(struct dscbiterator *it); /** * @brief Get the next DSCB in the VTOC. */ int lzds_dscbiterator_get_next_dscb(struct dscbiterator *it, struct dscb **dscb); /** * @brief Find and get a specific DSCB record by its cylinder, head * and record address (cchhb_t) */ int lzds_raw_vtoc_get_dscb_from_cchhb(struct raw_vtoc *rv, cchhb_t *p, struct dscb **dscb); /** @} */ /* end of group libzds_functions_mid */ /** * @defgroup libzds_functions_high High level interface functions * @{ * @brief These functions give access to the data on a DASD on a * high abstraction level. * * These functions abstract away most of the low level details. * They give access to the user data stored on the DASD using abstract * concepts like 'data set' without requiring the user * to do any low level analysis. */ /** * @brief Search zdsroot for a specific data set. */ int lzds_zdsroot_find_dataset(struct zdsroot *root, const char *name, struct dataset **ds); /** * @brief Allocate an iterator that will allow to iterate through all * datasets on the index. */ int lzds_zdsroot_alloc_dsiterator(struct zdsroot *zdsroot, struct dsiterator **it); /** * @brief Free memory of the given data set iterator. */ void lzds_dsiterator_free(struct dsiterator *it); /** * @brief Return the next data set the iterator points to. */ int lzds_dsiterator_get_next_dataset(struct dsiterator *it, struct dataset **ds); /** * @brief Get the 'partitioned data set' status of a data set? */ void lzds_dataset_get_is_PDS(struct dataset *ds, int *ispds); /** * @brief Are all parts of a multi volume data set available? */ void lzds_dataset_get_is_complete(struct dataset *ds, int *iscomplete); /** * @brief Can the data set be opened and read with this library? */ void lzds_dataset_get_is_supported(struct dataset *ds, int *issupported); /** * @brief Get the name of a dataset as ASCII string. */ void lzds_dataset_get_name(struct dataset *ds, char **name); /** * @brief Get the format 1 DSCB for a data set. In case of a multi volume * data set it returns the DSCB of the first volume. */ void lzds_dataset_get_format1_dscb(struct dataset *ds, format1_label_t **f1); /** * @brief Search the data set for a given member name and if a matching * member is found return a struct pdsmember. */ int lzds_dataset_get_member_by_name(struct dataset *ds, char *membername, struct pdsmember **member); /** * @brief Allocate an iterator that will allow to iterate through all members * on a datasets. */ int lzds_dataset_alloc_memberiterator(struct dataset *ds, struct memberiterator **it); /** * @brief Free memory of the given member iterator. */ void lzds_memberiterator_free(struct memberiterator *it); /** * @brief Return the next data set member the iterator points to. */ int lzds_memberiterator_get_next_member(struct memberiterator *it, struct pdsmember **member); /** * @brief Allocate a new data set context structure for given data set. */ int lzds_dataset_alloc_dshandle(struct dataset *ds, unsigned int tracks_per_frame, struct dshandle **dsh); /** * @brief Free the memory of the given dshandle structure. */ void lzds_dshandle_free(struct dshandle *dsh); /** * @brief If the dsh points to a partitioned data set, this function will * set which member of that PDS is read via the dsh. */ int lzds_dshandle_set_member(struct dshandle *dsh, char *membername); /** * @brief Read out the member pointer that has been set on this dshandle. */ void lzds_dshandle_get_member(struct dshandle *dsh, struct pdsmember **member); /** * @brief Set the flag that causes the library to keep the record descriptor * word (RDW) of variable records in the data stream. */ int lzds_dshandle_set_keepRDW(struct dshandle *dsh, int keepRDW); /** * @brief Read out the current setting of the RDW flag. */ void lzds_dshandle_get_keepRDW(struct dshandle *dsh, int *keepRDW); /** * @brief Prepares the dsh and the related devices for read operations. */ int lzds_dshandle_open(struct dshandle *dsh); /** * @brief Matching close operation for the data set context. */ void lzds_dshandle_close(struct dshandle *dsh); /** * @brief Read data from the data set to which the dsh points. */ int lzds_dshandle_read(struct dshandle *dsh, char *buf, size_t size, ssize_t *rcsize); /** * @brief Move buffer position of dsh to offset. */ int lzds_dshandle_lseek(struct dshandle *dsh, long long offset, long long *rcoffset); /** * @brief Get the current buffer position. */ void lzds_dshandle_get_offset(struct dshandle *dsh, long long *offset); /** * @brief Get the errorlog. */ void lzds_dshandle_get_errorlog(struct dshandle *dsh, struct errorlog **log); /** * @brief Set an upper limit for the seek buffer. */ int lzds_dshandle_set_seekbuffer(struct dshandle *dsh, unsigned long long seek_buffer_size); /** * @brief Get the size of the data set in number of tracks (sum of all extents). */ void lzds_dataset_get_size_in_tracks(struct dataset *ds, unsigned long long *tracks); /** * @brief Get the name of a partitioned dataset member. */ void lzds_pdsmember_get_name(struct pdsmember *member, char **name); /** * @brief Extract the data set information from the rawvtoc stored in the * dasd and add it to the list of data sets stored in the zdsroot. */ int lzds_zdsroot_extract_datasets_from_dasd(struct zdsroot *root, struct dasd *dasd); /** @} */ /* end of group libzds_functions_high */ /** * @defgroup libzds_functions_helper Helper functions * @{ * * @brief These functions do not fit in the hierarchy of the other * libzds functions, but are useful helpers. */ /** * @brief Translates a DS1RECFM byte to a recfm format string. */ void lzds_DS1RECFM_to_recfm(char DS1RECFM, char *buffer); int lzds_analyse_open_count(struct zdsroot *root, int warn); /** @} */ /* end of group libzds_functions_helper */ #endif /* LIB_LIBZDS_H */ s390-tools-2.3.0/include/lib/u2s.h000066400000000000000000000007301323410431100164130ustar00rootroot00000000000000/* * * Copyright IBM Corp. 2004, 2017 * * s390-tools is free software; you can redistribute it and/or modify * it under the terms of the MIT license. See LICENSE for details. * * History of changes (starts July 2004) * 2004-07-02 initial */ #ifndef LIB_U2S_H #define LIB_U2S_H #define U2S_BUS_ID_SIZE 32 int u2s_getbusid(char *, char *); int u2s_read_attribute(char *, char *, char *, size_t); int u2s_get_host_access_count(char *); #endif /* LIB_U2S_H */ s390-tools-2.3.0/include/lib/util.h000066400000000000000000000005641323410431100166640ustar00rootroot00000000000000/* * util - Utility function library * * Library helper functions * * Copyright IBM Corp. 2013, 2017 * * s390-tools is free software; you can redistribute it and/or modify * it under the terms of the MIT license. See LICENSE for details. */ #ifndef UTIL_H #define UTIL_H #include "util_base.h" #include "util_list.h" #include "util_part.h" #endif /* UTIL_H */ s390-tools-2.3.0/include/lib/util_base.h000066400000000000000000000020301323410431100176440ustar00rootroot00000000000000/* * util - Utility function library * * General helper functions * * Copyright IBM Corp. 2013, 2017 * * s390-tools is free software; you can redistribute it and/or modify * it under the terms of the MIT license. See LICENSE for details. */ #ifndef LIB_UTIL_BASE_H #define LIB_UTIL_BASE_H #include #include void util_hexdump(FILE *fh, const char *tag, const void *data, int cnt); void util_hexdump_grp(FILE *fh, const char *tag, const void *data, int group, int cnt, int indent); void util_print_indented(const char *str, int indent); #define UTIL_ARRAY_SIZE(array) (sizeof(array) / sizeof((array)[0])) #define MIN(x, y) \ ({ \ __typeof__(x) _x = (x); \ __typeof__(y) _y = (y); \ \ _x < _y ? _x : _y; \ }) #define MAX(x, y) \ ({ \ __typeof__(x) _x = (x); \ __typeof__(y) _y = (y); \ \ _x > _y ? _x : _y; \ }) static inline void util_ptr_vec_free(void **ptr_vec, int count) { int i; for (i = 0; i < count; i++) free(ptr_vec[i]); free(ptr_vec); } #endif /* LIB_UTIL_BASE_H */ s390-tools-2.3.0/include/lib/util_file.h000066400000000000000000000020751323410431100176620ustar00rootroot00000000000000/** * @defgroup util_file_h util_file: File read/write interface * @{ * @brief Read and write files * * Copyright IBM Corp. 2016, 2017 * * s390-tools is free software; you can redistribute it and/or modify * it under the terms of the MIT license. See LICENSE for details. */ #ifndef LIB_UTIL_FILE_H #define LIB_UITL_FILE_H int util_file_read_line(char *str, size_t size, const char *fmt, ...); int util_file_read_l(long *val, int base, const char *fmt, ...); int util_file_read_ll(long long *val, int base, const char *fmt, ...); int util_file_read_ul(unsigned long *val, int base, const char *fmt, ...); int util_file_read_ull(unsigned long long *val, int base, const char *fmt, ...); int util_file_write_s(const char *str, const char *fmt, ...); int util_file_write_l(long val, int base, const char *fmt, ...); int util_file_write_ll(long long val, int base, const char *fmt, ...); int util_file_write_ul(unsigned long val, int base, const char *fmt, ...); int util_file_write_ull(unsigned long long val, int base, const char *fmt, ...); #endif /** LIB_UTIL_FILE_H @} */ s390-tools-2.3.0/include/lib/util_libc.h000066400000000000000000000072371323410431100176610ustar00rootroot00000000000000/** * @defgroup util_libc_h util_libc: Libc wrapper interface * @{ * @brief Handle standard errors for libc functions * * Copyright IBM Corp. 2016, 2017 * * s390-tools is free software; you can redistribute it and/or modify * it under the terms of the MIT license. See LICENSE for details. */ #ifndef LIB_UTIL_LIBC_H #define LIB_UTIL_LIBC_H #include /** * Allocate memory or panic in case of failure * * @param[in] size Number of bytes to be allocated * * @returns Pointer to memory buffer created with malloc() */ #define util_malloc(size) \ __util_malloc(__func__, __FILE__, __LINE__, size) void *__util_malloc(const char *func, const char *file, int line, size_t size); /** * Allocate zero-initialized memory or panic in case of failure * * @param[in] size Number of bytes to be allocated * * @returns Pointer to memory buffer created with calloc() */ #define util_zalloc(size) \ __util_zalloc(__func__, __FILE__, __LINE__, size) void *__util_zalloc(const char *func, const char *file, int line, size_t size); /** * Re-allocate memory or exit in case of failure * * @param[in] ptr Pointer ot old memory buffer * @param[in] size Number of bytes to be allocated * * @returns Pointer to memory buffer created with realloc() */ #define util_realloc(ptr, size) \ __util_realloc(__func__, __FILE__, __LINE__, ptr, size) void *__util_realloc(const char *func, const char *file, int line, void *ptr, size_t size); /** * Duplicate a string buffer or exit in case of failure * * @param[in] str String to be duplicated * * @returns Pointer to copied string allocated with malloc() */ #define util_strdup(str) \ __util_strdup(__func__, __FILE__, __LINE__, str) void *__util_strdup(const char *func, const char *file, int line, const char *str); /** * Print to allocated string or exit in case of failure * * @param[in,out] strp Pointer for returned string allocated with malloc() * @param[in] fmt Format string for generation of string * @param[in] ap Parameters for format string * * @returns num Number of formatted characters */ #define util_vasprintf(strp, fmt, ap) \ __util_vasprintf(__func__, __FILE__, __LINE__, strp, fmt, ap) #define UTIL_VASPRINTF(strp, fmt, ap) \ do { \ va_start(ap, fmt); \ util_vasprintf(strp, fmt, ap); \ va_end(ap); \ } while (0) int __util_vasprintf(const char *func, const char *file, int line, char **strp, const char *fmt, va_list ap); /** * Print to newly allocated string or exit in case of failure * * @param[in,out] strp Pointer for returned string allocated with malloc() * @param[in] ... Format string and parameters for format string * * @returns num Number of formatted characters */ #define util_asprintf(strp, ...) \ __util_asprintf(__func__, __FILE__, __LINE__, strp, ##__VA_ARGS__) int __util_asprintf(const char *func, const char *file, int line, char **strp, const char *fmt, ...); /** * Print to string buffer or exit in case of failure * * @param[in] str String buffer * @param[in] fmt Format string for generation of string * @param[in] ap Parameters for format string * * @returns num Number of formatted characters */ #define util_vsprintf(str, fmt, ap) \ __util_vsprintf(__func__, __FILE__, __LINE__, str, fmt, ap) #define UTIL_VSPRINTF(str, fmt, ap) \ do { \ va_start(ap, fmt); \ util_vsprintf(str, fmt, ap); \ va_end(ap); \ } while (0) int __util_vsprintf(const char *func, const char *file, int line, char *str, const char *fmt, va_list ap); char *util_strcat_realloc(char *str1, const char *str2); void util_str_toupper(char *str); #endif /** LIB_UTIL_LIBC_H @} */ s390-tools-2.3.0/include/lib/util_list.h000066400000000000000000000042301323410431100177110ustar00rootroot00000000000000/* * util - Utility function library * * Linked lists * * Copyright IBM Corp. 2013, 2017 * * s390-tools is free software; you can redistribute it and/or modify * it under the terms of the MIT license. See LICENSE for details. */ #ifndef LIB_UTIL_LIST_H #define LIB_UTIL_LIST_H #include struct util_list { unsigned long offset; /* Offset of struct util_list_node */ struct util_list_node *start; /* First element */ struct util_list_node *end; /* Last element */ }; struct util_list_node { struct util_list_node *next; struct util_list_node *prev; }; #define util_list_new(type, member) util_list_new_offset(offsetof(type, member)) #define util_list_init(list, type, member) \ util_list_init_offset(list, offsetof(type, member)) void util_list_free(struct util_list *list); struct util_list *util_list_new_offset(unsigned long offset); void util_list_init_offset(struct util_list *list, unsigned long offset); void util_list_add_tail(struct util_list *list, void *entry); void util_list_add_head(struct util_list *list, void *entry); void util_list_add_next(struct util_list *list, void *entry, void *list_entry); void util_list_add_prev(struct util_list *list, void *entry, void *list_entry); void util_list_remove(struct util_list *list, void *entry); void *util_list_next(struct util_list *list, void *entry); void *util_list_prev(struct util_list *list, void *entry); void *util_list_start(struct util_list *list); void *util_list_end(struct util_list *list); int util_list_is_empty(struct util_list *list); unsigned long util_list_len(struct util_list *list); /* * The compare function should return the following: * a < b --> < 0 * a > b --> > 0 * a = b --> = 0 */ typedef int (*util_list_cmp_fn)(void *a, void *b, void *data); void util_list_sort(struct util_list *list, util_list_cmp_fn fn, void *data); #define util_list_iterate(list, i) \ for (i = util_list_start(list); \ i != NULL; \ i = util_list_next(list, i)) \ #define util_list_iterate_safe(list, i, n) \ for (i = util_list_start(list), n = util_list_next(list, i); \ i != NULL; \ i = n, n = util_list_next(list, i)) \ #endif /* LIB_UTIL_LIST_H */ s390-tools-2.3.0/include/lib/util_opt.h000066400000000000000000000037441323410431100175510ustar00rootroot00000000000000/** * @defgroup util_opt_h util_opt: Command line options interface * @{ * @brief Parse the command line options * * Copyright IBM Corp. 2016, 2017 * * s390-tools is free software; you can redistribute it and/or modify * it under the terms of the MIT license. See LICENSE for details. */ #ifndef LIB_UTIL_OPT_H #define LIB_UTIL_OPT_H #include #include /* Flag indicating that an option does not have a short form */ #define UTIL_OPT_FLAG_NOSHORT 1 /* Flag indicating that an option does not have a long form */ #define UTIL_OPT_FLAG_NOLONG 2 /* Flag indicating that this is a section heading */ #define UTIL_OPT_FLAG_SECTION 4 /** * Command line option */ struct util_opt { /** Defined by getopt.h, see "man getopt_long" */ struct option option; /** For options with arguments: Argument name */ char *argument; /** Description displayed for --help */ char *desc; /** Flags for this option */ int flags; /** Command to which this option belongs. NULL means all commands */ char *command; }; /** * Standard option: --help */ #define UTIL_OPT_HELP \ { \ .option = { "help", 0, NULL, 'h' }, \ .desc = "Print this help, then exit", \ } /** * Standard option: --version */ #define UTIL_OPT_VERSION \ { \ .option = { "version", 0, NULL, 'v' }, \ .desc = "Print version information, then exit", \ } /** * End-marker for the option pointer vector */ #define UTIL_OPT_END \ { \ .option = { NULL, 0, NULL, 0 }, \ } /** * Section header */ #define UTIL_OPT_SECTION(title) \ { \ .desc = (title), \ .flags = UTIL_OPT_FLAG_SECTION, \ } /* * Option functions */ void util_opt_init(struct util_opt *opt_vec, const char *opt_prefix); void util_opt_set_command(const char *command); int util_opt_getopt_long(int argc, char *argv[]); void util_opt_print_help(void); void util_opt_print_indented(const char *opt, const char *desc); void util_opt_print_parse_error(char opt, char *argv[]); #endif /** LIB_UTIL_OPT_H @} */ s390-tools-2.3.0/include/lib/util_panic.h000066400000000000000000000024451323410431100200360ustar00rootroot00000000000000/** * @defgroup util_panic_h util_panic: Panic interface * @{ * @brief Collect FFDC data for unexpected errors * * Copyright IBM Corp. 2016, 2017 * * s390-tools is free software; you can redistribute it and/or modify * it under the terms of the MIT license. See LICENSE for details. */ #ifndef LIB_UTIL_PANIC_H #define LIB_UTIL_PANIC_H #include "zt_common.h" /** * Write message, print backtrace and then call the abort() function * * @param[in] ... Format string and parameters describing the panic reason */ #define util_panic(...) \ __util_panic(__func__, __FILE__, __LINE__, ##__VA_ARGS__) void __noreturn __util_panic(const char *func, const char *file, int line, const char *fmt, ...); /** * Ensure that assumption is not true, otherwise panic * * Example: util_assert(ptr == NULL, "The ptr must be NULL, but is %p", ptr) * * @param[in] assumption This assumption has to be true * @param[in] ... Format string and parameters describing the assumption */ #define util_assert(assumption, ...) \ __util_assert(#assumption, __func__, __FILE__, __LINE__, \ assumption, ##__VA_ARGS__) void __util_assert(const char *assertion_string, const char *func, const char *file, int line, int assumption, const char *fmt, ...); #endif /** LIB_UTIL_PANIC_H @} */ s390-tools-2.3.0/include/lib/util_part.h000066400000000000000000000010451323410431100177050ustar00rootroot00000000000000/* * util - Utility function library * * Partition detection functions * * Copyright IBM Corp. 2013, 2017 * * s390-tools is free software; you can redistribute it and/or modify * it under the terms of the MIT license. See LICENSE for details. */ #ifndef LIB_UTIL_PART_H #define LIB_UTIL_PART_H int util_part_search(const char *dev, size_t blk_start, size_t blk_cnt, size_t blk_size, int *ext_part); int util_part_search_fh(int fh, size_t blk_start, size_t blk_cnt, size_t blk_size, int *ext_part); #endif /* LIB_UTIL_PART_H */ s390-tools-2.3.0/include/lib/util_path.h000066400000000000000000000012241323410431100176720ustar00rootroot00000000000000/** * @defgroup util_path_h util_path: Path interface * @{ * @brief Work with paths * * Copyright IBM Corp. 2016, 2017 * * s390-tools is free software; you can redistribute it and/or modify * it under the terms of the MIT license. See LICENSE for details. */ #ifndef LIB_UTIL_PATH_H #define LIB_UTIL_PATH_H #include char *util_path_sysfs(const char *fmt, ...); bool util_path_is_readable(const char *fmt, ...); bool util_path_is_writable(const char *fmt, ...); bool util_path_is_dir(const char *fmt, ...); bool util_path_is_reg_file(const char *fmt, ...); bool util_path_exists(const char *fmt, ...); #endif /** LIB_UTIL_PATH_H @} */ s390-tools-2.3.0/include/lib/util_prg.h000066400000000000000000000025761323410431100175410ustar00rootroot00000000000000/** * @defgroup util_prg_h util_prg: Program interface * @{ * @brief Print standard program messages * * Copyright IBM Corp. 2016, 2017 * * s390-tools is free software; you can redistribute it and/or modify * it under the terms of the MIT license. See LICENSE for details. */ #ifndef LIB_UTIL_PRG_H #define LIB_UTIL_PRG_H #include #include #include #include #include struct util_prg_copyright { /** Name of the copyright owner, e.g. IBM */ const char *owner; /** Year of first publishing */ int pub_first; /** Year of last major changes */ int pub_last; }; /** * @brief Coypright end marker */ #define UTIL_PRG_COPYRIGHT_END {NULL, 0, 0} /** * Program description */ struct util_prg { /** Description for help */ const char *desc; /** Command arguments in front of other options */ const char *command_args; /** Positional arguments */ const char *args; /** Copyright list */ struct util_prg_copyright copyright_vec[]; }; void util_prg_init(const struct util_prg *prg); void util_prg_set_command(const char *command); void util_prg_print_parse_error(void); void util_prg_print_required_arg(const char *option); void util_prg_print_invalid_option(const char *option); void util_prg_print_arg_error(const char *arg_name); void util_prg_print_version(void); void util_prg_print_help(void); #endif /** LIB_UTIL_PRG_H @} */ s390-tools-2.3.0/include/lib/util_proc.h000066400000000000000000000022431323410431100177030ustar00rootroot00000000000000/* * Scanner for the /proc files * * Copyright IBM Corp. 2001, 2017 * * s390-tools is free software; you can redistribute it and/or modify * it under the terms of the MIT license. See LICENSE for details. * */ #ifndef LIB_UTIL_PROC_H #define LIB_UTIL_PROC_H #include #include struct util_proc_part_entry { dev_t device; size_t blockcount; char *name; }; struct util_proc_dev_entry { int blockdev; dev_t device; char *name; }; /** * Container for the fields of the output of /proc/mounts (man fstab) */ struct util_proc_mnt_entry { char *spec; char *file; char *vfstype; char *mntOpts; char *dump; char *passno; }; 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); int util_proc_dev_get_entry(dev_t dev, int blockdev, struct util_proc_dev_entry *entry); void util_proc_dev_free_entry(struct util_proc_dev_entry *entry); int util_proc_mnt_get_entry(const char *file_name, const char *spec, struct util_proc_mnt_entry *entry); void util_proc_mnt_free_entry(struct util_proc_mnt_entry *entry); #endif /* LIB_UTIL_PROC_H */ s390-tools-2.3.0/include/lib/util_rec.h000066400000000000000000000037141323410431100175150ustar00rootroot00000000000000/** * @defgroup util_rec_h util_rec: Record interface * @{ * @brief Print records in different output formats * * Copyright IBM Corp. 2016, 2017 * * s390-tools is free software; you can redistribute it and/or modify * it under the terms of the MIT license. See LICENSE for details. */ #ifndef LIB_UTIL_REC_H #define LIB_UTIL_REC_H #include "lib/util_list.h" #define PAGE_SIZE 4096 /** * Opaque handle for a record * * The util_rec structure describes: * * - A set of fields * - An output format with the required formatting attributes (e.g. character * for header separator) */ struct util_rec; /** * Opaque handle for a record field (used for util_rec_iterate) * * The util_rec_fld structure describes: * * - Field value * - Field key name * - A set of attributes (e.g. width, alignment etc.) */ struct util_rec_fld; /** * Alignment in util_rec tables */ enum util_rec_align { /** Align field left */ UTIL_REC_ALIGN_LEFT, /** Align field right */ UTIL_REC_ALIGN_RIGHT, }; struct util_list *__util_rec_get_list(struct util_rec *rec); const char *util_rec_fld_get_key(struct util_rec_fld *fld); #define util_rec_iterate(rec, fld) \ util_list_iterate(__util_rec_get_list(rec), fld) struct util_rec *util_rec_new_wide(const char *hdr_sep); struct util_rec *util_rec_new_csv(const char *col_sep); struct util_rec *util_rec_new_long(const char *hdr_sep, const char *col_sep, const char *key, int key_size, int val_size); void util_rec_free(struct util_rec *rec); void util_rec_def(struct util_rec *rec, const char *key, enum util_rec_align align, int width, const char *hdr); void util_rec_set(struct util_rec *rec, const char *key, const char *fmt, ...); void util_rec_set_argz(struct util_rec *rec, const char *key, const char *argz, size_t len); const char *util_rec_get(struct util_rec *rec, const char *key); void util_rec_print_hdr(struct util_rec *rec); void util_rec_print(struct util_rec *rec); #endif /** LIB_UTIL_REC_H @} */ s390-tools-2.3.0/include/lib/util_scandir.h000066400000000000000000000013231323410431100203610ustar00rootroot00000000000000/** * @defgroup util_scandir_h util_scandir: Scandir interface * @{ * @brief Scan a directory for matching entries * * Copyright IBM Corp. 2016, 2017 * * s390-tools is free software; you can redistribute it and/or modify * it under the terms of the MIT license. See LICENSE for details. */ #ifndef LIB_UTIL_SCANDIR_H #define LIB_UTIL_SCANDIR_H #include int util_scandir_hexsort(const struct dirent **de1, const struct dirent **de2); int util_scandir(struct dirent ***namelist, int compar_fn(const struct dirent **, const struct dirent **), const char *path, const char *pattern, ...); void util_scandir_free(struct dirent **de_vec, int count); #endif /** LIB_UTIL_SCANDIR_H @} */ s390-tools-2.3.0/include/lib/vmdump.h000066400000000000000000000006131323410431100172120ustar00rootroot00000000000000/* * vmdump - VMDUMP conversion library * * Copyright IBM Corp. 2016, 2017 * * s390-tools is free software; you can redistribute it and/or modify * it under the terms of the MIT license. See LICENSE for details. */ #ifndef LIB_VMDUMP_H #define LIB_VMDUMP_H int vmdump_convert(const char* inputFileName, const char* outputFileName, const char* progName); #endif /* LIB_VMDUMP_H */ s390-tools-2.3.0/include/lib/vtoc.h000066400000000000000000000353711323410431100166660ustar00rootroot00000000000000/* * Copyright IBM Corp. 2002, 2017 * * s390-tools is free software; you can redistribute it and/or modify * it under the terms of the MIT license. See LICENSE for details. */ #ifndef LIB_VTOC_H #define LIB_VTOC_H #include #include #include #include #include #include #include #include #include #include #include "lib/dasd_base.h" #define LINE_LENGTH 80 #define VTOC_START_CC 0x0 #define VTOC_START_HH 0x1 #define FIRST_USABLE_CYL 1 #define FIRST_USABLE_TRK 2 #define DASD_3380_TYPE 13184 #define DASD_3390_TYPE 13200 #define DASD_9345_TYPE 37701 #define DASD_3380_VALUE 0xbb60 #define DASD_3390_VALUE 0xe5a2 #define DASD_9345_VALUE 0xbc98 #define VOLSER_LENGTH 6 #define BIG_DISK_SIZE 0x10000 #define LV_COMPAT_CYL 0xFFFE #define VTOC_ERROR "VTOC error:" typedef struct ttr { u_int16_t tt; u_int8_t r; } __attribute__ ((packed)) ttr_t; typedef struct cchhb { u_int16_t cc; u_int16_t hh; u_int8_t b; } __attribute__ ((packed)) cchhb_t; typedef struct cchh { u_int16_t cc; u_int16_t hh; } __attribute__ ((packed)) cchh_t; typedef struct labeldate { u_int8_t year; u_int16_t day; } __attribute__ ((packed)) labeldate_t; /* * The following structure is a merger of the cdl and ldl volume label. * On an ldl disk there is no key information, so when reading an * ldl label from disk, the data should be copied at the address of vollbl. * On the other side, the field ldl_version is reserved in a cdl record * and the field formatted_cyl exists only for ldl labels. So when * reading a cdl label from disk, the formatted_cyl field will contain * arbitrary data. * This layout may be a bit awkward, but the advantage of having the * same label type for both disk layout types is bigger than the effort * for taking a bit of extra care at the fringes. */ typedef struct volume_label { char volkey[4]; /* volume key = volume label */ char vollbl[4]; /* volume label */ char volid[6]; /* volume identifier */ u_int8_t security; /* security byte */ cchhb_t vtoc; /* VTOC address */ char res1[5]; /* reserved */ char cisize[4]; /* CI-size for FBA,... */ /* ...blanks for CKD */ char blkperci[4]; /* no of blocks per CI (FBA), blanks for CKD */ char labperci[4]; /* no of labels per CI (FBA), blanks for CKD */ char res2[4]; /* reserved */ char lvtoc[14]; /* owner code for LVTOC */ char res3[28]; /* reserved */ char ldl_version; /* version number, valid for ldl format */ unsigned long long formatted_blocks; /* valid when ldl_version >= f2 */ } __attribute__ ((packed)) volume_label_t; typedef struct extent { u_int8_t typeind; /* extent type indicator */ u_int8_t seqno; /* extent sequence number */ cchh_t llimit; /* starting point of this extent */ cchh_t ulimit; /* ending point of this extent */ } __attribute__ ((packed)) extent_t; typedef struct dev_const { u_int16_t DS4DSCYL; /* number of logical cyls */ u_int16_t DS4DSTRK; /* number of tracks in a logical cylinder */ u_int16_t DS4DEVTK; /* device track length */ u_int8_t DS4DEVI; /* non-last keyed record overhead */ u_int8_t DS4DEVL; /* last keyed record overhead */ u_int8_t DS4DEVK; /* non-keyed record overhead differential */ u_int8_t DS4DEVFG; /* flag byte */ u_int16_t DS4DEVTL; /* device tolerance */ u_int8_t DS4DEVDT; /* number of DSCB's per track */ u_int8_t DS4DEVDB; /* number of directory blocks per track */ } __attribute__ ((packed)) dev_const_t; /* * format 1 and format 8 label have the same layout so we use the following * structure for both. */ typedef struct format1_label { char DS1DSNAM[44]; /* data set name */ u_int8_t DS1FMTID; /* format identifier */ unsigned char DS1DSSN[6];/* data set serial number */ u_int16_t DS1VOLSQ; /* volume sequence number */ labeldate_t DS1CREDT; /* creation date: ydd */ labeldate_t DS1EXPDT; /* expiration date */ u_int8_t DS1NOEPV; /* number of extents on volume */ u_int8_t DS1NOBDB; /* no. of bytes used in last direction blk */ u_int8_t DS1FLAG1; /* flag 1 */ unsigned char DS1SYSCD[13]; /* system code */ labeldate_t DS1REFD; /* date last referenced */ u_int8_t DS1SMSFG; /* system managed storage indicators */ u_int8_t DS1SCXTF; /* sec. space extension flag byte */ u_int16_t DS1SCXTV; /* secondary space extension value */ u_int8_t DS1DSRG1; /* data set organisation byte 1 */ u_int8_t DS1DSRG2; /* data set organisation byte 2 */ u_int8_t DS1RECFM; /* record format */ u_int8_t DS1OPTCD; /* option code */ u_int16_t DS1BLKL; /* block length */ u_int16_t DS1LRECL; /* record length */ u_int8_t DS1KEYL; /* key length */ u_int16_t DS1RKP; /* relative key position */ u_int8_t DS1DSIND; /* data set indicators */ u_int8_t DS1SCAL1; /* secondary allocation flag byte */ char DS1SCAL3[3]; /* secondary allocation quantity */ ttr_t DS1LSTAR; /* last used track and block on track */ u_int16_t DS1TRBAL; /* space remaining on last used track */ u_int16_t res1; /* reserved */ extent_t DS1EXT1; /* first extent description */ extent_t DS1EXT2; /* second extent description */ extent_t DS1EXT3; /* third extent description */ cchhb_t DS1PTRDS; /* possible pointer to f2 or f3 DSCB */ } __attribute__ ((packed)) format1_label_t; typedef struct format3_label { char DS3KEYID[4]; /* key identifier */ extent_t DS3EXTNT[4]; /* first 4 extent descriptions */ u_int8_t DS3FMTID; /* format identifier */ extent_t DS3ADEXT[9]; /* last 9 extent description */ cchhb_t DS3PTRDS; /* pointer to next format3 DSCB */ } __attribute__ ((packed)) format3_label_t; typedef struct format4_label { char DS4KEYCD[44]; /* key code for VTOC labels: 44 times 0x04 */ u_int8_t DS4IDFMT; /* format identifier */ cchhb_t DS4HPCHR; /* highest address of a format 1 DSCB */ u_int16_t DS4DSREC; /* number of available DSCB's */ cchh_t DS4HCCHH; /* CCHH of next available alternate track */ u_int16_t DS4NOATK; /* number of remaining alternate tracks */ u_int8_t DS4VTOCI; /* VTOC indicators */ u_int8_t DS4NOEXT; /* number of extents in VTOC */ u_int8_t DS4SMSFG; /* system managed storage indicators */ u_int8_t DS4DEVAC; /* number of alternate cylinders. Subtract from first two bytes of DS4DEVSZ to get number of usable cylinders. can be zero. valid only if DS4DEVAV on. */ dev_const_t DS4DEVCT; /* device constants */ char DS4AMTIM[8]; /* VSAM time stamp */ char DS4AMCAT[3]; /* VSAM catalog indicator */ char DS4R2TIM[8]; /* VSAM volume/catalog match time stamp */ char res1[5]; /* reserved */ char DS4F6PTR[5]; /* pointer to first format 6 DSCB */ extent_t DS4VTOCE; /* VTOC extent description */ char res2[10]; /* reserved */ u_int8_t DS4EFLVL; /* extended free-space management level */ cchhb_t DS4EFPTR; /* pointer to extended free-space info */ char res3; /* reserved */ u_int32_t DS4DCYL; /* number of logical cyls */ char res4[2]; /* reserved */ u_int8_t DS4DEVF2; /* device flags */ char res5; /* reserved */ } __attribute__ ((packed)) format4_label_t; typedef struct ds5ext { u_int16_t t; /* RTA of the first track of free extent */ u_int16_t fc; /* number of whole cylinders in free ext. */ u_int8_t ft; /* number of remaining free tracks */ } __attribute__ ((packed)) ds5ext_t; typedef struct format5_label { char DS5KEYID[4]; /* key identifier */ ds5ext_t DS5AVEXT; /* first available (free-space) extent. */ ds5ext_t DS5EXTAV[7]; /* seven available extents */ u_int8_t DS5FMTID; /* format identifier */ ds5ext_t DS5MAVET[18]; /* eighteen available extents */ cchhb_t DS5PTRDS; /* pointer to next format5 DSCB */ } __attribute__ ((packed)) format5_label_t; typedef struct ds7ext { u_int32_t a; /* starting RTA value */ u_int32_t b; /* ending RTA value + 1 */ } __attribute__ ((packed)) ds7ext_t; typedef struct format7_label { char DS7KEYID[4]; /* key identifier */ ds7ext_t DS7EXTNT[5]; /* space for 5 extent descriptions */ u_int8_t DS7FMTID; /* format identifier */ ds7ext_t DS7ADEXT[11]; /* space for 11 extent descriptions */ char res1[2]; /* reserved */ cchhb_t DS7PTRDS; /* pointer to next FMT7 DSCB */ } __attribute__ ((packed)) format7_label_t; typedef struct format9_label { u_int8_t DS9KEYID; /* key code for format 9 labels (0x09) */ u_int8_t DS9SUBTY; /* subtype (0x01) */ u_int8_t DS9NUMF9; /* number of F9 datasets */ u_int8_t res1[41]; /* reserved */ u_int8_t DS9FMTID; /* format identifier */ u_int8_t res2[90]; /* reserved */ cchhb_t DS9PTRDS; /* pointer to next DSCB */ } __attribute__ ((packed)) format9_label_t; char * vtoc_ebcdic_enc (char *source, char *target, int l); char * vtoc_ebcdic_dec (char *source, char *target, int l); void vtoc_set_extent ( extent_t * ext, u_int8_t typeind, u_int8_t seqno, cchh_t * lower, cchh_t * upper); void vtoc_set_cchh ( cchh_t * addr, u_int32_t cc, u_int16_t hh); u_int32_t vtoc_get_cyl_from_cchh(cchh_t *addr); u_int16_t vtoc_get_head_from_cchh(cchh_t *addr); void vtoc_set_cchhb ( cchhb_t * addr, u_int32_t cc, u_int16_t hh, u_int8_t b); u_int32_t vtoc_get_cyl_from_cchhb(cchhb_t *addr); u_int16_t vtoc_get_head_from_cchhb(cchhb_t *addr); u_int64_t cchhb2blk(cchhb_t *p, struct hd_geometry *geo); u_int64_t cchh2blk (cchh_t *p, struct hd_geometry *geo); u_int32_t cchh2trk (cchh_t *p, struct hd_geometry *geo); void vtoc_set_date ( labeldate_t * d, u_int8_t year, u_int16_t day); void vtoc_volume_label_init ( volume_label_t *vlabel); int vtoc_read_volume_label ( char * device, unsigned long vlabel_start, volume_label_t * vlabel); int vtoc_write_volume_label ( char *device, unsigned long vlabel_start, volume_label_t *vlabel); void vtoc_volume_label_set_volser ( volume_label_t *vlabel, char *volser); char *vtoc_volume_label_get_volser ( volume_label_t *vlabel, char *volser); void vtoc_volume_label_set_key ( volume_label_t *vlabel, char *key); void vtoc_volume_label_set_label ( volume_label_t *vlabel, char *lbl); char *vtoc_volume_label_get_label ( volume_label_t *vlabel, char *lbl); void vtoc_read_label ( char *device, unsigned long position, format1_label_t *f1, format4_label_t *f4, format5_label_t *f5, format7_label_t *f7); void vtoc_write_label ( char *device, unsigned long position, format1_label_t *f1, format4_label_t *f4, format5_label_t *f5, format7_label_t *f7, format9_label_t *f9); void vtoc_init_format1_label ( unsigned int blksize, extent_t *part_extent, format1_label_t *f1); void vtoc_init_format4_label ( format4_label_t *f4lbl, unsigned int compat_cylinders, unsigned int real_cylinders, unsigned int tracks, unsigned int blocks, unsigned int blksize, u_int16_t dev_type); void vtoc_update_format4_label ( format4_label_t *f4, cchhb_t *highest_f1, u_int16_t unused_update); void vtoc_init_format5_label ( format5_label_t *f5); void vtoc_update_format5_label_add ( format5_label_t *f5, int verbose, int trk, u_int16_t a, u_int16_t b, u_int8_t c); void vtoc_update_format5_label_del ( format5_label_t *f5, int verbose, int trk, u_int16_t a, u_int16_t b, u_int8_t c); void vtoc_init_format7_label ( format7_label_t *f7); void vtoc_update_format7_label_add ( format7_label_t *f7, int verbose, u_int32_t a, u_int32_t b); void vtoc_update_format7_label_del ( format7_label_t *f7, int verbose, u_int32_t a, u_int32_t b); void vtoc_init_format8_label ( unsigned int blksize, extent_t *part_extent, format1_label_t *f1); void vtoc_update_format8_label ( cchhb_t *associated_f9, format1_label_t *f8); void vtoc_init_format9_label ( format9_label_t *f9); void vtoc_set_freespace( format4_label_t *f4, format5_label_t *f5, format7_label_t *f7, char ch, int verbose, u_int32_t start, u_int32_t stop, u_int32_t cyl, u_int32_t trk); #endif /* LIB_VTOC_H */ s390-tools-2.3.0/include/lib/zt_common.h000066400000000000000000000022101323410431100177020ustar00rootroot00000000000000/* * s390-tools/include/zt_common.h * common s390-tools definitions. * * Copyright IBM Corp. 2004, 2017 * * s390-tools is free software; you can redistribute it and/or modify * it under the terms of the MIT license. See LICENSE for details. * */ #ifndef LIB_ZT_COMMON_H #define LIB_ZT_COMMON_H #define STRINGIFY_1(x) #x #define STRINGIFY(x) STRINGIFY_1(x) #ifdef UNUSED #elif defined(__GNUC__) # define UNUSED(x) UNUSED_ ## x __attribute__((unused)) #else # define UNUSED(x) x #endif #define RELEASE_STRING STRINGIFY (S390_TOOLS_RELEASE) #define TOOLS_LIBDIR STRINGIFY (S390_TOOLS_LIBDIR) #define TOOLS_SYSCONFDIR STRINGIFY (S390_TOOLS_SYSCONFDIR) #define TOOLS_BINDIR STRINGIFY (S390_TOOLS_BINDIR) #define __noreturn __attribute__((noreturn)) #define __packed __attribute__((packed)) #define __aligned(x) __attribute__((aligned(x))) #define __may_alias __attribute__((may_alias)) typedef unsigned long long u64; typedef signed long long s64; typedef unsigned int u32; typedef signed int s32; typedef unsigned short int u16; typedef signed short int s16; typedef unsigned char u8; typedef signed char s8; #endif /* LIB_ZT_COMMON_H */ s390-tools-2.3.0/ip_watcher/000077500000000000000000000000001323410431100154655ustar00rootroot00000000000000s390-tools-2.3.0/ip_watcher/Makefile000066400000000000000000000011131323410431100171210ustar00rootroot00000000000000include ../common.mak all: xcec-bridge xcec-bridge: xcec-bridge.o clean: rm -f *.o core xcec-bridge install: ip_watcher.pl xcec-bridge start_hsnc.sh $(SED) -e 's/%S390_TOOLS_VERSION%/$(S390_TOOLS_RELEASE)/' \ < start_hsnc.sh >$(DESTDIR)$(USRSBINDIR)/start_hsnc.sh; \ chown $(OWNER).$(GROUP) $(DESTDIR)$(USRSBINDIR)/start_hsnc.sh; \ chmod 755 $(DESTDIR)$(USRSBINDIR)/start_hsnc.sh; \ $(INSTALL) -g $(GROUP) -o $(OWNER) -m 755 ip_watcher.pl \ $(DESTDIR)$(USRSBINDIR) $(INSTALL) -g $(GROUP) -o $(OWNER) -m 755 xcec-bridge \ $(DESTDIR)$(USRSBINDIR) .PHONY: all install clean s390-tools-2.3.0/ip_watcher/ip_watcher.pl000077500000000000000000000236771323410431100201710ustar00rootroot00000000000000#!/usr/bin/perl -w # # ip_watcher.pl - HiperSockets Network Concentrator # # looks for addresses in the HiperSockets and sets them as Proxy ARP on the # OSAs. Also adds routing entries towards the HiperSockets interfaces for # all IP addresses in it # $OPERATING_MODE="routing_only"; # ip_watcher just takes care of adapting the routing entries. ipv4 # forwarding needs to be switched on, if desired mrouted or some multicast # routing daemon should run. ip_watcher also sets the proxy arp entries # of all hsi addresses on the osa device. # # $OPERATING_MODE="full_bridging"; # this is like routing_only mode, plus xcec-bridge will bridge all # kinds of traffic (uni-, multi-, broadcast) between the interfaces, # so the stack will not do forwarding. # if interfaces come and go, xcec-bridge will be sent a SIGUSR1. # # $OPERATING_MODE="mc_bridging"; # this is a mixture of the above -- ipv4 forwarding of unicast packets # is done by the kernel, multi- and broadcast traffic is bridged by # xcec-bridge. # # $OPERATING_MODE="bc_bridging"; # this is another mixture of the above -- ipv4 forwarding of unicast # packets is done by the kernel, multicast is handled by mrouted or some # multicast router, and broadcast traffic is bridged by xcec-bridge. # # Copyright 2017 IBM Corp. # # s390-tools is free software; you can redistribute it and/or modify # it under the terms of the MIT license. See LICENSE for details. # $OPERATING_MODE="mc_bridging"; $XCEC_BRIDGE="xcec-bridge"; $XCEC_BRIDGE_FULL_PARAM="also_unicast"; $XCEC_BRIDGE_MC_PARAM=""; $XCEC_BRIDGE_BC_PARAM="only_broadcast"; $KILLALL="killall"; $SIGNAL="-USR1"; $MASK_PARAM="netmask"; $DEV_PARAM="dev"; $QETHARP="qetharp -c -q"; $ROUTE_ADD_CMD='route add -net '; $ROUTE_DEL_CMD='route del -net '; $PA_ADD_CMD='qethconf parp add -x'; $PA_DEL_CMD='qethconf parp del -x'; # $PA_ADD_CMD='echo add_rxip4'; # $PA_DEL_CMD='echo del_rxip4'; $CHECK_ONLY="no"; $nextarg=0; if ($#ARGV>=$nextarg) { if ($ARGV[$nextarg] eq "--check") { $CHECK_ONLY="yes"; $nextarg++; } } # if there is a parameter to ip_watcher.pl, the parameter will be the # Proxy ARP interface (i.e. the outgoing OSA interface). In this case, # xcec-bridge will not be started, so that only unicast is forwarded. # eth0 is default OSA interface if ($#ARGV>=$nextarg) { $PA_INTERFACE=$ARGV[$nextarg]; $START_XCEC_BRIDGE="no"; } else { $PA_INTERFACE=""; $START_XCEC_BRIDGE="yes"; } $SLEEP_TIME=2; #$TIME_LIMIT=4; #@time_array=(time,time-1,time-2); sub print_list($@) { my($h)=shift; my(@a)=@_; my($i); foreach $i (@a) { print "DEBUG ". $h .": ". $i ."\n"; } } # get outgoing OSA interface (connecting the CECs) sub get_proxy_arp_interface { my($devnos); my($chpid); my($if_name); my($type); my($port); my($chksum); my($prio); my($rtr); my($rest); if (opendir(SYSQETH, "/sys/devices/qeth")) { @ALLDEV = grep { /^.+\..+\..+$/ } readdir SYSQETH; closedir SYSQETH; foreach $DEV (@ALLDEV) { open(IFNAME, "0) { # sleep($sleep_time); # } sleep($SLEEP_TIME); } # creates a 0x01020304 out of a 1.2.3.4 sub convert_ip_string_to_number($) { my($ip_str)=shift; my(@ip); my($ip_oct1); my($ip_oct2); my($ip_oct3); my($ip_oct4); @ip=split(/\./,$ip_str); # check for parsing error if ($#ip<3) { return 0; } ($ip_oct1,$ip_oct2,$ip_oct3,$ip_oct4)=@ip; if ( ($ip_oct1<0) || ($ip_oct1>255) || ($ip_oct2<0) || ($ip_oct2>255) || ($ip_oct3<0) || ($ip_oct3>255) || ($ip_oct4<0) || ($ip_oct4>255) ) { return 0; } return ($ip_oct1<<24)+($ip_oct2<<16)+($ip_oct3<<8)+($ip_oct4); } # returns sorted list of ips (in integer format like __u32) of the interface sub get_ips_on_interface($) { my($interface)=shift; my($cmdline)="$QETHARP $interface |"; my(@ip_list)=(); my($OUTPUT); my($ip); unless (open(OUTPUT,$cmdline)) { print STDERR "can't open $cmdline"; return @ip_list; } while () { chop; $ip=convert_ip_string_to_number($_); if ($ip>0) { push(@ip_list,$ip); } } close(OUTPUT) || print STDERR "can't close $cmdline"; return sort @ip_list; } # creates a 1.2.3.4 out of a 0x1020304 sub convert_string_to_ip($) { my($ip)=shift; my($ip_oct1); my($ip_oct2); my($ip_oct3); my($ip_oct4); $ip_oct4=$ip&0xff; $ip>>=8; $ip_oct3=$ip&0xff; $ip>>=8; $ip_oct2=$ip&0xff; $ip>>=8; $ip_oct1=$ip&0xff; return "$ip_oct1.$ip_oct2.$ip_oct3.$ip_oct4"; } sub __min($$) { my($a)=shift; my($b)=shift; if ($a<$b) { return $a; } else { return $b; } } # will create an array of routes in string format sub get_routes_of_ip_list(@) { my(@ip_list)=@_; my(@route_list)=(); my($ip); my($ips_left); my($ips_to_combine); my($ip_shifted); my($ips_found); my($end); my($order); my($mask); my($ip_str); my($mask_str); my($ips_fetched); while ($#ip_list>=0) { # ips_left is the number of ips left in the list $ips_left=$#ip_list; $ip=shift(@ip_list); $ips_to_combine=1; $ip_shifted=$ip; while ($ip_shifted%2==0) { $ips_to_combine<<=1; $ip_shifted>>=1; # 0 should never be in the list, anyway... if (!$ip_shifted) { last; } } # ips_to_combine is a power of 2 and contains the max number # of entries that could compressed into one route due to its # alignment $end=__min($ips_to_combine-1,$ips_left); $order=1; $ips_found=1; while ($ips_found<=$end) { # ips_found-1, as we have shifted the first ip # already if ($ip_list[$ips_found-1]!=$ip+$ips_found) { last; } $ips_found++; if ($ips_found==2*$order) { $order<<=1; } } # ips_found is now the number of subsequent ips that we can # subsum (one of which is shifted already) $mask=(-$order)&0xffffffff; $ips_fetched=1; while ($ips_fetched<$order) { $ips_fetched++; shift(@ip_list); } $mask_str=convert_string_to_ip($mask); $ip_str=convert_string_to_ip($ip); unshift(@route_list,"$ip_str $MASK_PARAM $mask_str"); } return @route_list; } # will create an array of rxips in string format sub get_pas_of_ip_list(@) { my(@ip_list)=@_; my(@pa_list)=(); foreach $ip (@ip_list) { unshift(@pa_list,"" . sprintf("%08x",$ip)); } return @pa_list; } sub is_in_list($@) { my($item)=shift; my(@list)=@_; my($i); foreach $i (@list) { if ($i eq $item) { return 1; } } return 0; } sub exec_for_diff(@) { my($cmd)=shift; my($new_list,$old_list)=@_; foreach $line (@$new_list) { unless (is_in_list($line,@$old_list)) { system($cmd . $line . "> /dev/null 2>&1"); } } } sub wait_for_changes() { # blocking ioctl to be informed on SETIP/DELIPs (once it's implemented in # hardware) or sleep for X timeunits } sub main() { my(@routes)=(); my(@pas)=(); my(@new_routes); my(@new_pas); my(@interface_list)=(); my(@old_if_list); my($interface); my(@ip_list); my($route); my(@tmp_routes); get_proxy_arp_interface(); if ($CHECK_ONLY eq "yes") { exit 0; } if ($START_XCEC_BRIDGE eq "yes") { if ($OPERATING_MODE eq "full_bridging") { system("$XCEC_BRIDGE $XCEC_BRIDGE_FULL_PARAM &")==0 || die "can't fork $XCEC_BRIDGE: $?"; } if ($OPERATING_MODE eq "mc_bridging") { system("$XCEC_BRIDGE $XCEC_BRIDGE_MC_PARAM &")==0 || die "can't fork $XCEC_BRIDGE: $?"; } if ($OPERATING_MODE eq "bc_bridging") { system("$XCEC_BRIDGE $XCEC_BRIDGE_BC_PARAM &")==0 || die "can't fork $XCEC_BRIDGE: $?"; } } for (;;) { if ( ($OPERATING_MODE eq "mc_bridging") || ($OPERATING_MODE eq "bc_bridging") || ($OPERATING_MODE eq "full_bridging") ) { @old_if_list=@interface_list; } @interface_list=update_interface_list(); if ( ($OPERATING_MODE eq "mc_bridging") || ($OPERATING_MODE eq "bc_bridging") || ($OPERATING_MODE eq "full_bridging") ) { if ( join(':',@old_if_list) ne join(':',@interface_list) ) { if ($START_XCEC_BRIDGE eq "yes") { system("$KILLALL $SIGNAL $XCEC_BRIDGE")==0 || print STDERR "can't send signal " . "to $XCEC_BRIDGE to update " . "interfaces.\n"; } } } @new_routes=(); @new_pas=(); foreach $interface (@interface_list) { @ip_list=get_ips_on_interface($interface); @tmp_routes=get_routes_of_ip_list(@ip_list); foreach $route (@tmp_routes) { unshift(@new_routes, "$route $DEV_PARAM $interface"); } @tmp_pas=get_pas_of_ip_list(@ip_list); foreach $pa (@tmp_pas) { unshift(@new_pas, "$pa $PA_INTERFACE"); } } exec_for_diff($ROUTE_ADD_CMD,\@new_routes,\@routes); exec_for_diff($ROUTE_DEL_CMD,\@routes,\@new_routes); @routes=@new_routes; exec_for_diff($PA_ADD_CMD,\@new_pas,\@pas); exec_for_diff($PA_DEL_CMD,\@pas,\@new_pas); @pas=@new_pas; wait_for_changes(); limit_frequency(); } } main(); s390-tools-2.3.0/ip_watcher/start_hsnc.sh000066400000000000000000000055711323410431100202010ustar00rootroot00000000000000#!/bin/bash # # start_hsnc.sh - HiperSockets Network Concentrator # # Wrapper start script for ip_watcher.pl, also cleanup, when ip_watcher.pl # gets killed. # # Copyright IBM Corp. 2003, 2017 # # s390-tools is free software; you can redistribute it and/or modify # it under the terms of the MIT license. See LICENSE for details. # # # functions # function __usage { echo "" echo "For more information about HiperSocket Network Concentrator" echo "please refer to the 'Device Drivers, Features, and Commands'" echo "manual." exit 0 } function PrintVersion { echo "$script_name version %S390_TOOLS_VERSION%" echo "Copyright IBM Corp. 2003, 2017" } # # main # script_name="HiperSocket Network Concentrator" # name of this script # # what is the kernel version we are on ? # kernel_version=`uname -r` xcec_bridge="yes" if [ "${kernel_version:0:1}" \> 2 ]; then kernel_version="ok" else if [ "${kernel_version:4:2}" \< 26 ]; then xcec_bridge="no" fi if [ "${kernel_version:2:1}" \> 4 ]; then kernel_version="ok" else echo kernel version too old for this hsnc version. exit 1 fi fi # # parse options (currently none avail) # case "$1" in -v | --version ) PrintVersion exit 0 ;; -h | --help ) __usage ;; esac if [ X${1}X != XX ] && [ $kernel_version = "ok" ] ; then if ! ls /sys/class/net | grep "^$1$" > /dev/null; then echo interface $1 does not exist. exit 1 fi else if [ $xcec_bridge = "no" ] ; then echo kernel version too old for this hsnc version. exit 1 fi fi ip_watcher.pl $* echo ip_watcher.pl was terminated, cleaning up. if [ X${1}X == XX ] ; then echo killing xcec-bridge killall xcec-bridge fi echo removing all parp entries from mc interfaces if [ X${1}X == XX ] ; then for DEV in $(ls /sys/devices/qeth/ | egrep '^.+\..+\..+') do if_name=`cat /sys/devices/qeth/$DEV/if_name | sed 's/$/\$/'` rtr=`cat /sys/devices/qeth/$DEV/route4 2> /dev/null | egrep 'multicast'` if [ -n "$rtr" ] ; then echo $if_name >> /tmp/ip_watcher.cleanup1 fi done else echo ${1}$ > /tmp/ip_watcher.cleanup1 fi qethconf rxip list | sed 's/add/del/' | egrep -f /tmp/ip_watcher.cleanup1 > /tmp/ip_watcher.cleanup2 while read line; do qethconf $line > /dev/null 2>&1 done < /tmp/ip_watcher.cleanup2 rm /tmp/ip_watcher.cleanup1 rm /tmp/ip_watcher.cleanup2 echo removing all routes from connector interfaces for DEV in $(ls /sys/devices/qeth/ | egrep '^.+\..+\..+') do if_name=`cat /sys/devices/qeth/$DEV/if_name | sed 's/$/\$/'` rtr=`cat /sys/devices/qeth/$DEV/route4 2> /dev/null | egrep 'connector'` if [ -n "$rtr" ] ; then echo $if_name >> /tmp/ip_watcher.cleanup1 fi done route -n | egrep -f /tmp/ip_watcher.cleanup1 > /tmp/ip_watcher.cleanup2 while read line; do route del -net `echo $line | awk '{print $1 " netmask " $3 " dev " $8}'` done < /tmp/ip_watcher.cleanup2 rm /tmp/ip_watcher.cleanup1 rm /tmp/ip_watcher.cleanup2 s390-tools-2.3.0/ip_watcher/xcec-bridge.c000066400000000000000000000327461323410431100200210ustar00rootroot00000000000000/* * xcec-bridge - HiperSockets Network Concentrator * * Parameters: * also_unicast - uni-, multi-, broadcast is bridged * - multi-, broadcast is bridged * only broadcast - only broadcast is bridged * * Copyright IBM Corp. 2003, 2017 * * 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "lib/zt_common.h" /* a signal causes the interfaces to be re-checked */ #define LOGGING_FACILITY LOG_LOCAL0 #define UPDATE_SIGNAL SIGUSR1 #define DEV_NAME_LEN IFNAMSIZ #define BUFFER_LEN 65536 int so_sndbuf=(8*1024*1024); int do_unicast_bridging=0; int do_multicast_bridging=1; int do_broadcast_bridging=0; struct int_sock { #define I_S_FEATURE_PASSTHROUGH 0x01 int features; int i_fd; int o_fd; char dev_name[DEV_NAME_LEN]; int mtu_warning; struct int_sock *next; }; fd_set work_fd_set; /* used in and changed by select */ struct set { fd_set fds; int highest_fd; struct int_sock *i_s_list; }; struct set select_set; volatile int update_interface_trigger=0; int open_incoming_socket(char *dev_name) { int fd,retval; struct sockaddr_ll sock_addr; struct ifreq if_req; struct packet_mreq mc_req; /* we want to receive everything. we filter out stuff that we * don't forward by ourselves */ fd=socket(PF_PACKET,SOCK_RAW,htons(ETH_P_ALL)); if (fd==-1) { syslog(LOG_ERR,"can't open raw packet socket, " \ "interface %s will not be used: %s", dev_name,strerror(errno)); return -1; } strncpy(if_req.ifr_name,dev_name,DEV_NAME_LEN); retval=ioctl(fd,SIOCGIFINDEX,&if_req); if (retval==-1) { syslog(LOG_ERR,"can't ioctl on raw packet socket, " \ "interface %s will not be used: %s", dev_name,strerror(errno)); close(fd); return -1; } sock_addr.sll_protocol=htons(ETH_P_ALL); sock_addr.sll_ifindex=if_req.ifr_ifindex; sock_addr.sll_family=AF_PACKET; retval=bind(fd,(struct sockaddr *)&sock_addr, sizeof(struct sockaddr_ll)); if (retval==-1) { syslog(LOG_ERR,"can't bind packet raw packet socket to " \ "interface %s -- it will not be used: %s", dev_name,strerror(errno)); close(fd); return -1; } mc_req.mr_ifindex=if_req.ifr_ifindex; mc_req.mr_type=PACKET_MR_ALLMULTI; mc_req.mr_alen=0; retval=setsockopt(fd,SOL_SOCKET,PACKET_ADD_MEMBERSHIP, &mc_req,sizeof(struct packet_mreq)); if (retval==-1) { syslog(LOG_ERR,"can't set socket options to join all " \ "multicast groups -- multicast may not be " \ "forwarded from %s: %s",dev_name,strerror(errno)); } return fd; } int open_outgoing_socket(char *dev_name) { int fd,retval; int val; fd=socket(PF_INET,SOCK_RAW,IPPROTO_RAW); if (fd==-1) { syslog(LOG_ERR,"can't open raw inet socket, " \ "interface %s will not be used: %s", dev_name,strerror(errno)); return -1; } /* IP_HDRINCL should be set by the stack already, we'll set * it nevertheless */ val=1; retval=setsockopt(fd,SOL_IP,IP_HDRINCL,&val,sizeof(int)); if (retval==-1) { syslog(LOG_ERR,"can't set IP_HDRINCL on raw inet socket, " \ "interface %s will not be used: %s", dev_name,strerror(errno)); close(fd); return -1; } /* we bind the socket to the device */ retval=setsockopt(fd,SOL_SOCKET,SO_BINDTODEVICE, dev_name,strlen(dev_name)+1); if (retval==-1) { syslog(LOG_ERR,"can't bind raw inet socket to device, " \ "interface %s will not be used: %s", dev_name,strerror(errno)); close(fd); return -1; } /* get max socket buffer */ retval=setsockopt(fd,SOL_SOCKET,SO_SNDBUF,&so_sndbuf,sizeof(int)); if (retval==-1) { syslog(LOG_ERR,"can't set socket buffer size, " \ "interface %s will not be used: %s", dev_name,strerror(errno)); close(fd); return -1; } /* and enable broadcast on the socket */ val=1; retval=setsockopt(fd,SOL_SOCKET,SO_BROADCAST,&val,sizeof(int)); if (retval==-1) { syslog(LOG_ERR,"can't enable broadcast on raw inet socket, " \ "interface %s will not be used: %s", dev_name,strerror(errno)); close(fd); return -1; } return fd; } int interface_in_list(struct int_sock *item,struct int_sock *list) { for (;list;list=list->next) { if (!strncmp(item->dev_name,list->dev_name,DEV_NAME_LEN)) { return 1; } } return 0; } int read_sys(struct int_sock **nlist) { DIR *qdir; FILE *qfile; struct dirent *qde; char fname[256]; char *tmp; char if_name[256]; char rtr[256]; struct int_sock *is = NULL; int i; qdir = opendir("/sys/devices/qeth"); if (!qdir) { syslog(LOG_ERR,"failed to open directory /sys/devices/qeth"); return errno; } while ((qde = readdir(qdir))) { if ((qde->d_type == DT_DIR) && (qde->d_name[0] != '.')) { strcpy(fname, "/sys/devices/qeth/"); strcat(fname, qde->d_name); strcat(fname, "/if_name"); qfile = fopen(fname, "r"); if (!qfile) { continue; } tmp = if_name; while ((i = fgetc(qfile)) != EOF) { if ((char)i == '\n') { *tmp = 0; break; } else { *tmp = (char)i; } tmp++; } *tmp = 0; fclose(qfile); strcpy(fname, "/sys/devices/qeth/"); strcat(fname, qde->d_name); strcat(fname, "/route4"); qfile = fopen(fname, "r"); if (!qfile) { continue; } tmp = rtr; while ((i = fgetc(qfile)) != EOF) { if ((char)i == '\n') { *tmp = 0; break; } else { *tmp = (char)i; } tmp++; } *tmp = 0; fclose(qfile); if (!strstr(rtr, "multicast") && !strstr(rtr, "connector")) continue; is = malloc(sizeof(struct int_sock)); if (!is) { syslog(LOG_ERR,"no memory while reading from "\ "/sys/devices/qeth some interface"\ "might not be used"); continue; } /* as soon as we have one interface echoeing back * broadcasts to us, we don't bridge broadcast * traffic */ if (!(strstr(rtr, "connector+") || strstr(rtr, "multicast router+"))) do_broadcast_bridging=0; is->mtu_warning=0; strncpy(is->dev_name, if_name, DEV_NAME_LEN); if (!strncmp(if_name,"hsi",3)) { is->features=I_S_FEATURE_PASSTHROUGH; } is->next = *nlist; *nlist = is; } } closedir(qdir); return 0; } void update_interfaces() { struct int_sock *new_list=NULL; struct int_sock *i=NULL,*j,*prev; struct int_sock *new_int=NULL; int i_fd,o_fd; /* if all interfaces are '+'-interfaces, we bridge broadcast */ do_broadcast_bridging=1; update_interface_trigger=0; syslog(LOG_DEBUG,"rechecking interfaces"); if (read_sys(&new_list)) return; for (i=select_set.i_s_list;i;i=i->next) { if (!interface_in_list(i,new_list)) { /* remove interface i */ j=select_set.i_s_list; prev=NULL; while (j) { if (!strncmp(j->dev_name,i->dev_name, DEV_NAME_LEN)) { if (!prev) { select_set.i_s_list=j->next; } else { prev->next=j->next; } prev=j; j=j->next; free(j); } else { j=j->next; } } /* and close the socket */ close(i->i_fd); close(i->o_fd); syslog(LOG_INFO,"removed interface %s",i->dev_name); } } for (i=new_list;i;i=i->next) { if (!interface_in_list(i,select_set.i_s_list)) { /* add interface i */ new_int=malloc(sizeof(struct int_sock)); if (!new_int) { syslog(LOG_ERR,"can't add interface %s -- " \ "no memory",i->dev_name); continue; } i_fd=open_incoming_socket(i->dev_name); if (i_fd==-1) { free(new_int); continue; } o_fd=open_outgoing_socket(i->dev_name); if (o_fd==-1) { close(i_fd); free(new_int); continue; } strncpy(new_int->dev_name,i->dev_name,DEV_NAME_LEN); new_int->i_fd=i_fd; new_int->o_fd=o_fd; new_int->features=i->features; new_int->next=select_set.i_s_list; select_set.i_s_list=new_int; syslog(LOG_INFO,"added interface %s",i->dev_name); } } /* kill temporary new_list */ while (new_list) { i=new_list->next; free(new_list); new_list=i; } /* prepare the fd_set for select */ FD_ZERO(&select_set.fds); for (i=select_set.i_s_list;i;i=i->next) { FD_SET(i->i_fd,&select_set.fds); select_set.highest_fd=(i->i_fd>select_set.highest_fd)? i->i_fd:select_set.highest_fd; } } void process_packet(struct int_sock *i_s) { int retval; char buffer[BUFFER_LEN]; int buffer_len; struct int_sock *i_s_item; struct sockaddr_ll s_ll; struct sockaddr_in s_in; socklen_t sll_len; sll_len=(socklen_t)sizeof(struct sockaddr_ll); buffer_len=recvfrom(i_s->i_fd,buffer,BUFFER_LEN,0, (struct sockaddr *)&s_ll,&sll_len); if (buffer_len==-1) { syslog(LOG_WARNING,"recvfrom failed on %s: %s\n", i_s->dev_name,strerror(errno)); return; } /* nothing read */ if (buffer_len==0) return; /* no packets that came from our own stack... that could lead to * traffic loops */ if (s_ll.sll_pkttype==PACKET_OUTGOING) return; /* only do unicast bridging when required */ if ( (s_ll.sll_pkttype==PACKET_HOST) && (!do_unicast_bridging) ) return; /* only do multicast bridging when required */ if ( (s_ll.sll_pkttype==PACKET_MULTICAST) && (!do_multicast_bridging) ) return; /* broadcast is critical, see comment above */ if (!do_broadcast_bridging) { if (s_ll.sll_pkttype==PACKET_BROADCAST) return; } /* only do v4 at this time */ if (s_ll.sll_protocol!=ETH_P_IP) return; /* forward buffer to each interface ... */ for (i_s_item=select_set.i_s_list;i_s_item;i_s_item=i_s_item->next) { /* ... but i_s */ if (i_s_item==i_s) continue; s_ll.sll_ifindex=0; s_in.sin_family=AF_INET; s_in.sin_port=0; if (s_ll.sll_pkttype==PACKET_BROADCAST) { s_in.sin_addr.s_addr=INADDR_BROADCAST; } else { memcpy(&s_in.sin_addr, &buffer[16 + ETH_HLEN], 4); } retval=sendto(i_s_item->o_fd, buffer + ETH_HLEN, buffer_len - ETH_HLEN, 0, (struct sockaddr *)&s_in, sizeof(struct sockaddr_in)); if (retval==-1) { if ( (errno==EMSGSIZE) && (!i_s_item->mtu_warning) ) { syslog(LOG_WARNING,"MTU of %s too small " \ "to forward packet with size of %i" \ " -- won't show warning again.", i_s_item->dev_name,buffer_len); i_s_item->mtu_warning=1; } else { syslog(LOG_WARNING,"sendto failed on %s: " \ "%s\n",i_s_item->dev_name, strerror(errno)); } } else if (retval != (buffer_len - ETH_HLEN)) { syslog(LOG_WARNING,"sendto sent only %i instead " \ "of %i bytes on %s\n", retval,buffer_len,i_s->dev_name); } } } void action_handler(int UNUSED(s)) { update_interface_trigger=1; syslog(LOG_DEBUG,"signal caught"); /* select will return, interfaces will be re-checked */ } int main(int argc,char *argv[]) { struct int_sock *i_s; int retval,r; struct sigaction s_a; if ( (argc>1) && (!strncmp(argv[1],"also_unicast",12)) ) { do_unicast_bridging=1; } else if ( (argc>1) && (!strncmp(argv[1],"only_broadcast",14)) ) { do_multicast_bridging=0; } openlog("xcec-bridge",LOG_NDELAY,LOGGING_FACILITY); FD_ZERO(&select_set.fds); select_set.i_s_list=NULL; select_set.highest_fd=0; s_a.sa_handler=action_handler; if (sigemptyset(&s_a.sa_mask)) { syslog(LOG_ERR,"problem in sigemptyset: %s -- exiting", strerror(errno)); return 1; } s_a.sa_flags=0; retval=sigaction(UPDATE_SIGNAL,&s_a,NULL); if (sigemptyset(&s_a.sa_mask)) { syslog(LOG_ERR,"problem in sigemptyset: %s -- exiting", strerror(errno)); return 1; } if (sigaddset(&s_a.sa_mask,UPDATE_SIGNAL)) { syslog(LOG_ERR,"problem in sigaddset: %s -- exiting %s", argv[0],strerror(errno)); return 1; } r=sigprocmask(SIG_BLOCK,&s_a.sa_mask,NULL); if (r) { syslog(LOG_ERR,"sigprocmask: %s",strerror(errno)); } syslog(LOG_INFO,"*** started ***"); update_interfaces(); while (1) { r=sigprocmask(SIG_UNBLOCK,&s_a.sa_mask,NULL); if (r) { /* while blocked: */ update_interfaces(); syslog(LOG_INFO,"sigprocmask (unblock): %s", strerror(errno)); /* try until sigprocmask is not interrupted by a * signal */ while (sigprocmask(SIG_UNBLOCK,&s_a.sa_mask,NULL)) ; } memcpy(&work_fd_set,&select_set.fds,sizeof(fd_set)); retval=select(select_set.highest_fd+1,&work_fd_set, NULL,NULL,NULL); r=sigprocmask(SIG_BLOCK,&s_a.sa_mask,NULL); if (r) { syslog(LOG_INFO,"sigprocmask (block): %s", strerror(errno)); /* try until sigprocmask is not interrupted by a * signal */ while (sigprocmask(SIG_BLOCK,&s_a.sa_mask,NULL)) ; /* when blocked: */ update_interfaces(); } /* a signal came in after we unblocked * or before we blocked? we may process one packet before * the list gets updated, but we do this check here never- * theless in order to not catch a signal during some * system call in update_interfaces */ if (update_interface_trigger) { update_interfaces(); } if (retval==-1) { if (errno==EINTR) { update_interfaces(); } else if (errno) { syslog(LOG_WARNING,"select returned with %s", strerror(errno)); } continue; /* fds are undefined -> no packets came in at this time */ } /* check all fds regardless of retval */ for (i_s=select_set.i_s_list;i_s;i_s=i_s->next) { if (FD_ISSET(i_s->i_fd,&work_fd_set)) { process_packet(i_s); } } } /* cleanup... no. */ } s390-tools-2.3.0/ipl_tools/000077500000000000000000000000001323410431100153445ustar00rootroot00000000000000s390-tools-2.3.0/ipl_tools/Makefile000066400000000000000000000017611323410431100170110ustar00rootroot00000000000000include ../common.mak all: chreipl lsreipl chshut lsshut objects = main.o ccw.o fcp.o system.o shutdown.o \ cmd_lsshut.o cmd_chshut.o cmd_lsreipl.o cmd_chreipl.o proc.o chreipl: $(objects) $(LINK) $(ALL_LDFLAGS) $^ $(LOADLIBES) $(LDLIBS) -o $@ lsreipl: ln -sf chreipl lsreipl chshut: ln -sf chreipl chshut lsshut: ln -sf chreipl lsshut clean: rm -f *.o lsreipl chreipl chshut lsshut install: all $(INSTALL) -g $(GROUP) -o $(OWNER) -m 755 chreipl \ $(DESTDIR)$(USRSBINDIR) ln -f -s chreipl $(DESTDIR)$(USRSBINDIR)/lsreipl ln -f -s chreipl $(DESTDIR)$(USRSBINDIR)/chshut ln -f -s chreipl $(DESTDIR)$(USRSBINDIR)/lsshut $(INSTALL) -g $(GROUP) -o $(OWNER) -m 644 man/chreipl.8 \ $(DESTDIR)$(MANDIR)/man8 $(INSTALL) -g $(GROUP) -o $(OWNER) -m 644 man/lsreipl.8 \ $(DESTDIR)$(MANDIR)/man8 $(INSTALL) -g $(GROUP) -o $(OWNER) -m 644 man/lsshut.8 \ $(DESTDIR)$(MANDIR)/man8 $(INSTALL) -g $(GROUP) -o $(OWNER) -m 644 man/chshut.8 \ $(DESTDIR)$(MANDIR)/man8 .PHONY: all install clean s390-tools-2.3.0/ipl_tools/ccw.c000066400000000000000000000053021323410431100162640ustar00rootroot00000000000000/* * ipl_tools - Linux for System z reipl and shutdown tools * * CCW device functions * * Copyright IBM Corp. 2008, 2017 * * 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 #include #include #include #include #include "ipl_tools.h" /* * Check if the specified device number is a valid device number * which can be found in the /sys/bus/ccw/drivers/dasd-eckd/ * structure. * * This does not work when booting from tape. */ int ccw_is_device(const char *busid) { char path[PATH_MAX]; 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; } /* * Return CCW Bus ID (old sysfs) */ 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) { char path[PATH_MAX], buf[4096]; memset(buf, 0, sizeof(buf)); snprintf(path, sizeof(path), "/sys/block/%s/device", device); if (realpath(path, buf) == NULL) return -1; /* * 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(buf, "/sys/devices/css0/%*[0-9a-f.]/%[0-9a-f.]", busid) != 1) return -1; return 0; } /* * 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); } s390-tools-2.3.0/ipl_tools/cmd_chreipl.c000066400000000000000000000433321323410431100177660ustar00rootroot00000000000000/* * ipl_tools - Linux for System z reipl and shutdown tools * * Command: chreipl * * Copyright IBM Corp. 2008, 2017 * * 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 #include #include "lib/zt_common.h" #include "ipl_tools.h" #include "proc.h" #define BOOTPARMS_NSS_MAX 56 #define BOOTPARMS_CCW_MAX 64 #define BOOTPARMS_FCP_MAX 3452 enum target_type { TT_CCW, TT_FCP, TT_NSS, TT_NODE, }; enum reipl_type { REIPL_FCP, REIPL_CCW, REIPL_NSS }; static const char *const usage_chreipl = "Usage: %s [TARGET] [ARGS] [OPTIONS]\n" "\n" " chreipl [ccw] [-d] [OPTIONS]\n" " chreipl [fcp] [-d] [-w] [-l] [OPTIONS]\n" " chreipl [node] [OPTIONS]\n" " chreipl nss [-n] [OPTIONS]\n" " chreipl [-h] [-v]\n" "\n" "The following re-IPL targets are supported:\n" " ccw IPL from CCW device\n" " fcp IPL from FCP device\n" " nss IPL from NSS\n" " node IPL from device specified by device node or directory\n" "\n" "General options:\n" " -f, --force Allow targets that cannot be verified by the system\n" " -p, --bootparms Boot parameter specification\n" " -h, --help Print this help, then exit\n" " -v, --version Print version information, then exit\n" "\n" "Options for ccw target:\n" " -d, --device Device number of the CCW IPL device\n" " -L, --loadparm Loadparm specification\n" "\n" "Options for fcp target:\n" " -d, --device Device number of the adapter of the FCP IPL device\n" " -l --lun Logical unit number of the FCP IPL device\n" " -w --wwpn World Wide Port Name of the FCP IPL device\n" " -b, --bootprog Bootprog specification\n" " -L, --loadparm Loadparm specification\n" "\n" "Options for nss target:\n" " -n, --name Identifier of the NSS\n" "\n" "Options for node target:\n" " Depending on underlying target type (ccw or fcp)\n"; static struct locals { char loadparm[9]; /* Entry in the boot menu */ int loadparm_set; char bootprog[11]; /* bootprog number (32 bit)*/ int bootprog_set; char wwpn[20]; /* 18 character +0x" */ int wwpn_set; char lun[20]; /* 18 character +0x" */ int lun_set; char busid[10]; /* Bus ID e.g. 0.0.4711 */ int busid_set; char dev[15]; /* Device (e.g. dasda) */ int dev_set; char nss_name[9]; /* NSS name */ int nss_name_set; char bootparms[4096]; int bootparms_set; int force_set; enum target_type target_type; /* CCW, FCP, NSS or NODE */ int target_type_set; int target_type_auto_mode; enum reipl_type reipl_type; /* CCW, FCP, NSS */ } l; static void __noreturn print_usage_chreipl_exit(void) { printf(usage_chreipl, g.prog_name); exit(0); } static int busid_strtok(char *str, unsigned long max_len, unsigned long *val) { char *token, *end; token = strtok(str, "."); if (!token) return -1; if (strlen(token) > max_len) return -1; *val = strtoul(token, &end, 16); if (*end) return -1; return 0; } static int mk_busid(char busid_out[9], const char *busid_in) { unsigned long devno, cssid = 0, ssid = 0; char busid_tmp[10]; if (strlen(busid_in) > 9) return -1; strcpy(busid_tmp, busid_in); if (strstr(busid_in, ".")) { /* Check xx.x.xxxx full bus-ID format */ if (busid_strtok(busid_tmp, 2, &cssid)) return -1; if (busid_strtok(NULL, 1, &ssid)) return -1; if (busid_strtok(NULL, 4, &devno)) return -1; /* Ensure that there are no more additional fields */ if (strtok(NULL, ".")) return -1; } else { /* Check xxxx short bus-ID format */ if (busid_strtok(busid_tmp, 4, &devno)) return -1; } sprintf(busid_out, "%lx.%lx.%04lx", cssid, ssid, devno); return 0; } static void set_device(const char *busid) { if (mk_busid(l.busid, busid)) ERR_EXIT("Invalid device number \"%s\" specified", busid); l.busid_set = 1; } static void set_nss_name(const char *nss_name) { if (strlen(nss_name) > 8) ERR_EXIT("NSS name \"%s\" exceeds maximum of 8 characters", nss_name); strcpy(l.nss_name, nss_name); l.nss_name_set = 1; } static void set_loadparm(const char *loadparm) { if (strlen(loadparm) > 8) ERR_EXIT("Loadparm \"%s\" exceeds 8 characters", loadparm); strcpy(l.loadparm, loadparm); if (strcmp(l.loadparm, " ") == 0) l.loadparm[0] = '\0'; l.loadparm_set = 1; } static void set_bootprog(const char *bootprog) { long long bootprog_int; char *endptr; bootprog_int = strtoll(bootprog, &endptr, 10); if (*endptr) ERR_EXIT("Bootprog \"%s\" is not a decimal number", bootprog); if (bootprog_int > UINT_MAX) ERR_EXIT("Invalid bootprog specified"); strncpy(l.bootprog, bootprog, sizeof(l.bootprog)); l.bootprog_set = 1; } static void set_bootparms(const char *bootparms) { unsigned int i; for (i = 0; i < strlen(bootparms); i++) { if (isascii(bootparms[i])) continue; ERR_EXIT("Non ASCII characters found in boot parameters"); } if (strlen(bootparms) + 1 > sizeof(l.bootparms)) ERR_EXIT("Boot parameter line is too long"); strcpy(l.bootparms, bootparms); l.bootparms_set = 1; } static void set_lun(const char *lun) { unsigned long long lun_tmp; char *endptr; lun_tmp = strtoull(lun, &endptr, 16); if (*endptr) ERR_EXIT("LUN \"%s\" is not a hexadecimal number", lun); snprintf(l.lun, sizeof(l.lun), "0x%016llx", lun_tmp); l.lun_set = 1; } static void set_wwpn(const char *wwpn) { unsigned long long wwpn_tmp; char *endptr; wwpn_tmp = strtoull(wwpn, &endptr, 16); if (*endptr) ERR_EXIT("WWPN \"%s\" is not a hexadecimal number", wwpn); snprintf(l.wwpn, sizeof(l.wwpn), "0x%016llx", wwpn_tmp); l.wwpn_set = 1; } static void parse_fcp_args(char *nargv[], int nargc) { /* * we might be called like this: * chreipl fcp 4711 0x12345... 0x12345... */ if (l.busid_set || l.wwpn_set || l.lun_set) ERR_EXIT("Use either options or positional parameters"); if (nargc > 3) ERR_EXIT("Too many arguments specified for \"fcp\" re-IPL " "type"); else if (nargc != 3) ERR_EXIT("The \"fcp\" re-IPL type requires device, WWPN, " "and LUN"); set_device(nargv[0]); set_wwpn(nargv[1]); set_lun(nargv[2]); } static void parse_ccw_args(char *nargv[], int nargc) { /* * we might be called like this: * chreipl ccw 4711 */ if (l.busid_set) ERR_EXIT("Use either options or positional parameters"); if (nargc == 0) ERR_EXIT("The \"ccw\" re-IPL type requires device"); else if (nargc > 1) ERR_EXIT("Too many arguments specified for \"ccw\" re-IPL " "type"); set_device(nargv[0]); } static void parse_nss_args(char *nargv[], int nargc) { /* * we might be called like this: * chreipl nss lnxnss */ if (l.nss_name_set) ERR_EXIT("Use either options or positional parameters"); if (nargc == 0) ERR_EXIT("A NSS name must be specified"); if (nargc > 1) ERR_EXIT("Too many arguments specified for \"nss\" re-IPL " "type"); set_nss_name(nargv[0]); } static void dev_from_part(char *dev_name) { int i; for (i = strlen(dev_name) - 1; isdigit(dev_name[i]); i--) dev_name[i] = 0; } static int set_reipl_type(const char *dev_name) { if (strncmp(dev_name, "dasd", strlen("dasd")) == 0 || strncmp(dev_name, "vd", strlen("vd")) == 0) l.reipl_type = REIPL_CCW; else if (strncmp(dev_name, "sd", strlen("sd")) == 0) l.reipl_type = REIPL_FCP; else return -1; strncpy(l.dev, dev_name, sizeof(l.dev)); dev_from_part(l.dev); l.dev_set = 1; return 0; } static int get_chreipl_helper_cmd(dev_t dev, char cmd[PATH_MAX]) { char chreipl_helper[PATH_MAX]; struct proc_dev_entry pde; if (proc_dev_get_entry(dev, 1, &pde) != 0) return -1; snprintf(chreipl_helper, PATH_MAX, "%s/%s.%s", TOOLS_LIBDIR, "chreipl_helper", pde.name); if (access(chreipl_helper, X_OK) != 0) { proc_dev_free_entry(&pde); return -1; } sprintf(cmd, "%s %d:%d", chreipl_helper, major(dev), minor(dev)); proc_dev_free_entry(&pde); return 0; } /* * Use chreipl_helper (E.g. for device mapper devices) */ static int set_reipl_type_helper(int maj, int min) { char helper_cmd[PATH_MAX], buf[4096]; struct proc_part_entry ppe; int rc = -1; dev_t dev; FILE *fh; if (get_chreipl_helper_cmd(makedev(maj, min), helper_cmd) != 0) return -1; fh = popen(helper_cmd, "r"); if (fh == NULL) ERR_EXIT_ERRNO("Could not start chreipl_helper"); if (fread(buf, 1, sizeof(buf), fh) == 0) ERR_EXIT_ERRNO("Could not read from chreipl_helper"); if (sscanf(buf, "%d:%d", &maj, &min) != 2) goto fail_pclose; dev = makedev(maj, min); if (proc_part_get_entry(dev, &ppe) != 0) goto fail_pclose; if (set_reipl_type(ppe.name)) goto fail_part_free; rc = 0; fail_part_free: proc_part_free_entry(&ppe); fail_pclose: pclose(fh); return rc; } static void get_dev_by_path(const char *path, dev_t *dev) { struct stat sb; if (stat(path, &sb) != 0) ERR_EXIT_ERRNO("Could not access device node \"%s\"", path); if (S_ISDIR(sb.st_mode)) *dev = sb.st_dev; else if (S_ISBLK(sb.st_mode)) *dev = sb.st_rdev; else ERR_EXIT("Only block device nodes or directories are valid for" " \"node\" target"); } static void parse_node_args(char *nargv[], int nargc) { struct proc_part_entry ppe; char *path = nargv[0]; dev_t dev; if (nargc == 0) ERR_EXIT("No device node specified"); if (l.busid_set || l.wwpn_set || l.lun_set) ERR_EXIT("Do not use device, WWPN, or LUN for \"node\" target"); get_dev_by_path(path, &dev); if (proc_part_get_entry(dev, &ppe) != 0) 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) goto out; ERR_EXIT("Unsupported device node \"%s\" specified", path); out: proc_part_free_entry(&ppe); } static void parse_pos_args(char *nargv[], int nargc) { switch (l.target_type) { case TT_FCP: parse_fcp_args(nargv, nargc); break; case TT_CCW: parse_ccw_args(nargv, nargc); break; case TT_NSS: parse_nss_args(nargv, nargc); break; case TT_NODE: parse_node_args(nargv, nargc); break; } } static void check_fcp_opts(void) { if (l.nss_name_set) ERR_EXIT("Invalid option for \"fcp\" target specified"); if (!(l.busid_set && l.wwpn_set && l.lun_set)) ERR_EXIT("The \"fcp\" target requires device, WWPN, " "and LUN"); } static void check_ccw_opts(void) { if (l.bootprog_set || l.lun_set || l.wwpn_set || l.nss_name_set) ERR_EXIT("Invalid option for \"ccw\" target specified"); if (!l.busid_set) ERR_EXIT("The \"ccw\" target requires device"); } static void check_nss_opts(void) { if (l.bootprog_set || l.loadparm_set || l.busid_set || l.wwpn_set || l.lun_set) ERR_EXIT("Invalid option for \"nss\" target specified"); if (!l.nss_name_set) ERR_EXIT("The \"nss\" target requires NSS name"); } static void set_target_type(enum target_type tt, int mode_auto) { l.target_type = tt; l.target_type_set = 1; l.target_type_auto_mode = mode_auto; } static void set_target_type_auto(const char *arg) { char busid[10]; if (access(arg, R_OK) == 0) { set_target_type(TT_NODE, 1); return; } if (mk_busid(busid, arg) == 0) { if (ccw_is_device(busid)) set_target_type(TT_CCW, 1); else if (fcp_is_device(busid)) set_target_type(TT_FCP, 1); } } static void parse_chreipl_options(int argc, char *argv[]) { int opt, idx; const struct option long_opts[] = { { "help", no_argument, NULL, 'h'}, { "bootprog", required_argument, NULL, 'b' }, { "device", required_argument, NULL, 'd' }, { "lun", required_argument, NULL, 'l' }, { "wwpn", required_argument, NULL, 'w' }, { "loadparm", required_argument, NULL, 'L' }, { "name", required_argument, NULL, 'n' }, { "bootparms", required_argument, NULL, 'p' }, { "force", no_argument, NULL, 'f' }, { "version", no_argument, NULL, 'v' }, { NULL, 0, NULL, 0 } }; static const char optstr[] = "hcd:vw:l:fL:b:n:p:"; /* dont run without any argument */ if (argc == 1) print_usage_chreipl_exit(); if (strcmp(argv[1], "fcp") == 0) set_target_type(TT_FCP, 0); else if (strcmp(argv[1], "ccw") == 0) set_target_type(TT_CCW, 0); else if (strcmp(argv[1], "nss") == 0) set_target_type(TT_NSS, 0); else if (strcmp(argv[1], "node") == 0) set_target_type(TT_NODE, 0); else set_target_type_auto(argv[1]); while ((opt = getopt_long(argc, argv, optstr, long_opts, &idx)) != -1) { switch (opt) { case 'h': print_usage_chreipl_exit(); case 'd': set_device(optarg); break; case 'l': set_lun(optarg); break; case 'w': set_wwpn(optarg); break; case 'L': set_loadparm(optarg); break; case 'b': set_bootprog(optarg); break; case 'n': set_nss_name(optarg); break; case 'p': set_bootparms(optarg); break; case 'f': l.force_set = 1; break; case 'v': print_version_exit(); default: print_help_hint_exit(); } } if (!is_root()) ERR_EXIT("You must be root to perform this operation"); if (!l.target_type_set) ERR_EXIT("No valid target specified"); /* * optind is a index which points to the first unrecognized * command line argument. In case of no auto action we have to * skip the action argument. */ if (!l.target_type_auto_mode) optind += 1; if (argc - optind > 0) parse_pos_args(&argv[optind], argc - optind); } static void verify_write_access(const char *path, const char *attr) { char fpath[PATH_MAX]; snprintf(fpath, sizeof(fpath), "/sys/firmware/%s", path); if (access(fpath, W_OK) != 0) ERR_EXIT("System does not allow to set %s", attr); } /* * Check if device is on the cio_ignore blacklist * * IMPLEMENTATION: * * "cio_ignore --is-ignored " returns 0 if the device is ignored, * 1 for internal errrors, 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(). * * If no shell is available or the "cio_ignore" tool is not available * we get system() rc != 0 and WEXITSTATUS() = 127. */ static int is_ignored(const char *busid) { const char *fmt = "cio_ignore --is-ignored %s > /dev/null 2>&1"; char cmd[256]; int rc; snprintf(cmd, sizeof(cmd), fmt, busid); rc = system(cmd); if ((rc != -1) && (WEXITSTATUS(rc) == 0)) return 1; return 0; } static void chreipl_ccw(void) { check_ccw_opts(); if (!ccw_is_device(l.busid) && !l.force_set) { if (is_ignored(l.busid)) ERR_EXIT("Device is on cio_ignore list, try \"cio_ignore -r %s\"?", l.busid); ERR_EXIT("Could not find DASD CCW device \"%s\"", l.busid); } /* * On old systems that use CCW reipl loadparm cannot be set */ if (l.loadparm_set) verify_write_access("reipl/ccw/loadparm", "loadparm"); if (l.bootparms_set) { verify_write_access("reipl/ccw/parm", "boot parameters"); if (strlen(l.bootparms) > BOOTPARMS_CCW_MAX) ERR_EXIT("Maximum boot parameter length exceeded " "(%zu/%u)", strlen(l.bootparms), BOOTPARMS_CCW_MAX); } if (access("/sys/firmware/reipl/ccw/parm", W_OK) == 0) write_str(l.bootparms, "reipl/ccw/parm"); if (access("/sys/firmware/reipl/ccw/loadparm", W_OK) == 0) write_str(l.loadparm, "reipl/ccw/loadparm"); write_str(l.busid, "reipl/ccw/device"); write_str("ccw", "reipl/reipl_type"); print_ccw(0); } static void chreipl_fcp(void) { check_fcp_opts(); if (!fcp_is_device(l.busid) && !l.force_set) { if (is_ignored(l.busid)) ERR_EXIT("Device is on cio_ignore list, try \"cio_ignore -r %s\"?", l.busid); ERR_EXIT("Could not find FCP device \"%s\"", l.busid); } verify_write_access("reipl/fcp/device", "\"fcp\" re-IPL target"); /* * On old systems the FCP reipl loadparm cannot be set */ if (l.loadparm_set) verify_write_access("reipl/fcp/loadparm", "loadparm"); if (l.bootparms_set) { verify_write_access("reipl/fcp/scp_data", "boot parameters"); if (strlen(l.bootparms) > BOOTPARMS_FCP_MAX) ERR_EXIT("Maximum boot parameter length exceeded " "(%zu/%u)", strlen(l.bootparms), BOOTPARMS_FCP_MAX); } if (access("/sys/firmware/reipl/fcp/scp_data", W_OK) == 0) write_str(l.bootparms, "reipl/fcp/scp_data"); if (access("/sys/firmware/reipl/fcp/loadparm", W_OK) == 0) write_str(l.loadparm, "reipl/fcp/loadparm"); write_str(l.busid, "reipl/fcp/device"); write_str(l.wwpn, "reipl/fcp/wwpn"); write_str(l.lun, "reipl/fcp/lun"); write_str("fcp", "reipl/reipl_type"); /* * set the boot record logical block address. Master boot * record. It is always 0 for Linux */ write_str("0", "reipl/fcp/br_lba"); if (!l.bootprog_set) sprintf(l.bootprog, "0"); write_str(l.bootprog, "reipl/fcp/bootprog"); print_fcp(0, 0); } static void chreipl_nss(void) { check_nss_opts(); verify_write_access("reipl/nss/name", "\"nss\" re-IPL target"); if (l.bootparms_set) { verify_write_access("reipl/nss/parm", "boot parameters"); if (strlen(l.bootparms) > BOOTPARMS_NSS_MAX) ERR_EXIT("Maximum boot parameter length exceeded " "(%zu/%u)", strlen(l.bootparms), BOOTPARMS_NSS_MAX); } write_str(l.nss_name, "reipl/nss/name"); if (access("/sys/firmware/reipl/nss/parm", W_OK) == 0) write_str(l.bootparms, "reipl/nss/parm"); write_str("nss", "reipl/reipl_type"); print_nss(0); } static void chreipl_node(void) { char path[PATH_MAX]; if (!l.dev_set) ERR_EXIT("No device node specified"); snprintf(path, sizeof(path), "/sys/block/%s/device", l.dev); if (chdir(path) != 0) ERR_EXIT("Could not find device \"%s\"", l.dev); switch (l.reipl_type) { case REIPL_CCW: ccw_busid_get(l.dev, l.busid); l.busid_set = 1; chreipl_ccw(); break; case REIPL_FCP: fcp_wwpn_get(l.dev, l.wwpn); l.wwpn_set = 1; fcp_lun_get(l.dev, l.lun); l.lun_set = 1; fcp_busid_get(l.dev, l.busid); l.busid_set = 1; chreipl_fcp(); break; default: ERR_EXIT("Internal error: chreipl_node"); } } void cmd_chreipl(int argc, char *argv[]) { parse_chreipl_options(argc, argv); switch (l.target_type) { case TT_CCW: chreipl_ccw(); break; case TT_FCP: chreipl_fcp(); break; case TT_NSS: chreipl_nss(); break; case TT_NODE: chreipl_node(); break; } } s390-tools-2.3.0/ipl_tools/cmd_chshut.c000066400000000000000000000103241323410431100176310ustar00rootroot00000000000000/* * ipl_tools - Linux for System z reipl and shutdown tools * * Command: chshut * * Copyright IBM Corp. 2008, 2017 * * 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 "ipl_tools.h" static const char *const usage_chshut = "Usage: %s TRIGGER ACTION [COMMAND] [OPTIONS]\n" "\n" "Change the shutdown actions for Linux on System z.\n" "\n" "TRIGGER specifies when the action is performed:\n" " halt System has been shut down (e.g. shutdown -h -H now)\n" " poff System has been shut down for power off (e.g. shutdown -h -P now)\n" " reboot System has been shut down for reboot (e.g. shutdown -r)\n" " Note: Depending on the distribution, \"halt\" might be mapped to \"poff\".\n" "\n" "ACTION specifies the action to be performed:\n" " ipl IPL with previous settings\n" " reipl IPL with re-IPL settings (see chreipl)\n" " stop Stop all CPUs\n" " vmcmd Run z/VM CP command defined by COMMAND\n" "\n" "COMMAND defines the z/VM CP command to issue.\n" "\n" "OPTIONS:\n" " -h, --help Print this help, then exit\n" " -v, --version Print version information, then exit\n"; static void __noreturn print_usage_chshut_exit(void) { printf(usage_chshut, g.prog_name); exit(0); } static void parse_chshut_options(int argc, char *argv[]) { int opt, idx; const struct option long_opts[] = { { "help", no_argument, NULL, 'h' }, { "version", no_argument, NULL, 'v' }, { NULL, 0, NULL, 0 } }; while ((opt = getopt_long(argc, argv, "hv", long_opts, &idx)) != -1) { switch (opt) { case 'h': print_usage_chshut_exit(); case 'v': print_version_exit(); default: print_help_hint_exit(); } } if (!is_root()) ERR_EXIT("You must be root to perform this operation"); } static struct shutdown_trigger *shutdown_trigger_get(const char *trigger) { int i; for (i = 0; shutdown_trigger_vec[i]; i++) { if (strcmp(trigger, shutdown_trigger_vec[i]->name) != 0) continue; if (shutdown_trigger_vec[i]->available) return shutdown_trigger_vec[i]; ERR_EXIT("Shutdown trigger \"%s\" is not available on " "your system", trigger); } ERR_EXIT("Unknown shutdown trigger \"%s\" specified", trigger); } static struct shutdown_action *shutdown_action_get(const char *action) { int i; for (i = 0; shutdown_action_vec[i]; i++) { if (strcmp(action, shutdown_action_vec[i]->name) == 0) return shutdown_action_vec[i]; } ERR_EXIT("Unknown shutdown action \"%s\" specified", action); } /* * Multiple CP commands can be specified via "vmcmd XY1 vmcmd XY2 ..." */ static void vmcmd_set(struct shutdown_trigger *st, int argc, char *argv[]) { char vmcmd[1024], path[PATH_MAX]; int first = 1, i; int vmcmd_length = 0; if (is_lpar()) ERR_EXIT("vmcmd works only under z/VM"); memset(vmcmd, 0, sizeof(vmcmd)); for (i = 2; i < argc; i++) { if (strcmp(argv[i], "vmcmd") != 0) ERR_EXIT("Invalid vmcmd command specification"); if (i == argc - 1) ERR_EXIT("vmcmd needs an additional argument"); if (!first) { strcat(vmcmd, "\n"); vmcmd_length++; } else { first = 0; } vmcmd_length += strlen(argv[i + 1]); if (vmcmd_length >= 127) ERR_EXIT("The vmcmd command must not exceed 127 " "characters"); strcat(vmcmd, argv[i + 1]); i++; } sprintf(path, "vmcmd/%s", st->name_sysfs); write_str(vmcmd, path); } void cmd_chshut(int argc, char *argv[]) { struct shutdown_trigger *st; struct shutdown_action *sa; char path[PATH_MAX]; parse_chshut_options(argc, argv); if (argc < 2) { ERR("No trigger specified"); print_help_hint_exit(); } shutdown_init(); st = shutdown_trigger_get(argv[1]); if (st == &shutdown_trigger_panic || st == &shutdown_trigger_restart) ERR_EXIT("Please use \"service dumpconf\" for " "configuring the %s trigger", st->name); if (argc < 3) { ERR("No action specified"); print_help_hint_exit(); } sa = shutdown_action_get(argv[2]); if (sa == &shutdown_action_vmcmd) { vmcmd_set(st, argc, argv); } else if (argc != 3) { ERR("Too many parameters specified"); print_help_hint_exit(); } sprintf(path, "shutdown_actions/%s", st->name_sysfs); if (write_str_errno(argv[2], path)) ERR_EXIT_ERRNO("Could not set \"%s\"", path); } s390-tools-2.3.0/ipl_tools/cmd_lsreipl.c000066400000000000000000000105361323410431100200120ustar00rootroot00000000000000/* * ipl_tools - Linux for System z reipl and shutdown tools * * Command: lsreipl * * Copyright IBM Corp. 2008, 2017 * * 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 "ipl_tools.h" static struct { int ipl_set; /* --ipl has been specified */ } l; static const char *const usage_lsreipl = "Usage: %s [OPTIONS]\n" "\n" "Show re-IPL or IPL settings.\n" "\n" "OPTIONS:\n" " -i, --ipl Print the IPL setting\n" " -h, --help Print this help, then exit\n" " -v, --version Print version information, then exit\n"; static void __noreturn print_usage_lsreipl_exit(void) { printf(usage_lsreipl, g.prog_name); exit(0); } static const char *get_ipl_banner(int show_ipl) { if (show_ipl) return "IPL type:"; else return "Re-IPL type:"; } 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"; 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"); } 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]; if (dump) printf("%-12s fcp_dump\n", get_ipl_banner(show_ipl)); else printf("%-12s fcp\n", get_ipl_banner(show_ipl)); print_fw_str("WWPN: %s\n", dir, "wwpn"); print_fw_str("LUN: %s\n", dir, "lun"); print_fw_str("Device: %s\n", dir, "device"); 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; printf("Loadparm: \"%s\"\n", loadparm); } if (access(path_bootparms, R_OK) == 0) print_fw_str("Bootparms: \"%s\"\n", dir, "scp_data"); } 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"; 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; printf("Loadparm: \"%s\"\n", loadparm); } if (access(path_bootparms, R_OK) == 0) print_fw_str("Bootparms: \"%s\"\n", dir, "parm"); } static void parse_lsreipl_options(int argc, char *argv[]) { int opt, idx; const struct option long_opts[] = { { "help", no_argument, NULL, 'h' }, { "ipl", no_argument, NULL, 'i' }, { "version", no_argument, NULL, 'v' }, { NULL, 0, NULL, 0 } }; while ((opt = getopt_long(argc, argv, "hvi", long_opts, &idx)) != -1) { switch (opt) { case 'i': l.ipl_set = 1; break; case 'h': print_usage_lsreipl_exit(); case 'v': print_version_exit(); default: print_help_hint_exit(); } } /* don't run with too many arguments */ if (optind != argc) ERR_EXIT("Invalid positional parameter \"%s\" specified", argv[optind]); } void cmd_lsreipl(int argc, char *argv[]) { char reipl_type_str[1024]; parse_lsreipl_options(argc, argv); if (l.ipl_set) read_fw_str(reipl_type_str, "ipl/ipl_type", sizeof(reipl_type_str)); else read_fw_str(reipl_type_str, "reipl/reipl_type", sizeof(reipl_type_str)); if (strcmp(reipl_type_str, "fcp") == 0) print_fcp(l.ipl_set, 0); else if (strcmp(reipl_type_str, "fcp_dump") == 0) print_fcp(l.ipl_set, 1); else if (strcmp(reipl_type_str, "ccw") == 0) print_ccw(l.ipl_set); else if (strcmp(reipl_type_str, "nss") == 0) print_nss(l.ipl_set); else printf("%s: %s (unknown)\n", get_ipl_banner(l.ipl_set), reipl_type_str); exit(0); } s390-tools-2.3.0/ipl_tools/cmd_lsshut.c000066400000000000000000000057301323410431100176620ustar00rootroot00000000000000/* * ipl_tools - Linux for System z reipl and shutdown tools * * Command: lsshut * * Copyright IBM Corp. 2008, 2017 * * 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 "ipl_tools.h" static const char *const usage_lsshut = "Usage: %s [OPTIONS]\n" "\n" "Print the shutdown action configuration for Linux on System z.\n" "\n" "OPTIONS:\n" " -h, --help Print this help, then exit\n" " -v, --version Print version information, then exit\n"; static __noreturn void print_usage_lsshut_exit(void) { printf(usage_lsshut, g.prog_name); exit(0); } static void parse_lsshut_options(int argc, char *argv[]) { int opt, idx; const struct option long_opts[] = { { "help", no_argument, NULL, 'h' }, { "version", no_argument, NULL, 'v' }, { NULL, 0, NULL, 0 } }; while ((opt = getopt_long(argc, argv, "hv", long_opts, &idx)) != -1) { switch (opt) { case 'h': print_usage_lsshut_exit(); case 'v': print_version_exit(); default: print_help_hint_exit(); } } /* don't run with too many arguments */ if (optind != argc) ERR_EXIT("Invalid positional parameter \"%s\" specified", argv[optind]); } /* * VMCMDs can have up to 128 characters. Newlines mark the end of a CP command. * Therefore we can have up to 64 single CP commands (with one character). * With quotes (2) and commas (1) we can have at most 4 * 64 = 256 characters * for the output string. */ static void read_vmcmd(char *str, const char *path) { char buf[128], tmp[512]; char *ptr_old, *ptr; *str = 0; ptr_old = ptr = buf; read_fw_str(buf, path, sizeof(buf)); while ((ptr = strchr(ptr_old, '\n'))) { *ptr = 0; sprintf(tmp, "\"%s\",", ptr_old); strcat(str, tmp); ptr_old = ptr + 1; } sprintf(tmp, "\"%s\"", ptr_old); strcat(str, tmp); } static void print_kdump(void) { struct stat sb; char tmp[1024]; if (stat("/sys/kernel/kexec_crash_loaded", &sb) != 0) return; read_str(tmp, "/sys/kernel/kexec_crash_loaded", sizeof(tmp)); if (strncmp(tmp, "1", 1) == 0) printf("kdump,"); } static void shutdown_trigger_print(struct shutdown_trigger *trigger) { char tmp[1024], cmd[1024], path[PATH_MAX]; sprintf(path, "shutdown_actions/%s", trigger->name_sysfs); printf("%-16s ", trigger->name_print); if ((trigger == &shutdown_trigger_panic || trigger == &shutdown_trigger_restart)) print_kdump(); read_fw_str(tmp, path, sizeof(tmp)); if (strncmp(tmp, "vmcmd", strlen("vmcmd")) == 0) { sprintf(path, "vmcmd/%s", trigger->name_sysfs); read_vmcmd(cmd, path); printf("vmcmd (%s)\n", cmd); } else { printf("%s\n", tmp); } } void cmd_lsshut(int argc, char *argv[]) { int i; parse_lsshut_options(argc, argv); shutdown_init(); printf("Trigger Action\n"); printf("========================\n"); for (i = 0; shutdown_trigger_vec[i]; i++) { if (!shutdown_trigger_vec[i]->available) continue; shutdown_trigger_print(shutdown_trigger_vec[i]); } } s390-tools-2.3.0/ipl_tools/fcp.c000066400000000000000000000036111323410431100162610ustar00rootroot00000000000000/* * ipl_tools - Linux for System z reipl and shutdown tools * * FCP device functions * * Copyright IBM Corp. 2008, 2017 * * 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 "ipl_tools.h" /* * Check if the specified device number is a valid device number * which can be found in the /sys/bus/ccw/drivers/zfcp/ structure */ int fcp_is_device(const char *devno) { char path[PATH_MAX]; snprintf(path, sizeof(path), "/sys/bus/ccw/drivers/zfcp/%s", devno); if (chdir(path) != 0) return 0; return 1; } /* * Return the wwpn of a device */ void fcp_wwpn_get(const char *device, char *wwpn) { char path[PATH_MAX], buf[4096]; FILE *fh; int rc; snprintf(path, sizeof(path), "/sys/block/%s/device/wwpn", device); fh = fopen(path, "r"); if (fh == NULL) ERR_EXIT_ERRNO("Could not open \"%s\"", path); rc = fscanf(fh, "%s", buf); if (rc <= 0) ERR_EXIT("Could not lookup WWPN \"%s\"", path); strncpy(wwpn, buf, 20); fclose(fh); } /* * Return the lun of a device */ void fcp_lun_get(const char *device, char *lun) { char path[PATH_MAX], buf[4096]; FILE *fh; int rc; snprintf(path, sizeof(path), "/sys/block/%s/device/fcp_lun", device); fh = fopen(path, "r"); if (fh == NULL) ERR_EXIT_ERRNO("Could not open \"%s\"", path); rc = fscanf(fh, "%s", buf); if (rc <= 0) ERR_EXIT("Could not lookup LUN \"%s\"", path); strncpy(lun, buf, 20); fclose(fh); } /* * Return the device number of a device */ void fcp_busid_get(const char *device, char *devno) { char buf[4096], path[PATH_MAX]; FILE *fh; int rc; snprintf(path, sizeof(path), "/sys/block/%s/device/hba_id", device); fh = fopen(path, "r"); if (fh == NULL) ERR_EXIT_ERRNO("Could not open \"%s\"", path); rc = fscanf(fh, "%s", buf); if (rc <= 0) ERR_EXIT("Could not find device \"%s\"", path); strcpy(devno, buf); fclose(fh); } s390-tools-2.3.0/ipl_tools/ipl_tools.h000066400000000000000000000055741323410431100175340ustar00rootroot00000000000000/* * ipl_tools - Linux for System z reipl tools (shutdown actions) * * Common macro definitions and declarations * * Copyright IBM Corp. 2008, 2017 * * s390-tools is free software; you can redistribute it and/or modify * it under the terms of the MIT license. See LICENSE for details. */ #ifndef IPL_TOOLS_H #define IPL_TOOLS_H #include #include #include #include #include #include #include #include #include #include #include #include "lib/zt_common.h" #define IPL_TYPE_LEN_MAX 100 #define NSS_NAME_LEN_MAX 8 extern struct globals { char prog_name[256]; /* Program name */ } g; /* * 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[]); extern void print_ccw(int show_ipl); extern void print_fcp(int show_ipl, int dump); extern void print_nss(int show_ipl); /* * Helper */ extern int is_lpar(void); extern int is_root(void); extern void strlow(char *s); 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); /* * 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); /* * CCW */ extern int ccw_is_device(const char *devno); extern void ccw_busid_get(const char *device, char *devno); /* * Shutdown trigger */ struct shutdown_trigger { const char *name; const char *name_print; const char *name_sysfs; int available; }; extern struct shutdown_trigger shutdown_trigger_panic; extern struct shutdown_trigger shutdown_trigger_restart; extern struct shutdown_trigger *shutdown_trigger_vec[]; extern void shutdown_init(void); /* * Shutdown actions */ struct shutdown_action { const char *name; }; extern struct shutdown_action shutdown_action_vmcmd; extern struct shutdown_action *shutdown_action_vec[]; /* * Error and print functions */ #define ERR(x...) \ do { \ fprintf(stderr, "%s: ", g.prog_name); \ fprintf(stderr, x); \ fprintf(stderr, "\n"); \ } while (0) #define ERR_EXIT(x...) \ do { \ ERR(x); \ exit(1); \ } while (0) #define ERR_EXIT_ERRNO(x...) \ do { \ fflush(stdout); \ fprintf(stderr, "%s: ", g.prog_name); \ fprintf(stderr, x); \ fprintf(stderr, " (%s)", strerror(errno)); \ fprintf(stderr, "\n"); \ exit(1); \ } while (0) #endif /* IPL_TOOLS_H */ s390-tools-2.3.0/ipl_tools/main.c000066400000000000000000000022021323410431100164300ustar00rootroot00000000000000/* * ipl_tools - Linux for System z reipl and shutdown tools * * Main functions * * Copyright IBM Corp. 2008, 2017 * * 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 "lib/zt_common.h" #include "ipl_tools.h" struct globals g; void __noreturn print_help_hint_exit(void) { fprintf(stderr, "Try '%s' --help' for more information.\n", g.prog_name); exit(1); } void __noreturn print_version_exit(void) { printf("%s: Linux on System z shutdown actions version %s\n", g.prog_name, RELEASE_STRING); printf("Copyright IBM Corp. 2008, 2017\n"); exit(0); } int main(int argc, char *argv[]) { strncpy(g.prog_name, argv[0], sizeof(g.prog_name)); if (strstr(argv[0], "chreipl") != NULL) { cmd_chreipl(argc, argv); return 0; } if (strstr(argv[0], "chshut") != NULL) { cmd_chshut(argc, argv); return 0; } if (strstr(argv[0], "lsreipl") != NULL) { cmd_lsreipl(argc, argv); return 0; } if (strstr(argv[0], "lsshut") != NULL) { cmd_lsshut(argc, argv); return 0; } ERR_EXIT("Invalid program name \"%s\"", argv[0]); return 1; } s390-tools-2.3.0/ipl_tools/man/000077500000000000000000000000001323410431100161175ustar00rootroot00000000000000s390-tools-2.3.0/ipl_tools/man/chreipl.8000066400000000000000000000160031323410431100176360ustar00rootroot00000000000000.\" Copyright 2017 IBM Corp. .\" s390-tools is free software; you can redistribute it and/or modify .\" it under the terms of the MIT license. See LICENSE for details. .\" .TH CHREIPL 8 "July 2010" "s390-tools" .SH NAME chreipl \- change the re-IPL configuration for Linux on System z .SH SYNOPSIS \fBchreipl\fP [TARGET] [OPTIONS] .SH DESCRIPTION Use the \fBchreipl\fP tool to modify the re-IPL configuration for Linux on System z. You can configure a certain boot device and, for zipl boot menu configurations, the boot menu entry that will be used for the next reboot. Also kernel parameters for the next Linux kernel can be defined. Initial program load (IPL) is the mainframe synonym for what is called "boot" under Linux. Accordingly re-IPL can be translated to "reboot" in the non-mainframe context. Normally for reboot the last IPL device is used to restart the system. To reboot from another IPL device, you first have to change the re-IPL settings with \fBchreipl\fP and then run the .BR reboot (8) command. All settings made with \fBchreipl\fP are preserved over reboots until they are changed again. .SH TARGET The first argument specifies the re-IPL target: .RS 3 .TP 8 .RB "- " ccw : Specify a DASD CCW device for reboot .TP .RB "- " fcp : Specify a FCP device for reboot .TP .RB "- " nss : Specify a named saved system (NSS) for reboot .TP .RB "- " node : Specify a device for reboot using a device node or directory .RE .PP If the target specification is non-ambiguous it can be omitted. See section \fBAUTOTARGET\fP for more information. Ambiguous setups are very rare, e.g. the name of a device node theoretically could be the same as a bus-ID. .SH OPTIONS .TP .BR "\-f" " or " "\-\-force" With this option, you can force the re-IPL from a target device even if the target cannot be verified by the system. This is the case, for example, if the device is on the cio_ignore blacklist. .B Note: Use this option with great care. You can specify non-existing devices, which will cause the re-IPL to fail. .TP .BR "\-p" " or " "\-\-bootparms" Specifies boot parameters for the next reboot. The boot parameters which are typically kernel parameters are appended to the kernel parameter line. If you specify the boot parameters with a leading equal sign (=), the boot parameters replace all parameters on the kernel parameter line. To remove boot parameters, specify an empty string for this option. Depending on the chreipl target a different maximum number of characters is allowed for boot parameters. Under LPAR it is not possible to specify boot parameters for the ccw target. .B Note: When replacing all parameters, you might inadvertently omit parameters that the boot configuration requires. Read .B /proc/cmdline to find out with which parameters a running Linux instance has been started. .TP .BR "\-h" " or " "\-\-help" Print help information, then exit. .TP .BR "\-v" " or " "\-\-version" Print version information, then exit. .PP All other options are specific to the re-IPL target and are discussed below in the respective section. For a more detailed description of the Linux on System z IPL mechanisms see .BR zipl.conf (6). The mandatory options (e.g. device, wwpn, lun, etc.) can also be specified as positional parameters. .SH AUTOTARGET For the ccw, fcp, and node targets \fBchreipl\fP can find automatically the correct re-IPL target. To do this, omit the re-IPL target parameter and start specifying the required positional parameters. .PP \fBExamples:\fP .br 1. Next time reboot from the DASD device /dev/dasda using the first boot configuration: .br \fB# chreipl /dev/dasda -L 1\fP .br 2. Next time reboot from the CCW device with the bus-ID 0.0.7e78 and empty loadparm: \fB# chreipl 0.0.7e78 \fP 3. Next time reboot from the SCSI disk with FCP bus-ID 0.0.1700, WWPN 0x500507630300c562, and LUN 0x401040b300000000. In addition to that append kernel parameter "mem=" to restrict memory to 512 MB: \fB# chreipl 0.0.1700 0x500507630300c562 0x401040b300000000 -p "mem=512M"\fP .SH ccw Use the ccw re-IPL target for DASD devices that are accessed by the hardware using channel command word (CCW) channels. .TP .BR "\-d" " or " "\-\-device" Specifies the CCW bus-ID. If the bus-ID starts with "0.0." this prefix can be omitted and the four digit short notation can be used (e.g. 5000 is the same as 0.0.5000). .TP .BR "\-L" " or " "\-\-loadparm" Specifies an entry in the .BR zipl (8) boot menu. If this option is omitted, the default menu entry is used. .PP \fBExamples:\fP .br 1. Next time reboot from the CCW device with the bus-ID 0.0.7e78 and empty loadparm: \fB# chreipl ccw 0.0.7e78\fP 2. Next time reboot from the CCW device with the bus-ID 0.0.7e78 using the first entry of the zipl boot menu: \fB# chreipl ccw -d 0.0.7e78 -L 1\fP .SH fcp Use the fcp re-IPL target for SCSI disks that are accessed by the hardware using Fibre Channel Protocol (FCP) channels. .TP .BR "\-d" " or " "\-\-device" Specifies the bus-ID of the FCP adapter that should be used to access the SCSI re-IPL device. If the bus-ID starts with "0.0." this prefix can be omitted and the four digit short notation can be used (e.g. 5000 is the same as 0.0.5000). .TP .BR "\-w" " or " "\-\-wwpn" Specifies the world wide port name (WWPN) for the FCP attached SCSI disk. .TP .BR "\-l" " or " "\-\-lun" Specifies the logical unit number (LUN) for the FCP attached SCSI disk. .TP .BR "\-b" " or " "\-\-bootprog" Specifies an entry in the FCP boot configuration by defining the IPL boot program selector. If omitted, '0' will be used. .TP .BR "\-L" " or " "\-\-loadparm" The loadparm for the fcp re-IPL target is not used to control the FCP boot configuration that is defined by the .BR zipl (8) boot menu. Instead it can be used to control higher level boot loaders like GRUB. For more details refer to distribution specific documentation. .PP \fBExamples:\fP .br 1. Next time reboot from the SCSI disk with FCP bus-ID 0.0.1700, WWPN 0x500507630300c562, LUN 0x401040b300000000, and boot program selector 0: .br \fB# chreipl fcp 0.0.1700 0x500507630300c562 0x401040b300000000\fP .br 2. Use same configuration as (1) but choose boot program selector 2 and use options instead of positional parameters: .br \fB# chreipl fcp -d 0.0.1700 -w 0x5005076... -l 0x401040b3... -b 2\fP .SH nss Use the nss re-IPL target to specify z/VM named saved systems (NSS) for reboot. .TP .BR "\-n" " or " "\-\-name" Specifies the name of the NSS. .PP \fBExamples:\fP .br Use the NSS named LINUX1 for the next reboot: \fB# chreipl nss LINUX1\fP .SH node You can identify DASD or SCSI re-IPL devices indirectly through a device node or directory. The chreipl tool then determines the information that you would otherwise have to specify with the ccw or fcp target. .PP \fBExamples:\fP .br 1. Next time reboot from the DASD device /dev/dasda: .br \fB# chreipl node /dev/dasda\fP .br 2. Next time reboot from the SCSI disk /dev/sda: .br \fB# chreipl node /dev/sda\fP 3. Next time reboot from the device where directory /mnt/boot is located: .br \fB# chreipl node /mnt/boot\fP .SH SEE ALSO .BR lsreipl (8), .BR zipl (8), .BR zipl.conf (5), .BR reboot (8) s390-tools-2.3.0/ipl_tools/man/chshut.8000066400000000000000000000035361323410431100175150ustar00rootroot00000000000000.\" Copyright 2017 IBM Corp. .\" s390-tools is free software; you can redistribute it and/or modify .\" it under the terms of the MIT license. See LICENSE for details. .\" .TH CHSHUT 8 "Sept 2011" "s390-tools" .SH NAME chshut \- change the shutdown actions for Linux on System z .SH SYNOPSIS \fBchshut\fR TRIGGER ACTION [VM Command] .SH DESCRIPTION Use \fBchshut\fR to configure the Linux on System z shutdown actions. The tool handles up to three parameters. The first parameter specifies the shutdown trigger. A shutdown trigger is an event that will stop Linux. The following shutdown triggers are supported: "halt", "poff", and "reboot". There are two other shutdown triggers "panic" and "restart" that are controlled by the .BR dumpconf (8) service script. The second parameter specifies the shutdown action that you want to run if the specified trigger occurs. Valid action arguments are "ipl", "reipl", "stop", and "vmcmd". If you have chosen "vmcmd" as action, a third parameter is required for the CP command you want to execute under z/VM. .B Note: VM CP commands, device addresses, and guest names must be uppercase. .SH OPTIONS .TP \fB-h\fR or \fB--help\fR Print help information, then exit. .TP \fB-v\fR or \fB--version\fR Print version information, then exit. .SH EXAMPLES Log off the z/VM guest if the Linux .BR poweroff (8) command was executed successfully: \fB# chshut poff vmcmd LOGOFF\fR If the system is halted it should start again: \fB# chshut halt ipl\fR If the Linux .BR poweroff (8) command is executed, send a message to guest MASTER and automatically log off the guest: \fB# chshut poff vmcmd "MSG MASTER Going down" vmcmd "LOGOFF"\fR .SH NOTES For most Linux distributions, "halt" is mapped by default to "power off". In this case you have to use the shutdown trigger "poff" instead of "halt". .SH SEE ALSO .BR dumpconf (8) .BR lsshut (8) s390-tools-2.3.0/ipl_tools/man/lsreipl.8000066400000000000000000000021521323410431100176620ustar00rootroot00000000000000.\" Copyright 2017 IBM Corp. .\" s390-tools is free software; you can redistribute it and/or modify .\" it under the terms of the MIT license. See LICENSE for details. .\" .TH LSREIPL 8 "July 2010" "s390-tools" .SH NAME lsreipl \- list the re-IPL configuration for Linux on System z .SH SYNOPSIS \fBlsreipl\fR [OPTION] .SH DESCRIPTION \fBlsreipl\fR lists the re-IPL (and IPL) configuration for Linux on System z. Using this tool you can see from which device your system will boot after you issue the reboot command. Furthermore you can query the system for information about the current boot device. IPL (initial program load) is the mainframe synonym for what is called "boot" in Linux. Accordingly re-IPL can be translated to "reboot" in the non mainframe context. .SH OPTIONS .TP \fB-i\fR or \fB--ipl\fR Print the IPL instead of the re-IPL configuration. This lists the configuration of the device from which your present Linux installation was booted. .TP \fB-h\fR or \fB--help\fR Print help information, then exit. .TP \fB-v\fR or \fB--version\fR Print version information, then exit. .SH SEE ALSO .BR chreipl (8) s390-tools-2.3.0/ipl_tools/man/lsshut.8000066400000000000000000000016051323410431100175340ustar00rootroot00000000000000.\" Copyright 2017 IBM Corp. .\" s390-tools is free software; you can redistribute it and/or modify .\" it under the terms of the MIT license. See LICENSE for details. .\" .TH LSSHUT 8 "Sept 2011" "s390-tools" .SH NAME lsshut \- print the shutdown action configuration for Linux on System z .SH SYNOPSIS \fBlsshut\fR [OPTIONS] .SH DESCRIPTION The \fBlsshut\fR command lists the Linux on System z shutdown action configuration. This configuration handles what the system should do for the following shutdown triggers: "halt", "poff", "reboot", "restart", and "panic". If kdump is enabled on your system, "restart" and "panic" also show the "kdump" action together with the action that is run in case of a "kdump" failure. .SH OPTIONS .TP \fB-h\fR or \fB--help\fR Print help information, then exit. .TP \fB-v\fR or \fB--version\fR Print version information, then exit. .SH SEE ALSO .BR chshut (8) s390-tools-2.3.0/ipl_tools/proc.c000066400000000000000000000214241323410431100164560ustar00rootroot00000000000000/* * ipl_tools - Linux for System z reipl and shutdown tools * * Scanner for /proc files * * Copyright IBM Corp. 2001, 2017 * * 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 #include #include #include #include #include #include #include "ipl_tools.h" #include "proc.h" static const char proc_part_filename[] = "/proc/partitions"; static const char proc_dev_filename[] = "/proc/devices"; struct file_buffer { char* buffer; off_t pos; size_t length; }; #define INITIAL_FILE_BUFFER_SIZE 1024 /* Read file into buffer without querying its size (necessary for reading files * from /proc or /sys). Upon success, return zero and set BUFFER to point to * the file buffer and SIZE (if non-null) to contain the file size. Return * non-zero otherwise. Add a null-byte at the end of the buffer if * NIL_TERMINATE is non-zero. */ static int misc_read_special_file(const char* filename, char** buffer, size_t* size, int nil_terminate) { FILE* file; char* data; char* new_data; size_t count; size_t current_size; int current; file = fopen(filename, "r"); if (file == NULL) ERR_EXIT_ERRNO("Could not open \"%s\"", filename); current_size = INITIAL_FILE_BUFFER_SIZE; count = 0; data = (char *) malloc(current_size); if (data == NULL) { fclose(file); return -1; } current = fgetc(file); while (current != EOF || nil_terminate) { if (current == EOF) { current = 0; nil_terminate = 0; } data[count++] = (char) current; if (count >= current_size) { new_data = (char *) malloc(current_size * 2); if (new_data == NULL) { free(data); fclose(file); return -1; } memcpy(new_data, data, current_size); free(data); data = new_data; current_size *= 2; } current = fgetc(file); } fclose(file); *buffer = data; if (size) *size = count; return 0; } /* Get the contents of a file and fill in the respective fields of * FILE. Return 0 on success, non-zero otherwise. */ static int get_file_buffer(struct file_buffer* file, const char *filename) { int rc; rc = misc_read_special_file(filename, &file->buffer, &file->length, 0); file->pos = 0; return rc; } /* Free resources allocated for file buffer identified by * FILE. */ static void free_file_buffer(struct file_buffer* file) { free(file->buffer); file->buffer = NULL; file->pos = 0; file->length = 0; } /* Return character at current FILE buffer position or EOF if at end of * file. */ static int current_char(struct file_buffer* file) { if (file->buffer != NULL) if (file->pos < (off_t) file->length) return file->buffer[file->pos]; return EOF; } /* Advance the current file pointer of file buffer FILE until the current * character is no longer a whitespace or until the end of line or file is * reached. Return 0 if at least one whitespace character was encountered, * non-zero otherwise. */ static int skip_whitespaces(struct file_buffer* file) { int rc; rc = -1; while ((current_char(file) != '\n') && isspace(current_char(file))) { rc = 0; file->pos++; } return rc; } /* Scan a positive integer number at the current position of file buffer FILE * and advance the position respectively. Upon success, return zero and set * NUMBER to contain the scanned number. Return non-zero otherwise. */ static int scan_number(struct file_buffer* file, size_t* number) { int rc; size_t old_number; *number=0; rc = -1; while (isdigit(current_char(file))) { rc = 0; old_number = *number; *number = *number * 10 + current_char(file) - '0'; /* Check for overflow */ if (old_number > *number) { rc = -1; break; } file->pos++; } return rc; } /* Scan a device node name at the current position of file buffer FILE and * advance the position respectively. Upon success, return zero and set * NAME to contain a copy of the scanned name. Return non-zero otherwise. */ static int scan_name(struct file_buffer* file, char** name) { off_t start_pos; start_pos = file->pos; while (!isspace(current_char(file)) && (current_char(file) != EOF)) file->pos++; if (file->pos > start_pos) { *name = (char *) malloc(file->pos - start_pos + 1); if (*name == NULL) return -1; memcpy((void *) *name, (void *) &file->buffer[start_pos], file->pos - start_pos); (*name)[file->pos - start_pos] = 0; return 0; } else return -1; } /* Scan for the specified STRING at the current position of file buffer FILE * and advance the position respectively. Upon success, return zero. Return * non-zero otherwise. */ static int scan_string(struct file_buffer* file, const char *string) { int i; for (i=0; string[i] && (current_char(file) == string[i]); i++, file->pos++); if (string[i] == '\0') return 0; return -1; } /* Advance the current file position to beginning of next line in file buffer * FILE or to end of file. */ static void skip_line(struct file_buffer* file) { while ((current_char(file) != '\n') && (current_char(file) != EOF)) file->pos++; if (current_char(file) == '\n') file->pos++; } /* Return non-zero if the current file position of file buffer FILE is at the * end of file. Return zero otherwise. */ static int eof(struct file_buffer* file) { return file->pos >= (off_t) file->length; } /* Scan a line of the specified /proc/partitions FILE buffer and advance the * current file position pointer respectively. If the current line matches * the correct pattern, fill in the corresponding data into ENTRY and return 0. * Return non-zero otherwise. */ static int scan_part_entry(struct file_buffer* file, struct proc_part_entry* entry) { int rc; size_t dev_major; size_t dev_minor; size_t blockcount; char* name; /* Scan for: (\s*)(\d+)(\s+)(\d+)(\s+)(\d+)(\s+)(\S+)(\.*)$ */ skip_whitespaces(file); rc = scan_number(file, &dev_major); if (rc) return rc; rc = skip_whitespaces(file); if (rc) return rc; rc = scan_number(file, &dev_minor); if (rc) return rc; rc = skip_whitespaces(file); if (rc) return rc; rc = scan_number(file, &blockcount); if (rc) return rc; rc = skip_whitespaces(file); if (rc) return rc; rc = scan_name(file, &name); if (rc) return rc; skip_line(file); entry->device = makedev(dev_major, dev_minor); entry->blockcount = blockcount; entry->name = name; return 0; } /* Release resources associated with ENTRY. */ void proc_part_free_entry(struct proc_part_entry* entry) { free(entry->name); entry->name = NULL; } /* Scan a line of the specified /proc/devices FILE buffer and advance the * current file position pointer respectively. If the current line matches * the correct pattern, fill in the corresponding data into ENTRY and return 0. * Return non-zero otherwise. */ static int scan_dev_entry(struct file_buffer* file, struct proc_dev_entry* entry, int blockdev) { int rc; size_t dev_major; char* name; /* Scan for: (\s*)(\d+)(\s+)(\S+)(\.*)$ */ skip_whitespaces(file); rc = scan_number(file, &dev_major); if (rc) return rc; rc = skip_whitespaces(file); if (rc) return rc; rc = scan_name(file, &name); if (rc) return rc; skip_line(file); entry->device = makedev(dev_major, 0); entry->name = name; entry->blockdev = blockdev; return 0; } /* Release resources associated with ENTRY. */ void proc_dev_free_entry(struct proc_dev_entry* entry) { free(entry->name); entry->name = NULL; } /* Scan /proc/partitions for an entry matching DEVICE. When there is a match, * store entry data in ENTRY and return 0. Return non-zero otherwise. */ int proc_part_get_entry(dev_t device, struct proc_part_entry* entry) { struct file_buffer file; int rc; rc = get_file_buffer(&file, proc_part_filename); if (rc) return rc; rc = -1; while (!eof(&file)) { if (scan_part_entry(&file, entry) == 0) { if (entry->device == device) { rc = 0; break; } proc_part_free_entry(entry); } else skip_line(&file); } free_file_buffer(&file); return rc; } /* Scan /proc/devices for a blockdevice (BLOCKDEV is 1) or a character * device (BLOCKDEV is 0) with a major number matching the major number of DEV. * When there is a match, store entry data in ENTRY and return 0. Return * non-zero otherwise. */ int proc_dev_get_entry(dev_t device, int blockdev, struct proc_dev_entry* entry) { struct file_buffer file; int rc; int scan_blockdev = 0; rc = get_file_buffer(&file, proc_dev_filename); if (rc) return rc; rc = -1; while (!eof(&file)) { if (scan_string(&file, "Block") == 0) { skip_line(&file); scan_blockdev = 1; continue; } else if (scan_dev_entry(&file, entry, scan_blockdev) == 0) { if ((major(entry->device) == major(device)) && blockdev == scan_blockdev) { rc = 0; break; } proc_dev_free_entry(entry); } else skip_line(&file); } free_file_buffer(&file); return rc; } s390-tools-2.3.0/ipl_tools/proc.h000066400000000000000000000014201323410431100164550ustar00rootroot00000000000000/* * ipl_tools - Linux for System z reipl and shutdown tools * * Scanner for /proc files * * Copyright IBM Corp. 2001, 2017 * * s390-tools is free software; you can redistribute it and/or modify * it under the terms of the MIT license. See LICENSE for details. */ #ifndef PROC_H #define PROC_H #include #include struct proc_part_entry { dev_t device; size_t blockcount; char* name; }; struct proc_dev_entry { int blockdev; dev_t device; char *name; }; int proc_part_get_entry(dev_t device, struct proc_part_entry* entry); void proc_part_free_entry(struct proc_part_entry* entry); int proc_dev_get_entry(dev_t dev, int blockdev, struct proc_dev_entry* entry); void proc_dev_free_entry(struct proc_dev_entry* entry); #endif /* not PROC_H */ s390-tools-2.3.0/ipl_tools/shutdown.c000066400000000000000000000041351323410431100173660ustar00rootroot00000000000000/* * ipl_tools - Linux for System z reipl and shutdown tools * * Shutdown actions and triggers common functions * * Copyright IBM Corp. 2008, 2017 * * 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 "ipl_tools.h" static struct shutdown_trigger shutdown_trigger_halt = { .name = "halt", .name_print = "Halt", .name_sysfs = "on_halt", }; static struct shutdown_trigger shutdown_trigger_poff = { .name = "poff", .name_print = "Power off", .name_sysfs = "on_poff", }; static struct shutdown_trigger shutdown_trigger_reboot = { .name = "reboot", .name_print = "Reboot", .name_sysfs = "on_reboot", }; struct shutdown_trigger shutdown_trigger_restart = { .name = "restart", .name_print = "Restart", .name_sysfs = "on_restart", }; struct shutdown_trigger shutdown_trigger_panic = { .name = "panic", .name_print = "Panic", .name_sysfs = "on_panic", }; struct shutdown_trigger *shutdown_trigger_vec[] = { &shutdown_trigger_halt, &shutdown_trigger_poff, &shutdown_trigger_reboot, &shutdown_trigger_restart, &shutdown_trigger_panic, NULL, }; static struct shutdown_action shutdown_action_ipl = { .name = "ipl", }; static struct shutdown_action shutdown_action_reipl = { .name = "reipl", }; static struct shutdown_action shutdown_action_dump = { .name = "dump", }; static struct shutdown_action shutdown_action_dump_reipl = { .name = "dump_reipl", }; static struct shutdown_action shutdown_action_stop = { .name = "stop", }; struct shutdown_action shutdown_action_vmcmd = { .name = "vmcmd", }; struct shutdown_action *shutdown_action_vec[] = { &shutdown_action_ipl, &shutdown_action_reipl, &shutdown_action_dump, &shutdown_action_dump_reipl, &shutdown_action_stop, &shutdown_action_vmcmd, NULL, }; void shutdown_init(void) { char path[PATH_MAX]; struct stat sb; int i; for (i = 0; shutdown_trigger_vec[i]; i++) { sprintf(path, "/sys/firmware/shutdown_actions/%s", shutdown_trigger_vec[i]->name_sysfs); if (stat(path, &sb) == 0) shutdown_trigger_vec[i]->available = 1; } } s390-tools-2.3.0/ipl_tools/system.c000066400000000000000000000053621323410431100170420ustar00rootroot00000000000000/* * ipl_tools - Linux for System z reipl and shutdown tools * * Shared system functions * * Copyright IBM Corp. 2008, 2017 * * 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 "ipl_tools.h" /* * Check if we are running in an LPAR environment */ int is_lpar(void) { size_t bytes_read; char buf[2048]; int rc = 0; FILE *fh; fh = fopen("/proc/cpuinfo", "r"); if (fh == NULL) ERR_EXIT_ERRNO("Could not open \"/proc/cpuinfo\""); bytes_read = fread(buf, 1, sizeof(buf), fh); if (bytes_read == 0) ERR_EXIT("Could not read \"/proc/cpuinfo\""); buf[bytes_read] = '\0'; if (strstr(buf, "version = FF") == NULL) rc = 1; fclose(fh); return rc; } /* * Check whether we are started as root */ int is_root(void) { if (geteuid() == 0) return 1; else 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 path[PATH_MAX]; snprintf(path, sizeof(path), "/sys/firmware/%s", file); return read_str(string, path, len); } /* * Print content of a file (path = dir/file) */ void print_fw_str(const char *fmt, const char *dir, const char *file) { char path[PATH_MAX], str[4096]; snprintf(path, sizeof(path), "%s/%s", dir, file); read_fw_str(str, path, sizeof(str)); printf(fmt, str); } /* * Write a string to a file */ void write_str(char *string, char *file) { char path[PATH_MAX], value[4096]; int fh; 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); } /* * Write a string to a file and return ERRNO */ int write_str_errno(char *string, char *file) { char path[PATH_MAX], value[4096]; int fh; 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) return errno; close(fh); return 0; } s390-tools-2.3.0/iucvterm/000077500000000000000000000000001323410431100151765ustar00rootroot00000000000000s390-tools-2.3.0/iucvterm/Makefile000066400000000000000000000007611323410431100166420ustar00rootroot00000000000000#! /usr/bin/make -f include ../common.mak GETTEXT_TEXTDOMAIN = iucvterm export GETTEXT_TEXTDOMAIN SUBDIRS = src po doc bin etc test RECURSIVE_TARGETS = all-recursive install-recursive clean-recursive \ check-recursive all: all-recursive check: check-recursive install: all install-recursive clean: clean-recursive $(RECURSIVE_TARGETS): @target=`echo $@ |sed s/-recursive//`; \ for d in $(SUBDIRS); do \ (cd $$d && $(MAKE) $$target) \ done .PHONY: install clean s390-tools-2.3.0/iucvterm/bin/000077500000000000000000000000001323410431100157465ustar00rootroot00000000000000s390-tools-2.3.0/iucvterm/bin/Makefile000066400000000000000000000016761323410431100174200ustar00rootroot00000000000000#! /usr/bin/make -f include ../../common.mak BIN_SCRIPTS = ts-shell SBIN_SCRIPTS = chiucvallow SCRIPTS = $(BIN_SCRIPTS) $(SBIN_SCRIPTS) all: check: install: $(SCRIPTS) for prg in $(BIN_SCRIPTS); do \ $(INSTALL) -g $(GROUP) -o $(OWNER) -m 755 $$prg $(DESTDIR)$(USRBINDIR) ; \ done for prg in $(SBIN_SCRIPTS); do \ $(INSTALL) -g $(GROUP) -o $(OWNER) -m 755 $$prg $(DESTDIR)$(USRSBINDIR) ; \ done $(INSTALL) -g $(GROUP) -o $(OWNER) -d $(DESTDIR)$(VARDIR)/log/ts-shell ln -f -s chiucvallow $(DESTDIR)$(USRSBINDIR)/lsiucvallow clean: -rm -f *.o $(SCRIPTS) %: %.in real_usrbin=$(USRBINDIR); \ real_var=$(VARDIR); \ my_sysconf=$(SYSCONFDIR)/iucvterm; \ $(SED) -e "s#@sysconf_path@#$$my_sysconf#g" \ -e "s#@iucvconn_path@#$$real_usrbin#g" \ -e "s#@var_path@#$$real_var#g" \ -e 's#@S390_TOOLS_RELEASE@#$(S390_TOOLS_RELEASE)#g' \ < $< > $@ perl: ts-shell perl -c $< scripts: $(SCRIPTS) .PHONY: install clean perl s390-tools-2.3.0/iucvterm/bin/chiucvallow.in000077500000000000000000000147251323410431100206320ustar00rootroot00000000000000#!/bin/sh # # z/VM IUCV HVC device driver -- Edit z/VM user ID filter # # Copyright IBM Corp. 2009, 2017 # # s390-tools is free software; you can redistribute it and/or modify # it under the terms of the MIT license. See LICENSE for details. # PRG=`basename $0` # sysfs file that contains the z/VM user ID filter for the # z/VM IUCV HVC device driver sysfs_hvc_iucv_allow="/sys/module/kernel/parameters/hvc_iucv_allow" # older kernel versions do not have the "kernel" directory test -f $sysfs_hvc_iucv_allow || \ sysfs_hvc_iucv_allow="/sys/module/hvc_iucv/parameters/hvc_iucv_allow" show_help(){ cat <<"EoHelp" Usage: chiucvallow [-h|--help] [-v|--version] chiucvallow -l|--list chiucvallow -c|--clear chiucvallow -e|--edit [] chiucvallow -s|--set chiucvallow -V|--verify Options: -h|--help Print help information, then exit. -v|--version Print version information, then exit. -l|--list List current z/VM user ID filter. -c|--clear Clear the z/VM user ID filter. -e|--edit [] Edit the z/VM user ID filter. -s|--set Set the z/VM user ID filter from a filter file. -V|--verify Verify the z/VM user ID filter file. The list, edit, set and clear options require root authority. EoHelp } show_version(){ cat <&2 exit ${2:-1} } hvciucv_available(){ test -r "$sysfs_hvc_iucv_allow" \ && cat "$sysfs_hvc_iucv_allow" >/dev/null 2>&1 if test $? -gt 0; then cat >&2 </dev/null |wc -c` if test "$fsize" -gt 4095; then printf "$PRG: The z/VM user ID filter exceeds the maximum size (%d of %d bytes)\n" \ $fsize 4095 >&2 return 1 fi while read userid; do # skip empty lines and lines starting with '#' echo "$userid" |grep -q -E '^(\s*$|#)' && continue printf "Verify z/VM user ID: %-8s : " "$userid" if echo -n "$userid" |grep -q -E -i "$regex"; then printf "OK\n" else printf "FAILED\n" failed=$((failed + 1)) fi count=$((count + 1)) done <$filename test $count -gt 500 && \ error "The z/VM user ID filter exceeds the maximum of 500 user IDs" printf "\n$PRG: Verification summary: verified=%d failed=%d size=%d bytes\n" \ $count $failed $fsize test $failed -eq 0 || return 2 } # # Edit the z/VM user ID filter edit_filter(){ local fromfile="$1" local context=$2 local tmpfile=`mktemp /tmp/hvc_iucv_allow.XXXXXX` local md5file=`mktemp /tmp/hvc_iucv_allow.md5.XXXXXX` if test -w $tmpfile && test -w $md5file; then :; else error "Creating temporary files failed" fi # save list in temp file if test -r "$fromfile"; then cat $fromfile; else list_filter; fi > $tmpfile # check whether to open editor if test "x$context" != xnoeditor; then md5sum $tmpfile > $md5file # save checksum to track changes ${EDITOR:-vi} $tmpfile # open editor if md5sum --status -c $md5file; then cat </dev/null; then :; else cat >&2 < $sysfs_hvc_iucv_allow if test x$? != x0; then cat >&2 </dev/null exit 0 } # # Clear z/VM user ID filter clear_filter(){ echo > $sysfs_hvc_iucv_allow } # # Check whether we run as root, otherwise complain and exit for_root_only(){ local euid=`id -u 2>/dev/null` test "x$euid" = x0 && return error "You need root authority to use option '$1'" } lock_operation(){ ( flock -nx 9 \ || error "The filter is currently being changed. Try again later." $@ ) 9>/var/lock/hvc_iucv_allow } # Common options case $1 in -h|--help) show_help exit 0 ;; -v|--version) show_version exit 0 ;; esac # check the name under which we have been called case `basename $0` in lsiucvallow) exec chiucvallow --list ;; esac # chiucvallow program options case $1 in -l|--list) for_root_only "$1" hvciucv_available list_filter ;; -e|--edit) for_root_only "$1" hvciucv_available lock_operation edit_filter "$2" ;; -c|--clear) for_root_only "$1" hvciucv_available lock_operation clear_filter ;; -s|--set) for_root_only "$1" test -n "$2" \ || error "This option requires a file as argument" test -r "$2" \ || error "The specified file must be readable" hvciucv_available lock_operation edit_filter "$2" noeditor ;; -V|--verify) test -n "$2" \ || error "This option requires a file as argument" verify_filter "$2" exit $? ;; '') echo "$PRG: One or more arguments are missing" >&2 echo "Try '$0 --help' for more information." >&2 exit 201 ;; *) echo "$PRG: Invalid option -- '$1'" >&2 echo "Try '$0 --help' for more information." >&2 exit 201 ;; esac exit 0 s390-tools-2.3.0/iucvterm/bin/ts-shell.in000077500000000000000000000423001323410431100200330ustar00rootroot00000000000000#! /usr/bin/perl -W # # ts-shell - Simple terminal server shell to access systems over IUCV # # This script can be used as login shell for users to restrict # IUCV-based terminal access to other systems. # # Copyright IBM Corp. 2008, 2017 # # s390-tools is free software; you can redistribute it and/or modify # it under the terms of the MIT license. See LICENSE for details. # use strict; use warnings; use File::Basename; use Getopt::Long qw(:config no_ignore_case); use Term::ReadLine; use POSIX; $ENV{'PERL_RL'} = " o=0"; # use best avail. readline $ENV{'PATH'} = "/bin:/sbin:/usr/bin:/usr/sbin"; $ENV{'LESSSECURE'} = 1; # let less run in "secure" mode $ENV{'PAGER'} = $ENV{'PAGER'} || "/usr/bin/less"; my $ts_shell = fileparse($0, qr/\.[^.]+/); $SIG{__WARN__} = sub { print STDERR "$ts_shell: $_[0]"; }; $SIG{__DIE__} = sub { print STDERR "$ts_shell: $_[0]"; exit 255; }; # Terminal server configuration settings my %config = ( # general options 'conffile' => "@sysconf_path@/ts-shell.conf", 'authfile' => "@sysconf_path@/ts-authorization.conf", 'sysfile' => "@sysconf_path@/ts-systems.conf", 'auditfile' => "@sysconf_path@/ts-logsys.conf", 'auditdir' => "@var_path@/log/ts-shell", 'iucvconn' => "@iucvconn_path@/iucvconn", 'prompt' => getpwuid($>) . '@'."$ts_shell> ", # runtime options 'rl' => undef, # terminal readline (rl) 'user' => getpwuid($>), # user name 'groups' => [], # user groups (list ref) 'service' => "lnxhvc0", # default service 'auth_func' => sub { return -1; }, # no authorization 'regex' => [], # re to match systems (list ref) 'systems' => [], # system list (list reference) 'services' => [qw/lnxhvc0/], # services list (completion) 'termsys' => {}, # global ts sys list (hash ref) 'auditsys' => {}, # audit ts sys list (hash ref) ); sub main(); sub help(); sub usage(); sub version(); sub intro(); sub auth_global($$); sub auth_regex($$); sub auth_list($$); sub get_auditlog_file($$$); sub cmd_connect($\%); sub cmd_service($\%); sub cmd_list(\%); sub rl_cmd_completion($$$); sub list_regex_match($); sub updateConfiguration(\%$); sub loadAuthorization(\%); sub readFile($$); sub log_debug($); sub log_error($); sub log_info($); sub pager($); # main() - Terminal server program # # The program loads the system authorizations for the effective user id, # and then initializes the term readline environment and starts the shell. # sub main() { unless (GetOptions("v|version" => sub { version(); exit 0; }, "h|help" => sub { usage(); exit 0; })) { log_error "Enter '$ts_shell --help' for more information"; exit 1; } unless (-t STDIN) { log_error "The $ts_shell requires a terminal to run on"; exit 1; } unless ($config{user}) { log_error "Resolving the name of user ID $> failed"; exit 2; } # update ts-shell configuration unless (readFile($config{conffile}, sub { updateConfiguration(%config, $_); })) { log_error "Reading the ts-shell configuration file " . "$config{conffile} failed: $!"; exit 3; } # load list of systems the ts-shell is allowed to use; and # hash the systems names in uppercase unless (readFile($config{sysfile}, sub { $config{termsys}->{uc $_} = 1; })) { log_error "Reading $config{sysfile} failed: $!"; exit 4; } # load list of systems for for that auditing should be enabled # hash the system names in uppercase unless (readFile($config{auditfile}, sub { $config{auditsys}->{uc $_} = 1; })) { log_error "Reading $config{auditsys} failed: $!"; exit 5; } # retrieve user group membership and load authorizations my %uniq_groups = (); # $) returns egid twice, so we hash group names to avoid dups foreach(map {$_ = getgrgid($_)} split / /, $)) { $uniq_groups{$_} = 1; } push @{$config{groups}}, keys %uniq_groups; # finally, load user specific authorization data from file unless (loadAuthorization(%config)) { log_error "Reading $config{authfile} failed: $!"; exit 5; } # set up terminal readline $config{rl} = new Term::ReadLine 'Terminal Server Shell'; $config{rl}->Attribs->{'completion_function'} = \&rl_cmd_completion; select ($config{rl}->OUT || \*STDOUT); # setup signal handler to ignore SIGINT my $sigint = POSIX::SigAction->new(sub { return 1; }, POSIX::SigSet->new(), &POSIX::SA_NODEFER); POSIX::sigaction(&POSIX::SIGINT, $sigint); log_debug "User: $config{user} / Groups: $) [@{$config{groups}}]"; log_debug("Using Term::ReadLine backend: " . $config{rl}->ReadLine); intro(); SHELL: while (defined($_ = $config{rl}->readline($config{prompt}))) { chomp; s/^\s+|\s+$//; next if /^$/; SWITCH: { /^(?:q|quit|exit)/ and last SHELL; /^help/ and help(), last SWITCH; /^version/ and version(), last SWITCH; /^(?:list|ls)/ and cmd_list(%config), last SWITCH; /^terminal\s*(\w*)/ and cmd_service($1, %config), last SWITCH; if (/^connect\s*(\w*$|\w+\s+\w+)/) { cmd_connect($1, %config); last SWITCH; } log_error "$_ is not a known command"; } $config{rl}->addhistory($_) if (/\S/ && !$config{rl}->Features->{autohistory}); } exit 0; } # updateConfiguration() - Load ts-shell configuration from ts-shell.conf # # %cfg: Hash reference to terminal server configuration # $line: Configuration line (key = value pair) # # The routine updates the ts-shell configuration based on configuration lines # from the ts-shell.conf file. # sub updateConfiguration(\%$) { my ($cfg, $line) = @_; my ($option, $value) = split /\s*=\s*/, $line; $cfg->{sysfile} = $value if $option =~ /^ts-systems$/; $cfg->{authfile} = $value if $option =~ /^ts-authorization$/; $cfg->{auditdir} = $value if $option =~ /^transcript-directory$/; $cfg->{auditfile} = $value if $option =~ /^transcript-systems$/; } # loadAuthorization() - Load system authorizations from file # # $cfg: Hash reference to terminal server configuration # # The routine parses the authorization data for the effective user # and stores the information in the configuration hash. Further, it # sets the auth_func reference to either auth_list() or auth_regex() # to abstract authorization checks. # sub loadAuthorization(\%) { my $cfg = shift(); return 0 unless open(AUTH, "<$cfg->{authfile}"); AUTH_ENT: while () { chomp; next if /^#/; # ignore comments next if /^\s*$/; # skip empty lines s/^\s+|\s+$//g; # trim my $authorized = 0; my ($key, $val) = split /\s*=\s*/; # read authorization configuration for user and its groups if ($key =~ /^$cfg->{user}$/) { $authorized = 1; log_debug "Found user: $key"; } elsif ($key =~ /^@(\S+)$/) { my $group = $1; $authorized = 1 if grep {/^${group}$/} @{$cfg->{groups}}; log_debug "Found group: $key" if $authorized; } # skip line if there was no auth data for current user next unless $authorized; # build authorization policy and set auth_func if ($val =~ /^list:\s*(.+)$/) { if (@{$cfg->{regex}}) { log_error "Authorization by list is ignored because " ."$cfg->{user} uses regular expressions " ."($cfg->{authfile}:$.)"; next AUTH_ENT; } $cfg->{auth_func} = \&auth_list; push @{$cfg->{systems}}, split /\s*[;,]\s*/, $1; } elsif ($val =~ /^file:(.+)$/) { if (@{$cfg->{regex}}) { log_error "Authorization by list is ignored because " . "$cfg->{user} uses regular expressions " . "($cfg->{authfile}:$.)"; next AUTH_ENT; } $cfg->{auth_func} = \&auth_list; unless(readFile($1, sub { push @{$cfg->{systems}}, $_; })) { log_error "Reading $1 failed: $! " . "($cfg->{authfile}:$.)"; } } elsif ($val =~ /^regex:(.+)$/) { if (@{$cfg->{systems}}) { log_error "Authorization by regular expression " . "is ignored because $cfg->{user} " . "uses lists ($cfg->{authfile}:$.)"; next AUTH_ENT; } # check regex syntax and complain if it is not correct my $re = eval "qr/$1/io"; if ($@) { log_error "An authorization entry is not a valid" . " regular expression " . "($cfg->{authfile}:$.):\n$@"; next AUTH_ENT; } $cfg->{auth_func} = \&auth_regex; push @{$cfg->{regex}}, $re; } else { log_error "An authorization entry is not valid " . "($cfg->{authfile}:$.)"; } log_debug "'$key' => '$val'"; } close(AUTH); return 1; } # readFile() - Helper routine to read data from file # # The routine reads the file content, line by line, skips # comments and empty lines. It calls a routine for each line # containing data. The routine can access the data either # using the first parameter or using the context variable $_. # # Error handling is up to the caller; the routine exits with # zero if the file could not be opened. open() should set $!. # # $file: File name # $sub: Reference to sub routine # sub readFile($$) { my ($file, $sub) = @_; return 0 unless open(CONF, "<$file"); while () { chomp; next if /^#/; # ignore comments next if /^\s*$/; # skip empty lines s/^\s+|\s+$//g; # trim &{$sub}($_); # execute sub in current context } close(CONF); return 1; } # auth_global() - Authorization check using global system list # # $guest: Scalar reference to the guest name string # $cfg: Hash reference to terminal server configuration # sub auth_global($$) { my ($guest, $cfg) = @_; return -1 unless $cfg->{termsys}; # unstricted return 1 if exists $cfg->{termsys}->{'[*ALL*]'}; # if restricted, check if system has been hashed (in uppercase) return 1 if exists $cfg->{termsys}->{uc $$guest}; # guest has not been defined, so deny authorization request return -2; } # auth_regex() - Authorization check using a regex # # $guest: Scalar reference to the guest name string # $cfg: Hash reference to terminal server configuration # sub auth_regex($$) { my ($guest, $cfg) = @_; return -1 unless @{$cfg->{regex}}; foreach my $re (@{$cfg->{regex}}) { return 1 if $$guest =~ /$re/; # $re has been compiled as //io } return 0; } # auth_list() - Authorization check using a list of systems # # $guest: Scalar reference to the guest name string # $cfg: Hash reference to terminal server configuration # sub auth_list($$) { my ($guest, $cfg) = @_; return -1 unless @{$cfg->{systems}}; return 1 if grep(/^\s*\Q$$guest\E\s*$/i, @{$cfg->{systems}}); return 0; } # get_auditlog_file() - Set file path for session transcript # # Return false if an error has occurred; otherwise true. # # $filepath: Scalar reference to file path variable # $guest: Name of the virtual guest machine # $cfg: Hash reference to terminal server configuration sub get_auditlog_file($$$) { my ($filepath, $guest, $cfg) = @_; unless (exists $cfg->{auditsys}->{'[*ALL*]'} || exists $cfg->{auditsys}->{uc $guest}) { $$filepath = ""; return 1; # no transcript, return success } # check and create log directory for user unless (-d "$cfg->{auditdir}/$cfg->{user}") { unless (mkdir "$cfg->{auditdir}/$cfg->{user}") { log_error "Creating the session transcript directory " . "$cfg->{auditdir}/$cfg->{user} failed: $!"; $$filepath = ""; return 0; # return error } } $$filepath = "$cfg->{auditdir}/$cfg->{user}/" . uc $guest . "_"; $$filepath .= strftime '%F-%H%M%S', localtime; log_debug "Session transcript file: $$filepath"; return 1; # return success } # cmd_connect() - Perform auth check; and on success, connect to system # # $guest: Name of the virtual guest machine # $cfg: Hash reference to terminal server configuration # sub cmd_connect($\%) { my ($params, $cfg) = @_; my $service = $cfg->{service}; unless (length($params)) { log_error "The z/VM guest name is missing"; return; } my ($guest, $srv) = split /\s+/, $params; $service = $srv if $srv && $srv =~ /\w{1,8}/; # check authorization: # The auth_func contract is to return a code, that must be one of: # -2: ts-shell is not permitted to connect to the guest # -1: The user does not have any authorization policy # 0: User does not have authorization to connect to guest # 1: User is permitted to connect to guest (success) # Other return codes are not allowed! # # call auth_func to check authorization my $rc = &{$cfg->{auth_func}}(\$guest, $cfg); # if the user is authorized, finally check if the system is # (globally) restricted to be used by the terminal server $rc = auth_global(\$guest, $cfg) if $rc > 0; if ($rc == -2) { log_error "$ts_shell is not configured to connect to $guest"; return; } if ($rc == -1) { log_error "You are not authorized to connect to " . "any z/VM virtual machines"; return; } if ($rc == 0) { log_error "You are not authorized to connect to $guest"; return; } # construct iucvconn command line my @iucvconn = ("$cfg->{iucvconn}"); # check if the terminal session to $guest requires session logging my $session_path = ""; # return if get_auditlog_file() failed return unless get_auditlog_file(\$session_path, $guest, $cfg); push @iucvconn, "-s", $session_path if $session_path; # add $guest name and terminal identifier (service) push @iucvconn, $guest, $service; log_info "Connecting to $guest (terminal identifier: $service)..."; if (-x $cfg->{iucvconn}) { system @iucvconn; log_info "Connection ended"; } else { log_error "Running $cfg->{iucvconn} failed: $!"; } } # cmd_service() - Show / set service/terminal name to identify remote terminal # # $new: New terminal name; can be empty # %cfg: Hash reference to terminal server configuration # sub cmd_service($\%) { my ($new, $cfg) = @_; unless ($new) { print "$cfg->{service}\n"; return; } if ($new =~ /\w{1,8}/) { $cfg->{service} = $new; # push new service for cmd completion unless (grep /$new/, @{$cfg->{services}}) { push @{$cfg->{services}}, $new; } } else { log_error("Terminal identifier $new is not valid"); log_error("Terminal identifiers consist of up to " . "eight alphanumerical characters"); } } # cmd_list() - List system authorization for effective user # # $cfg: Hash reference to terminal server configuration # sub cmd_list(\%) { my $cfg = shift(); if (@{$cfg->{regex}}) { pager sub { print "Regular expressions for your authorization:\n"; print "$_\n" foreach (@{$cfg->{regex}}); my $matches = list_regex_match($cfg); if (@$matches) { print "\nYou are authorized to connect to ". "these z/VM guest virtual machines:\n"; print "$_\n" foreach (@$matches); } }; } elsif (@{$cfg->{systems}}) { pager sub { foreach (@{$cfg->{systems}}) { printf "$_\n" if auth_global(\$_, $cfg) > 0; } }; } else { log_error "You are not authorized to connect to " . "any z/VM virtual machines"; } } sub log_debug($) { print STDERR "[DEBUG] " . shift() . "\n" if $ENV{'TS_SHELL_DEBUG'}; } sub log_error($) { print STDERR "$ts_shell: " . shift() . "\n"; } sub log_info($) { print "$ts_shell: " . shift() . "\n"; } sub pager($) { my $eval = shift(); unless (open(PAGER, "|$ENV{'PAGER'}")) { eval &$eval; return; } my $old_out = select PAGER; eval &$eval; select $old_out; close (PAGER); } sub intro() { print < Connect to specified z/VM guest virtual machine. terminal [] Display or set the terminal identifier. q | quit | exit Exit the current shell session. help Display help information. version Display version information. EoHelp } sub usage() { printf <{termsys}->{'[*ALL*]'}; # get a list of systems if ts shell runs unrestricted foreach my $re (@{$cfg->{regex}}) { foreach (grep (/$re/, keys %{$cfg->{termsys}})) { $uniq_sys{$_} = 1; } } push @result, (keys %uniq_sys); return \@result; } # rl_cmd_completion() - Term::ReadLine completion for ts-shell commands # # $text: Text to complete # $line: Line buffer # $start: offset # sub rl_cmd_completion($$$) { my ($text, $line, $start) = @_; my @cmds = qw/help version list connect terminal quit exit/; # complete commands return grep (/^$text/, @cmds) unless $start; # complete terminal names for service and optionally for connect command if ($line =~ /^(?:terminal|connect\s+\w+)\s+(?:\w+)?$/) { return grep (/^$text/, @{$config{services}}); } # complete systems for connect command if ($line =~ /^connect\s+(\w+)?$/) { my $systems = []; # list ref for completion # complete for simple lists if (@{$config{systems}}) { foreach my $sys (@{$config{systems}}) { if (auth_global(\$sys, \%config) > 0) { push @$systems, $sys; } } # complete if ts shell uses regex's } elsif (@{$config{regex}}) { $systems = list_regex_match(\%config); } return grep {/^$text/} @$systems; } return (); } # start the terminal server shell &main(); __DATA__ __END__ # vim: set ai noet ts=8 sw=8 tw=80: s390-tools-2.3.0/iucvterm/doc/000077500000000000000000000000001323410431100157435ustar00rootroot00000000000000s390-tools-2.3.0/iucvterm/doc/Makefile000066400000000000000000000010071323410431100174010ustar00rootroot00000000000000#! /usr/bin/make -f include ../../common.mak MANS = iucvconn.1 iucvtty.1 ts-shell.1 hvc_iucv.9 chiucvallow.8 ttyrun.8 all: check: install: install-man install-man: $(MANS) for man in $(MANS); do \ msection=`echo $$man |sed 's/.*\.\([1-9]\)$$/man\1/'` ; \ $(INSTALL) -g $(GROUP) -o $(OWNER) -m 644 -D $$man $(DESTDIR)$(MANDIR)/$$msection/$$man ; \ done clean: pdf: $(MANS) for man in $(MANS); do \ man -t ./$$man |ps2pdf -sPAPERSIZE=a4 - $${man}.pdf ; \ done .PHONY: install-man install clean pdf s390-tools-2.3.0/iucvterm/doc/chiucvallow.8000066400000000000000000000070501323410431100203560ustar00rootroot00000000000000.\" chiucvallow.8 .\" .\" .\" Copyright IBM Corp. 2009, 2017 .\" s390-tools is free software; you can redistribute it and/or modify .\" it under the terms of the MIT license. See LICENSE for details. .\" ---------------------------------------------------------------------- .TH chiucvallow "8" "March 2015" "s390-tools" "z/VM IUCV HVC device driver" . . .ds f \fBchiucvallow\fP . . .SH NAME lsiucvallow, chiucvallow \- List, change, and verify z/VM user ID filter settings . . . .SH SYNOPSIS .B lsiucvallow .br .B \*f .BR \-l | \-\-list .B \*f .BR \-e | \-\-edit .RI " [" filter ] .br .B \*f .BR \-s | \-\-set .I filter .br .B \*f .BR \-c | \-\-clear .B \*f .BR \-V | \-\-verify .I filter . . . .SH DESCRIPTION The \*f program lists, verifies, and changes the z/VM user ID filter of the z/VM IUCV hypervisor console (HVC) device driver. The z/VM user ID filter lists the z/VM guest virtual machines (user IDs) that are permitted to establish IUCV communication paths with the z/VM IUCV HVC device driver. The filter list is a comma-separated list of z/VM user IDs as specified by the \fBhvc_iucv_allow\fP kernel parameter. A filter entry might end with an asterisk (*) to match multiple z/VM user IDs. Use \*f to change the filter list at runtime. \*f works with the currently active filter or with filter files that list each z/VM user ID on a separate line. Filter files might also contain comment lines starting with '#'. .br You can edit and verify existing filter files and replace the current z/VM user ID filter. . . . .SH OPTIONS .TP .BR \-h ", " \-\-help Display a short help text, then exit. . .TP .BR \-v ", " \-\-version Display the version of \*f, then exit. . .TP .BR \-l ", " \-\-list Display the z/VM user IDs contained in the current filter. Using this parameter requires root authority. . .TP .BR \-e ", " \-\-edit " [" \fIfilter\fP ] Edit the current z/VM user ID filter. \fIfilter\fP specifies a filter file listing z/VM user IDs. If \fIfilter\fP is not specified, the entries of the current z/VM user ID filter are used. The filter is opened temporarily in an editor program. When the editor is closed, the changed filter is verified and, if verification succeeds, the current filter is replaced. Otherwise a backup copy is saved for correcting errors. Using this parameter requires root authority. . .TP .BR \-c ", " \-\-clear Clear the current z/VM user ID filter by deleting all user ID entries. Using this parameter requires root authority. . .TP .BR \-s ", " \-\-set " " "\fIfilter\fP" Replace the current filter with the filter entries specified by \fIfilter\fP. The current filter can be replaced only after the new filter has been successfully verified. Using this parameter requires root authority. . .TP .BR \-V ", " \-\-verify " " "\fIfilter\fP" Verify the z/VM user ID filter specified by \fIfilter\fP. The verification process checks the following rules: .RS 10 .IP "\(bu" 2 z/VM user IDs consist of up to eight alphanumeric characters or underscores (_) .IP "\(bu" 2 The total number of z/VM user IDs in the filter must not exceed 500 .IP "\(bu" 2 The filter size in bytes must not exceed 4095 bytes .RE . . . .SH "FILES" .TP .B /sys/module/kernel/parameters/hvc_iucv_allow sysfs attribute for accessing the \fBhvc_iucv_allow\fP kernel parameter. Use \*f to modify and access this sysfs attribute. . . . .SH "ENVIRONMENT" .TP .B EDITOR Specifies an editor program that is used for editing z/VM user ID filters. If \fBEDITOR\fP is not set or empty, .BR vi (1) is used. . . . .SH "SEE ALSO" .BR hvc_iucv (9) .I "Linux on System z - Device Drivers, Features, and Commands" s390-tools-2.3.0/iucvterm/doc/hvc_iucv.9000066400000000000000000000242411323410431100176460ustar00rootroot00000000000000.\" hvc_iucv.9 .\" .\" z/VM IUCV hypervisor console (HVC) device driver .\" .\" Copyright IBM Corp. 2008, 2017 .\" s390-tools is free software; you can redistribute it and/or modify .\" it under the terms of the MIT license. See LICENSE for details. .\" .\" ------------------------------------------------------------------------- .TH "hvc_iucv" "9" "March 2015" "s390-tools" "z/VM IUCV HVC device driver" . . . .SH NAME hvc_iucv \- Introduction to the z/VM IUCV hypervisor console (HVC) device driver . . . .SH SYNOPSIS .BR hvc_iucv "=" \fInumber\fP .BR hvc_iucv_allow "=" "\fIcomma-separated list of z/VM user IDs" . . . .SH DESCRIPTION The Hypervisor Console (HVC) device driver is a generic TTY device driver providing terminal devices with pluggable transport device drivers. The z/VM IUCV hypervisor console (HVC) device driver is a transport plug-in that uses z/VM IUCV communication to establish terminal connections and to transfer terminal data. The kernel parameter \fBhvc_iucv\fP controls the number of HVC terminals managed by the z/VM IUCV HVC device driver. \fInumber\fP is an integer in the range 0 to 8. If \fInumber\fP is zero, the z/VM IUCV HVC device driver is switched off; otherwise up to \fInumber\fP z/VM IUCV HVC terminal devices are created. .\"(The maximum of 8 is a hypervisor console layer constant and might change .\"in future kernel versions.) The \fBhvc_iucv_allow\fP kernel parameter optionally specifies a comma separated list of z/VM user IDs. If the kernel parameter has been specified, the z/VM IUCV HVC device driver accepts IUCV connections from listed z/VM user IDs only. The first z/VM IUCV HVC terminal device is registered automatically for use as the Linux console. The .BR iucvconn (1) program establishes connections to z/VM IUCV HVC terminal devices. If a terminal is disconnected, output written by Linux is not displayed or saved. Therefore, a newly opened connection to a terminal is always blank. For most applications, like login or shell prompts, it is sufficient to press "Return" to obtain a new prompt. . . . .SH USAGE .SS "Connecting to z/VM IUCV HVC terminal devices" Connections to z/VM IUCV HVC terminal devices are established with the IUCV terminal application .BR iucvconn (1). .PP To connect to the first z/VM IUCV HVC terminal device on the z/VM guest virtual machine "LNX1234", run: .PP .ft CW .in +0.25in .nf iucvconn LNX1234 lnxhvc0 .fi .in -0.25in .ft .PP The \fBlnxhvc\fP\fIn\fP identifier refers to the IUCV name of z/VM IUCV HVC terminal devices; where \fIn\fP corresponds to the terminal device number. .PP For z/VM IUCV HVC terminal devices, .BR iucvconn (1) does not set the terminal environment. See section "Setting up the terminal environment" below for further instructions. .PP The terminal connection is disconnected if the terminal session ends by logging off (for example, by typing exit). Alternatively, .BR iucvconn (1) provides an escape function for disconnecting from the terminal at any time. See also the manual page of .BR iucvconn (1). . .TP .B Note: If the first z/VM HVC terminal device is configured as the preferred Linux console, a logoff might be followed by a new login prompt. End the terminal session by using the disconnect escape function. Reconnecting always resumes the existing terminal session. Explicitly logging off before using the disconnect escape function assures that users must log in again. . . .SS "Using the magic sysrequest function on the first z/VM IUCV HVC terminal" The first z/VM IUCV HVC terminal, which can be used as Linux console, supports the "magic sysrequest" function if the Linux kernel has been built with \f(CWCONFIG_MAGIC_SYSRQ\fP. .PP To invoke the "magic sysrequest" function, press "Ctrl\^+\^o" followed by a second character that designates the debugging or emergency function. .PP The most important magic sysrequest functions are: .RS 4 .IP "0 .. 9" 8 Set the Linux console log level and control which kernel message are written to Linux console devices. . .IP "b" 8 Re-IPL immediately (without syncing or unmounting file systems). The re-IPL configuration is used for the re-IPL of the Linux system. . .IP "s" 8 Emergency sync all file file systems. . .IP "u" 8 Emergency remount all mounted file systems read-only. . .IP "t" 8 Show task info. . .IP "m" 8 Show memory. . .IP "e" 8 Send the TERM signal to end all tasks except .BR init (8). . .IP "i" 8 Send the KILL signal to end all tasks except .BR init (8). . .RE .PP See \fBsysrq.txt\fP of the Linux kernel documentation for a more complete list of functions. .PP This feature can be switched on or off during runtime by echoing "1" (on) or "0" (off) to \fB/proc/sys/kernel/sysrq\fP. . . . .SH CONFIGURATION .SS "Using the z/VM IUCV HVC terminal device as Linux console" The first z/VM IUCV HVC terminal device can display kernel messages and it can also be used as the preferred Linux console (i.e. become \fB/dev/console\fP). The preferred console is used as the initial input and output device, beginning at the stage of the boot process when the .BR init (8) program is called. Messages issued by programs that run at this stage are only displayed on the preferred console. .TP .B Note: Console messages are not displayed if the z/VM IUCV HVC terminal is not connected. If console messages flood the terminal, the z/VM HVC terminal device driver might discard console messages if they come in too fast. .PP To use the z/VM IUCV hypervisor console as the preferred console, append the \fBconsole=hvc0\fP parameter to the kernel command line. To display console messages only and use another device as preferred console, append \fBconsole=hvc0\fP, followed by an additional \fBconsole=\fP parameter that explicitly defines the preferred console, e.g. \fBconsole=ttyS0\fP. .TP .B Note: The last \fBconsole=\fP parameter designates the device for use as the preferred console. For Linux on System z, the default preferred console device is \fBttyS0\fP. . . .SS "Restricting terminal connections using a z/VM user ID filter" The z/VM IUCV HVC device driver can filter incoming connection requests based on the user ID of the originating z/VM guest virtual machine. The z/VM user ID filter is specified as a comma separated list of user IDs for the \fBhvc_iucv_allow\fP kernel parameter. If an user ID ends with an asterisk (*), only the characters up to the asterisk must match. You can use the asterisk to match multiple z/VM user IDs. The kernel parameter can be set initially on the kernel command line or through a sysfs attribute at runtime. For changing the parameter value at runtime, see the .BR chiucvallow (8) command. If the z/VM user ID filter is changed at runtime, the new filter applies only to new incoming connection requests. Existing terminal connections remain active until they get closed. Any re-connection attempt is subject to the new z/VM user ID filter. .TP .B Note: If z/VM user ID filtering is active, connection attempts from the z/VM guest virtual machine to itself are also subject to filtering. If the local z/VM user ID is not listed, local connection requests are refused. . . .SS "Configuring HVC terminals for user logins" Typically, \fBgetty\fP programs initialize terminals and prepare terminals for user logins. For each HVC terminal device, a \fBgetty\fP program must be started. To configure and start \fBgetty\fP on a HVC terminal, open .BR /etc/inittab (5), and add a new entry similar to this one: .ft CW .in +0.25in .nf h0:2345:respawn:/sbin/mingetty hvc0 .fi .in -0.25in .ft Add new entries for each HVC terminal device that is designated for user logins. You can use .BR ttyrun (8) to start a getty program on each HVC terminal device and to prevent respawns through the .BR init (8) program when a HVC terminal is not available. You can then change the number of HVC terminal devices without adding or removing entries every time. See .BR inittab (5) about the format of \fBinittab\fP entries; see .BR getty (8), or .BR mingetty (8) for options that are related to terminal setup and user login. . . .SS "Setting up HVC terminals for root logins" To allow root logins on HVC terminals, ensure that the respective terminal device names are listed in .BR /etc/securetty (5)\fR.\fP HVC terminal device names start with \fBhvc\fP followed by an integer in the range 0 to 7. . . .SS "Setting up the terminal environment" The terminal environment is changed by assigning the terminal name to the \fBTERM\fP environment variable. This can be done manually, or by a getty program. Complete the following steps for setting up the terminal environment for a z/VM IUCV HVC terminal: .RS 4 .IP "1." 4 To display the current terminal name, issue the command: .ft CW .in +0.25in .nf user@host:~$ echo $TERM xterm .fi .in -0.25in .ft .IP "2." 4 To connect to a z/VM IUCV HVC terminal, run .BR iucvconn (1) and log in as usual: .ft CW .in +0.25in .nf user@host:~$ iucvconn MYLNX01 lnxhvc0 ... user@MYLNX01:~$ .fi .in -0.25in .ft .IP "3." 4 To assign the terminal name from step 1. to the terminal environment variable, issue the following command: .ft CW .in +0.25in .nf user@MYLNX01:~$ export TERM=xterm .fi .in -0.25in .ft .RE If unsure which terminal name to use, assign "\f(CWlinux\fP" to the terminal environment variable. .PP For getty programs, a sample terminal environment configuration might look like: .ft CW .in +0.25in .nf h0:2345:respawn:/sbin/agetty -L 9600 hvc0 linux .fi .in -0.25in .ft The terminal name typically follows the terminal device name. In the example, .BR agetty (8) sets the terminal environment variable to "linux" before .BR login (1) is started. Some getty programs do not support the terminal name parameter, see the manual page of the respective getty program for more information. . . . .SH "DIAGNOSTICS" The z/VM IUCV hypervisor console device driver uses the kernel message infrastructure for its messages. The message prefix is \fBhvc_iucv\fP. . . . .SH "SEE ALSO" .BR iucvconn (1), .BR chiucvallow (8), .BR af_iucv (7), .BR bootparam (7), .BR inittab (5), .BR getty (8), .BR agetty (8), .BR mingetty (8), .BR ttyrun (8), .BR login (1), .BR securetty (5) See .BR kernel-parameters.txt " and" .BR sysrq.txt of the Linux kernel documentation for more information about kernel boot parameters and the magic sysrequest function. .I "Linux on System z - Device Drivers, Features, and Commands" s390-tools-2.3.0/iucvterm/doc/iucvconn.1000066400000000000000000000146211323410431100176550ustar00rootroot00000000000000.\" iucvconn.1 .\" .\" .\" Copyright IBM Corp. 2008, 2017 .\" s390-tools is free software; you can redistribute it and/or modify .\" it under the terms of the MIT license. See LICENSE for details. .\" ---------------------------------------------------------------------- .TH iucvconn "1" "March 2009" "s390-tools" "IUCV terminal applications" . .ds i \fBiucvconn\fP . . . .SH NAME iucvconn \- start terminal connection over z/VM IUCV . . . .SH SYNOPSIS .B iucvconn .RB [ \-h | \-\-help ] .br .B iucvconn .RB [ \-v | \-\-version ] .br .B iucvconn .RB [ \-e | \-\-escape-char .IR escape_char ] .RB [ \-s | \-\-sessionlog .IR log_file ] .I vm_guest .I terminal_id . . . .SH DESCRIPTION The \*i application establishes a terminal connection to another Linux instance. The Linux instance where \*i runs and the target Linux instance must be z/VM guest operating systems of the same z/VM instance. The communication path between both virtual machines is based on the z/VM Inter-User Communication Vehicle (IUCV). Because z/VM IUCV is independent from TCP/IP, Linux instances with no external network can be accessed. .PP \*i accesses the terminal specified by \fIterminal_id\fP on the z/VM guest virtual machine \fIvm_guest\fP. The \fIterminal_id\fP is similar to a port number in TCP/IP communications. . \fIterminal_id\fP is case-sensitive and consists of up to 8 alphanumeric characters. \fIterminal_id\fP must either identify a running \fBiucvtty\fP instance, or a terminal device provided by the z/VM IUCV hypervisor console (HVC) device driver. . . . .SH OPTIONS .TP .BR \-h ", " \-\-help Display a short help text, then exit. . .TP .BR \-v ", " \-\-version Display the version number of \*i, then exit. . .TP .BR \-e ", " \-\-escape-char\~\fIescape_char\fP Set an escape character for this terminal session. You need an escape character to access special \*i functions. The default escape character is underscore (_). If \fIescape_char\fP is set to "none", escaping is not possible. The escape character can be the closing bracket (]), the caret (^), the underscore (_), or any alphabetical character except C, D, Q, S and Z. The \fIescape_char\fP character is not case-sensitive. To invoke a \*i function, press Ctrl\^+\^\fIescape_char\fP, followed by the function character. For example, press "Ctrl\^+\^_ d" to disconnect the terminal session and exit \*i. For details, see section "ESCAPE CHARACTERS" below. . .TP .BR \-s ", " \-\-sessionlog\~\fIlogfile\fP Create a transcript of the terminal session and write session data to three different files. \fIlog_file\fP contains the raw terminal data stream. The .I log_file\fB.timing\fP file contains timing data that can be used for replaying the raw terminal data stream using realistic output delays. Additional terminal session information is logged to .IR log_file\fB.info\fP "." If any of these files already exist, \*i exits with an error. To proceed either delete the files or choose another file name for \fIlog_file\fP. . . . .SH "ESCAPE CHARACTERS" \*i supports functions through the use of an escape character. The default escape character (\fIescape_char\fP) is underscore (_). To enter the "escape mode" of \*i, press the Control and the \fIescape_char\fP key together ("Ctrl\^+\^_") In the "escape mode", \*i expects a single character designating the special function. The special functions are (assuming \fIescape_char\fP is set to underscore (_)): .RS 4 .IP "\fBCtrl\^+\^_\fP" 8 Send the escape character to the connected terminal. .IP "\fB.\fP or \fBd\fP" 8 Disconnect and exit \*i. .IP "\fBr\fP" 8 Force resizing of the connected terminal. .RE .PP Any other single character is ignored and is not send to the terminal. If multiple characters are entered together, \*i leaves the "escape mode" and sends the characters to the connected terminal. . .\" FIXME: change "connected terminal" to terminal . . .SH "EXAMPLES" To access the "lnxterm" terminal on the Linux instance in z/VM guest virtual machine LNXSYS01: .PP .ft CW .in +0.25in .nf iucvconn LNXSYS01 lnxterm .fi .in -0.25in .ft .PP To access the first z/VM IUCV HVC terminal on the Linux instance in z/VM guest virtual machine LNXSYS02: .PP .ft CW .in +0.25in .nf iucvconn LNXSYS02 lnxhvc0 .fi .in -0.25in .ft .PP To create a transcript of the terminal session to the Linux instance in z/VM guest virtual machine LNXSYS99: .PP .ft CW .in +0.25in .nf iucvconn -s ~/transcripts/lnxsys99 LNXSYS99 lnxhvc0 .fi .in -0.25in .ft . . . .SH ENVIRONMENT .TP .B TERM The terminal environment variable contains the name of the terminal (e.g. linux, xterm, ...) and also specifies the terminal capabilities. \*i transfers the value of the \fBTERM\fP environment variable at connection startup to the target system. The \fBiucvtty\fP application sets the \fBTERM\fP environment variable on the target system before the login program is started. Setting the \fBTERM\fP environment variable ensures that the terminal capabilities are the same on both sides of the connection. The z/VM IUCV hypervisor console (HVC) device driver ignores the content of the \fBTERM\fP environment variable transferred by \*i. See the .BR hvc_iucv (9) manual page for instructions to configure the terminal environment. . . . .SH DIAGNOSTICS If the Linux kernel does not include kernel support for the AF_IUCV network addressing family, \*i exits and displays the message .I 'AF_IUCV address family is not available: Address family not supported by .IR protocol' "." . . . .SH SECURITY .SS Linux The \*i program does not require superuser privileges and can be used by regular users. Each connection attempt and failure is logged to the .BR authpriv syslog facility. .\".PP .\"Depending on the login program started by the \fBiucvtty\fP program, .\"the user must authenticate to the remote system. .PP Terminal session transcripts facilitate logging and auditing of terminal activity on remote systems or recording and playing back terminal sessions, for example for education and presentations. . . .SS z/VM guest virtual machine The z/VM guest virtual machine must have authorization to establish IUCV communication paths to other z/VM guest virtual machines. See the .BR af_iucv (7) manual page for details about IUCV authorizations. . . . .SH NOTES The Linux instance where \*i runs and the target Linux instances must be z/VM guest operating systems of the same z/VM instance. . . . .SH "SEE ALSO" .BR iucvtty (1), .BR hvc_iucv (9), .BR af_iucv (7), .BR scriptreplay (1) .I "Linux on System z - Device Drivers, Features, and Commands" s390-tools-2.3.0/iucvterm/doc/iucvtty.1000066400000000000000000000117161323410431100175420ustar00rootroot00000000000000.\" iucvtty.1 .\" .\" .\" Copyright IBM Corp. 2008, 2017 .\" s390-tools is free software; you can redistribute it and/or modify .\" it under the terms of the MIT license. See LICENSE for details. .\" ---------------------------------------------------------------------- .TH iucvtty "1" "March 2009" "s390-tools" "IUCV terminal applications" . .ds t \fBiucvtty\fP .ds i \fBiucvconn\fP . . . .SH NAME iucvtty \- allow remote logins over z/VM IUCV . . . .SH SYNOPSIS .B iucvtty .RB [ \-h | \-\-help ] .br .B iucvtty .RB [ \-v | \-\-version ] .br .B iucvtty .RB [ \-a | \-\-allow-from .IR regex ] .IR terminal_id .RB [\-\- .IR login_program " [" login_options ]] . . . .SH DESCRIPTION The \*t application provides full-screen terminal access to a Linux instance running as a z/VM guest operating system. The \*i application is used to connect to a running \*t instance. The terminal connection is based on the z/VM Inter-User Communication Vehicle (IUCV). z/VM IUCV establishes a communication path between two z/VM guest virtual machines on the same z/VM instance. Because z/VM IUCV is independent from TCP/IP, Linux instances with no external network can be accessed. \fIterminal_id\fP identifies the z/VM IUCV connection and is similar to a port number in TCP/IP communications. \fIterminal_id\fP is case-sensitive and consists of up to 8 alphanumeric characters. It must be specified as a parameter in connection requests against a \*t instance. When a connection is established, \*t starts a login program. When the login program ends, \*t also exits. Consider an .BR inittab (5) entry .\"or an upstart job file to ensure that \*t is restarted and ready for the next terminal session. . . . .SH OPTIONS .TP .BR \-\^h ", " \-\^\-help Display a short help text and exit. . .TP .BR \-\^v ", " \-\^\-version Display the version number of \*t, then exit. . .TP .BR \-\^a ", " \-\^\-allow-from " " \fIregex\fP Limit permissions for incoming connections to z/VM user IDs that match the regular expression \fIregex\fP. The connection is refused if the ID does not match. If this parameter is omitted, connections are permitted from any z/VM user ID. . .TP .I login_program \fIlogin_program\fP specifies the absolute path to the login program to be started when a connection is established. The default is .BR /bin/login (1). .br The \fIlogin_program\fP must be separated by two dashes (--). . .TP .I login_options Depending on the particular login program used, \fIlogin_options\fP specifies additional options. . . . .SH EXAMPLES To allow remote logins using the terminal identifier "lnxterm": .PP .ft CW .in +0.25in .nf iucvtty lnxterm .fi .in -0.25in .ft To only allow users from LNXSYS01 to connect to terminal "lnxterm": .PP .ft CW .in +0.25in .nf iucvtty -a LNXSYS01 lnxterm .fi .in -0.25in .ft To only allow users from LNXSYS10 through LNXSYS19 to connect to terminal "lnxterm": .PP .ft CW .in +0.25in .nf iucvtty -a "LNXSYS1[0-9]" lnxterm .fi .in -0.25in .ft To use \fB/sbin/sulogin\fP instead of \fB/bin/login\fP for terminal "suterm": .PP .ft CW .in +0.25in .nf iucvtty suterm -- /sbin/sulogin .fi .in -0.25in .ft An entry in \fB/etc/inittab\fP to facilitate user logins on terminal "lnxterm" with \fB/bin/login\fP could be: .PP .ft CW .in +0.25in .nf t1:2345:respawn:/usr/bin/iucvtty lnxterm .fi .in -0.25in .ft An entry in \fB/etc/inittab\fP to facilitate user logins on terminal "suterm" with \fB/sbin/sulogin\fP in single user mode could be: .PP .ft CW .in +0.25in .nf s1:S:respawn:/usr/bin/iucvtty suterm -- /sbin/sulogin .fi .in -0.25in .ft . . . .SH DIAGNOSTICS If the Linux kernel does not include kernel support for the AF_IUCV network addressing family, \*t exits and displays the message .I 'AF_IUCV address family is not available: Address family not supported by .IR protocol' "." . . . .SH SECURITY .SS Linux The \*t program can be used by regular users. Depending on the particular login program, \*t must be started with superuser privileges for user authentication and authorization. For instance, \fB/bin/login\fP requires superuser privileges and, thus, regular users must use a different login program. Each connection attempt is logged to the \fBauthpriv\fP syslog facility. \*t uses pseudo-terminal (pts) devices to communicate with the login program. For security reasons, some login programs, like \fB/bin/login\fP, do not permit root logins on pseudo-terminal devices (see also .BR /etc/securetty "(5))." To permit root logins, consider using HVC terminal devices that are provided by the z/VM IUCV hypervisor console (HVC) device driver. .\"Enabling root logins on pseudo-terminal devices can compromise system security. .\"To avoid this potential security exposure, consider using HVC terminal devices .\"that are provided by the z/VM IUCV hypervisor console (HVC) device driver. . . . .SS z/VM guest virtual machine See the .BR af_iucv (7) manual page for details about IUCV authorizations. . . . .SH "SEE ALSO" .BR iucvconn (1), .BR login (1), .BR pts (4), .BR regex (7), .BR securetty (5), .BR af_iucv (7), .BR hvc_iucv (9) s390-tools-2.3.0/iucvterm/doc/ts-shell.1000066400000000000000000000404451323410431100175670ustar00rootroot00000000000000.\" ts-shell.1 .\" .\" .\" Copyright IBM Corp. 2008, 2017 .\" s390-tools is free software; you can redistribute it and/or modify .\" it under the terms of the MIT license. See LICENSE for details. .\" ---------------------------------------------------------------------- .TH "ts-shell" "1" "March 2009" "s390-tools" "Terminal Server over IUCV" . . . .SH "NAME" ts\-shell \- Login shell for terminal servers over z/VM IUCV . . . .SH "SYNOPSIS" .B ts\-shell .RB [ \-h | \-\-help ] .br .B ts\-shell .RB [ \-v | \-\-version ] . . . .SH "DESCRIPTION" \fBts-shell\fP is a login shell for terminal server environments using the IUCV terminal applications. \fBts-shell\fP authorizes Linux users based on user names and group memberships for accessing terminals. Linux users can list the authorizations and access terminals. If a user is authorized to access a terminal, \fBts-shell\fP establishes the terminal connection using the .BR iucvconn (1) program. Apart from \fBiucvconn\fP the IUCV terminal applications include \fBiucvtty\fP. .BR iucvtty (1) provides full-screen terminal access to a Linux instance running as a z/VM guest operating system. .BR iucvconn (1) can also establish terminal connections to z/VM IUCV hypervisor console (HVC) device drivers. The Hypervisor Console (HVC) is a generic TTY device driver for the Linux kernel providing terminals. One of the terminals can be used as the Linux console. The Linux instances where \fBts-shell\fP and \fBiucvconn\fP run and the target Linux instance must be z/VM guest operating systems of the same z/VM instance. Because z/VM IUCV is independent from TCP/IP, you can access Linux instances with no external network connection. . . . . .SH "OPTIONS" .TP .BR \-\^h ", " \-\^\-help Display a short help text, then exit. . .TP .BR \-\^v ", " \-\^\-version Display the version information, then exit. . . . .SH "USAGE" .SS "Terminal server shell commands" The terminal server shell provides the following commands: .PP .TP 4 .B list The \fBlist\fP command lists z/VM guest virtual machines to which the Linux user is authorized to connect. The output of the \fBlist\fP command depends on the configured authorization method which can be "list" or "regex". The available authorization methods are explained in section "Configure terminal authorization for Linux users". The output for "list" authorization is a list of z/VM guest virtual machines, for example: .ft CW .in +0.25i .nf user@ts-shell> list guest1 guest2 guest3 guest5 .fi .in -0.25i .ft The output for "regex" authorization is a list of one or more regular expressions, for example: .ft CW .in +0.25i .nf user@ts-shell> list Regular expressions for your authorization: (?i-xsm:lnx\\w{5}) (?i-xsm:^palim$) .fi .in -0.25i .ft If \fBts-shell\fP is configured to connect to particular z/VM guest virtual machines only, the output for "regex" authorization is followed by a list of the user IDs that match at least one of the regular expressions: .ft CW .in +0.25i .nf user@ts-shell> list Regular expressions for your authorization: (?i-xsm:lnx\\w{5}) (?i-xsm:^palim$) You are authorized to connect to these z/VM guest virtual machines: LNXSYS42 LNXSYS01 .fi .in -0.25i .ft . .TP 4 .B connect \fIvm_guest\fP \fR[\fP\fIterminal_id\fP\fR]\fP \fBconnect\fP establishes a terminal connection to a particular z/VM guest virtual machine specified as \fIvm_guest\fP. \fIvm_guest\fP consists of up to eight alphanumeric characters. An optional terminal identifier can be specified with \fIterminal_id\fP. If not specified, the default terminal identifier is used. To change the default terminal identifier, use the \fBterminal\fP command. In the following example, a user opens a terminal connection to the Linux instance in z/VM guest virtual machine LNXSYS01: .ft CW .in +0.25i .nf user@ts-shell> connect LNXSYS01 ts-shell: Connecting to LNXSYS01 (terminal identifier: lnxterm)... ... ts-shell: Connection ended .fi .in -0.25i .ft . .TP 4 .B terminal \fR[\fP\fIidentifier\fP\fR]\fP The \fBterminal\fP command displays or sets the default terminal identifier that is used by subsequent \fBconnect\fP commands. \fIidentifier\fP is case-sensitive and consists of up to eight alphanumeric characters. If \fBterminal\fP is called with the \fIidentifier\fP being specified, \fIidentifier\fP is set as the new default terminal identifier. If \fIidentifier\fP is not specified, the current default terminal identifier is displayed: .ft CW .in +0.25i .nf user@ts-shell> terminal lnxterm .fi .in -0.25i .ft . .TP 4 .BR quit ", " exit Exit the terminal server shell session. . .TP 4 .B help Display the help about terminal server shell commands. . .TP 4 .B version Display the \fBts-shell\fP version. . . . .SH "CONFIGURATION" To set up a Linux system as a terminal server and to use \fBts-shell\fP for Linux users, complete the following configuration steps: .IP "1." 4 Authorize the terminal server z/VM guest virtual machine for IUCV. .IP "2." 4 Create a terminal server shell configuration file. .IP "3." 4 List z/VM guest virtual machines providing terminal access over IUCV. .IP "4." 4 Configure terminal session transcripts. .IP "5." 4 Configure terminal authorizations for Linux users. .IP "6." 4 Install \fBts-shell\fP as the login shell for Linux users. . . .SS "Authorize the terminal server z/VM guest virtual machine for IUCV" The z/VM guest virtual machine on which the terminal server shell runs needs particular authorization to establish IUCV communication paths to other z/VM guest virtual machines. A typical \fBIUCV\fP authorization statement in the z/VM directory entry of the terminal server z/VM guest virtual machine might be: .PP .ft CW .in +0.25in .nf IUCV ANY OPTION MAXCONN 256 .fi .in -0.25in .ft .PP The example allows the terminal server shell to establish IUCV communication paths with any z/VM guest virtual machine. The number of IUCV connections is limited to 256. See the .BR af_iucv (7) manual page for further details. . . .SS "Create a terminal server shell configuration file" When \fBts-shell\fP starts, it reads its configuration from the \fB/etc/iucvterm/ts-shell.conf\fP configuration file. The file contains configuration options that specify further configuration files with lists of z/VM guest virtual machines and terminal authorization definitions. .PP Supported configuration options (with default settings) are: .RS 4 .TP .BR ts-systems " = " \fI/etc/iucvterm/ts-systems.conf\fP The \fBts-systems\fP configuration option specifies a file that lists z/VM guest virtual machines. \fBts-shell\fP permits connections to these z/VM guest virtual machines only. See also section "List z/VM guest virtual machines providing terminal access over IUCV". . .TP .BR ts-authorization " = " \fI/etc/iucvterm/ts-authorization.conf\fP The \fBts-authorization\fP option specifies a file containing the terminal authorization definitions for Linux users. See section "Configure terminal authorization for Linux users" about the file format. . .TP .BR transcript-systems " = " \fI/etc/iucvterm/ts-audit-systems.conf\fP The \fBtranscript-systems\fP option specifies a file that lists z/VM guest virtual machines for which terminal sessions are logged. See section "Configure terminal session transcripts" for details. . .TP .BR transcript-directory " = " \fI/var/log/ts-shell\fP The \fBtranscript-directory\fP option specifies a directory where the terminal session transcripts are saved. See section "Configure terminal session transcripts" for details. . .RE . . .SS "List z/VM guest virtual machines providing terminal access over IUCV" \fBts-shell\fP establishes terminal connections only if a Linux user has been authorized. In some cases, the administrator might want to explicitly restrict connections to particular z/VM guest virtual machines independent of the user. The \fBts-systems\fP configuration option specifies a file that lists z/VM guest virtual machines to which \fBts-shell\fP is permitted to connect. The file lists each z/VM guest virtual machine on a separate line. If a line contains "[*ALL*]", \fBts-shell\fP is permitted to connect to any z/VM guest virtual machine. . .TP .B Note: The \fBts-systems\fP options applies to the \fBts-shell\fP program only. If necessary, further restrictions can be configured for the z/VM guest virtual machine itself using the \fBIUCV\fP z/VM directory statement. See the section about IUCV authorizations in the .BR af_iucv (7) manual page. . . .SS "Create lists of z/VM guest virtual machines" A convenient method for creating lists of z/VM guest virtual machines is to use the information from the z/VM user directory, which contains all the names of the z/VM guest virtual machines that are defined on a z/VM operating system instance. For example, to create a list of all z/VM guest virtual machines with names that start with "LINUX" and are followed by digits, use: .ft CW .in +0.25in .nf vmur receive -H -t 1234 -O |grep -E "^USER LINUX[0-9]+" |cut -d" " -f2 .fi .in -0.25in .ft Spool ID 1234 refers to the z/VM user directory file in the z/VM virtual reader device. .br The output of the command can be saved in a file. The file can then be specified for the .BR ts-systems " or " transcript-systems configuration options in the \fBts-shell.conf\fP file. In addition, use these files to configure list authorizations. . . .SS "Configure terminal session transcripts" \fBts-shell\fP can create transcripts of terminal sessions to z/VM guest virtual machines. The \fBts-audit-systems.conf\fP configuration file lists z/VM guest virtual machines for which terminal sessions are logged. If the file contains "[*ALL*]", each terminal session is logged. To create a list of z/VM guest virtual machines, see section "Create lists of z/VM guest virtual machines". For saving the terminal session transcripts, \fBts-shell\fP requires a directory that is specified by the \fBtranscript-directory\fP option in the \fBts-shell.conf\fP configuration file. .TP .B Note: The terminal session transcript directory must be writable by all \fBts-shell\fP users. The system administrator might use a "ts-shell" group containing all \fBts-shell\fP users as members. The directory can be made writable for the "ts-shell" group only. .PP \fBts-shell\fP uses a combination of the Linux user name, z/VM guest virtual machine and a time stamp for creating new terminal session transcript files. The format is as follows: .br .RS 4 .RI "/var/log/ts-shell/" user_name "/" VMGUEST "_" YY "-" MM "-" DD "-" HHMMSS .RE .PP Terminal session transcripts consist of three different files: the raw terminal data stream, timing data information and connection information. See .BR iucvconn (1) for more details about terminal session transcripts. . . .SS "Configure terminal authorizations for Linux users" \fBts-shell\fP performs authorization checks for Linux users before connecting to z/VM guest virtual machines. The authorization configuration grants Linux users or groups to establish terminal connections only to particular z/VM guest virtual machines. These authorization definitions are stored in the \fBts-authorization.conf\fP configuration file. This configuration file consists of authorization mappings where mappings can be created for Linux users or groups. For the specification of z/VM guest virtual machines, a list or regular expression is used. .br A Linux user is referenced by the user name; a Linux group is referenced by the group name and prefixed with "@". Here is an example of a Linux user and group authorization: .PP .ft CW .in +0.25in .nf alice = list:guest01,guest02 @users = list:guest03,guest04 .fi .in -0.25in .ft .PP To create lists of z/VM guest virtual machines, use the following prefixes: .RS 4 .IP "\fIlist:\fP" 8 followed by a comma-separated list of names. . .IP "\fIfile:\fP" 8 followed by a file path. The file lists z/VM guest virtual machines, each name on a separate line. .RE . .PP The following example shows the usage of the \fIfile:\fP prefix: .PP .ft CW .in +0.25in .nf @testgrp = file:/etc/iucvterm/auth/test-systems.list @prodgrp = file:/etc/iucvterm/auth/production-systems.list .fi .in -0.25in .ft .PP See section "Create lists of z/VM guest virtual machines" above about creating lists of z/VM guest virtual machines with names that match a specific pattern. . .PP Instead of listing each z/VM guest virtual machine individually, regular expressions can be used to match names of z/VM guest virtual machines. If naming schemes exist for z/VM guest virtual machines, using regular expressions might be more efficient and allow for future additions. .br The \fIregex:\fP prefix starts the definition of a regular expression to match the names of z/VM guest virtual machines. The regular expression must be a Perl-compatible or an extended regular expression (ERE) as documented in POSIX. Basic regular expressions (BRE) cannot be used. See .BR regex (7) for POSIX extended regular expressions; and the Perl reference manual .BR perlre about regular expression in Perl. To authorize user bob for all z/VM guest virtual machines with names that start with "lnx" and are followed with at least three but not more than five alphanumeric characters, use: .PP .ft CW .in +0.25in .nf bob = regex:lnx\\w{3,5} .fi .in -0.25in .ft .PP . If a naming scheme exists for z/VM guest virtual machines belonging to the test or production environment: authorize all users in the "testgrp" group for all systems in the test environment; and respectively, authorize all users in the "prodgrp" group for all systems in the production environment: .PP .ft CW .in +0.25in .nf @testgrp = regex:test\\w+ @prodgrp = regex:prod\\w+ .fi .in -0.25in .ft . .PP You can have multiple authorizations for the same user, either directly through user authorizations or indirectly through authorizations for groups that the user is a member of. Be aware that \fBts-shell\fP accepts only one type of authorization, list or regex, for a particular user. The first type of authorization that is found for a user sets the authorization type for this user. Further authorizations of the same type are accumulated. Authorizations of the other type are ignored. Example: .PP .ft CW .in +0.25in .nf @users = list:guest01,guest03,guest05 alice = list:guest02,guest04 eve = regex:guest0[7890] .fi .in -0.25in .ft .PP If both alice and eve are members of group users, alice is authorized for guest01, guest02, guest03, guest04, and guest05. For eve, the regular expression is ignored and the authorizations are for guest01, guest03, guest05 as defined for the group. . . . .SS "Install ts-shell as login shell for Linux users" To use the \fBts-shell\fP as the login shell for Linux users, follow these steps: .IP "1." 4 Add the path of the \fBts-shell\fP program to the \fI/etc/shells\fP file that contains the list of valid login shells: .PP .ft CW .in +0.25in .nf echo $(which ts-shell) >> /etc/shells .fi .in -0.25in .ft .PP . .IP "2." 4 Change the login shell of a particular Linux user using the .BR chsh (1) program: .PP .ft CW .in +0.25in .nf chsh -s $(which ts-shell) alice .fi .in -0.25in .ft . . . .SH "FILES" .TP .B /etc/iucvterm/ts-shell.conf General terminal server shell configuration file. . .TP .BR /etc/iucvterm/ts-systems.conf ", " /etc/iucvterm/unrestricted.conf The \fBts-systems.conf\fP file lists z/VM guest virtual machines to which connections are permitted. \fBunrestricted.conf\fP contains "[*ALL*]" to permit .BR ts-shell (1) to connect to any z/VM guest virtual machine. The \fBts-systems\fP configuration option in the \fBts-shell.conf\fP file might specify one of these files. . .TP .B /etc/iucvterm/ts-authorization.conf The \fBts-authorization.conf\fP file grants Linux users or groups to establish terminal connections only to particular z/VM guest virtual machines. . .TP .B /etc/iucvterm/ts-audit-systems.conf The \fBts-audit-systems.conf\fP file lists z/VM guest virtual machines for which terminal sessions are logged. . .TP .B /var/log/ts-shell Directory for saving terminal session transcripts. . . . .SH "ENVIRONMENT" .TP .B PAGER The \fBPAGER\fP environment variable designates a program used as pager for the \fBlist\fP command of the terminal server shell. If \fBPAGER\fP is not set or empty, .BR less (1) is used. . .TP .B LESSSECURE \fBts-shell\fP sets this variable to run .BR less (1) in "secure" mode. See the SECURITY section in the .BR less (1) man page. . . . .SH "SEE ALSO" .BR iucvconn (1), .BR iucvtty (1), .BR af_iucv (7), .BR less (1), .BR chsh (1), .BR shells (5), .BR regex (7), .BR perlre .I "Linux on System z - Device Drivers, Features, and Commands" .br .I "z/VM CP Planning and Administration" s390-tools-2.3.0/iucvterm/doc/ts-shell/000077500000000000000000000000001323410431100174765ustar00rootroot00000000000000s390-tools-2.3.0/iucvterm/doc/ts-shell/README.ts-shell000066400000000000000000000107321323410431100221130ustar00rootroot00000000000000Terminal server setup ===================== This README file provides additional information for configuring a terminal server. Installation overview --------------------- The s390-tools package installs the ts-shell(1) program along with a set of configuration files in `/etc/iucvterm`. The ts-shell program is pre-configured to connect to any other z/VM guest virtual machines (if the z/VM guest virtual machine has the respective IUCV authorizations). The administrator can change the ts-shell configuration to restrict connections to particular z/VM guest virtual machines only. There are no user authorizations by default. The administrator must edit `/etc/iucvterm/ts-authorization.conf` to assign authorizations. See the manual page for ts-shell(1) and the `authorization-sample.conf` file in the documentation directory for configuration examples. In addition, the documentation directory also contains the iucvconn_on_login sample program that is an alternative to ts-shell. Setup considerations for the terminal server shell (ts-shell) ------------------------------------------------------------- Adding new ts-shell users ~~~~~~~~~~~~~~~~~~~~~~~~~ The ts-shell installation creates a system group ts-shell. If you intend to use ts-shell as a login shell for users, ensure that these users are all members of ts-shell. To add existing users to group ts-shell, use +usermod -G ts-shell 'username'+. The ts-shell configuration files and `/var/log/ts-shell` are readable only by members of the *ts-shell* group. Enabling terminal session transcripts ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ts-shell(1) can be configured to create transcripts of terminal sessions to particular z/VM guest virtual machines. The transcripts are written to log files in the `/var/log/ts-shell` directory. NOTE: The `/var/log/ts-shell` directory permission has the set-group-ID bit set. Sub-directories that are created by different users will inherit the group ownership of the `/var/log/ts-shell` directory. See the ts-shell(1) manual page for more information about terminal session transcripts. Setting up command completion (optional) ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ *ts-shell* supports completion for commands and, if possible, for z/VM guest virtual machines. The completion is triggered using the 'Tab' key. Command completion is an optional feature. To enable command completion, install one of the following Perl ReadLine libraries: - Term::ReadLine::Gnu - Term::ReadLine::Perl Check your Linux distribution for packages that provide these Perl ReadLine interfaces. Setup a terminal server using the iucvconn_on_login program ----------------------------------------------------------- *iucvconn_on_login* is a sample script that uses the iucvconn(1) program to establish an IUCV terminal connection to a Linux instance that runs as z/VM guest operating system. It can be used as an alternative to set up a terminal server. The idea of iucvconn_on_login is to establish a terminal connection at login time. The z/VM guest virtual machine to which iucvconn_on_login will connect is the name of the Linux user that logs in. For example, if you log in as Linux user "lxguest1", iucvconn_on_login is started and establishes a terminal connection to the z/VM guest virtual machine lxguest1. Adding new users for z/VM guest virtual machines ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ To set up a terminal server using the iucvconn_on_login program, create a new Linux user for each z/VM guest virtual machine using the z/VM user ID as name. Set the login shell of those users to *iucvconn_on_login*. NOTE: User names are case-sensitive in Linux. To ensure consistency, always use either lower or upper case characters for your user names. To create a new user for z/VM guest virtual machine LXGUEST1 ---- useradd -s /usr/bin/iucvconn_on_login lxguest1 ---- If you log in as user 'lxguest1' on the terminal server (for example using ssh(1)), *iucvconn_on_login* immediately establishes the terminal connection to the z/VM virtual guest virtual machine 'lxguest1'. To access the default terminal ('lnxhvc0') on the Linux instance in z/VM guest virtual machine LXGUEST1 ---- ssh lxguest1@terminal.server ---- To access the terminal 'lnxterm' on the Linux instance in z/VM guest virtual machine LXGUEST1 ---- ssh -t lxguest1@terminal.server lnxterm ---- For ssh(1), you must use the "-t" parameter if you want to specify an alternate terminal identifier. The "-t" parameter ensures that ssh starts iucvconn_on_login on a terminal. s390-tools-2.3.0/iucvterm/doc/ts-shell/authorization-sample.conf000066400000000000000000000014331323410431100245250ustar00rootroot00000000000000# Terminal server authorization configuration # # See ts-shell(1) manual page for file format. ## Examples -- System lists # alice is authorized to connect to guest1 .. guest5 alice = list:guest1,guest2,guest3,guest4,guest5 ## bob is authorized to connect to systems listed in the file "test-systems.list" bob = file:/etc/iucvterm/systems/test-systems.list # authorize all members of the Linux 'admins' group to connect to systems # listed in the file "admin-systems.list" @admins = file:/etc/iucvterm/groups/admin-systems.list ## Examples -- Regular expressions # eve has authorization to connect to systems with # a) names starting with 'guest', followed by exactly 2 digits # b) names starting with 'lnx', followed by 5 alphanumeric characters eve = regex:(?:guest\d{2}|lnx\w{5}) s390-tools-2.3.0/iucvterm/doc/ts-shell/iucvconn_on_login000077500000000000000000000022471323410431100231410ustar00rootroot00000000000000#! /bin/sh # # iucvconn_on_login - start terminal connection at login # # Shell script to connect to a Linux guest operation system # using the iucvconn(1) program. # The z/VM guest virtual machine to which iucvconn_on_login will connect # is the name of the Linux user that logs in. # # Copyright IBM Corp. 2008, 2017 # # s390-tools is free software; you can redistribute it and/or modify # it under the terms of the MIT license. See LICENSE for details. # prog_name=`basename $0` guest_name=${USER:-`whoami 2>/dev/null`} terminal=lnxhvc0 iucvconn=`which iucvconn 2>/dev/null` __error() { printf "$prog_name: $@\n" >&2 exit 1 } # check if we have been called with -c to specify an alternate # terminal identifier. This can be used by ssh: "ssh -t guest@ts my_term" case "$1" in -c) test -n "$2" && terminal=$2 ;; esac test -t 1 || __error "The $prog_name program requires a terminal to run on" test "x$guest_name" = x && \ __error "Failed to determine the target z/VM guest virtual machine" test -x "$iucvconn" || __error "Failed to run the 'iucvconn' program" printf "$prog_name: Connecting to $guest_name (terminal ID: $terminal)\n\n" exec $iucvconn $guest_name $terminal s390-tools-2.3.0/iucvterm/doc/ts-shell/sample-system.list000066400000000000000000000004651323410431100232030ustar00rootroot00000000000000# Terminal server shell -- system list # # This file lists z/VM guest virtual machines, each name on a separate line. # Empty lines or lines starting with '#' are ignored. # See ts-shell(1) for more information. # #------|<--------------------------------------------------- LNXSYSA LNXSYSB LNXSYS01 LNXSYS42 s390-tools-2.3.0/iucvterm/doc/ttyrun.8000066400000000000000000000070331323410431100174040ustar00rootroot00000000000000.\" ttyrun.8 .\" .\" .\" Copyright IBM Corp. 2010, 2017 .\" s390-tools is free software; you can redistribute it and/or modify .\" it under the terms of the MIT license. See LICENSE for details. .\" ------------------------------------------------------------------------- .TH "ttyrun" "8" "December 2011" "s390-tools" "System Management Commands" . .ds s ttyrun . . .SH NAME ttyrun \- Start a program if a specified terminal device is available . . . .SH SYNOPSIS .B \*s .RB [ \-V | \-\-verbose ] .RB [ \-e | \-\-exitstatus .IR status ] .I term .I program .RI [ "program_options" ] .br .B \*s .RB [ \-h | \-\-help ] .br .B \*s .RB [ \-v | \-\-version ] . . . .SH DESCRIPTION \fB\*s\fP is typically started during system initialization and is used to prevent a respawn through the .BR init (8) program when a terminal is not available. \fIterm\fP is the name of the terminal device and is a path relative to the \f(CW/dev\fP directory, for example, specify \f(CWhvc0\fP for \f(CW/dev/hvc0\fP. .br If the specified terminal device can be opened, \fB\*s\fP starts the specified program. If the terminal device cannot be opened, the behavior of \fB\*s\fP depends on the \fB\-e\fP option: . .RS 2 .IP "\(bu" 2 If the \fB\-e\fP option has been specified, \fB\*s\fP exits with the specified return value, or .IP "\(bu" 2 If the \fB\-e\fP option has not been specified, \fB\*s\fP sleeps until it receives a signal that causes an exit. .RE .PP \fIprogram\fP is an absolute path to the program to be started by \fB\*s\fP and \fIprogram_options\fP specify additional arguments. Depending on the program, arguments might be required. The variable \f(CW%t\fP in the \fIprogram_options\fP is resolved to the terminal device specified with \fIterm\fP. . . . .SH OPTIONS .TP .BR \-e ", " \-\-exitstatus\~\fIstatus\fP Specifies an exit status that is returned when the terminal device is not available. \fIstatus\fP must be an integer in the range 1 to 255. You can use this status value in an upstart job file to prevent respawning. . .TP .BR \-V ", " \-\-verbose Displays syslog messages. . .TP .BR \-h ", " \-\-help Displays a short help text, then exits. . .TP .BR \-v ", " \-\-version Displays the version number of \fB\*s\fP, then exits. . . . .SH "RETURN VALUES" \fB\*s\fP exits with one of the following return values to report an error condition: .TP .B 1 \fB\*s\fP has been started with an argument that is not valid or required but missing. .TP .B 2 \fB\*s\fP could open the file specified for \fIterm\fP but the file is not a terminal device. .TP .B 3 \fB\*s\fP could not start the specified program. .PP The return values 1 to 3 might also be returned when the \fB\-e\fP option is used and the terminal device is not available. .TP .B 4 \- 255 The terminal device is not available and the \fB\-e\fP option specifies an exit status in this range. . . . .SH "EXAMPLES" .SS inittab To start \fB/sbin/agetty\fP on terminal device "hvc1", specify: .PP .ft CW .in +0.25in .nf h1:2345:respawn:/sbin/\*s hvc1 /sbin/agetty -L 9600 %t linux .fi .in -0.25in .ft . .SS upstart job/event files To start \fB/sbin/agetty\fP on terminal device "hvc1", add the following settings to the job file: .PP .ft CW .in +0.25in .nf respawn normal exit 42 exec /sbin/\*s -e 42 hvc1 /sbin/agetty -L 9600 %t linux .fi .in -0.25in .ft .PP With the normal exit statement, you specify an exit status that will prevent upstart from respawning the program. To prevent respawning with \fB\*s\fP, you must specify the same value for the \fB\-e\fP option. . . . .SH "SEE ALSO" .BR agetty (8), .BR mingetty (8), .BR inittab (5), .BR events (5) s390-tools-2.3.0/iucvterm/etc/000077500000000000000000000000001323410431100157515ustar00rootroot00000000000000s390-tools-2.3.0/iucvterm/etc/Makefile000066400000000000000000000012131323410431100174060ustar00rootroot00000000000000#! /usr/bin/make -f include ../../common.mak IUCVTERM_DIR = $(SYSCONFDIR)/iucvterm CONFIG_FILES = ts-shell.conf unrestricted.conf \ ts-authorization.conf ts-audit-systems.conf all: check: install: $(CONFIG_FILES) $(INSTALL) -g $(GROUP) -o $(OWNER) -d $(DESTDIR)$(IUCVTERM_DIR) ; \ for cnf in $(CONFIG_FILES); do \ $(INSTALL) -g $(GROUP) -o $(OWNER) -m 644 $$cnf $(DESTDIR)$(IUCVTERM_DIR) ; \ done clean: -rm -f *.o ts-shell.conf %: %.in my_sysconf=$(IUCVTERM_DIR); \ cat $< \ |sed -e "s#@sysconf_path@#$$my_sysconf#g" \ -e 's#@S390_TOOLS_RELEASE@#$(S390_TOOLS_RELEASE)#g' \ > $@ .PHONY: install clean perl s390-tools-2.3.0/iucvterm/etc/ts-audit-systems.conf000066400000000000000000000001201323410431100220500ustar00rootroot00000000000000# List of z/VM guest virtual machines # for which terminal session are logged # s390-tools-2.3.0/iucvterm/etc/ts-authorization.conf000066400000000000000000000002571323410431100221500ustar00rootroot00000000000000# Terminal server authorization configuration # # See ts-shell(1) manual page for file format syntax. # # See also authorization-sample.conf in the documentation directory. # s390-tools-2.3.0/iucvterm/etc/ts-shell.conf.in000066400000000000000000000013621323410431100207620ustar00rootroot00000000000000# Terminal server shell (ts-shell) configuration file # # See ts-shell(1) manual page for file format syntax. # System and authorization settings # --------------------------------- # ts-system # file path to the ts-systems.conf configuration file ts-systems = @sysconf_path@/unrestricted.conf # ts-authorization # file path to the ts-authorization.conf configuration file ts-authorization = @sysconf_path@/ts-authorization.conf # Session transcript settings # --------------------------- # transcript-systems # file path to a list of systems for which session transcript are created transcript-systems = @sysconf_path@/ts-audit-systems.conf # transcript-directory # directory path for saving transcripts transcript-directory = /var/log/ts-shell s390-tools-2.3.0/iucvterm/etc/unrestricted.conf000066400000000000000000000001251323410431100213310ustar00rootroot00000000000000# Allow connections to all z/VM guest virtual machines on this z/VM instance [*ALL*] s390-tools-2.3.0/iucvterm/include/000077500000000000000000000000001323410431100166215ustar00rootroot00000000000000s390-tools-2.3.0/iucvterm/include/af_iucv.h000066400000000000000000000012521323410431100204060ustar00rootroot00000000000000/* * Copyright IBM Corp. 2006, 2017 * * s390-tools is free software; you can redistribute it and/or modify * it under the terms of the MIT license. See LICENSE for details. * IUCV protocol stack for Linux on zSeries * Version 1.0 * */ #ifndef __AFIUCV_H #define __AFIUCV_H #include #ifndef AF_IUCV # define AF_IUCV 32 # define PF_IUCV AF_IUCV #endif /* IUCV socket address */ struct sockaddr_iucv { sa_family_t siucv_family; unsigned short siucv_port; /* Reserved */ unsigned int siucv_addr; /* Reserved */ char siucv_nodeid[8]; /* Reserved */ char siucv_user_id[8]; /* Guest User Id */ char siucv_name[8]; /* Application Name */ }; #endif s390-tools-2.3.0/iucvterm/include/iucvterm/000077500000000000000000000000001323410431100204575ustar00rootroot00000000000000s390-tools-2.3.0/iucvterm/include/iucvterm/config.h000066400000000000000000000021201323410431100220700ustar00rootroot00000000000000/* * iucvtty / iucvconn - IUCV Terminal Applications * * Configuration structure and command line processing function * * Copyright IBM Corp. 2008, 2017 * * s390-tools is free software; you can redistribute it and/or modify * it under the terms of the MIT license. See LICENSE for details. */ #ifndef __IUCVTTY_CONFIG_H_ #define __IUCVTTY_CONFIG_H_ enum iucvterm_prg { PROG_IUCV_TTY = 0, PROG_IUCV_CONN = 1, }; struct iucvterm_cfg { char client_re[129]; /* Regexp to match incoming clients */ char host[9]; /* IUCV target host name */ char service[9]; /* IUCV service name */ char **cmd_parms; /* ptr to commandline parms */ char *sessionlog; /* ptr to session log file path */ unsigned char esc_char; /* Escape character */ unsigned int flags; /* Configuration flags */ }; /* configuration flags */ #define CFG_F_CHKCLNT 0x0001 /* Check for permitted clients */ /* configuration macros */ #define CFG_CHKCLNT(c) ((c)->flags & CFG_F_CHKCLNT) extern void parse_options(enum iucvterm_prg, struct iucvterm_cfg *, int, char **); #endif /* __IUCVTTY_CONFIG_H_ */ s390-tools-2.3.0/iucvterm/include/iucvterm/functions.h000066400000000000000000000045031323410431100226420ustar00rootroot00000000000000/* * iucvtty / iucvconn - IUCV Terminal Applications * * Definition of common functions * * Copyright IBM Corp. 2008, 2017 * * s390-tools is free software; you can redistribute it and/or modify * it under the terms of the MIT license. See LICENSE for details. */ #ifndef __FUNCTIONS_H_ #define __FUNCTIONS_H_ #include #include "af_iucv.h" #include "inttypes.h" #include "iucvterm/config.h" #include "iucvterm/proto.h" /* Message buffer: message header + 4096 bytes of data */ #define MSG_BUFFER_SIZE (MSG_DATA_OFFSET + (4096)) /* Error macros */ #define print_error(s) program_error(PRG_COMPONENT, (s)) #define iucvtty_error(m) \ do { \ uint32_t *err = (uint32_t *) (m)->data; \ iucv_msg_error(PRG_COMPONENT, *err); \ } while (0); /* Error codes */ /* Unable to fork new process */ #define ERR_FORK 105 /* Cannot execute login program */ #define ERR_CANNOT_EXEC_LOGIN 106 /* Cannot set up terminal as a login terminal */ #define ERR_SETUP_LOGIN_TTY 107 /* Client (vm guest) is not authorized */ #define ERR_NOT_AUTHORIZED 110 extern int iucvtty_handle_req(int); extern int iucvtty_socket(struct sockaddr_iucv *, const char *, const char *); extern int iucvtty_tx_termenv(int, char *); extern int iucvtty_rx_termenv(int, void *, size_t); extern int iucvtty_tx_winsize(int, int); extern int iucvtty_tx_data(int, int, struct iucvtty_msg *, size_t); extern int iucvtty_tx_error(int, uint32_t); extern int iucvtty_copy_data(int, struct iucvtty_msg *); extern int iucvtty_read_data(int, struct iucvtty_msg *, size_t); extern int iucvtty_read_msg(int, struct iucvtty_msg *, size_t, size_t *); extern int iucvtty_write_msg(int, struct iucvtty_msg *); extern void iucvtty_skip_msg_residual(int, size_t *); extern ssize_t __write(int, const void*, size_t); extern int strmatch(const char *, const char *); extern int is_regex_valid(const char *); extern int is_client_allowed(const char *, const struct iucvterm_cfg *); extern void userid_cpy(char [8], const char [8]); extern void iucv_msg_error(const char *, uint32_t); extern void program_error(const char *, const char *); /* Audit/session log */ extern int open_session_log(const char *); extern ssize_t write_session_log(const void*, size_t); extern void write_session_info(const char *, ...); extern void close_session_log(void); #endif /* __FUNCTIONS_H_ */ s390-tools-2.3.0/iucvterm/include/iucvterm/gettext.h000066400000000000000000000033101323410431100223110ustar00rootroot00000000000000/* * iucvtty / iucvconn - IUCV Terminal Applications * * National language support (NLS) functions * * Copyright IBM Corp. 2008, 2017 * * s390-tools is free software; you can redistribute it and/or modify * it under the terms of the MIT license. See LICENSE for details. */ #ifndef __IUCVTTY_GETTEXT_H_ #define __IUCVTTY_GETTEXT_H_ #ifdef USE_NLS # include # include #endif /* Gettext constants (should be supplied from Makefile) */ #ifndef GETTEXT_TEXTDOMAIN # define GETTEXT_TEXTDOMAIN "iucvterm" #endif #ifndef GETTEXT_NLS_PATH # define GETTEXT_NLS_PATH "/usr/share/locale" #endif /* Gettext macros */ #ifdef USE_NLS # define _(translatable) gettext(translatable) #else # define _(translatable) (translatable) #endif #define N_(translatable) (translatable) /** * gettext_setup_locale() - Init gettext text domain using a specific locale * @locale: Locale to be set. * * The function sets the program locale for LC_MESSAGES and then initializes * gettext. * * The @locale parameter is directly passed to the setlocale() function. * If @locale is "", LC_MESSAGES is set according to the environment variable. */ static inline int gettext_init_locale(const char *locale) { #ifdef USE_NLS if (setlocale(LC_MESSAGES, locale) == NULL) return -1; if (bindtextdomain(GETTEXT_TEXTDOMAIN, GETTEXT_NLS_PATH) == NULL) return -1; if (textdomain(GETTEXT_TEXTDOMAIN) == NULL) return -1; #endif return 0; } /** * gettext_setup() - Initialize gettext text domain * * Calls gettext_setup_locale() with "" as locale parameter value. */ static inline int gettext_init(void) { #ifdef USE_NLS return gettext_init_locale(""); #else return 0; #endif } #endif /* __IUCVTTY_GETTEXT_H_ */ s390-tools-2.3.0/iucvterm/include/iucvterm/proto.h000066400000000000000000000055141323410431100220000ustar00rootroot00000000000000/* * iucvtty / iucvconn - IUCV Terminal Applications * * Structure and function definitions for the IUCV terminal message protocol * * Copyright IBM Corp. 2008, 2017 * * s390-tools is free software; you can redistribute it and/or modify * it under the terms of the MIT license. See LICENSE for details. */ #ifndef __IUCVTTY_PROTO_H_ #define __IUCVTTY_PROTO_H_ #include #include #include #include "lib/util_base.h" /* Standard macros */ #ifndef offsetof # define offsetof(type, member) ((size_t) &((type *)0)->member) #endif /* Packet version magic */ #define MSG_VERSION 0x02 /* Message types */ #define MSG_TYPE_ERROR 0x01 /* Error message. */ #define MSG_TYPE_TERMENV 0x02 /* Terminal environment variable. */ #define MSG_TYPE_TERMIOS 0x04 /* Terminal IO struct update. */ #define MSG_TYPE_WINSIZE 0x08 /* Terminal window size update. */ #define MSG_TYPE_DATA 0x10 /* Terminal data. */ struct iucvtty_msg { uint8_t version; /* Message version */ uint8_t type; /* Message type */ uint16_t datalen; /* Number of bytes in payload */ uint8_t data[]; /* Payload buffer */ } __attribute__((packed)); #define MSG_DATA_OFFSET (offsetof(struct iucvtty_msg, data)) #define msg_size(m) (MSG_DATA_OFFSET + (m)->datalen) /** * msg_cpy_from() - Copy data to message * @msg: IUCV terminal message * @src: Pointer to source data buffer * @len: Length of source data buffer * * Copies @len bytes from @src to the message data buffer. The caller must * ensure not to overwrite the message data buffer. * Finally, the message datalen is set to @len */ static inline void msg_cpy_from(struct iucvtty_msg *msg, const void *src, size_t len) { memcpy(msg->data, src, len); msg->datalen = len; } /** * msg_cpy_to() - Copy data from a message * @msg: IUCV terminal message * @dst: Destination buffer * @len: Destination buffer length. * * Copies up to min(@len, message datalen) number of bytes from the IUCV * terminal message to the destination buffer. */ static inline void msg_cpy_to(const struct iucvtty_msg *msg, void *dst, size_t len) { memcpy(dst, msg->data, MIN(msg->datalen, len)); } /** * msg_alloc() - Allocates a new iucv terminal message. * @type: Message type * @size: Message data size * * The function allocates a new iucv terminal message with the given data size * @size. (The total message size is @size plus MSG_DATA_OFFSET.) * The new message is initialized with the specific @type. */ static inline struct iucvtty_msg *msg_alloc(uint8_t type, uint16_t size) { struct iucvtty_msg *m; m = malloc(size + MSG_DATA_OFFSET); if (m != NULL) { m->version = MSG_VERSION; m->type = type; m->datalen = size; } return m; } /** * msg_free() - free an allocated iucv tty message */ static inline void msg_free(struct iucvtty_msg *m) { free(m); } #endif /* __IUCVTTY_PROTO_H_ */ s390-tools-2.3.0/iucvterm/po/000077500000000000000000000000001323410431100156145ustar00rootroot00000000000000s390-tools-2.3.0/iucvterm/po/Makefile000066400000000000000000000027451323410431100172640ustar00rootroot00000000000000#! /usr/bin/make -f # # This Makefile creates a message.po template for translation input. # # To create a localized message.po file from the template, run # msginit -i progname.pot -l de_DE # --no-translator # # To finally create the binary message.mo file, run # msgfmt de.po -o /usr/share/locale/de/LC_MESSAGES/progname.mo # #include ../../common.mak ifndef GETTEXT_TEXTDOMAIN GETTEXT_TEXTDOMAIN = iucvterm endif XGETTEXT = xgettext XGETTEXT_ARGS = --keyword=print_error POTFILES = POTFILES.in all: $(GETTEXT_TEXTDOMAIN).pot check: install: clean: -rm -f $(GETTEXT_TEXTDOMAIN).pot $(GETTEXT_TEXTDOMAIN).pot: @$(XGETTEXT) --default-domain=$(GETTEXT_TEXTDOMAIN) --directory='..' \ --add-comments --keyword=_ --keyword=N_ \ --flag=g_strdup_printf:1:c-format \ --flag=g_string_printf:2:c-format \ --flag=g_string_append_printf:2:c-format \ --flag=g_error_new:3:c-format \ --flag=g_set_error:4:c-format \ --flag=g_markup_printf_escaped:1:c-format \ --flag=g_log:3:c-format \ --flag=g_print:1:c-format \ --flag=g_printerr:1:c-format \ --flag=g_printf:1:c-format \ --flag=g_fprintf:2:c-format \ --flag=g_sprintf:2:c-format \ --flag=g_snprintf:3:c-format \ --flag=g_scanner_error:2:c-format \ --flag=g_scanner_warn:2:c-format \ --files-from=$(POTFILES) $(XGETTEXT_ARGS) \ && test ! -f $(GETTEXT_TEXTDOMAIN).po \ || ( rm -f $(GETTEXT_TEXTDOMAIN).pot \ && mv $(GETTEXT_TEXTDOMAIN).po $(GETTEXT_TEXTDOMAIN).pot ) .PHONY: install clean $(GETTEXT_TEXTDOMAIN).pot s390-tools-2.3.0/iucvterm/po/POTFILES.in000066400000000000000000000000721323410431100173700ustar00rootroot00000000000000src/iucvconn.c src/functions.c src/iucvtty.c src/getopt.c s390-tools-2.3.0/iucvterm/src/000077500000000000000000000000001323410431100157655ustar00rootroot00000000000000s390-tools-2.3.0/iucvterm/src/Makefile000066400000000000000000000014571323410431100174340ustar00rootroot00000000000000#! /usr/bin/make -f include ../../common.mak ifndef GETTEXT_TEXTDOMAIN GETTEXT_TEXTDOMAIN = iucvterm endif ALL_CPPFLAGS += -I../include ALL_CPPFLAGS += -DUSE_NLS -DGETTEXT_TEXTDOMAIN=\"$(GETTEXT_TEXTDOMAIN)\" #ALL_CPPFLAGS += -D__DEBUG__ PROGRAMS = iucvconn iucvtty SYSTOOLS = ttyrun all: $(PROGRAMS) $(SYSTOOLS) check: install: all for prg in $(PROGRAMS); do \ $(INSTALL) -g $(GROUP) -o $(OWNER) -m 755 $$prg $(DESTDIR)$(USRBINDIR) ; \ done for prg in $(SYSTOOLS); do \ $(INSTALL) -g $(GROUP) -o $(OWNER) -m 755 $$prg $(DESTDIR)$(BINDIR) ; \ done clean: -rm -f *.o $(PROGRAMS) $(SYSTOOLS) iucvconn: iucvconn.o getopt.o auditlog.o functions.o iucvtty: LDLIBS = -lutil iucvtty: iucvtty.o getopt.o auditlog.o functions.o ttyrun: GETTEXT_TEXTDOMAIN = ttyrun ttyrun: ttyrun.o .PHONY: install clean s390-tools-2.3.0/iucvterm/src/auditlog.c000066400000000000000000000117461323410431100177520ustar00rootroot00000000000000/* * iucvtty / iucvconn - IUCV Terminal Applications * * Functions for session logging/auditing. * The session log and timing data files adhere to the format * described in script(1). * * Copyright IBM Corp. 2008, 2017 * * 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 #include #include #include #include #include #include #include #include #include #include #include "iucvterm/functions.h" #define OPEN_FILEMODE (O_WRONLY | O_CREAT | O_EXCL) #define OPEN_FILEMASK (S_IRUSR | S_IWUSR | S_IRGRP) static int script_fd = -1; /* fd of typescript file */ static int timing_fd = -1; /* fd of timing data file */ static FILE *info_file = NULL; /* FILE of info file */ static struct timeval last_tv; /* tv to calculate timing */ /** * print_on_time() - Append formatted time string to a message. * @prefix: message * * Returns a new buffer starting with @prefix and appends * a formatted string representation of the current time. * Returns NULL if memory allocation has failed. * * The caller must free the returned buffer after use. */ static char *print_on_time(const char *prefix) { char *buf = malloc(64 + strlen(prefix) + 1); time_t t = time(NULL); if (buf != NULL) { if (t == (time_t) -1) sprintf(buf, "%s\n", prefix); else { sprintf(buf, "%s on ", prefix); ctime_r(&t, buf + strlen(prefix) + 4); } } return buf; } /** * write_session_info() - Write data to the session info file * @format: Format string, shall not be NULL * * Writes informational messages to the session info file. * The info message is prefixed with a timestamp as returned by the * time(2) syscall. */ void write_session_info(const char *format, ...) { va_list ap; if (info_file == NULL) return; fprintf(info_file, "%lu ", time(NULL)); va_start(ap, format); vfprintf(info_file, format, ap); va_end(ap); if (strrchr(format, '\n') == NULL) fprintf(info_file, "\n"); } /** * write_session_log() - Write session data to the session log file * @buf: Pointer to a buffer with data to log * @len: Copy up to @len bytes from @buf * * The routines writes up to @len bytes of data from buffer @buf to * the session transcript; write appropriate timing data to the timing * file. */ ssize_t write_session_log(const void* buf, size_t len) { ssize_t rc; int count; char data[64] = ""; struct timeval curr_tv; long time_diff; /* immediately return if there is no fd to write to */ if (script_fd == -1) return -1; rc = __write(script_fd, buf, len); if (rc < 0) return rc; /* calculate delay and write timing info */ if (gettimeofday(&curr_tv, NULL)) time_diff = 1000000; /* one second (in usecs) */ else { time_diff = (curr_tv.tv_sec - last_tv.tv_sec) * 1000000 + curr_tv.tv_usec - last_tv.tv_usec; last_tv = curr_tv; /* reset last timeval */ } count = sprintf(data, "%.6f %zu\n", (double) time_diff / (double) 1000000, len); rc = __write(timing_fd, data, (count < 0) ? 0 : count ); if (rc < 0) return rc; return 0; } /** * close_session_log() - Close session logging * * The routine writes a trailer to the session log file and * closes the session log, timing and info file descriptor. */ void close_session_log(void) { char *trailer; if (script_fd > 0) { trailer = print_on_time("Script done"); if (trailer != NULL) { __write(script_fd, trailer, strlen(trailer)); write_session_info(trailer); free(trailer); } close(script_fd); } if (timing_fd > 0) close(timing_fd); if (info_file != NULL) fclose(info_file); } /** * open_session_log() - Open session logging * @filepath: File path to the session transcript * * Opens the session, timing and info log file. * If the session specified by @filepath already exists; or one of the * files cannot be opened successfully, return an error. */ int open_session_log(const char *filepath) { char *buf; int old_errno; int info_fd; buf = calloc(11 + strlen(filepath), sizeof(char)); if (buf == NULL) goto out_no_mem; script_fd = open(filepath, OPEN_FILEMODE, OPEN_FILEMASK); if (script_fd == -1) goto out_error_open; sprintf(buf, "%s.timing", filepath); timing_fd = open(buf, OPEN_FILEMODE, OPEN_FILEMASK); if (timing_fd == -1) goto out_error_open; sprintf(buf, "%s.info", filepath); info_fd = open(buf, OPEN_FILEMODE, OPEN_FILEMASK); if (info_fd == -1) goto out_error_open; info_file = fdopen(info_fd, "w"); if (info_file == NULL) goto out_error_open; if(gettimeofday(&last_tv, NULL)) goto out_error_open; free(buf); buf = print_on_time("Script started"); if (buf != NULL) { __write(script_fd, buf, strlen(buf)); write_session_info(buf); free(buf); } return 0; out_error_open: old_errno = errno; /* preserve errno */ free(buf); close_session_log(); errno = old_errno; /* restore errno from failed open call */ out_no_mem: return -1; } s390-tools-2.3.0/iucvterm/src/functions.c000066400000000000000000000305111323410431100201410ustar00rootroot00000000000000/* * iucvtty / iucvconn - IUCV Terminal Applications * * Common functions * * Copyright IBM Corp. 2008, 2017 * * 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 #include #include #include #include #include #include #include #include #include "af_iucv.h" #include "iucvterm/config.h" #include "iucvterm/functions.h" #include "iucvterm/gettext.h" #include "iucvterm/proto.h" /* Global program component for iucv terminal tools */ #define PRG_COMPONENT "iucvterm" /** * __write() - Write data * @fd: File descriptor * @buf: Pointer to data buffer * @len: Buffer length * * Write @len number of bytes from the buffer @buf to the file * descriptor @fd. The routines handles EINTR and partially writes. * Returns the error code from the underlying write(2) syscall. */ ssize_t __write(int fd, const void *buf, size_t len) { ssize_t rc; size_t written = 0; while (written < len) { rc = write(fd, buf + written, len - written); if (rc == -1 && errno == EINTR) continue; if (rc <= 0) return rc; written += rc; } return written; } #ifdef __DEBUG__ static void __dump_msg(int fd, const struct iucvtty_msg *m, char dir) { write_session_info("%c: (fd=%d) MSG: ver=%02x type=%02x datalen=%u\n", dir, fd, m->version, m->type, (uint16_t) m->datalen); } #endif /** * iucvtty_socket() - Creates and return an IUCV socket * @sai: AF_IUCV socket address structure * @host: z/VM guest name * @service: Terminal name passed as additional data to @host after connect * * This function sets up the struct sockaddr_iucv with the specified * VM guest virtual machine and terminal information. * Finally, it returns an AF_IUCV socket. */ int iucvtty_socket(struct sockaddr_iucv *sai, const char *host, const char *service) { char temp[9]; memset(sai, 0, sizeof(struct sockaddr_iucv)); sai->siucv_family = AF_IUCV; if (host != NULL) { snprintf(temp, 9, "%-8s", host); memcpy(sai->siucv_user_id, temp, 8); } else memset(sai->siucv_user_id, ' ', 8); if (service != NULL) { snprintf(temp, 9, "%-8s", service); memcpy(sai->siucv_name, temp, 8); } else memset(sai->siucv_name, ' ', 8); return socket(PF_IUCV, SOCK_STREAM, 0); } /** * iucvtty_tx_termenv() - Send terminal environment variable * @dest: File descriptor to output data * @dflt: TERM environment string ('\0' terminated) * * Copy terminal environment variable to destination @dest. */ int iucvtty_tx_termenv(int dest, char *dflt) { struct iucvtty_msg *msg; char *term = getenv("TERM"); size_t len; int rc; if (term == NULL && dflt != NULL) term = dflt; len = 0; if (term != NULL) len = 1 + strlen(term); /* Note: The server console tool waits for terminal environment * information: the message is sent even if it is empty */ msg = msg_alloc(MSG_TYPE_TERMENV, len); if (msg == NULL) return -1; msg_cpy_from(msg, term, len); rc = iucvtty_write_msg(dest, msg); msg_free(msg); return rc; } /** * iucvtty_rx_termenv() - Receive terminal environment variable * @fd: File descriptor to read data from * @buf: Buffer to store the terminal environment variable * @len: Size of buffer @buf */ int iucvtty_rx_termenv(int fd, void *buf, size_t len) { int rc; size_t skip; struct iucvtty_msg *msg = msg_alloc(MSG_TYPE_TERMENV, len); if (msg == NULL) return -1; skip = 0; rc = iucvtty_read_msg(fd, msg, msg_size(msg), &skip); iucvtty_skip_msg_residual(fd, &skip); if (!rc) { if (msg->datalen == 0) memset(buf, 0, MIN(1u, len)); else msg_cpy_to(msg, buf, len); } msg_free(msg); return rc; } /** * iucvtty_tx_data() - Send terminal data * @from: File descriptor to read data from * @msg: Pointer to iucv tty message buffer * @len: Size of message buffer * * This routine reads data from file descriptor @from and stores them in * a data array of the specified iucv tty message @msg. It reads up to * @len - MSG_DATA_OFFSET bytes from fd @from. */ int iucvtty_read_data(int from, struct iucvtty_msg *msg, size_t len) { ssize_t r; r = read(from, msg->data, len - MSG_DATA_OFFSET); if (r == -1 && errno == EINTR) /* REVIEW: loop if EINTR ? */ r = read(from, msg->data, len - MSG_DATA_OFFSET); if (r <= 0) return -1; msg->version = MSG_VERSION; msg->type = MSG_TYPE_DATA; msg->datalen = (uint16_t) r; return 0; } /** * iucvtty_tx_data() - Send terminal data * @dest: File descriptor to send data to * @from: File descriptor to read data from * @msg: Pointer to iucv tty message buffer * @len: Size of message buffer * * This routine reads data from file descriptor @from and stores them in * a data array of the specified iucv tty message @msg. It reads up to * @len - MSG_DATA_OFFSET bytes from fd @from. * Finally, the iucv tty message written to file descriptor @dest. */ int iucvtty_tx_data(int dest, int from, struct iucvtty_msg *msg, size_t len) { if (iucvtty_read_data(from, msg, len)) return -1; if (iucvtty_write_msg(dest, msg)) return -1; return 0; } /** * iucvtty_tx_winsize() - Send terminal window size information. * @dest: Destination * @from: Terminal file descriptor to request winsize * * Sends the terminal window size from terminal file descriptor * @from to the destination @dest. * If the window size is not retrieved, the routine will not fail. * The routine fails if there is a problem sending the window size * to @dest. The return codes are specified by iucvtty_write_msg(). */ int iucvtty_tx_winsize(int dest, int from) { int rc; struct iucvtty_msg *msg = msg_alloc(MSG_TYPE_WINSIZE, sizeof(struct winsize)); if (msg == NULL) return -1; rc = 0; if (ioctl(from, TIOCGWINSZ, msg->data) > -1) rc = iucvtty_write_msg(dest, msg); msg_free(msg); return rc; } /** * iucvtty_tx_error() - Send an error code * @dest: Destination * @errCode: Error code */ int iucvtty_tx_error(int dest, uint32_t errCode) { struct iucvtty_msg *msg; int rc; msg = msg_alloc(MSG_TYPE_ERROR, sizeof(errCode)); if (msg == NULL) return -1; msg_cpy_from(msg, &errCode, sizeof(errCode)); rc = iucvtty_write_msg(dest, msg); msg_free(msg); return rc; } /** * iucvtty_copy_data() - Copy IUCV message data * @dest: Destination to copy data to * @msg: IUCV message */ int iucvtty_copy_data(int dest, struct iucvtty_msg *msg) { if (__write(dest, msg->data, msg->datalen) <= 0) return -1; return 0; } /** * iucvtty_skip_msg_residual() - Skip (receive & forget) count number of bytes * @fd: File descriptor * @residual: Residual of an iucv tty message received by iucvtty_read_msg() * * See iucvtty_read_msg() for an explanation when to use this routine. * Note: The @residual parameter shall not be NULL. */ void iucvtty_skip_msg_residual(int fd, size_t *residual) { char b; size_t i; if (*residual <= 0) return; for (i = 0; i < *residual; i++) if (read(fd, &b, 1) <= 0) break; *residual = 0; } /** * iucvtty_read_msg() - Read/Receive an IUCV message * @fd: File descriptor to read from * @msg: Pointer to IUCV message buffer * @len: IUCV message data len * @residual: Status to be used by next call * * The function reads up to @len bytes from file descriptor @fd. * If the received message is larger than @len bytes, the @residual value * is set to the number of bytes remaining. * The function shall then be re-called to create a new message and receive * the next chunk of size @residual; or the remaining characters must be * skipped using the iucvtty_skip_msg() routine. * Note: The @len parameter shall be greater than MSG_DATA_OFFSET. * The @residual parameter shall not be NULL. */ int iucvtty_read_msg(int fd, struct iucvtty_msg *msg, size_t len, size_t *residual) { int rc; ssize_t r; /* number of bytes read from fd */ if (*residual) len = MIN(len - MSG_DATA_OFFSET, *residual); while (1) { if (*residual) { r = read(fd, msg->data, len); if (r > 0) msg->datalen = r; } else r = read(fd, msg, len); if (r == -1 && errno == EINTR) continue; if (r <= 0) { rc = -1; goto out_read_error; } break; /* exit loop for a successful read */ } #ifdef __DEBUG__ if (!*residual) __dump_msg(fd, msg, 'R'); #endif /* (re)calculate next chunk */ if (*residual) *residual -= msg->datalen; else if (msg->datalen > (r - MSG_DATA_OFFSET)) { /* calculate pending msg data and update datalen */ *residual = msg->datalen - (r - MSG_DATA_OFFSET); msg->datalen = r - MSG_DATA_OFFSET; } /* check for a sane message */ if (msg->version != MSG_VERSION) { fprintf(stderr, _("%s: %s\n"), PRG_COMPONENT, _("The version of the received data " "message is not supported\n")); rc = -2; goto out_read_error; } rc = 0; out_read_error: return rc; } /** * iucvtty_write_msg() - Write/Send IUCV message * @fd: File descriptor * @msg: Pointer to IUCV message */ int iucvtty_write_msg(int fd, struct iucvtty_msg *msg) { msg->version = MSG_VERSION; if (__write(fd, msg, msg_size(msg)) <= 0) return -1; #ifdef __DEBUG__ __dump_msg(fd, msg, 'S'); #endif return 0; } /** * iucv_msg_error() - Reports an IUCV message error * @comp: Program component * @errnum: IUCV message error code */ void iucv_msg_error(const char *comp, uint32_t errnum) { const char *translated; switch (errnum) { case ERR_FORK: translated = _("Creating a new process to run the " "login program failed"); break; case ERR_CANNOT_EXEC_LOGIN: translated = _("Running the login program failed"); break; case ERR_SETUP_LOGIN_TTY: translated = _("Setting up a terminal for user login failed"); break; case ERR_NOT_AUTHORIZED: translated = _("The z/VM guest virtual machine is not " "permitted to connect"); break; default: translated = _("The specified error code is not known"); break; } fprintf(stderr, "%s: %s (%s=%" PRIu32 ")\r\n", comp, translated, _("error code"), errnum); } /** * program_error() - Report an program/syscall error. * @comp: Program component name * @d: Error message, subject to gettext translation */ void program_error(const char *comp, const char *d) { fprintf(stderr, _("%s: %s: %s\n"), comp, _(d), strerror(errno)); } /** * __regerror - Report an error from a previous regex api call * @error: Error code * @re: Reference to the used regular expression */ static inline void __regerror(int error, const regex_t *re) { char errbuf[81]; regerror(error, re, errbuf, 81); fprintf(stderr, _("The regular expression has an error: %s\n"), errbuf); return; } /** * is_regex_valid() - Check if the specified regex is syntactically correct. * @re: String representation of the regular expression * * Returns zero on success, otherwise -1. */ int is_regex_valid(const char *re) { regex_t regex; int rc; if (re == NULL) return -1; rc = regcomp(®ex, re, REG_EXTENDED | REG_ICASE | REG_NOSUB); if (rc) { __regerror(rc, ®ex); rc = -1; } regfree(®ex); return rc; } /** * strmatch() - Match a string using a regular expression * @str: String to match * @re: Regular expression * * Returns zero on success, -1 on error or if @str is NULL; and * 1 if the regular expression did not match the string. */ int strmatch(const char *str, const char *re) { regex_t regex; regmatch_t pmatch[1]; size_t nmatch = 0; int rc; if (re == NULL) return -1; rc = regcomp(®ex, re, REG_EXTENDED | REG_ICASE | REG_NOSUB); if (rc) { __regerror(rc, ®ex); regfree(®ex); return -1; } rc = regexec(®ex, str, nmatch, pmatch, 0); if (rc == REG_NOMATCH) rc = 1; regfree(®ex); return rc; } /** * is_client_allowed() - Check if the client is allowed to connect. * @client: Client name * @cfg: Pointer to the IUCV terminal configuration structure * * The return code is identical to strmatch(). If client checking is * disabled, the function returns zero. */ int is_client_allowed(const char *client, const struct iucvterm_cfg *cfg) { if (!CFG_CHKCLNT(cfg)) return 0; return strmatch(client, cfg->client_re); } /** * userid_cpy() - Copy z/VM user ID and skip trailing spaces. * @dest: Destination buffer * @userid: z/VM user ID */ void userid_cpy(char dest[9], const char userid[8]) { ssize_t pos; /* find pos of last character (pos 0..7) or -1 if user ID is empty */ for (pos = 7; pos >= 0; pos--) if (userid[pos] != ' ') break; if (pos >= 0) memcpy(dest, userid, pos + 1); dest[pos + 1] = '\0'; } s390-tools-2.3.0/iucvterm/src/getopt.c000066400000000000000000000135101323410431100174330ustar00rootroot00000000000000/* * iucvtty / iucvconn - IUCV Terminal Applications * * Processing of command line arguments * * Copyright IBM Corp. 2008, 2017 * * 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 #include #include #include #include #include "lib/zt_common.h" #include "iucvterm/config.h" #include "iucvterm/functions.h" #include "iucvterm/gettext.h" static const char iucvtty_usage[] = N_( "Usage: %s [-h|--help] [-v|--version]\n" " %s [-a ] [-- []]\n\n" "Options:\n" " -h, --help Print this help, then exit.\n" " -v, --version Print version information, then exit.\n" " -a, --allow-from Permit connections from particular z/VM guests only.\n" " A z/VM guest is permitted if regex matches its name.\n" ); static const char iucvconn_usage[] = N_( "Usage: %s [-h|--help] [-v|--version]\n" " %s [-e esc] [-s ] \n\n" "Options:\n" " -h, --help Print this help, then exit.\n" " -v, --version Print version information, then exit.\n" " -s, --sessionlog Write terminal session to file.\n" " -e, --escape-char Escape character (can be one of: A-Y, ], ^ or _)\n" " Characters C, D, Q, S, Z and [ are not allowed.\n" ); static const struct option iucvterm_long_opts[] = { { "help", no_argument, NULL, 'h' }, { "version", no_argument, NULL, 'v' }, { "allow-from", required_argument, NULL, 'a' }, { "sessionlog", required_argument, NULL, 's' }, { "escape-char", required_argument, NULL, 'e' }, { NULL, no_argument, NULL, 0 } }; struct tool_info { char name[10]; char optstring[10]; const char *usage; unsigned char reqNonOpts; }; /* program specific command line settings */ static const struct tool_info iucv_tool[2] = { { .name = "iucvtty", .optstring = "-hva:", .usage = iucvtty_usage, .reqNonOpts = 1, }, { .name = "iucvconn", .optstring = "-hvs:e:", .usage = iucvconn_usage, .reqNonOpts = 2, } }; static void __noreturn usage_exit(const struct tool_info *prg, int is_error, const char *msg) { if (msg != NULL) fprintf(stderr, _("%s: %s\n"), prg->name, msg); fprintf(stderr, _(prg->usage), prg->name, prg->name); exit(is_error ? 1 : 0); /* rc=1 .. invalid args */ } static void __noreturn version_exit(const struct tool_info *prg) { printf(_("%s: IUCV Terminal Applications, version %s\n"), prg->name, RELEASE_STRING); printf(_("Copyright IBM Corp. 2008, 2017\n")); exit(0); } static void cpy_or_exit(char *dest, const char *src, size_t size, const struct tool_info *prg, const char *param) { if (strlen(src) >= size){ fprintf(stderr, _("%s: %s exceeds the maximum of %zu characters\n"), prg->name, param, size - 1); exit(1); } strncpy(dest, src, size); dest[size - 1] = 0; } static void set_esc_or_exit(const struct tool_info *prg, const char val, unsigned char *esc) { unsigned char upval = toupper(val); /* range of valid escape keys: A-Z [ \ ] ^ _ */ if (upval < 'A' || upval > '_') usage_exit(prg, 1, _("The specified character is not a " "valid escape character")); switch (upval) { case 'C': /* interrupt (ISIG) */ case 'D': /* EoF / EoT */ case 'Q': /* XON */ case 'S': /* XOFF */ case 'Z': /* suspend (shell) */ case '[': /* ESC */ usage_exit(prg, 1, _("The specified character is not a " "valid escape character")); default: *esc = upval ^ 0100; /* see ascii(7) */ break; } } void parse_options(enum iucvterm_prg prg, struct iucvterm_cfg *config, int argc, char **argv) { int c; int index; int nonOpts = 0; config->cmd_parms = NULL; config->sessionlog = NULL; config->esc_char = '_' ^ 0100; /* Ctrl-_ (0x1f) */ config->flags = 0; while (1) { index = -1; c = getopt_long(argc, argv, iucv_tool[prg].optstring, iucvterm_long_opts, &index); if (c == -1) break; switch (c) { case 1: if (nonOpts >= iucv_tool[prg].reqNonOpts) { usage_exit(&iucv_tool[prg], 1, NULL); break; } switch (nonOpts) { case 0: if (prg == PROG_IUCV_CONN) cpy_or_exit(config->host, optarg, sizeof(config->host), &iucv_tool[prg], _("")); else cpy_or_exit(config->service, optarg, sizeof(config->service), &iucv_tool[prg], _("")); break; case 1: cpy_or_exit(config->service, optarg, sizeof(config->service), &iucv_tool[prg], _("")); break; default: usage_exit(&iucv_tool[prg], 1, NULL); break; } ++nonOpts; break; case 'a':/* max 80 */ cpy_or_exit(config->client_re, optarg, sizeof(config->client_re), &iucv_tool[prg], _("")); if (is_regex_valid(config->client_re)) exit(1); config->flags |= CFG_F_CHKCLNT; break; case 'e': switch (strlen(optarg)) { case 1: set_esc_or_exit(&iucv_tool[prg], optarg[0], &config->esc_char); break; case 4: if (memcmp(optarg, "none", 4) == 0) { config->esc_char = 0; break; } /* fall through */ default: usage_exit(&iucv_tool[prg], 1, _("The escape character must be a " "single character or 'none'")); } break; case 's': config->sessionlog = optarg; break; case 'h': usage_exit(&iucv_tool[prg], 0, NULL); case 'v': version_exit(&iucv_tool[prg]); case '?': printf(_("Try '%s --help' for more information.\n"), iucv_tool[prg].name); exit(1); break; } } if (optind < argc) /* save additional parameters */ config->cmd_parms = argv + optind; if (nonOpts < iucv_tool[prg].reqNonOpts) /* not enough args */ usage_exit(&iucv_tool[prg], 1, _("The command does not have enough arguments")); } s390-tools-2.3.0/iucvterm/src/iucvconn.c000066400000000000000000000203441323410431100177600ustar00rootroot00000000000000/* * iucvconn - Application that establishes a terminal connection over IUCV * * Core application * * Copyright IBM Corp. 2008, 2017 * * 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 #include #include #include #include #include #include #include #include #include #include #include "lib/util_base.h" #include "iucvterm/config.h" #include "iucvterm/functions.h" #include "iucvterm/gettext.h" #define SYSLOG_IDENT "iucvconn" #define PRG_COMPONENT SYSLOG_IDENT #define DEFAULT_TERM "linux" #define AUDIT(f, ...) do { \ syslog(LOG_INFO, (f), __VA_ARGS__); \ write_session_info((f), __VA_ARGS__); \ } while (0); /* escape mode actions */ enum esc_action_t { DISCONNECT, /* Disconnect from terminal */ RESIZE, /* Force terminal resizing */ SEND, /* Send data (default action) */ IGNORE, /* Ignore escape character */ }; static volatile sig_atomic_t resize_tty; static struct termios ios_orig; /* store original termio settings */ /** * sig_handler() - Signal handler * @sig: Signal number */ static void sig_handler(int sig) { switch (sig) { case SIGWINCH: resize_tty = sig; break; case SIGTERM: tcsetattr(STDIN_FILENO, TCSANOW, &ios_orig); close_session_log(); _exit(0); break; } } /** * get_msg_char() - Returns single character from message * @msg: The IUCV terminal message * * Returns the (single) character of an IUCV terminal message * if @msg is of type MSG_TYPE_DATA and contains a single character * (datalen == 1). Otherwise the routine returns zero. */ static unsigned char get_msg_char(const struct iucvtty_msg *msg) { if (msg->type != MSG_TYPE_DATA || msg->datalen != 1) return 0; return msg->data[0]; } /** * is_esc_char() - Check message for escape character * @msg: The IUCV terminal message * @esc: The escape character * * Returns 1 if @msg contains the escape character @esc only; otherwise the * function returns 0. */ static int is_esc_char(const struct iucvtty_msg *msg, unsigned char esc) { if (!esc) return 0; return (get_msg_char(msg) == esc) ? 1 : 0; } /** * get_action() - Returns the action from an escaped character * @msg: The IUCV terminal message * @esc: The escape character * * Returns the appropriate action of the escaped character. * The action is derived from the single character stored in the IUCV terminal * message @msg. If the escape character is recognized, SEND is returned for * sending the "escaped" escape character to the terminal. * * If it contains multiple characters, the default SEND action is used to * indicate that the escape mode is done and to force sending the complete data * characters (e.g. entered by copy & paste). * * NOTE: This routine must be called in "escape mode". */ static enum esc_action_t get_action(const struct iucvtty_msg *msg, unsigned char esc) { if (is_esc_char(msg, esc)) return SEND; switch (get_msg_char(msg)) { case 0: return SEND; case '.': case 'd': return DISCONNECT; case 'r': return RESIZE; default: return IGNORE; } } /** * iucvtty_worker() - Handle server connection * @terminal: IUCV TTY server file descriptor */ static int iucvtty_worker(int terminal, const struct iucvterm_cfg *cfg) { struct iucvtty_msg *msg; fd_set set; size_t chunk; int in_esc_mode; enum esc_action_t action; /* setup buffers */ msg = malloc(MSG_BUFFER_SIZE); if (msg == NULL) { print_error("Allocating memory for the data buffer failed"); return -1; } /* multiplex i/o between login program and socket */ chunk = 0; in_esc_mode = 0; /* escape mode state */ action = SEND; while (1) { if (resize_tty) { iucvtty_tx_winsize(terminal, STDIN_FILENO); resize_tty = 0; /* clear signal flag */ } FD_ZERO(&set); FD_SET(terminal, &set); FD_SET(STDIN_FILENO, &set); if (select(MAX(STDIN_FILENO, terminal) + 1, &set, NULL, NULL, NULL) == -1) { if (errno == EINTR) continue; break; } if (FD_ISSET(terminal, &set)) { if (iucvtty_read_msg(terminal, msg, MSG_BUFFER_SIZE, &chunk)) break; switch (msg->type) { case MSG_TYPE_DATA: iucvtty_copy_data(STDOUT_FILENO, msg); write_session_log(msg->data, msg->datalen); break; case MSG_TYPE_ERROR: iucvtty_error(msg); break; } } if (FD_ISSET(STDIN_FILENO, &set)) { if (iucvtty_read_data(STDIN_FILENO, msg, MSG_BUFFER_SIZE)) break; if (in_esc_mode) { in_esc_mode = 0; /* reset */ action = get_action(msg, cfg->esc_char); } else { if (is_esc_char(msg, cfg->esc_char)) { in_esc_mode = 1; action = IGNORE; } else action = SEND; } /* handle escape mode */ switch (action) { case SEND: /* non-escape mode (default) */ if (iucvtty_write_msg(terminal, msg)) goto out_worker_loop; break; case DISCONNECT:/* disconnect */ goto out_worker_loop; case RESIZE: /* force terminal resize */ iucvtty_tx_winsize(terminal, STDIN_FILENO); break; case IGNORE: break; } } } out_worker_loop: free(msg); return 0; } /** * main() - IUCV CONN program startup */ int main(int argc, char *argv[]) { int rc; int server; struct sockaddr_iucv addr; struct termios ios; struct sigaction sigact; struct passwd *passwd; struct iucvterm_cfg conf; /* gettext initialization */ gettext_init(); /* parse command line options */ parse_options(PROG_IUCV_CONN, &conf, argc, argv); /* open session audit log */ if (conf.sessionlog != NULL) if (open_session_log(conf.sessionlog)) { print_error("Creating the terminal session " "log files failed"); return 1; } /* open socket and connect to server */ server = iucvtty_socket(&addr, conf.host, conf.service); if (server == -1) { print_error((errno == EAFNOSUPPORT) ? N_("The AF_IUCV address family is not available") : N_("Creating the AF_IUCV socket failed")); return 1; } /* syslog */ openlog(SYSLOG_IDENT, LOG_PID, LOG_AUTHPRIV); /* get user information for syslog */ passwd = getpwuid(geteuid()); if (connect(server, (struct sockaddr *) &addr, sizeof(addr)) == -1) { switch (errno) { case EAGAIN: print_error("The new connection would exceed the " "maximum number of IUCV connections"); break; case ENETUNREACH: print_error("The target z/VM guest virtual machine " "is not logged on"); break; case EACCES: print_error("The IUCV authorizations do not permit " "connecting to the target z/VM guest"); break; default: print_error("Connecting to the z/VM guest virtual " "machine failed"); break; } AUDIT("Connection to %s/%s failed for user %s (uid=%i)", conf.host, conf.service, (passwd != NULL) ? passwd->pw_name : "n/a", geteuid()); rc = 2; goto return_on_error; } AUDIT("Established connection to %s/%s for user %s (uid=%i)", conf.host, conf.service, (passwd != NULL) ? passwd->pw_name : "n/a", geteuid()); /* send client parameters */ iucvtty_tx_termenv(server, DEFAULT_TERM); iucvtty_tx_winsize(server, STDIN_FILENO); /* register signal handler */ sigemptyset(&sigact.sa_mask); sigact.sa_flags = SA_RESTART; sigact.sa_handler = sig_handler; sigaction(SIGWINCH, &sigact, NULL); sigaction(SIGTERM, &sigact, NULL); /* modify terminal settings */ if (tcgetattr(STDIN_FILENO, &ios_orig)) { print_error("Getting the terminal I/O settings failed"); rc = 3; goto return_on_error; } memcpy(&ios, &ios_orig, sizeof(ios)); /* put terminal into raw mode */ cfmakeraw(&ios); /* NOTE: If the TTY driver (ldisc) runs in TTY_DRIVER_REAL_RAW, * we need to do the input character processing here; * that means to translate CR into CR + NL (ICRNL). * Define TTY_REAL_RAW in for that case. */ #ifdef TTY_REAL_RAW ios.c_iflag |= ICRNL; /* | IGNPAR | IGNBRK; */ #endif tcflush(STDIN_FILENO, TCIOFLUSH); if (tcsetattr(STDIN_FILENO, TCSANOW, &ios)) { print_error("Modifying the terminal I/O settings failed"); rc = 4; goto return_on_error; } iucvtty_worker(server, &conf); tcsetattr(STDIN_FILENO, TCSANOW, &ios_orig); rc = 0; return_on_error: close(server); closelog(); close_session_log(); return rc; } s390-tools-2.3.0/iucvterm/src/iucvtty.c000066400000000000000000000157051323410431100176500ustar00rootroot00000000000000/* * iucvtty - Application that provides a full-screen terminal for iucvconn * * Core application * * Copyright IBM Corp. 2008, 2017 * * 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include "lib/util_base.h" #include "af_iucv.h" #include "iucvterm/config.h" #include "iucvterm/functions.h" #include "iucvterm/gettext.h" #define SYSLOG_IDENT "iucvtty" #define PRG_COMPONENT SYSLOG_IDENT #define TERM_BUFSIZE 256 #define TERM_DEFAULT "linux" static volatile sig_atomic_t sig_shutdown; /** * sig_handler() - Signal handler * @sig: Signal number. */ static void sig_handler(int sig) { sig_shutdown = sig; } /** * exec_login_prog() - execute a login program * @cmd: Path to the (login) program executable */ static int exec_login_prog(char *cmd[]) { int rc; if (cmd != NULL) rc = execv(cmd[0], cmd); else rc = execl("/bin/login", "/bin/login", (char *) NULL); return rc; } /** * iucvtty_worker() - Handle an incoming client connection * @client: Client file descriptor * @master: PTY master file descriptor * @slave: PTY slave file descriptor * @cfg: IUCV TTY configuration structure. */ static int iucvtty_worker(int client, int master, int slave, const struct iucvterm_cfg *cfg) { int rc; struct iucvtty_msg *msg; pid_t child; fd_set set; size_t chunk; char term_env[TERM_BUFSIZE]; /* flush pending terminal data */ tcflush(master, TCIOFLUSH); /* read terminal parameters from client */ if (iucvtty_rx_termenv(client, term_env, TERM_BUFSIZE)) sprintf(term_env, TERM_DEFAULT); /* start login program */ child = fork(); if (child == -1) { print_error("Creating a new process to run the " "login program failed"); iucvtty_tx_error(client, ERR_FORK); return 1; /* return from worker */ } if (child == 0) { /* child process */ closelog(); /* close syslog */ /* setup terminal */ if (login_tty(slave)) { print_error("Setting up a terminal for user login failed"); iucvtty_tx_error(client, ERR_SETUP_LOGIN_TTY); exit(2); } setenv("TERM", term_env, 1); if (exec_login_prog(cfg->cmd_parms)) { print_error("Running the login program failed"); iucvtty_tx_error(client, ERR_CANNOT_EXEC_LOGIN); } exit(3); /* we only reach here if exec has failed */ } /* setup buffers */ msg = malloc(MSG_BUFFER_SIZE); if (msg == NULL) { print_error("Allocating memory for the data buffer failed"); rc = 2; goto out_kill_login; } /* multiplex i/o between login program and socket. */ rc = 0; chunk = 0; while (!sig_shutdown) { FD_ZERO(&set); FD_SET(client, &set); FD_SET(master, &set); if (select(MAX(master, client) + 1, &set, NULL, NULL, NULL) == -1) { if (errno == EINTR) continue; break; } if (FD_ISSET(client, &set)) { if (iucvtty_read_msg(client, msg, MSG_BUFFER_SIZE, &chunk)) break; switch (msg->type) { case MSG_TYPE_DATA: iucvtty_copy_data(master, msg); break; case MSG_TYPE_WINSIZE: if (msg->datalen != sizeof(struct winsize)) break; if (ioctl(master, TIOCSWINSZ, (struct winsize *) msg->data)) print_error("Resizing the terminal " "window failed"); break; case MSG_TYPE_TERMIOS: /* ignored */ break; case MSG_TYPE_ERROR: iucvtty_error(msg); break; } } if (FD_ISSET(master, &set)) if (iucvtty_tx_data(client, master, msg, MSG_BUFFER_SIZE)) break; } free(msg); out_kill_login: /* ensure the chld is terminated before calling waitpid: * - in case a sigterm has been received, * - or a sigchld from other than the chld */ kill(child, SIGKILL); /* cause a sigchld */ waitpid(child, NULL, 0); return rc; } /** * main() - IUCV TTY program startup */ int main(int argc, char *argv[]) { struct iucvterm_cfg conf; /* program configuration */ struct sockaddr_iucv saddr, caddr; /* IUCV socket address info */ char client_host[9]; /* client guest name */ int server, client; /* socket file descriptors */ int master, slave; /* pre-allocated PTY fds */ struct sigaction sigact; /* signal handler */ int rc; socklen_t len; /* gettext initialization */ gettext_init(); /* parse command line arguments */ parse_options(PROG_IUCV_TTY, &conf, argc, argv); /* create server socket... */ server = iucvtty_socket(&saddr, NULL, conf.service); if (server == -1) { print_error((errno == EAFNOSUPPORT) ? N_("The AF_IUCV address family is not available") : N_("Creating the AF_IUCV socket failed")); return 1; } if (bind(server, (struct sockaddr *) &saddr, sizeof(saddr)) == -1) { print_error("Binding the AF_IUCV socket failed"); close(server); return 1; } if (listen(server, 1) == -1) { print_error("Listening for incoming connections failed"); close(server); return 1; } /* pre-allocate PTY master/slave file descriptors */ if (openpty(&master, &slave, NULL, NULL, NULL)) { print_error("Opening a new PTY master/slave device pair failed"); close(server); return 1; } /* set close-on-exec for file descriptors */ fcntl(master, F_SETFD, FD_CLOEXEC); fcntl(server, F_SETFD, FD_CLOEXEC); /* syslog */ openlog(SYSLOG_IDENT, LOG_PID, LOG_AUTHPRIV); syslog(LOG_INFO, "Listening on terminal ID: %s, using pts device: %s", conf.service, ttyname(slave)); rc = 0; len = sizeof(struct sockaddr_iucv); /* accept a new client connection */ client = accept(server, (struct sockaddr *) &caddr, &len); if (client == -1) { print_error("An incoming connection could not be accepted"); rc = 2; goto exit_on_error; } /* check if client is allowed to connect */ userid_cpy(client_host, caddr.siucv_user_id); if (is_client_allowed(client_host, &conf)) { iucvtty_tx_error(client, ERR_NOT_AUTHORIZED); syslog(LOG_WARNING, "Rejected client connection from %s; " "Client is not allowed to connect.", client_host); rc = 3; } else { /* client is allowed to connect */ syslog(LOG_INFO, "Accepted client connection from %s", client_host); /* set close-on-exec for client socket */ fcntl(client, F_SETFD, FD_CLOEXEC); /* close server socket */ close(server); /* setup signal handler to notify shutdown signal */ sigemptyset(&sigact.sa_mask); sigact.sa_flags = SA_RESTART; sigact.sa_handler = sig_handler; if (sigaction(SIGCHLD, &sigact, NULL) || sigaction(SIGTERM, &sigact, NULL) || sigaction(SIGINT, &sigact, NULL) || sigaction(SIGPIPE, &sigact, NULL)) { print_error("Registering a signal handler failed"); rc = 4; goto exit_on_error; } /* handle client terminal connection */ rc = iucvtty_worker(client, master, slave, &conf); } close(client); exit_on_error: close(slave); close(master); closelog(); return rc; } s390-tools-2.3.0/iucvterm/src/ttyrun.c000066400000000000000000000117411323410431100175020ustar00rootroot00000000000000/* * ttyrun - Start a program if a specified terminal device is available * * * ttyrun is typically used to prevent a respawn through the init(8) * program when a terminal is not available. * ttyrun runs the specific program if the specified terminal device * can be opened successfully. Otherwise the program enters a sleep or * exits with a specified return value. * * Example: To start /sbin/agetty on terminal device hvc1, use: * * h1:2345:respawn:/sbin/ttyrun hvc1 /sbin/agetty -L 9600 %t linux * * Note: %t is resolved to the terminal device "hvc1" before /sbin/agetty * is started. * * Return values: * 1 - invalid argument or parameter is missing * 2 - terminal does not resolve to a terminal device * 3 - starting the specified program failed * 1..255 - terminal is not available and the return code is * specified with the -e option * * Copyright 2017 IBM Corp. * * s390-tools is free software; you can redistribute it and/or modify * it under the terms of the MIT license. See LICENSE for details. */ #include #include #include #include #include #include #include #include #include #include #include #include "lib/zt_common.h" #define TTY_ESCAPE_STR "%t" #define EXIT_INVALID_ARG 1 #define EXIT_NO_TERMINAL 2 #define EXIT_EXEC_FAILED 3 static const char usage[] = "Usage: %s [-e status] []\n" " %s [-h|--help] [-v|--version]\n" "\n" "Start the program if the specified terminal device is available.\n" "If the terminal device cannot be opened, sleep until a signal is received\n" "that causes an exit or exit with the return value specified with status.\n" "\n" "-e, --exitstatus Specifies an exit status in the range 1 to 255.\n" "-V, --verbose Displays syslog messages.\n" "-h, --help Displays this help, then exits.\n" "-v, --version Displays version information, then exits.\n"; static void __noreturn help_exit(const char *prg) { printf(usage, prg, prg); exit(EXIT_SUCCESS); } static void __noreturn version_exit(const char *prg) { printf("%s: Start a program if a terminal device is available, " "version %s\n", prg, RELEASE_STRING); printf("Copyright IBM Corp. 2010, 2017\n"); exit(EXIT_SUCCESS); } static void err_exit(const char *prg, const char *msg) { fprintf(stderr, "%s: %s\n", prg, msg); exit(EXIT_INVALID_ARG); } static void wait_and_exit(void) { /* sleep until a signal is received, then exit */ pause(); exit(EXIT_SUCCESS); } static const struct option prog_opts[] = { { "help", no_argument, NULL, 'h'}, { "version", no_argument, NULL, 'v'}, { "exitstatus", required_argument, NULL, 'e'}, { "verbose", no_argument, NULL, 'V'}, { NULL, no_argument, NULL, 0 }, }; int main(int argc, char *argv[]) { int rc, tty, i, c, index, done, term_index, verbose; char terminal[PATH_MAX] = ""; unsigned long exitstatus; /* parse command options */ if (argc == 1) err_exit(argv[0], "One or more options are required but missing"); exitstatus = done = term_index = verbose = 0; while (!done) { c = getopt_long(argc, argv, "-hve:V", prog_opts, NULL); switch (c) { case -1: done = 1; break; case 1: /* the first non-optional argument must be the * terminal device */ if (!strncmp(optarg, "/", 1)) strncpy(terminal, optarg, PATH_MAX - 1); else snprintf(terminal, PATH_MAX, "/dev/%s", optarg); terminal[PATH_MAX - 1] = 0; term_index = optind - 1; done = 1; break; case 'e': errno = 0; exitstatus = strtoul(optarg, (char **) NULL, 10); if (errno == ERANGE) err_exit(argv[0], "The exit status must be " "an integer in the range 1 to 255"); if (!exitstatus || exitstatus > 255) err_exit(argv[0], "The exit status must be " "in the range 1 to 255"); break; case 'V': verbose = 1; break; case 'h': help_exit(argv[0]); case 'v': version_exit(argv[0]); case '?': fprintf(stderr, "Try %s --help for more information\n", argv[0]); exit(EXIT_INVALID_ARG); } } index = optind; /* check terminal */ if (!strlen(terminal)) err_exit(argv[0], "You must specify the name of " "a terminal device"); /* any program to start? */ if (index == argc) err_exit(argv[0], "You must specify a program to start"); /* open and check terminal device */ tty = open(terminal, O_NOCTTY | O_RDONLY | O_NONBLOCK); if (tty == -1) { if (verbose) { openlog(argv[0], LOG_PID, LOG_DAEMON); syslog(LOG_INFO, "Could not open tty %s (%s)", terminal, strerror(errno)); closelog(); } /* enter wait or exit */ if (exitstatus) exit(exitstatus); wait_and_exit(); } rc = !isatty(tty); close(tty); if (rc) exit(EXIT_NO_TERMINAL); /* start getty program */ for (i = index; i < argc; i++) if (!strcmp(argv[i], TTY_ESCAPE_STR) && term_index) argv[i] = argv[term_index]; if (execv(argv[index], argv + index)) exit(EXIT_EXEC_FAILED); exit(EXIT_SUCCESS); } s390-tools-2.3.0/iucvterm/test/000077500000000000000000000000001323410431100161555ustar00rootroot00000000000000s390-tools-2.3.0/iucvterm/test/Makefile000066400000000000000000000015431323410431100176200ustar00rootroot00000000000000#! /usr/bin/make -f include ../../common.mak ALL_CPPFLAGS += -I../include ALL_CFLAGS += -g PROGRAMS = test_afiucv TEST_PROGRAMS = test_functions test_allow TEST_PROGRAMS_NORUN = test_auditlog test_afiucv: test_afiucv.o test_functions: test_functions.o test_common.o ../src/functions.o test_allow: test_allow.o test_common.o ../src/functions.o test_auditlog: test_auditlog.o test_common.o ../src/auditlog.o \ ../src/functions.o all: $(PROGRAMS) check: $(TEST_PROGRAMS) $(TEST_PROGRAMS_NORUN) @for prg in $(TEST_PROGRAMS); do \ failed=0 ;\ echo ; echo "=== RUN : $$prg ===" ;\ ./$$prg || failed=$$? ;\ if test x$$failed = x0; then \ echo "=== PASS: $$prg ===" ;\ else \ echo "=== FAIL: $$prg (rc=$$failed) ===" ;\ fi ;\ done install: clean: -rm -f *.o $(PROGRAMS) $(TEST_PROGRAMS) $(TEST_PROGRAMS_NORUN) .PHONY: install clean s390-tools-2.3.0/iucvterm/test/test.h000066400000000000000000000011361323410431100173060ustar00rootroot00000000000000/* * test_common - Test program for the IUCV Terminal Applications * * Definition of common functions for test programs * * Copyright IBM Corp. 2008, 2017 * * s390-tools is free software; you can redistribute it and/or modify * it under the terms of the MIT license. See LICENSE for details. */ #ifndef __TEST_H_ #define __TEST_H_ #include #include #include "lib/util_base.h" #include "iucvterm/proto.h" #define __fail() assert(0); extern int __socketpair(int sv[2]); extern int __msgcmp(const struct iucvtty_msg *, const struct iucvtty_msg *); #endif /* __TEST_H_ */ s390-tools-2.3.0/iucvterm/test/test_afiucv.c000066400000000000000000000012241323410431100206340ustar00rootroot00000000000000/* * test_afiucv - Test program for the IUCV Terminal Applications * * Test program to check if the AF_IUCV family is supported by * the running Linux kernel * * Copyright IBM Corp. 2008, 2017 * * 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 #include #include #include #include "af_iucv.h" int main(void) { int sk; sk = socket(AF_IUCV, SOCK_STREAM, 0); if (sk == -1) { if (errno == EAFNOSUPPORT) perror("AF_IUCV address family not supported"); return -1; } else close(sk); return 0; } s390-tools-2.3.0/iucvterm/test/test_allow.c000066400000000000000000000037121323410431100205010ustar00rootroot00000000000000/* * test_allow - Test program for the IUCV Terminal Applications * * Test program to check common functions that use the regex api * * Copyright IBM Corp. 2008, 2017 * * 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 #include #include "iucvterm/config.h" #include "iucvterm/functions.h" #include "test.h" static int test_userid_cpy(char padded[9]) { char uid[8]; char id_str[9]; size_t len; memcpy(uid, padded, 8); userid_cpy(id_str, uid); len = strlen(id_str); assert(0 == memcmp(padded, id_str, len)); if (strchr(id_str, ' ') != NULL) __fail(); return len; } int main(void) { char re_ok[] = "^T63[[:digit:]]{1,5}$"; char re_wrong[] = "^t63[[:alpha:]+$"; char re_short[] = "^lnx[[:digit:]]{3}$"; char id_short[9]; char user_id[8]; /* redirect stderr to /dev/null to avoid regex error output */ freopen("/dev/null", "w", stderr); assert(-1 == is_regex_valid(re_wrong)); assert(-1 == is_regex_valid(NULL)); assert(0 == is_regex_valid(re_ok)); assert(0 == is_regex_valid(re_short)); /* simple */ assert(0 == strmatch("T6345050", "T6345050")); /* using re */ assert(0 == strmatch("T6345050", re_ok)); assert(0 == strmatch("t6345050", re_ok)); /* icase */ assert(-1 == strmatch("T6345050", NULL)); assert(-1 == strmatch("T6345050", re_wrong)); assert(1 == strmatch("T634505A", re_ok)); /* test userid_cpy() function */ snprintf(id_short, 9, "%-8s", ""); assert(0 == test_userid_cpy(id_short)); snprintf(id_short, 9, "1"); assert(1 == test_userid_cpy(id_short)); snprintf(id_short, 9, "12345678"); assert(8 == test_userid_cpy(id_short)); snprintf(id_short, 9, "ABCD"); assert(4 == test_userid_cpy(id_short)); /* check for user IDs < 8 characters */ snprintf(id_short, 9, "%-8s", "LNX001"); memcpy(user_id, id_short, 8); userid_cpy(id_short, user_id); assert(0 == strmatch(id_short, re_short)); return 0; } s390-tools-2.3.0/iucvterm/test/test_auditlog.c000066400000000000000000000031601323410431100211700ustar00rootroot00000000000000/* * test_auditlog - Test program for the IUCV Terminal Applications * * Test program for the session logging functions used by iucvconn(1). * * Copyright IBM Corp. 2008, 2017 * * 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 #include #include #include #include #include #include "iucvterm/functions.h" #include "test.h" #define BUF_SIZE 1024 static void do_cleanup(const char *filename) { char tmp[64]; if (access(filename, W_OK) == 0) unlink(filename); snprintf(tmp, 64, "%s.timing", filename); if (access(tmp, W_OK) == 0) unlink(tmp); snprintf(tmp, 64, "%s.info", filename); if (access(tmp, W_OK) == 0) unlink(tmp); } int main(int argc, char* argv[]){ char *filepath = NULL; char *buf; char tmpfile[64]; int interactive; ssize_t rc; if (argc < 2) { fprintf(stderr, "Usage: %s \n", argv[0]); sprintf(tmpfile, "/tmp/test_auditlog.a1b2c3.%u", getpid()); fprintf(stderr, "now using file %s\n", tmpfile); filepath = tmpfile; /* cleanup stale file */ if (access(tmpfile, W_OK) == 0) do_cleanup(tmpfile); interactive = 0; } else { interactive = 1; filepath = argv[1]; } buf = malloc(BUF_SIZE); rc = open_session_log(filepath); assert(rc == 0); write_session_info("The name of the log file is: %s\n", filepath); sprintf(buf, "This is an entry\n"); rc = write_session_log(buf, strlen(buf)); assert(rc == 0); close_session_log(); free(buf); if (!interactive) do_cleanup(tmpfile); return 0; } s390-tools-2.3.0/iucvterm/test/test_chiucvallow.sh000077500000000000000000000073501323410431100221000ustar00rootroot00000000000000#!/bin/sh # # Test program for chiucvallow # # # Copyright IBM Corp. 2009, 2017 # # s390-tools is free software; you can redistribute it and/or modify # it under the terms of the MIT license. See LICENSE for details. # # set -x # test data file test_data=`mktemp /tmp/chiucvallow.testdata.XXXXXX` LOG_FILE=/dev/null TEST_PROG=${1:-../bin/chiucvallow.in} # run test program (we can savely use the .in version here) tprg() { test -x $TEST_PROG || chmod +x $TEST_PROG $TEST_PROG $@ >$LOG_FILE 2>&1 return $? } failed() { echo $1 exit 3 } # Init test -r "$test_data" || failed "Failed to create temporary file" trap "rm -f $test_data" EXIT TERM INT # 1: Test special characters cat > $test_data <<'EoData' # user ID filter with some exotic user ID names # # z/VM user ID consists mainly of alphanumeric characters. # Additional characters like @#_-$ are also allowed. However, @ and # are not # recommended. Additional characters can be configured. # # To use a common set of all the values possible, the iucv terminal tools # uses alphnumeric and underscore. The chiucvallow program also allows to use # the dollar sign ($). # # All other characters must not be used. # # NORMAL # underscores are allowed WITH_ # dollar sign is allowed for chiucvallow only WITH$ $VAR $ALLOC$ $$$$$$$$ 12345678 EoData tprg -V $test_data || failed "1: Failed: verify special z/VM user ID chars" # 2: Test special characters cat > $test_data <<'EoData' # the following entry fails -HYPHEN EoData tprg -V $test_data && failed "2: Failed: verify special z/VM user ID chars" # 3: Test maximum number of filter entries for i in `seq 1 500`; do printf "ID%05d\n" $i ; done > $test_data tprg -V $test_data || failed "3: Failed: verify correct numbers of filter entries" # 4: Test verification fails because of too many filter entries for i in `seq 1 501`; do printf "ID%05d\n" $i ; done > $test_data tprg -V $test_data && failed "4: Failed: verify correct numbers of filter entries" # 5: Test verification fails because of too many filter entries for i in `seq 1 510`; do printf "ID%05d\n" $i ; done > $test_data tprg -V $test_data && failed "5: Failed: verify correct numbers of filter entries" # 6: Test filter size (final size of filter entries, not file size) cat > $test_data <<'EoData' # z/VM user ID filter file for testing chiucvallow # # This file defines 500 user IDs with few comments to # increase the file size. # # The comments must not be ignored for file size calculation, because # comments are not part of the hvc_iucv_allow sysfs attribute. # # The size of the z/VM user ID filer that is reported by chiucvallow # must be: 4000 bytes = 7 * 500 + 500 # (500 user IDs a 7 bytes (in length) + 500 carriage returns) EoData for i in `seq 1 500`; do printf "ID%05d\n" $i ; done >> $test_data echo '# End of z/VM user ID filter file ' >> $test_data test `stat -c%s $filename 2>/dev/null || echo 0` -ge 4095 \ && failed "6: Failed to create test pre-req" tprg -V $test_data || failed "6: Failed: verify filter with correct filter size" # 7: Test filter size that exceed 4K (500 * 8 + 500 = 4500) for i in `seq 1 500`; do printf "ID%06d\n" $i ; done > $test_data tprg -V $test_data && failed "7: Failed: verify filter with incorred filter size" # 8: Test valid wildcard filter entries cat > $test_data <<'EoData' # z/VM user ID filter for testing chiucvallow # # This file contains wildcard filter entries that must be considered as # valid. # ID100* A* AB* ABC* ABCD* ABCDE* ABCDEF* ABCDEFG* 12345678 EoData tprg -V $test_data || failed "8: Failed: verify filter with valid wildcards" # 9: Test wildcard filter entries that are not valid for ent in "*" "*A" "ABC*A" "A2345678*"; do echo "$ent" > $test_data tprg -V $test_data && failed "9: Failed: verified invalid wildcard: '$ent'" done exit 0 s390-tools-2.3.0/iucvterm/test/test_common.c000066400000000000000000000015421323410431100206520ustar00rootroot00000000000000/* * test_common library - Test program lib for the IUCV Terminal Applications * * Common functions for test programs * * Copyright IBM Corp. 2008, 2017 * * 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 #include #include #include #include #include #include "iucvterm/proto.h" int __socketpair(int sv[2]) { int rc; rc = socketpair(AF_LOCAL, SOCK_STREAM, 0, sv); if (rc) perror("Could not create socketpair"); assert(rc == 0); return rc; } int __msgcmp(const struct iucvtty_msg *m1, const struct iucvtty_msg *m2) { if (m1->type != m2->type) return 1; if (m1->datalen != m2->datalen) return 2; if (0 != memcmp(m1->data, m2->data, m1->datalen)) return 3; return 0; } s390-tools-2.3.0/iucvterm/test/test_functions.c000066400000000000000000000135111323410431100213710ustar00rootroot00000000000000/* * test_functions - Test program for the IUCV Terminal Applications * * Test program for the common functions used by the * IUCV Terminal Applications * * Copyright IBM Corp. 2008, 2017 * * 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 #include #include #include #include #include #include "iucvterm/functions.h" #include "iucvterm/proto.h" #include "test.h" #define BUF_SIZE 256 static int __testReadWriteMessage(int[2]); static int __testReadWriteMessage_chunks(int[2]); static int __testReadWriteMessage_nochunks(int[2]); static int __testTransmitData(int[2]); static int __testTermEnvVar(int[2]); static int __testWinsize(int[2]); static int __testCopyData(int[2]); int main(void) { int sockv[2]; __socketpair(sockv); assert(!__testReadWriteMessage(sockv)); assert(!__testReadWriteMessage_chunks(sockv)); assert(!__testReadWriteMessage_nochunks(sockv)); assert(!__testTermEnvVar(sockv)); assert(!__testWinsize(sockv)); assert(!__testCopyData(sockv)); assert(!__testTransmitData(sockv)); close(sockv[0]); close(sockv[1]); return 0; } static int __testReadWriteMessage_chunks(int sv[2]) { struct iucvtty_msg *msg[2]; char data[] = "Hallo\0test1\0test2\0test3\0test4\0test5\0test6\0test7\0test8"; size_t chunk; int i; /* setup msg to be sent */ msg[0] = msg_alloc(MSG_TYPE_DATA, BUF_SIZE); msg[1] = msg_alloc(MSG_TYPE_DATA, BUF_SIZE); /* send msg */ msg_cpy_from(msg[0], data, sizeof(data)); if (iucvtty_write_msg(sv[0], msg[0])) return 1; /* read msg */ chunk = 0; for (i = 0; i < 9; i++) { if (iucvtty_read_msg(sv[1], msg[1], 6 + MSG_DATA_OFFSET, &chunk)) return 2; assert(msg[1]->datalen == 6); assert(0 == memcmp(msg[1]->data, data + (i * 6), msg[1]->datalen)); /* printf("chunk=%i datalen=%u data='%s'\n", chunk, msg[1]->datalen, msg[1]->data); */ } assert(chunk == 0); msg_free(msg[0]); msg_free(msg[1]); return 0; } static int __testReadWriteMessage_nochunks(int sv[2]) { struct iucvtty_msg *msg[2]; char data[] = "Hallo\0test1\0test2\0test3\0test4\0test5\0test6\0test7\0test8"; int i; size_t residual = 0; /* setup msg to be sent */ msg[0] = msg_alloc(MSG_TYPE_DATA, BUF_SIZE); msg[1] = msg_alloc(MSG_TYPE_DATA, BUF_SIZE); /* send and read msg .. with chunks disabled. * The iucvtty_read_msg() shall ignore any data more than the given * length. */ i = 7; while (i--) { msg_cpy_from(msg[0], data, sizeof(data)); if (iucvtty_write_msg(sv[0], msg[0])) return 1; /* read msg */ if (iucvtty_read_msg(sv[1], msg[1], 6 + MSG_DATA_OFFSET, &residual)) return 2; assert(0 == memcmp(msg[1]->data, data, msg[1]->datalen)); iucvtty_skip_msg_residual(sv[1], &residual); assert(0 == residual); /*printf("datalen=%u data='%s'\n", msg[1]->datalen, msg[1]->data);*/ } msg_free(msg[0]); msg_free(msg[1]); return 0; } static int __testReadWriteMessage(int sv[2]) { struct iucvtty_msg *msg[2]; char data[10] = "Hallo"; size_t chunk = 0; /* setup msg to be sent */ msg[0] = msg_alloc(MSG_TYPE_DATA, BUF_SIZE); msg[1] = msg_alloc(MSG_TYPE_DATA, BUF_SIZE); /* send msg */ msg_cpy_from(msg[0], data, sizeof(data)); if (iucvtty_write_msg(sv[0], msg[0])) return 1; /* read msg */ if (iucvtty_read_msg(sv[1], msg[1], BUF_SIZE, &chunk)) return 2; iucvtty_skip_msg_residual(sv[1], &chunk); /* compare msg */ if (__msgcmp(msg[0], msg[1])) return 3; msg_free(msg[0]); msg_free(msg[1]); return 0; } static int __testTransmitData(int sv[2]) { struct iucvtty_msg *msg; char data[] = "Vah __testTransmitData hai."; size_t chunk = 0; /* I. write something to sv[0] * II. tx_data: read from sv[1], write to sv[0] * III. read iucvtty_msg from sv[1] */ assert(sizeof(data) == write(sv[0], data, sizeof(data))); msg = msg_alloc(MSG_TYPE_DATA, BUF_SIZE); assert(0 == iucvtty_tx_data(sv[0], sv[1], msg, BUF_SIZE)); assert(0 == iucvtty_read_msg(sv[1], msg, BUF_SIZE, &chunk)); assert(MSG_TYPE_DATA == msg->type); assert(sizeof(data) == msg->datalen); assert(0 == memcmp(&data, msg->data, msg->datalen)); msg_free(msg); return 0; } static int __testTermEnvVar(int sv[2]) { #define __BUF_SIZE 81 char term[__BUF_SIZE] = "my_default_term"; char buf[__BUF_SIZE]; char *orig_term = getenv("TERM"); /* I. */ if (orig_term != NULL) { assert(0 == iucvtty_tx_termenv(sv[0], NULL)); assert(0 == iucvtty_rx_termenv(sv[1], buf, __BUF_SIZE)); assert(0 == strncmp(orig_term, buf, MIN(__BUF_SIZE, strlen(orig_term)))); } unsetenv("TERM"); /* clear term */ /* II. */ assert(0 == iucvtty_tx_termenv(sv[0], term)); assert(0 == iucvtty_rx_termenv(sv[1], buf, __BUF_SIZE)); assert(0 == strncmp(buf, term, __BUF_SIZE)); /* III. */ assert(0 == iucvtty_tx_termenv(sv[0], NULL)); assert(0 == iucvtty_rx_termenv(sv[1], buf, __BUF_SIZE)); assert(0 == strncmp("", buf, 1)); setenv("TERM", orig_term, 1); /* restore original term */ return 0; #undef __BUF_SIZE } static int __testWinsize(int sv[2]) { struct iucvtty_msg *msg; struct winsize winsz; size_t chunk = 0; msg = msg_alloc(MSG_TYPE_DATA, BUF_SIZE); assert(0 == ioctl(STDIN_FILENO, TIOCGWINSZ, &winsz)); assert(0 == iucvtty_tx_winsize(sv[0], STDIN_FILENO)); assert(0 == iucvtty_read_msg(sv[1], msg, BUF_SIZE, &chunk)); assert(MSG_TYPE_WINSIZE == msg->type); assert(sizeof(struct winsize) == msg->datalen); assert(0 == memcmp(&winsz, msg->data, msg->datalen)); msg_free(msg); return 0; } static int __testCopyData(int sv[2]) { struct iucvtty_msg *msg; char data[] = "palim palim"; char buf[BUF_SIZE]; msg = msg_alloc(MSG_TYPE_DATA, BUF_SIZE); msg_cpy_from(msg, data, sizeof(data)); assert(0 == iucvtty_copy_data(sv[0], msg)); assert(sizeof(data) == read(sv[1], &buf, BUF_SIZE)); assert(0 == memcmp(data, buf, sizeof(data))); msg_free(msg); return 0; } s390-tools-2.3.0/libccw/000077500000000000000000000000001323410431100146035ustar00rootroot00000000000000s390-tools-2.3.0/libccw/Makefile000066400000000000000000000002001323410431100162330ustar00rootroot00000000000000include ../common.mak lib = libccw.a all: $(lib) objects = ccw.o $(lib): $(objects) install: all clean: rm -f *.o $(lib) s390-tools-2.3.0/libccw/ccw.c000066400000000000000000000017321323410431100155260ustar00rootroot00000000000000/** * ccw - Channel Command Word function library * * Process ccw_devid * * Copyright 2017 IBM Corp. * * s390-tools is free software; you can redistribute it and/or modify * it under the terms of the MIT license. See LICENSE for details. */ #include #include #include "lib/ccw.h" /** * Parse a string into a ccw_devid structure * * @param[in,out] devid Pointer to ccw_devid structure to be initialized * @parm[in] id String to parse * * @returns true if the input string has been parsed successfully; * otherwise false. */ bool ccw_parse_str(struct ccw_devid *devid, const char *id) { unsigned int cssid, ssid, devno; char d; if (strncasecmp(id, "0x", 2) == 0) return false; if (sscanf(id, "%4x %c", &devno, &d) == 1) { cssid = 0; ssid = 0; } else if (sscanf(id, "%2x.%1x.%4x %c", &cssid, &ssid, &devno, &d) != 3) { return false; } ccw_devid_init(devid, cssid, ssid, devno); return true; } s390-tools-2.3.0/libdasd/000077500000000000000000000000001323410431100147425ustar00rootroot00000000000000s390-tools-2.3.0/libdasd/Makefile000066400000000000000000000002341323410431100164010ustar00rootroot00000000000000include ../common.mak lib = libdasd.a all: $(lib) objects = dasd_sys.o dasd_ioctl.o $(lib): $(objects) Makefile install: all clean: rm -f *.o $(lib) s390-tools-2.3.0/libdasd/dasd_ioctl.c000066400000000000000000000126171323410431100172220ustar00rootroot00000000000000/* * dasd_ioctl - Library for dasd-related ioctls * * DASD related helper functions for accessing device information via ioctls * * Copyright IBM Corp. 2013, 2017 * Copyright Red Hat Inc. 2017 * * 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 #include #include #include #include #include #include "lib/dasd_base.h" #define COULD_NOT_OPEN_WARN(device) \ warn("Could not open device '%s'", device) #define COULD_NOT_CLOSE_WARN \ warn("Unable to close device") #define RUN_IOCTL(fd, req, argp) \ do { \ if (ioctl(fd, req, argp) != 0) { \ int err = errno; \ if (err != EBADF) \ dasd_close_device(fd); \ return err; \ } \ } while (0) static int dasd_open_device(const char *device, int flags) { int fd; fd = open(device, flags); if (fd == -1) COULD_NOT_OPEN_WARN(device); return fd; } static void dasd_close_device(int fd) { if (close(fd) != 0) COULD_NOT_CLOSE_WARN; } /* * Get DASD block size information for device * * @param[in] device node device node's name * @param[out] blksize the block size * * @retval 0 in case of success * @retval errno in case of failure * */ int dasd_get_blocksize(const char *device, unsigned int *blksize) { int fd; fd = dasd_open_device(device, O_RDONLY); RUN_IOCTL(fd, BLKSSZGET, blksize); dasd_close_device(fd); return 0; } /* * Get DASD block size information (in bytes) for device * * @param[in] device node device node's name * @param[out] blksize block size in bytes * * @retval 0 in case of success * @retval errno in case of failure */ int dasd_get_blocksize_in_bytes(const char *device, unsigned long long *blksize) { int fd; fd = dasd_open_device(device, O_RDONLY); RUN_IOCTL(fd, BLKGETSIZE64, blksize); dasd_close_device(fd); return 0; } /* * Get DASD disk information for device * * @param[in] device node device node's name * @param[out] info pointer to dasd information * * @retval 0 in case of success * @retval errno in case of failure */ int dasd_get_info(const char *device, dasd_information2_t *info) { int fd; fd = dasd_open_device(device, O_RDONLY); RUN_IOCTL(fd, BIODASDINFO2, info); dasd_close_device(fd); return 0; } /* * Get DASD disk geometry information for device * * @param[in] device node device node's name * @param[out] geo pointer to device geometry information * * @retval errno in case of failure * @retval 0 in case of success */ int dasd_get_geo(const char *device, struct hd_geometry *geo) { int fd; fd = dasd_open_device(device, O_RDONLY); RUN_IOCTL(fd, HDIO_GETGEO, geo); dasd_close_device(fd); return 0; } /* * Get DASD read/write status information for device * * @param[in] device node device node's name * @param[out] ro read-only status (1 for ro, 0 for rw) * * @retval 0 in case of success * @retval errno in case of failure */ int dasd_is_ro(const char *device, bool *ro) { int fd, val; fd = dasd_open_device(device, O_RDWR); RUN_IOCTL(fd, BLKROGET, &val); *ro = (val != 0) ? true : false; return 0; } /* * Disable DASD disk. * * @param[in] device node device node's name * * @retval 0 in case of success * @retval errno in case of failure * * Note: Before BIODASDFMT can be called, a DASD has to be disabled (or rather * put into a BASIC state) via BIODASDDISABLE. However, if you disable the DASD, * close the file descriptor, and then try to reopen it, it won't work as the * device isn't fully usable anymore. For BIODASDFMT to work, the file * descriptor opened for BIODASDISABLE has to be kept open until BIODASDFMT has * finished. */ int dasd_disk_disable(const char *device, int *fd) { *fd = dasd_open_device(device, O_RDWR); RUN_IOCTL(*fd, BIODASDDISABLE, NULL); return 0; } /* * Enable DASD disk * * @param[in] device node device node's name * * @retval 0 in case of success * @retval errno in case of failure */ int dasd_disk_enable(int fd) { RUN_IOCTL(fd, BIODASDENABLE, NULL); dasd_close_device(fd); return 0; } /* * Format DASD disk * * @param[in] fd device node's file descriptor * @param[in] p format options * * @retval 0 in case of success * @retval errno in case of failure */ int dasd_format_disk(int fd, format_data_t *p) { RUN_IOCTL(fd, BIODASDFMT, p); return 0; } /* * Check DASD format * * @param[in] device node device node's name * @param[in] p format params * * @retval 0 in case of success * @retval errno in case of failure */ int dasd_check_format(const char *device, format_check_t *p) { int fd; fd = dasd_open_device(device, O_RDONLY); RUN_IOCTL(fd, BIODASDCHECKFMT, p); dasd_close_device(fd); return 0; } /* * Reread partition table * * @param[in] device node device node's name * @param[in] ntries maximum number of tries * * @retval 0 in case of success * @retval errno in case of failure */ int dasd_reread_partition_table(const char *device, int ntries) { int i, fd, err = 0; fd = dasd_open_device(device, O_RDONLY); /* * If the BLKRRPART ioctl fails, it is most likely due to the device * just being in use by udev. So it is worthwhile to retry the ioctl * after a second as it is likely to succeed. */ for (i = 0; i < ntries; i++) { if (ioctl(fd, BLKRRPART, NULL) != 0) { err = errno; sleep(1); } else { err = 0; break; } } dasd_close_device(fd); return err; } s390-tools-2.3.0/libdasd/dasd_sys.c000066400000000000000000000055351323410431100167270ustar00rootroot00000000000000/* * dasd - Library for DASD related functions * * DASD related helper functions for accessing device information via sysfs * * Copyright IBM Corp. 2016, 2017 * * 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 #include #include "lib/dasd_sys.h" /** * Get raw-track access mode status * * The "devnode" parameter can be any valid relative or absolute path to * a DASD device node, for example: * * - /dev/dasda * - /dev/disk/by-path/ccw-0.0.bf20 * * @param[in] devnode Device node of interest * * @retval 1 Raw-track access mode is enabled * @retval 0 Raw-track access mode is disabled or * cannot be determined */ int dasd_sys_raw_track_access(char *devnode) { char busid[9]; char path[47]; FILE *fp; int rc; if (u2s_getbusid(devnode, busid)) return 0; sprintf(path, "/sys/bus/ccw/devices/%s/raw_track_access", busid); fp = fopen(path, "r"); if (!fp) return 0; rc = fgetc(fp) - '0'; fclose(fp); return (rc == 1) ? 1 : 0; } int dasd_get_pm_from_chpid(char *busid, unsigned int chpid, int *mask) { unsigned int val; char path[40]; int count, i; FILE *fp; sprintf(path, "/sys/bus/ccw/devices/%s/../chpids", busid); *mask = 0; fp = fopen(path, "r"); if (!fp) return ENODEV; for (i = 0; i < 8; i++) { count = fscanf(fp, " %x", &val); if (count != 1) { fclose(fp); return EIO; } if (val == chpid) *mask = 0x80 >> i; } fclose(fp); return 0; } /** * reset chpid * * The "devnode" parameter can be any valid relative or absolute path to * a DASD device node, for example: * * - /dev/dasda * - /dev/disk/by-path/ccw-0.0.bf20 * * @param[in] devnode Device node of interest * @param[in] chpid The chpid to reset * If NULL all chpids will be reset * * @return 0 on success, otherwise one of the following error codes: * - EINVAL No valid chpid specified. * - ENODEV Could not open device. * - ENOENT Specified chpid not found. * - EIO Other I/O error * */ int dasd_reset_chpid(char *devnode, char *chpid_char) { unsigned int chpid; char path[41]; char busid[9]; int mask, rc; char *endptr; FILE *fp; if (u2s_getbusid(devnode, busid)) return ENODEV; if (!chpid_char) { sprintf(path, "/sys/bus/ccw/devices/%s/path_reset", busid); fp = fopen(path, "w"); if (!fp) return ENODEV; fprintf(fp, "%s", "all\n"); fclose(fp); return 0; } errno = 0; chpid = strtoul(chpid_char, &endptr, 16); if (errno || (endptr && (*endptr != '\0'))) return EINVAL; rc = dasd_get_pm_from_chpid(busid, chpid, &mask); if (rc) return rc; if (!mask) return ENOENT; sprintf(path, "/sys/bus/ccw/devices/%s/path_reset", busid); fp = fopen(path, "w"); if (!fp) return ENODEV; fprintf(fp, "%02x", mask); fclose(fp); return 0; } s390-tools-2.3.0/libu2s/000077500000000000000000000000001323410431100145405ustar00rootroot00000000000000s390-tools-2.3.0/libu2s/Makefile000066400000000000000000000002071323410431100161770ustar00rootroot00000000000000include ../common.mak lib = libu2s.a all: $(lib) objects = u2s.o misc.o $(lib): $(objects) install: all clean: rm -f *.o $(lib) s390-tools-2.3.0/libu2s/misc.c000066400000000000000000000010361323410431100156370ustar00rootroot00000000000000/* * Misc - Local helper functions * * Copyright IBM Corp. 2016, 2017 * * 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 #include "lib/util_base.h" /* * Helper function that copies a string safely */ size_t misc_strlcpy(char *dest, const char *src, size_t size) { size_t str_len = strlen(src); size_t len; if (size) { len = MIN(size - 1, str_len); memcpy(dest, src, len); dest[len] = '\0'; } return str_len; } s390-tools-2.3.0/libu2s/misc.h000066400000000000000000000005011323410431100156400ustar00rootroot00000000000000/* * Misc - Local helper functions * * Copyright IBM Corp. 2016, 2017 * * s390-tools is free software; you can redistribute it and/or modify * it under the terms of the MIT license. See LICENSE for details. */ #ifndef MISC_H #define MISC_H size_t misc_strlcpy(char *, const char *, size_t); #endif /* MISC_H */ s390-tools-2.3.0/libu2s/u2s.c000066400000000000000000000205341323410431100154210ustar00rootroot00000000000000/* * * Copyright IBM Corp. 2004, 2017 * * 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 #include #include #include #include #include #include #include #include #include #include #include "lib/u2s.h" #include "misc.h" #define DEV_BUFFER_LENGTH 20 #define PATH_BUFFER_LENGTH 256 #define BUSIDSIZE 9 #define BLOCKPATH "/sys/block/" #define DEVICE_LINK "device" #define DEV_ATTRIBUTE "dev" /* * Helper function that expects a file name and returns 1 if this * is a directory or 0 otherwise. */ static int isdir(char *name) { struct stat statbuf; if (stat(name, &statbuf) < 0) return 0; return S_ISDIR(statbuf.st_mode); } /* * Helper function that expects a directory name in sysfs of the form * /sys/block// or /sys/block///. * It will try to read the file "dev" in this directory and compare * it's contents with the given dev string of the form :. * Trailing white space (newline) is ignored. * The buffer name is expected to be long enough to hold the additional "dev". * Returns 1 if the directory matches dev, 0 otherwise. */ static int check_directory(char *name, char *dev) { char buffer[DEV_BUFFER_LENGTH]; char *end; int fd; ssize_t count; int dev_attr_len, dev_parm_len; unsigned int namelen; namelen = strlen(name); if ((PATH_BUFFER_LENGTH - namelen) < sizeof(DEV_ATTRIBUTE)) return 0; end = name + namelen; strcpy(end, DEV_ATTRIBUTE); fd = open(name, O_RDONLY); *end = 0; if (fd < 0) return 0; count = read(fd, buffer, DEV_BUFFER_LENGTH); close(fd); if (count < 0) return 0; dev_attr_len = strspn(buffer, "1234567890:"); dev_parm_len = strlen(dev); if (dev_attr_len != dev_parm_len ) return 0; return (strncmp(dev, buffer, dev_parm_len) == 0); } /* * Helper function that expects a directory name in sysfs of the form * /sys/block//. It will try to read a link "device" * in this directory and extract the busid, which is the last part * of that link. The buffer name is expected to be long enough * to hold the additional "device". * name: block device path in sysfs. * busid: buffer in which the busid string will be returned * returns 0 for successful operation and -1 in case of an error. */ static int extract_busid(char *name, char *busid) { int count; unsigned int namelen; char linkbuffer[PATH_BUFFER_LENGTH]; char *start, *end; size_t len; namelen = strlen(name); if ((PATH_BUFFER_LENGTH - namelen) < sizeof(DEVICE_LINK)) return 0; end = name + namelen; strcpy(end, DEVICE_LINK); count = readlink(name, linkbuffer, PATH_BUFFER_LENGTH - 1); if (count < 0) return -1; linkbuffer[count] = 0; start = strrchr(linkbuffer, '/'); if (!start) return -1; start++; len = misc_strlcpy(busid, start, BUSIDSIZE); if (len >= BUSIDSIZE) return -1; return 0; }; /* * Helper function that makes some basic checks on a directory entry. * The function checks if there is still enough space left in the buffer * for the new string, excludes '.' and '..', and verifies that the entry * is actually a directory. * buffer: the beginning of the name buffer * oldend: the current end of the string in the name buffer * dir: the dirent in question * returns: a pointer to the new end of the string in buffer or NULL if * one of the checks failed */ static char *append_if_directory(char *buffer, char *oldend, struct dirent *dir) { char *newend; int oldlength, dirlength; if (strcmp(dir->d_name, ".") == 0 || strcmp(dir->d_name, "..") == 0) return NULL; oldlength = strlen(buffer); dirlength = strlen(dir->d_name); if (PATH_BUFFER_LENGTH < oldlength + dirlength + 2) return NULL; strcpy(oldend, dir->d_name); if (!isdir(buffer)) { *oldend = 0; return NULL; } newend = oldend + dirlength; strcpy(newend, "/"); newend++; return newend; } /* * helper function that searches for a specific block device and returns * it's busid * dev: : of the device * busid: buffer in which the busid string will be returned * returns 0 for successful operation and -1 in case of an error. */ static int find_busid_in_sysfs(char *dev, char *busid) { DIR *blockdir, *diskdir; struct dirent *blockde, *diskde; int found = 0; char namebuffer[PATH_BUFFER_LENGTH]; char *blockend, *diskend = NULL, *partend; /* everything, including the other helper functions, works on the * same buffer area 'namebuffer'. The pointers blockend, diskend * and partend point to the end of the various names. * Example: * "/sys/block/dasda/dasda1/" * ^ blockend * ^ diskend * ^ partend */ strcpy(namebuffer,BLOCKPATH); blockdir = opendir(namebuffer); if (!blockdir) return -1; blockend = namebuffer + strlen(namebuffer); /* check each entry in /sys/block */ while ((blockde = readdir(blockdir))) { diskend = append_if_directory(namebuffer, blockend, blockde); if (!diskend) continue; found = check_directory(namebuffer, dev); if (found) break; diskdir = opendir(namebuffer); if (!diskdir) continue; /* check each entry in /sys/block/ */ while ((diskde = readdir(diskdir))) { partend = append_if_directory( namebuffer, diskend, diskde); if (!partend) continue; found = check_directory(namebuffer, dev); if (found) break; } closedir(diskdir); if (found) break; } closedir(blockdir); if (found) { *diskend = 0; /* remove partition directory from name */ return extract_busid(namebuffer, busid); } else return -1; } /* * helper function that searches for a specific block device in * /proc/dasd/devices and returns it's bus-ID * maja, mina: , of the device * busid: buffer in which the bus-ID string will be returned * returns 0 for successful operation and -1 in case of an error * e.g. /proc/dasd/devices does not exist. * * An entry looks like: * 0.0.XXXX(DISCIPLINE) at ( MAJ: MIN) is dasdX : * active at blocksize: BLOCKSIZE, BLOCKS blocks, SIZE MB */ static int find_busid_in_proc(int maja, int mina, char *busid) { FILE *filp; char bus[BUSIDSIZE]; int majb, minb, rc; size_t len; rc = -1; filp = fopen("/proc/dasd/devices", "r"); if (!filp) return rc; while (fscanf(filp, "%[^(] %*[^)] ) at ( %d : %d %*[^\n]\n", bus, &majb, &minb) != EOF) { if ((maja == majb) && (mina == minb)) { len = misc_strlcpy(busid, bus, BUSIDSIZE); if (len < BUSIDSIZE) rc = 0; break; } } fclose(filp); return rc; } /* * Return the busid of a given device node. * Works only for block devices. * devicenode: path to the device node * busid: buffer in which the busid string will be returned * returns 0 for successful operation and -1 in case of an error. */ int u2s_getbusid(char *devicenode, char *busid) { int maj, min, rc; struct stat stat_buf; char dev_string[DEV_BUFFER_LENGTH]; /* * Get major and minor information of the device special file * and combine them to a : string, as returned by * the dev attributes in sysfs */ if (stat(devicenode, &stat_buf)) return -1; if (!S_ISBLK(stat_buf.st_mode)) return -1; maj = major(stat_buf.st_rdev); min = minor(stat_buf.st_rdev); rc = find_busid_in_proc(maj, min, busid); if (rc) { snprintf(dev_string, DEV_BUFFER_LENGTH, "%u:%u", maj, min); rc = find_busid_in_sysfs(dev_string, busid); } return rc; } /* * Attempts to find the sysfs entry for the given busid and reads * the contents of a specified attribute to the buffer */ int u2s_read_attribute(char *busid, char *attribute, char *buffer, size_t count) { char path[100]; int rc, fd; ssize_t rcount; rc = 0; snprintf(path, sizeof(path), "/sys/bus/ccw/devices/%s/%s", busid, attribute); fd = open(path, O_RDONLY); if (fd < 0) return errno; rcount = read(fd, buffer, count); if (rcount < 0) rc = errno; close(fd); return rc; } int u2s_get_host_access_count(char *devicenode) { char busid[BUSIDSIZE]; unsigned long value; char buffer[10]; char *endp; u2s_getbusid(devicenode, busid); u2s_read_attribute(busid, "host_access_count", buffer, sizeof(buffer)); value = strtoul(buffer, &endp, 0); if (endp == buffer) return -EINVAL; return value; } s390-tools-2.3.0/libutil/000077500000000000000000000000001323410431100150045ustar00rootroot00000000000000s390-tools-2.3.0/libutil/Makefile000066400000000000000000000021111323410431100164370ustar00rootroot00000000000000include ../common.mak lib = libutil.a examples = util_base_example \ util_panic_example \ util_path_example \ util_scandir_example \ util_file_example \ util_libc_example \ util_opt_example \ util_opt_command_example \ util_prg_example \ util_rec_example all: $(lib) $(examples) objects = util_base.o \ util_path.o \ util_scandir.o \ util_file.o \ util_libc.o \ util_list.o \ util_opt.o \ util_panic.o \ util_part.o \ util_prg.o \ util_proc.o \ util_rec.o util_base_example: util_base_example.o $(lib) util_panic_example: util_panic_example.o $(lib) util_path_example: util_path_example.o $(lib) util_scandir_example: util_scandir_example.o $(lib) util_file_example: util_file_example.o $(lib) util_libc_example: util_libc_example.o $(lib) util_opt_example: util_opt_example.o $(lib) util_opt_command_example: util_opt_command_example.o $(lib) util_panic_example: util_panic_example.o $(lib) util_prg_example: util_prg_example.o $(lib) util_rec_example: util_rec_example.o $(lib) $(lib): $(objects) install: all clean: rm -f *.o $(lib) $(examples) s390-tools-2.3.0/libutil/util_base.c000066400000000000000000000040521323410431100171200ustar00rootroot00000000000000/* * util - Utility function library * * General helper functions * * Copyright IBM Corp. 2013, 2017 * * 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 #include "lib/util_base.h" #include "lib/util_libc.h" /* * Print hexdump for buffer with variable group parameter */ void util_hexdump_grp(FILE *fh, const char *tag, const void *data, int grp, int count, int indent) { const char *buf = data; int i, first = 1; for (i = 0; i < count; i++) { if (first) { fprintf(fh, "%*s", indent, " "); if (tag) fprintf(fh, "%s: ", tag); fprintf(fh, "%08x: ", i); first = 0; } fprintf(fh, "%02x", buf[i]); if (i % 16 == 15 || i + 1 == count) { fprintf(fh, "\n"); first = 1; } else if (i % grp == grp - 1) { fprintf(fh, " "); } } } /* * Print hexdump for buffer with fix grp parameter */ void util_hexdump(FILE *fh, const char *tag, const void *data, int count) { util_hexdump_grp(fh, tag, data, sizeof(long), count, 0); } #define MAX_CHARS_PER_LINE 80 /* * Print string with indentation * * Print a string while accounting for a given indent value, characters per line * limit, and line breaks ('\n') within the string. The first line has to be * indented manually. * * @param[in] str String that should be printed * @param[in] indent Indentation for printing */ void util_print_indented(const char *str, int indent) { char *word, *line, *desc, *desc_ptr; int word_len, pos = indent; desc = desc_ptr = util_strdup(str); line = strsep(&desc, "\n"); while (line) { word = strsep(&line, " "); pos = indent; while (word) { word_len = strlen(word); if (pos + word_len + 1 > MAX_CHARS_PER_LINE) { printf("\n%*s", indent, ""); pos = indent; } if (pos == indent) printf("%s", word); else printf(" %s", word); pos += word_len + 1; word = strsep(&line, " "); } if (desc) printf("\n%*s", indent, ""); line = strsep(&desc, "\n"); } printf("\n"); free(desc_ptr); } s390-tools-2.3.0/libutil/util_base_example.c000066400000000000000000000017141323410431100206350ustar00rootroot00000000000000/** * util_base_example - Example program for util_base * * Copyright 2017 IBM Corp. * * s390-tools is free software; you can redistribute it and/or modify * it under the terms of the MIT license. See LICENSE for details. */ //! [code] #include #include "lib/util_base.h" #define INDENTATION 24 const char *msg = "There is a theory which states that if ever anybody discovers exactly " "what the Universe is for and why it is here, it will instantly disappear " "and be replaced by something even more ...\n" "\n" " ... bizarre\n" " ... and inexplicable.\n" "\n" "There is another theory which states that this has already happened."; /* * Demonstrate util_base functions */ int main(void) { printf("TEST: util_print_indented(msg, %d)\n", INDENTATION); printf("----------------------------------\n"); printf("%-*s", INDENTATION, "Douglas Adams:"); util_print_indented(msg, INDENTATION); return EXIT_SUCCESS; } //! [code] s390-tools-2.3.0/libutil/util_file.c000066400000000000000000000234751323410431100171370ustar00rootroot00000000000000/* * util - Utility function library * * Read and write files * * Copyright IBM Corp. 2016, 2017 * * 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 #include #include #include #include #include #include #include #include #include #include "lib/util_base.h" #include "lib/util_file.h" #include "lib/util_libc.h" #include "lib/util_panic.h" #include "lib/util_prg.h" /* * Read the first line of a file into given buffer */ static int file_gets(char *str, size_t size, const char *path) { char *p, *end; FILE *fp; int rc; /* In case of error we always return empty string */ str[0] = 0; /* Read the string */ fp = fopen(path, "r"); if (!fp) return -1; p = fgets(str, size, fp); if (p) { /* Check for end of line */ end = memchr(str, '\n', size); if (end) *end = 0; else str[size - 1] = 0; } if (strlen(str) == 0) { rc = -1; goto out_fclose; } rc = 0; out_fclose: fclose(fp); return rc; } /** * Read the first line of a file * * If read is successful 'str' contains first line of a file without the * trailing newline. If read fails, an empty string is returned for 'str' * The resulting string will always be null-terminated. * * @param[out] str Result buffer * @param[in] size Size of the result buffer * @param[in] fmt Format string for generation of the path name * @param[in] ... Parameters for format string * * @retval 0 File was read * @retval -1 Error while reading file */ int util_file_read_line(char *const str, size_t size, const char *fmt, ...) { char path[PATH_MAX]; va_list ap; /* Construct the file name */ UTIL_VSPRINTF(path, fmt, ap); return file_gets(str, size, path); } /* * Write string to a file */ static int file_puts(const char *str, const char *path) { FILE *fp; int rc; /* write the string */ fp = fopen(path, "w"); if (!fp) return -1; if (fputs(str, fp) == EOF) { rc = -1; goto out_fclose; } rc = 0; out_fclose: fclose(fp); return rc; } /** * Write string to a file without the terminating null byte * * @param[in] str Content is to be written * @param[in] fmt Format string for generation of the path name * @param[in] ... Parameters for format string * * @retval 0 Write was successful * @retval -1 Error while writing file */ int util_file_write_s(const char *str, const char *fmt, ...) { char path[PATH_MAX]; va_list ap; /* Construct the file name */ UTIL_VSPRINTF(path, fmt, ap); return file_puts(str, path); } /** * Write signed long value to a file according to given base * * @param[in] val Value is to be written * @param[in] base Base for conversion, either 8, 10, or 16 * @param[in] fmt Format string for generation of the path name * @param[in] ... Parameters for format string * * @retval 0 Write was successful * @retval -1 Error while writing file */ int util_file_write_l(long val, int base, const char *fmt, ...) { char *str, path[PATH_MAX]; va_list ap; int rc; /* Construct the file name */ UTIL_VSPRINTF(path, fmt, ap); switch (base) { case 8: util_asprintf(&str, "%lo", val); break; case 10: util_asprintf(&str, "%ld", val); break; case 16: util_asprintf(&str, "%lx", val); break; default: util_panic("Invalid base: %d\n", base); } rc = file_puts(str, path); free(str); return rc; } /** * Write signed long long value to a file according to given base * * @param[in] val Value is to be written * @param[in] base Base for conversion, either 8, 10, or 16 * @param[in] fmt Format string for generation of the path name * @param[in] ... Parameters for format string * * @retval 0 Write was successful * @retval -1 Error while writing file */ int util_file_write_ll(long long val, int base, const char *fmt, ...) { char *str, path[PATH_MAX]; va_list ap; int rc; /* Construct the file name */ UTIL_VSPRINTF(path, fmt, ap); switch (base) { case 8: util_asprintf(&str, "%llo", val); break; case 10: util_asprintf(&str, "%lld", val); break; case 16: util_asprintf(&str, "%llx", val); break; default: util_panic("Invalid base: %d\n", base); } rc = file_puts(str, path); free(str); return rc; } /** * Write unsigned long value to a file according to given base * * @param[in] val Value is to be written * @param[in] base Base for conversion, either 8, 10, or 16 * @param[in] fmt Format string for generation of the path name * @param[in] ... Parameters for format string * * @retval 0 Write was successful * @retval -1 Error while writing file */ int util_file_write_ul(unsigned long val, int base, const char *fmt, ...) { char *str, path[PATH_MAX]; va_list ap; int rc; /* Construct the file name */ UTIL_VSPRINTF(path, fmt, ap); switch (base) { case 8: util_asprintf(&str, "%lo", val); break; case 10: util_asprintf(&str, "%lu", val); break; case 16: util_asprintf(&str, "%lx", val); break; default: util_panic("Invalid base: %d\n", base); } rc = file_puts(str, path); free(str); return rc; } /** * Write unsigned long long value to a file according to given base * * @param[in] val Value is to be written * @param[in] base Base for conversion, either 8, 10, or 16 * @param[in] fmt Format string for generation of the path name * @param[in] ... Parameters for format string * * @retval 0 Write was successful * @retval -1 Error while writing file */ int util_file_write_ull(unsigned long long val, int base, const char *fmt, ...) { char *str, path[PATH_MAX]; va_list ap; int rc; /* Construct the file name */ UTIL_VSPRINTF(path, fmt, ap); switch (base) { case 8: util_asprintf(&str, "%llo", val); break; case 10: util_asprintf(&str, "%llu", val); break; case 16: util_asprintf(&str, "%llx", val); break; default: util_panic("Invalid base: %d\n", base); } rc = file_puts(str, path); free(str); return rc; } /** * Read a file and convert it to signed long according to given base * * @param[out] val Buffer for value * @param[in] base Base for conversion, either 8, 10, or 16 * @param[in] fmt Format string for generation of the path name * @param[in] ... Parameters for format string * * @retval 0 Long integer has been read correctly * @retval -1 Error while reading file */ int util_file_read_l(long *val, int base, const char *fmt, ...) { char path[PATH_MAX], buf[512]; va_list ap; int count; /* Construct the file name */ UTIL_VSPRINTF(path, fmt, ap); if (file_gets(buf, sizeof(buf), path)) return -1; switch (base) { case 8: count = sscanf(buf, "%lo", val); break; case 10: count = sscanf(buf, "%ld", val); break; case 16: count = sscanf(buf, "%lx", val); break; default: util_panic("Invalid base: %d\n", base); } return (count == 1) ? 0 : -1; } /** * Read a file and convert it to signed long long according to given base * * @param[out] val Buffer for value * @param[in] base Base for conversion, either 8, 10, or 16 * @param[in] fmt Format string for generation of the path name * @param[in] ... Parameters for format string * * @retval 0 Long integer has been read correctly * @retval -1 Error while reading file */ int util_file_read_ll(long long *val, int base, const char *fmt, ...) { char path[PATH_MAX], buf[512]; va_list ap; int count; /* Construct the file name */ UTIL_VSPRINTF(path, fmt, ap); if (file_gets(buf, sizeof(buf), path)) return -1; switch (base) { case 8: count = sscanf(buf, "%llo", val); break; case 10: count = sscanf(buf, "%lld", val); break; case 16: count = sscanf(buf, "%llx", val); break; default: util_panic("Invalid base: %d\n", base); } return (count == 1) ? 0 : -1; } /** * Read a file and convert it to unsigned long according to given base * * @param[out] val Buffer for value * @param[in] base Base for conversion, either 8, 10, or 16 * @param[in] fmt Format string for generation of the path name * @param[in] ... Parameters for format string * * @retval 0 Long integer has been read correctly * @retval -1 Error while reading file */ int util_file_read_ul(unsigned long *val, int base, const char *fmt, ...) { char path[PATH_MAX], buf[512]; va_list ap; int count; /* Construct the file name */ UTIL_VSPRINTF(path, fmt, ap); if (file_gets(buf, sizeof(buf), path)) return -1; switch (base) { case 8: count = sscanf(buf, "%lo", val); break; case 10: count = sscanf(buf, "%lu", val); break; case 16: count = sscanf(buf, "%lx", val); break; default: util_panic("Invalid base: %d\n", base); } return (count == 1) ? 0 : -1; } /** * Read a file and convert it to unsigned long long according to given base * * @param[out] val Buffer for value * @param[in] base Base for conversion, either 8, 10, or 16 * @param[in] fmt Format string for generation of the path name * @param[in] ... Parameters for format string * * @retval 0 Long integer has been read correctly * @retval -1 Error while reading file */ int util_file_read_ull(unsigned long long *val, int base, const char *fmt, ...) { char path[PATH_MAX], buf[512]; va_list ap; int count; /* Construct the file name */ UTIL_VSPRINTF(path, fmt, ap); if (file_gets(buf, sizeof(buf), path)) return -1; switch (base) { case 8: count = sscanf(buf, "%llo", val); break; case 10: count = sscanf(buf, "%llu", val); break; case 16: count = sscanf(buf, "%llx", val); break; default: util_panic("Invalid base: %d\n", base); } return (count == 1) ? 0 : -1; } s390-tools-2.3.0/libutil/util_file_example.c000066400000000000000000000035671323410431100206520ustar00rootroot00000000000000/** * util_file_example - Example program for util_file * * Copyright IBM Corp. 2016, 2017 * * s390-tools is free software; you can redistribute it and/or modify * it under the terms of the MIT license. See LICENSE for details. */ //! [code] #include #include #include #include #include "lib/util_file.h" /* * Write buffer to file and read it back again */ int main(void) { char buf_wr[4096], buf_rd[4096]; unsigned long long value_ull; long value_l; /* Generate input */ sprintf(buf_wr, "Say something interesting!\nSecond line\n"); printf("Write.....:\n%s", buf_wr); /* Write string to file */ if (util_file_write_s(buf_wr, "/tmp/%s", "testfile")) { perror("util_file_write_s failed\n"); return EXIT_FAILURE; } /* Read back first line of file */ if (util_file_read_line(buf_rd, sizeof(buf_rd), "/tmp/%s", "testfile")) { perror("util_file_read_line failed\n"); return EXIT_FAILURE; } printf("Read......: %s\n", buf_rd); /* Write long to file */ printf("Write.....: %ld\n", 4711L); if (util_file_write_l(4711L, 10, "/tmp/%s", "testfile")) { perror("util_file_write failed\n"); return EXIT_FAILURE; } /* Read back long from file */ if (util_file_read_l(&value_l, 10, "/tmp/%s", "testfile")) { perror("util_file_read_l failed\n"); return EXIT_FAILURE; } printf("Read......: %ld\n", value_l); /* Write long long hexadecimal to file */ printf("Write.....: 0x%llx\n", 0x4712ULL); if (util_file_write_ull(0x4712ULL, 16, "/tmp/%s", "testfile")) { perror("util_file_write failed\n"); return EXIT_FAILURE; } /* Read back long long hexadecimal from file */ if (util_file_read_ull(&value_ull, 16, "/tmp/%s", "testfile")) { perror("util_file_read_ull failed\n"); return EXIT_FAILURE; } printf("Read......: 0x%llx\n", value_ull); /* Remove file */ unlink("/tmp/testfile"); return EXIT_SUCCESS; } //! [code] s390-tools-2.3.0/libutil/util_libc.c000066400000000000000000000101311323410431100171120ustar00rootroot00000000000000/* * util - Utility function library * * Handle standard errors for libc functions * * Copyright IBM Corp. 2016, 2017 * * 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 #include #include #include #include #include #include "lib/util_base.h" #include "lib/util_libc.h" #include "lib/util_panic.h" /* * Return size as string of largest unit, e.g. 1025 = "1 KiB" */ static void format_size(char *str, size_t size) { static const char * const unit_vec[] = {"byte", "KiB", "MiB", "GiB", "TiB", "PiB", "EiB"}; unsigned int i; for (i = 0; i < UTIL_ARRAY_SIZE(unit_vec); i++) { if (size / 1024 == 0) { sprintf(str, "%zu %s", size, unit_vec[i]); return; } size /= 1024; } sprintf(str, "huge"); } static void __util_oom(const char *func, const char *file, int line, size_t size) { char size_str[256]; fprintf(stderr, "%s: Failed to allocate memory", program_invocation_short_name); if (size > 0) { format_size(size_str, size); fprintf(stderr, " (%s)", size_str); } fprintf(stderr, " at %s:%d %s()\n", file, line, func); exit(EXIT_FAILURE); } /* * Allocate memory or exit in case of failure */ void *__util_malloc(const char *func, const char *file, int line, size_t size) { void *buf; buf = malloc(size); if (buf == NULL) __util_oom(func, file, line, size); return buf; } /* * Allocate zero-initialized memory or exit in case of failure */ void *__util_zalloc(const char *func, const char *file, int line, size_t size) { void *buf = __util_malloc(func, file, line, size); memset(buf, 0, size); return buf; } /* * Re-allocate memory or exit in case of failure */ void *__util_realloc(const char *func, const char *file, int line, void *ptr, size_t size) { void *buf; buf = realloc(ptr, size); if (buf == NULL) __util_oom(func, file, line, size); return buf; } /* * Duplicate a string buffer or exit in case of failure */ void *__util_strdup(const char *func, const char *file, int line, const char *str) { void *buf = strdup(str); if (buf == NULL) __util_oom(func, file, line, strlen(str) + 1); return buf; } /** * Concatenate two strings or exit in case of failure * * The first string \a str1 is resized and a copy of the second * string \a str2 is appended to it. * * Therefore the first string \a str1 must either have been allocated * using malloc(), calloc(), or realloc() or must be NULL. * * @param[in] str1 Pointer to first string to concatenate, which * becomes invalid * @param[in] str2 Constant pointer to second string to concatenate * * @returns Pointer to concatenated string */ char *util_strcat_realloc(char *str1, const char *str2) { char *buf; if (str1) { buf = util_realloc(str1, strlen(str1) + strlen(str2) + 1); strcat(buf, str2); } else { buf = util_strdup(str2); } return buf; } /** * Convert string to uppercase * * String \a str is converted to uppercase * * @param[in,out] str String to convert */ void util_str_toupper(char *str) { int i; for (i = 0; str[i] != '\0'; i++) str[i] = toupper(str[i]); } /* * Print to newly allocated string or exit in case of failure */ int __util_vasprintf(const char *func, const char *file, int line, char **strp, const char *fmt, va_list ap) { int rc; rc = vasprintf(strp, fmt, ap); if (rc == -1) __util_oom(func, file, line, 0); return rc; } /* * Print to newly allocated string or exit in case of failure */ int __util_asprintf(const char *func, const char *file, int line, char **strp, const char *fmt, ...) { va_list ap; int rc; va_start(ap, fmt); rc = __util_vasprintf(func, file, line, strp, fmt, ap); va_end(ap); return rc; } /* * Print to string buffer or exit in case of failure */ int __util_vsprintf(const char *func, const char *file, int line, char *str, const char *fmt, va_list ap) { int rc; rc = vsprintf(str, fmt, ap); if (rc == -1) __util_assert("rc != -1", func, file, line, rc != -1, "Could not format string\n"); return rc; } s390-tools-2.3.0/libutil/util_libc_example.c000066400000000000000000000026371323410431100206410ustar00rootroot00000000000000/** * util_libc_example - Example program for util_libc * * Copyright IBM Corp. 2016, 2017 * * s390-tools is free software; you can redistribute it and/or modify * it under the terms of the MIT license. See LICENSE for details. */ //! [code] #include #include #include "lib/util_libc.h" #include "lib/util_panic.h" /* * Demonstrate that out of memory is automatically handled via panic() */ int main(void) { unsigned long ulong_max = (unsigned long)-1; void *ptr; char *zeroes, *str; /* Use util_strcat_realloc() for string concatenation */ fprintf(stderr, "Try to concatenate \"Hello\", \", \" and \"world!\": "); str = util_strdup("Hello"); str = util_strcat_realloc(str, ", "); str = util_strcat_realloc(str, "world!"); fprintf(stderr, "result = \"%s\"\n", str); free(str); /* One byte allocation should work */ fprintf(stderr, "Try to allocate 1 byte: "); ptr = util_malloc(1); fprintf(stderr, "done\n"); /* One byte zeroed-allocation should work */ fprintf(stderr, "Try to allocate 1 byte initialized with zeroes: "); zeroes = util_zalloc(1); fprintf(stderr, "done\n"); util_assert(*zeroes == 0, "Garbage found in zero initialized memory\n"); /* The next allocation will probably fail */ fprintf(stderr, "Try to allocate %lu bytes:\n", ulong_max); ptr = util_malloc(ulong_max); fprintf(stderr, "You should not see me (ptr=%p)!\n", ptr); return EXIT_FAILURE; } //! [code] s390-tools-2.3.0/libutil/util_list.c000066400000000000000000000114671323410431100171710ustar00rootroot00000000000000/* * util - Utility function library * * Linked list functions * * Copyright IBM Corp. 2013, 2017 * * 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 #include #include #include "lib/util_libc.h" #include "lib/util_list.h" /* * Node to entry */ static inline void *n2e(struct util_list *list, struct util_list_node *node) { return ((void *) node) - list->offset; } /* * Entry to node */ static inline struct util_list_node *e2n(struct util_list *list, void *entry) { return entry + list->offset; } /* * Initialize linked list */ void util_list_init_offset(struct util_list *list, unsigned long offset) { memset(list, 0, sizeof(*list)); list->offset = offset; } /* * Create new linked list */ struct util_list *util_list_new_offset(unsigned long offset) { struct util_list *list = util_malloc(sizeof(*list)); util_list_init_offset(list, offset); return list; } /* * Free linked list */ void util_list_free(struct util_list *list) { free(list); } /* * Add new element to end of list */ void util_list_add_tail(struct util_list *list, void *entry) { struct util_list_node *node = e2n(list, entry); node->next = NULL; if (!list->start) { list->start = node; node->prev = NULL; } else { list->end->next = node; node->prev = list->end; } list->end = node; } /* * Add new element to front of list */ void util_list_add_head(struct util_list *list, void *entry) { struct util_list_node *node = e2n(list, entry); node->prev = NULL; node->next = NULL; if (!list->start) { list->end = node; } else { list->start->prev = node; node->next = list->start; } list->start = node; } /* * Add new element (entry) after an existing element (list_entry) */ void util_list_add_next(struct util_list *list, void *entry, void *list_entry) { struct util_list_node *node = e2n(list, entry); struct util_list_node *list_node = e2n(list, list_entry); node->next = list_node->next; node->prev = list_node; if (list_node->next) list_node->next->prev = node; else list->end = node; list_node->next = node; } /* * Add new element (entry) before an existing element (list_entry) */ void util_list_add_prev(struct util_list *list, void *entry, void *list_entry) { struct util_list_node *node = e2n(list, entry); struct util_list_node *list_node = e2n(list, list_entry); node->prev = list_node->prev; node->next = list_node; if (list_node->prev) list_node->prev->next = node; else list->start = node; list_node->prev = node; } /* * Remove element from list */ void util_list_remove(struct util_list *list, void *entry) { struct util_list_node *node = e2n(list, entry); if (list->start == node) list->start = node->next; if (list->end == node) list->end = node->prev; if (node->prev) node->prev->next = node->next; if (node->next) node->next->prev = node->prev; } /* * Get first element of list */ void *util_list_start(struct util_list *list) { if (!list->start) return NULL; return ((void *) list->start) - list->offset; } /* * Get last element of list */ void *util_list_end(struct util_list *list) { if (!list->end) return NULL; return n2e(list, list->end); } /* * Get next element after entry */ void *util_list_next(struct util_list *list, void *entry) { struct util_list_node *node; if (!entry) return NULL; node = e2n(list, entry); node = node->next; if (!node) return NULL; return n2e(list, node); } /* * Get previous element before entry */ void *util_list_prev(struct util_list *list, void *entry) { struct util_list_node *node; if (!entry) return NULL; node = e2n(list, entry); node = node->prev; if (!node) return NULL; return n2e(list, node); } /* * Get number of list entries */ unsigned long util_list_len(struct util_list *list) { unsigned long cnt = 0; void *entry; util_list_iterate(list, entry) cnt++; return cnt; } /* * Sort table (bubble sort) */ void util_list_sort(struct util_list *list, util_list_cmp_fn cmp_fn, void *data) { struct util_list_node *node1, *node2; unsigned long list_cnt, i, j; void *entry1, *entry2; list_cnt = util_list_len(list); for (i = 1; i < list_cnt; i++) { node1 = list->start; for (j = 0; j < list_cnt - i; j++) { node2 = node1->next; entry1 = n2e(list, node1); entry2 = n2e(list, node2); if (cmp_fn(entry1, entry2, data) > 0) { node1->next = node2->next; if (node1->next) node1->next->prev = node1; else list->end = node1; node2->next = node1; node2->prev = node1->prev; if (node2->prev) node2->prev->next = node2; else list->start = node2; node1->prev = node2; } else { node1 = node2; } } } } /* * Check if list is empty */ int util_list_is_empty(struct util_list *list) { return list->start == NULL; } s390-tools-2.3.0/libutil/util_opt.c000066400000000000000000000167051323410431100170200ustar00rootroot00000000000000/* * util - Utility function library * * Parse the command line options * * Copyright IBM Corp. 2016, 2017 * * 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 #include #include #include #include #include #include "lib/util_base.h" #include "lib/util_libc.h" #include "lib/util_opt.h" #include "lib/util_panic.h" #include "lib/util_prg.h" /* * Private data */ /// @cond static struct util_opt_l { /* Option character string for getopt_long() */ char *opt_str; /* Option array for getopt_long() */ struct option *option_vec; /* Original util_opt array */ struct util_opt *opt_vec; /* Length of longest option string */ int opt_max; /* Command used for parsing */ const char *command; } l; struct util_opt_l *util_opt_l = &l; /// @endcond #define util_opt_iterate(opt) \ for (opt = &l.opt_vec[0]; opt->desc != NULL; opt++) #define MAX_OPTLEN 256 static int opt_max_len(void); /** * Initialize the command line options * * Build short option string and long option array to be used for getopt_long(). * The ":" prefix is added to the short option string for handling of "missing * required arguments". * * @param[in] opt_vec Option array * @param[in] opt_prefix Optional option string prefix */ void util_opt_init(struct util_opt *opt_vec, const char *opt_prefix) { int i, j, count; char *str; size_t prefix_len = opt_prefix ? strlen(opt_prefix) : 0; opterr = 0; /* Get number of options */ for (count = 0; opt_vec[count].desc != NULL; count++); /* * Allocate short option string for worst case when all options have * optional parameters e.g "x::" and long option string. */ l.opt_str = util_malloc(sizeof(char) * count * 3 + 2 + prefix_len); l.option_vec = util_malloc(sizeof(struct option) * (count + 1)); l.opt_vec = opt_vec; str = l.opt_str; if (opt_prefix) { strcpy(str, opt_prefix); str += prefix_len; } /* Force getopt_long() to return ':' for missing required arguments */ *str++ = ':'; /* Construction of input structures for getopt_long() function. */ for (i = 0, j = 0; i < count; i++) { if (opt_vec[i].flags & UTIL_OPT_FLAG_SECTION) continue; if (!(opt_vec[i].flags & UTIL_OPT_FLAG_NOLONG)) { memcpy(&l.option_vec[j++], &opt_vec[i].option, sizeof(struct option)); } if (opt_vec[i].flags & UTIL_OPT_FLAG_NOSHORT) continue; *str++ = opt_vec[i].option.val; switch (opt_vec[i].option.has_arg) { case no_argument: break; case required_argument: *str++ = ':'; break; case optional_argument: *str++ = ':'; *str++ = ':'; break; default: util_panic("Unexpected \"has_arg\" parameter: %d\n", opt_vec[i].option.has_arg); } } /* Add end marker to option array and short option string */ memset(&l.option_vec[j], 0, sizeof(struct option)); *str = '\0'; } /** * Set the current command for command line option processing * * @param[in] command The current command or NULL for no command */ void util_opt_set_command(const char *command) { l.command = command; } /* * Return true, if option belongs to current command setting */ static bool opt_is_active(struct util_opt *opt) { if (!opt->command || !l.command) return true; return (strcmp(opt->command, l.command) == 0); } /** * Wrapper for getopt_long * * @param[in] argc Count of command line parameters * @param[in] argv Array of command line parameters */ int util_opt_getopt_long(int argc, char *argv[]) { struct util_opt *opt; int val; val = getopt_long(argc, argv, l.opt_str, l.option_vec, NULL); switch (val) { case ':': case '?': case -1: break; default: if (!l.command) break; util_opt_iterate(opt) { if (!opt_is_active(opt)) continue; if (opt->option.val == val) goto out; } /* No valid option found for command */ val = '?'; if (optarg) optind--; break; } out: return val; } /* * Format option name: Add short, long option and argument (as applicable) */ static void format_opt(char *buf, size_t maxlen, const struct util_opt *opt) { int has_arg, flags, rc; char val, *arg_str; const char *name; has_arg = opt->option.has_arg; name = opt->option.name; val = opt->option.val; flags = opt->flags; /* Prepare potential option argument string */ if (has_arg == optional_argument) { if (flags & UTIL_OPT_FLAG_NOLONG) util_asprintf(&arg_str, "[%s]", opt->argument); else util_asprintf(&arg_str, "[=%s]", opt->argument); } else if (has_arg == required_argument) { util_asprintf(&arg_str, " %s", opt->argument); } else { util_asprintf(&arg_str, ""); } /* Format the option */ if (flags & UTIL_OPT_FLAG_NOLONG) rc = snprintf(buf, maxlen, "-%c%s", val, arg_str); else if (flags & UTIL_OPT_FLAG_NOSHORT) rc = snprintf(buf, maxlen, " --%s%s", name, arg_str); else rc = snprintf(buf, maxlen, "-%c, --%s%s", val, name, arg_str); util_assert(rc < (int)maxlen, "Option too long: %s\n", name); free(arg_str); } /* * Return true, if option is to be printed for the current command setting */ static bool should_print_opt(const struct util_opt *opt) { if (l.command) { /* Print only options that belong to command */ return opt->command ? !strcmp(opt->command, l.command) : false; } else { /* Print only common options (standard for non-command tools) */ return opt->command ? false : true; } } /* * Return size of the longest formatted option */ static int opt_max_len(void) { const struct util_opt *opt; unsigned int max = 0; char opt_str[MAX_OPTLEN]; util_opt_iterate(opt) { if (opt->flags & UTIL_OPT_FLAG_SECTION) continue; if (!should_print_opt(opt)) continue; format_opt(opt_str, MAX_OPTLEN, opt); max = MAX(max, strlen(opt_str)); } return max; } /** * Print an option name, followed by a description indented to fit the * longest option name */ void util_opt_print_indented(const char *opt, const char *desc) { printf(" %-*s ", l.opt_max + 1, opt); util_print_indented(desc, 3 + l.opt_max); } /** * Print the usage of the command line options to the console */ void util_opt_print_help(void) { char opt_str[MAX_OPTLEN]; struct util_opt *opt; int first = 1; /* * Create format string: " -%c, --%-s %s" * * Example: * * -p, --print STRING Print STRING to console */ l.opt_max = opt_max_len(); util_opt_iterate(opt) { if (!should_print_opt(opt)) continue; if (opt->flags & UTIL_OPT_FLAG_SECTION) { printf("%s%s\n", first ? "" : "\n", opt->desc); first = 0; continue; } format_opt(opt_str, MAX_OPTLEN, opt); util_opt_print_indented(opt_str, opt->desc); } } /** * Print option parsing error message * * This function should be used when the return code of the * util_opt_getopt_long() function returns a character that does * not match any of the expected options. * * @param[in] opt Short option returned by getopt_long() * @param[in] argv Option array */ void util_opt_print_parse_error(char opt, char *argv[]) { char optopt_str[3]; switch (opt) { case ':': /* A required option argument has not been specified */ util_prg_print_required_arg(argv[optind - 1]); break; case '?': /* An invalid option has been specified */ if (optopt) { /* Short option */ sprintf(optopt_str, "-%c", optopt); util_prg_print_invalid_option(optopt_str); } else { /* Long option */ util_prg_print_invalid_option(argv[optind - 1]); } break; default: util_panic("Option '%c' should not be handled here\n", opt); } } s390-tools-2.3.0/libutil/util_opt_command_example.c000066400000000000000000000114521323410431100222230ustar00rootroot00000000000000/* * util_opt_command_example - Example program for util_opt with commands * * Copyright 2017 IBM Corp. * * s390-tools is free software; you can redistribute it and/or modify * it under the terms of the MIT license. See LICENSE for details. */ //! [code] #include #include #include #include #include "lib/util_opt.h" #include "lib/util_prg.h" #define OPT_NOSHORT 256 #define COMMAND_PULL "pull" #define COMMAND_PUSH "push" /* * Define the command line options */ static struct util_opt opt_vec[] = { { .desc = "OPTIONS", .flags = UTIL_OPT_FLAG_SECTION, .command = COMMAND_PULL, }, { .option = { "single", no_argument, NULL, 's'}, .desc = "A single option without an argument", .command = COMMAND_PULL, }, { .option = { "req_arg", required_argument, NULL, 'r'}, .argument = "REQ_ARG", .desc = "Option with a required argument REQ_ARG", .command = COMMAND_PULL, }, { .desc = "OPTIONS", .flags = UTIL_OPT_FLAG_SECTION, .command = COMMAND_PUSH, }, { .option = { "noshort", no_argument, NULL, OPT_NOSHORT}, .desc = "Option with only a long name", .flags = UTIL_OPT_FLAG_NOSHORT, .command = COMMAND_PUSH, }, { .option = { NULL, no_argument, NULL, 'l'}, .desc = "Option with only a short name", .flags = UTIL_OPT_FLAG_NOLONG, .command = "push", }, UTIL_OPT_SECTION("COMMON OPTIONS"), /* Standard option: -h,--help */ UTIL_OPT_HELP, /* Standard option: -v,--version */ UTIL_OPT_VERSION, /* End-marker for option vector */ UTIL_OPT_END }; static char usage_global_start[] = "Usage: util_opt_command_example [COMMAND] [OPTIONS]\n" "\n" "Demonstrate how programs with commands can use the \"util_opt\" library.\n" "\n" "COMMANDS\n" " push Push some content to somewhere\n" " pull Pull some content from somewhere\n"; static char usage_global_end[] = "For more information use 'util_opt_command_example COMMAND --help'.\n"; static char usage_push_start[] = "Usage: util_opt_command_example push [OPTIONS]\n" "\n" "Push some content to somewhere.\n"; static char usage_pull_start[] = "Usage: util_opt_command_example pull [OPTIONS]\n" "\n" "Pull some content from somewhere.\n"; /* * Print help header for command or program */ static void command_print_help_start(const char *command) { if (command == NULL) printf("%s", usage_global_start); else if (strcmp(command, COMMAND_PUSH) == 0) printf("%s", usage_push_start); else if (strcmp(command, COMMAND_PULL) == 0) printf("%s", usage_pull_start); printf("\n"); } /* * Print help footer */ static void command_print_help_end(const char *command) { if (command == NULL) printf("\n%s", usage_global_end); } /* * Parse the command line options with util_opt functions */ int main(int argc, char *argv[]) { int c, my_argc = argc; char **my_argv = argv; char *command = NULL; /* Install option vector */ util_opt_init(opt_vec, NULL); /* The command name is the very first argument */ if (argc >= 2 && strncmp(argv[1], "-", 1) != 0) { command = argv[1]; my_argc--; my_argv = &argv[1]; if (strcasecmp(command, COMMAND_PULL) != 0 && strcasecmp(command, COMMAND_PUSH) != 0) { fprintf(stderr, "%s: Invalid command '%s'\n", program_invocation_short_name, argv[1]); util_prg_print_parse_error(); return EXIT_FAILURE; } } /* Set the current command (if any) */ util_opt_set_command(command); util_prg_set_command(command); /* Parse all options specified in my_argv[] */ while (1) { /* Get the next option 'c' from my_argv[] */ c = util_opt_getopt_long(my_argc, my_argv); /* No more options on command line? */ if (c == -1) break; /* Find the right action for option 'c' */ switch (c) { case 'h': command_print_help_start(command); util_opt_print_help(); command_print_help_end(command); return EXIT_SUCCESS; case 'v': printf("Specified: --version\n"); return EXIT_SUCCESS; case 's': printf("Specified: --single\n"); break; case 'r': printf("Specified: --req_arg %s\n", optarg); break; case OPT_NOSHORT: printf("Specified: --noshort\n"); break; case 'l': printf("Specified: -l\n"); break; default: util_opt_print_parse_error(c, my_argv); return EXIT_FAILURE; } } if (optind < my_argc) { util_prg_print_arg_error(my_argv[optind]); return EXIT_FAILURE; } if (command == NULL) { fprintf(stderr, "%s: Command is required\n", program_invocation_short_name); util_prg_print_parse_error(); return EXIT_FAILURE; } if (strcasecmp(command, COMMAND_PULL) == 0) { printf("Run the pull command\n"); return EXIT_SUCCESS; } else if (!strcasecmp(command, COMMAND_PUSH)) { printf("Run the push command\n"); return EXIT_SUCCESS; } fprintf(stderr, "%s: Invalid command '%s'\n", program_invocation_short_name, argv[1]); util_prg_print_parse_error(); return EXIT_FAILURE; } //! [code] s390-tools-2.3.0/libutil/util_opt_example.c000066400000000000000000000060341323410431100205250ustar00rootroot00000000000000/* * util_opt_example - Example program for util_opt * * Copyright IBM Corp. 2016, 2017 * * s390-tools is free software; you can redistribute it and/or modify * it under the terms of the MIT license. See LICENSE for details. */ //! [code] #include #include #include "lib/util_opt.h" #define OPT_NOSHORT 256 /* * Define the command line options */ static struct util_opt opt_vec[] = { UTIL_OPT_SECTION("OPTION WITHOUT ARGUMENTS"), /* Our own options */ { .option = { "single", no_argument, NULL, 's'}, .desc = "A single option without an argument", }, UTIL_OPT_SECTION("OPTIONS WITH ARGUMENTS"), { .option = { "req_arg", required_argument, NULL, 'r'}, .argument = "REQ_ARG", .desc = "Option with a required argument REQ_ARG", }, { /* * NOTE: For specifying an optional parameter OPT_ARG use * either "-oOPT_ARG" or "--opt_arg=OPT_ARG" on the commandline. * Specifying "-o OPT_ARG" or "--opt_arg OPT_ARG" will not work. */ .option = { "opt_arg", optional_argument, NULL, 'o'}, .argument = "OPT_ARG", .desc = "Option with an optional argument OPT_ARG. " \ "We don't recommend using this feature.", }, UTIL_OPT_SECTION("OPTION WITHOUT SHORT OPTION"), { .option = { "noshort", no_argument, NULL, OPT_NOSHORT}, .desc = "Option with only a long name", .flags = UTIL_OPT_FLAG_NOSHORT, }, UTIL_OPT_SECTION("OPTION WITHOUT LONG OPTION"), { .option = { NULL, no_argument, NULL, 'l'}, .desc = "Option with only a short name", .flags = UTIL_OPT_FLAG_NOLONG, }, UTIL_OPT_SECTION("OPTION WITH MANUALLY FORMATTED DESCRIPTION"), { .option = { "manual", no_argument, NULL, 'm' }, .desc = "Option descriptions can be formatted\n" \ "using the new line character '\\n' to:\n" \ " - Display descriptions more meaningful\n" \ " - Create lists within the description", }, UTIL_OPT_SECTION("STANDARD OPTIONS"), /* Standard option: -h,--help */ UTIL_OPT_HELP, /* Standard option: -v,--version */ UTIL_OPT_VERSION, /* End-marker for option vector */ UTIL_OPT_END }; /* * Parse the command line options with util_opt functions */ int main(int argc, char *argv[]) { int c; /* Install option vector */ util_opt_init(opt_vec, NULL); /* Parse all options specified in argv[] */ while (1) { /* Get the next option 'c' from argv[] */ c = util_opt_getopt_long(argc, argv); /* No more options on command line? */ if (c == -1) break; /* Find the right action for option 'c' */ switch (c) { case 'h': util_opt_print_help(); return EXIT_SUCCESS; case 'v': printf("Specified: --version\n"); return EXIT_SUCCESS; case 's': printf("Specified: --single\n"); break; case 'o': if (optarg != NULL) printf("Specified: --opt_arg %s\n", optarg); else printf("Specified: --opt_arg [without arg]\n"); break; case 'r': printf("Specified: --req_arg %s\n", optarg); break; case OPT_NOSHORT: printf("Specified: --noshort\n"); break; default: util_opt_print_parse_error(c, argv); return EXIT_FAILURE; } } return EXIT_SUCCESS; } //! [code] s390-tools-2.3.0/libutil/util_panic.c000066400000000000000000000057471323410431100173140ustar00rootroot00000000000000/* * util - Utility function library * * Collect FFDC data for unexpected errors * * Copyright IBM Corp. 2016, 2017 * * 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 #include #include #include #include #include #include "lib/util_base.h" #include "lib/util_panic.h" /* * Obtain a backtrace and print it to stderr * * To get symbols, compile the code with "-rdynamic". */ static void print_backtrace(void) { void *array[256]; size_t i, size; char **strings; fprintf(stderr, "Backtrace:\n\n"); size = backtrace(array, UTIL_ARRAY_SIZE(array)); strings = backtrace_symbols(array, size); if (strings == NULL) { fprintf(stderr, " Could not obtain backtrace (ENOMEM)\n"); return; } for (i = 0; i < size; i++) fprintf(stderr, " %s\n", strings[i]); free(strings); } /* * Check for core ulimit */ static void ulimit_core_check(void) { struct rlimit limit; if (getrlimit(RLIMIT_CORE, &limit) != 0) return; if (limit.rlim_cur != 0) return; fprintf(stderr, "Core dump size is zero. To get a full core dump use 'ulimit -c unlimited'.\n"); } /* * Print FFDC data and then abort */ static void panic_finish(const char *func, const char *file, int line, const char *fmt, va_list ap) { /* Write panic error string */ fprintf(stderr, "\n"); fprintf(stderr, "Error string:\n"); fprintf(stderr, "\n"); fprintf(stderr, " "); vfprintf(stderr, fmt, ap); fprintf(stderr, "\n"); /* Write file, line number, and function name */ fprintf(stderr, "Location:\n\n"); fprintf(stderr, " %s:%d: %s()\n", file, line, func); fprintf(stderr, "\n"); /* Print the function backtrace */ print_backtrace(); fprintf(stderr, "\n"); ulimit_core_check(); fprintf(stderr, "----------------------------------------------------------------------->8-----\n"); abort(); } /* * Do panic processing if the assumption is not true */ void __util_assert(const char *assertion_str, const char *func, const char *file, int line, int assumption, const char *fmt, ...) { va_list ap; if (assumption) return; va_start(ap, fmt); fprintf(stderr, "---8<-------------------------------------------------------------------------\n"); fprintf(stderr, "ASSERTION FAILED: The application terminated due to an internal or OS error\n"); fprintf(stderr, "\n"); fprintf(stderr, "The following assumption was *not* true:\n"); fprintf(stderr, "\n"); fprintf(stderr, " %s\n", assertion_str); panic_finish(func, file, line, fmt, ap); } /* * Do panic processing */ void __noreturn __util_panic(const char *func, const char *file, int line, const char *fmt, ...) { va_list ap; va_start(ap, fmt); fprintf(stderr, "---8<-------------------------------------------------------------------------\n"); fprintf(stderr, "PANIC: The application terminated due to an unrecoverable error\n"); panic_finish(func, file, line, fmt, ap); while(1); } s390-tools-2.3.0/libutil/util_panic_example.c000066400000000000000000000042401323410431100210120ustar00rootroot00000000000000/** * util_panic_example - Example program for util_panic * * Copyright IBM Corp. 2016, 2017 * * s390-tools is free software; you can redistribute it and/or modify * it under the terms of the MIT license. See LICENSE for details. */ //! [code] #include #include #include #include #include #include #include "lib/util_panic.h" /* Make functions noinline to have a nice backtrace */ #define __noinline __attribute__((noinline)) /* * Test util_panic() */ __noinline void panic_test_func(void) { fprintf(stderr, "Testing util_panic() now\n"); util_panic("Adieu beautiful world ...\n"); fprintf(stderr, "You should not see this\n"); } /* * Test util_panic() with errno handling */ __noinline void panic_errno_test_func(void) { const char *file = "i_do_not_exist"; if (fopen("i_do_not_exist", "r") == NULL) { util_panic("Open file \"%s\" failed: %s\n", file, strerror(errno)); } fprintf(stderr, "You should not see this\n"); } /* * Test util_assert() */ __noinline void assert_test_func(void) { char *drink_actual = "beer"; fprintf(stderr, "Testing util_assert() now\n"); util_assert(strcmp(drink_actual, "water") == 0, "We expected \"%s\" but got \"%s\"\n", "water", drink_actual); fprintf(stderr, "You should not see this\n"); } /* * Demonstrate util_panic() and util_assert() */ int main(int argc, char *argv[]) { struct rlimit rlim_unlimited = {-1, -1}; struct rlimit rlim_zero = {0, 0}; if (argc != 2) goto fail; if (strcmp(argv[1], "util_panic") == 0) { fprintf(stderr, "Disable core files: ulimit -c 0\n"); setrlimit(RLIMIT_CORE, &rlim_zero); /* Do the panic */ panic_test_func(); } else if (strcmp(argv[1], "util_panic_errno") == 0) { setrlimit(RLIMIT_CORE, &rlim_unlimited); /* Do the panic */ panic_errno_test_func(); } else if (strcmp(argv[1], "util_assert") == 0) { fprintf(stderr, "Enable core files: ulimit -c unlimited\n"); setrlimit(RLIMIT_CORE, &rlim_unlimited); /* Do the assertion */ assert_test_func(); } fail: fprintf(stderr, "Usage: %s util_panic|util_panic_errno|util_assert\n", argv[0]); return EXIT_FAILURE; } //! [code] s390-tools-2.3.0/libutil/util_part.c000066400000000000000000000162011323410431100171530ustar00rootroot00000000000000/* * util - Utility function library * * Partition detection functions * * Copyright IBM Corp. 2013, 2017 * * 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 #include #include #include #include #include #include #include #include #include "lib/util_part.h" #define GPT_SIGNATURE 0x4546492050415254ULL /* EFI PART */ #define MBR_SIGNATURE 0x55aa #define MBR_PART_TYPE_DOS_EXT 0x05 /* DOS extended partition */ #define MBR_PART_TYPE_WIN98_EXT 0x0f /* Windows 98 extended partition */ #define MBR_PART_TYPE_LINUX_EXT 0x85 /* Linux extended partition */ #define MBR_PART_TYPE_GPT 0xee /* GPT partition */ #define MBR_EXT_PART_NUM_FIRST 5 /* Partition number for first logical vol */ /* * MBR/MSDOS partition entry */ struct mbr_part_entry { uint8_t status; uint8_t chs_start[3]; uint8_t type; uint8_t chs_end[3]; uint32_t blk_start; uint32_t blk_cnt; } __attribute__((packed)); /* * Master Boot Record (MBR) */ struct mbr { uint8_t reserved[0x1be]; struct mbr_part_entry part_entry_vec[4]; uint16_t signature; } __attribute__((packed)); /* * GUID Partition Table (GPT) header */ struct gpt { uint64_t signature; uint32_t version; uint32_t hdr_size; uint32_t hdr_crc; uint32_t reserved1; uint64_t blk_cur; uint64_t blk_back; uint64_t blk_first; uint64_t blk_last; uint8_t guid[16]; uint64_t part_tab_blk_start; uint32_t part_tab_cnt; uint32_t part_tab_entry_size; uint32_t part_tab_crc; } __attribute__((packed)); /* * GPT partition entry */ struct gpt_part_entry { uint8_t type[16]; uint8_t guid[16]; uint64_t blk_start; uint64_t blk_end; uint64_t attr; char name[72]; } __attribute__((packed)); /* * Check for extended partition */ static int mbr_part_is_ext(uint8_t type) { if ((type == MBR_PART_TYPE_DOS_EXT) || (type == MBR_PART_TYPE_WIN98_EXT) || (type == MBR_PART_TYPE_LINUX_EXT)) return 1; return 0; } /* * Check if disk has a classic MBR partion table */ static int mbr_table_valid(struct mbr *mbr) { return mbr->signature == MBR_SIGNATURE; } /* * Search partition in logical volumes of an extended partition */ static int mbr_table_ext_search(int fh, size_t blk_start_mbr, size_t blk_start, size_t blk_cnt, size_t blk_size, int part_num) { size_t start, cnt, start_next; struct mbr mbr; /* Read MBR for logical volume */ if (lseek(fh, blk_start_mbr * blk_size, SEEK_SET) == (off_t)-1) return -1; if (read(fh, &mbr, sizeof(mbr)) == -1) return -1; /* Check for invalid MBR or last entry */ if (mbr.signature != MBR_SIGNATURE) return -1; if (mbr.part_entry_vec[0].blk_start == 0) return -1; /* First entry contains a relative offset for current logical volume */ start = blk_start_mbr + le32toh(mbr.part_entry_vec[0].blk_start); cnt = le32toh(mbr.part_entry_vec[0].blk_cnt); if ((start == blk_start) && (cnt == blk_cnt)) return part_num; /* Second entry contains relative offset for next logical volume */ start_next = le32toh(mbr.part_entry_vec[1].blk_start); if (start_next == 0) return 0; start_next += blk_start_mbr; /* Recursively search for next logical volume in chain */ return mbr_table_ext_search(fh, start_next, blk_start, blk_cnt, blk_size, part_num + 1); } /* * Search partition in MBR partition table */ static int mbr_table_search(int fh, struct mbr *mbr, size_t blk_start, size_t blk_cnt, size_t blk_size, int *part_ext) { int part_num_ext, part_num; size_t start, cnt; uint8_t type; for (part_num = 1; part_num <= 4; part_num++) { type = mbr->part_entry_vec[part_num - 1].type; start = le32toh(mbr->part_entry_vec[part_num - 1].blk_start); cnt = le32toh(mbr->part_entry_vec[part_num - 1].blk_cnt); if (start == 0) /* Empty slot */ continue; /* * The kernel sets count for extended partitions explicitly. * Therefore we do not check count here. */ if (mbr_part_is_ext(type) && (start == blk_start)) { *part_ext = 1; return part_num; } if ((start == blk_start) && (cnt == blk_cnt)) return part_num; if (!mbr_part_is_ext(type)) continue; part_num_ext = mbr_table_ext_search(fh, start, blk_start, blk_cnt, blk_size, MBR_EXT_PART_NUM_FIRST); if (part_num_ext != 0) return part_num_ext; } return 0; } /* * Search partition in GPT partition table */ static int gpt_table_search(int fh, struct gpt *gpt, size_t blk_start, size_t blk_cnt, size_t blk_size) { size_t start, end, part_tab_blk_start, blk_end; uint32_t part_tab_cnt, part_tab_entry_size; struct gpt_part_entry *part_entry; unsigned int part_num; blk_end = blk_start + blk_cnt - 1; part_tab_entry_size = le32toh(gpt->part_tab_entry_size); part_tab_cnt = le32toh(gpt->part_tab_cnt); part_tab_blk_start = le64toh(gpt->part_tab_blk_start); if (lseek(fh, part_tab_blk_start * blk_size, SEEK_SET) == (off_t)-1) return -1; for (part_num = 1; part_num <= part_tab_cnt; part_num++) { char buf[part_tab_entry_size]; part_entry = (struct gpt_part_entry *) buf; if (read(fh, buf, sizeof(buf)) == -1) return -1; start = le64toh(part_entry->blk_start); end = le64toh(part_entry->blk_end); if (start == 0) /* Empty slot */ continue; if ((start == blk_start) && (end == blk_end)) return part_num; } return 0; } /* * Check if disk has a GPT partition table */ static int gpt_table_valid(struct gpt *gpt, struct mbr *mbr) { int cnt, part_num; uint32_t start; uint8_t type; if (gpt->signature != GPT_SIGNATURE) return 0; /* Check for protective MBR (one reserved GPT partition) */ for (part_num = 1, cnt = 0; part_num <= 4; part_num++) { start = le32toh(mbr->part_entry_vec[part_num - 1].blk_start); type = mbr->part_entry_vec[part_num - 1].type; if (!start) continue; if (type != MBR_PART_TYPE_GPT) return 0; if (++cnt > 1) return 0; } return 1; } /* * Search for partition with given start block and count * * Return partition number when found, 0 when not found, and on error -1 * Set "part_ext" to 1 for extended partitions otherwise to 0. */ int util_part_search_fh(int fh, size_t blk_start, size_t blk_cnt, size_t blk_size, int *part_ext) { struct gpt gpt; struct mbr mbr; if (lseek(fh, 0, SEEK_SET) == (off_t)-1) return -1; if (read(fh, &mbr, sizeof(mbr)) == -1) return -1; if (lseek(fh, blk_size, SEEK_SET) == (off_t)-1) return -1; if (read(fh, &gpt, sizeof(gpt)) == -1) return -1; *part_ext = 0; if (gpt_table_valid(&gpt, &mbr)) return gpt_table_search(fh, &gpt, blk_start, blk_cnt, blk_size); if (mbr_table_valid(&mbr)) return mbr_table_search(fh, &mbr, blk_start, blk_cnt, blk_size, part_ext); return -1; } /* * Search for partition with given start block and count * * Return partition number when found, 0 when not found, and on error -1 * Set "part_ext" to 1 for extended partitions otherwise to 0. */ int util_part_search(const char *device, size_t blk_start, size_t blk_cnt, size_t blk_size, int *part_ext) { int rc, fh; fh = open(device, O_RDONLY); if (fh == -1) return -1; rc = util_part_search_fh(fh, blk_start, blk_cnt, blk_size, part_ext); close(fh); return rc; } s390-tools-2.3.0/libutil/util_path.c000066400000000000000000000107511323410431100171450ustar00rootroot00000000000000/* * util - Utility function library * * Work with paths * * Copyright IBM Corp. 2016, 2017 * * 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 #include #include #include #include #include #include "lib/util_base.h" #include "lib/util_libc.h" #include "lib/util_path.h" #include "lib/util_prg.h" #include "lib/util_proc.h" /* * Verify that directory exists */ static void verify_dir(const char *dir) { struct stat sb; int rc; rc = stat(dir, &sb); if (rc < 0) err(EXIT_FAILURE, "Could not access directory: %s", dir); if (!S_ISDIR(sb.st_mode)) errx(EXIT_FAILURE, "Is not a directory: %s", dir); } /* * Return sysfs mount point */ static char *sys_mount_point(void) { struct util_proc_mnt_entry mnt_entry; static char *mount_point; char *dir; if (mount_point) return mount_point; /* Check the environment variable */ dir = getenv("SYSFS_ROOT"); if (dir) { mount_point = util_strdup(dir); } else { if (util_proc_mnt_get_entry("/proc/mounts", "sysfs", &mnt_entry)) errx(EXIT_FAILURE, "No mount point found for sysfs"); mount_point = util_strdup(mnt_entry.file); util_proc_mnt_free_entry(&mnt_entry); } verify_dir(mount_point); return mount_point; } /** * Construct a sysfs path * * The arguments of the function are used to specify a subdirectory under * sysfs root. * * @param[in] fmt Format string for path * @param[in] ... Variable arguments for format string * * @returns Allocated path */ char *util_path_sysfs(const char *fmt, ...) { char *path, *fmt_tot; va_list ap; util_asprintf(&fmt_tot, "%s/%s", sys_mount_point(), fmt); /* Format and return full sysfs path */ va_start(ap, fmt); util_vasprintf(&path, fmt_tot, ap); va_end(ap); free(fmt_tot); return path; } /** * Test if path exists and is readable * * This function has the same semantics as "-r path" in bash. * * @param[in] fmt Format string for path to test * @param[in] ... Variable arguments for format string * * @returns true Path exists and is readable * false Otherwise */ bool util_path_is_readable(const char *fmt, ...) { va_list ap; char *path; bool rc; UTIL_VASPRINTF(&path, fmt, ap); rc = access(path, R_OK) == 0 ? true : false; free(path); return rc; } /** * Test if path exists and is writable * * This function has the same semantics as "-w path" in bash. * * @param[in] fmt Format string for path to test * @param[in] ... Variable arguments for format string * * @returns true Path exists and is writable * false Otherwise */ bool util_path_is_writable(const char *fmt, ...) { va_list ap; char *path; bool rc; UTIL_VASPRINTF(&path, fmt, ap); rc = access(path, W_OK) == 0 ? true : false; free(path); return rc; } /** * Test if path exists and is a regular file * * This function has the same semantics as "-f path" in bash. * * @param[in] fmt Format string for path to test * @param[in] ... Variable arguments for format string * * @returns true Path exists and is a regular file * false Otherwise */ bool util_path_is_reg_file(const char *fmt, ...) { struct stat sb; va_list ap; char *path; bool rc; UTIL_VASPRINTF(&path, fmt, ap); if (stat(path, &sb)) { rc = false; goto free_str; } rc = (sb.st_mode & S_IFREG) ? true : false; free_str: free(path); return rc; } /** * Test if path exists and is a directory * * This function has the same semantics as "-d path" in bash. * * @param[in] fmt Format string for path to test * @param[in] ... Variable arguments for format string * * @returns true Path exists and is a directory * false Otherwise */ bool util_path_is_dir(const char *fmt, ...) { struct stat sb; va_list ap; char *path; bool rc; UTIL_VASPRINTF(&path, fmt, ap); if (stat(path, &sb)) { rc = false; goto free_str; } rc = (sb.st_mode & S_IFDIR) ? true : false; free_str: free(path); return rc; } /** * Test if path to directory or file exists * * This function has the same semantics as "-e path" in bash. * * @param[in] fmt Format string for path to test * @param[in] ... Variable arguments for format string * * @returns true Path exists * false Otherwise */ bool util_path_exists(const char *fmt, ...) { va_list ap; char *path; bool rc; UTIL_VASPRINTF(&path, fmt, ap); rc = access(path, F_OK) == 0 ? true : false; free(path); return rc; } s390-tools-2.3.0/libutil/util_path_example.c000066400000000000000000000043121323410431100206540ustar00rootroot00000000000000/** * util_path_example - Example program for util_path * * Copyright IBM Corp. 2016, 2017 * * s390-tools is free software; you can redistribute it and/or modify * it under the terms of the MIT license. See LICENSE for details. */ //! [code] #include #include #include #include #include #include "lib/util_path.h" #include "lib/util_prg.h" /* * Define program description */ const struct util_prg prg = { .desc = "Sample for util_dirlibrary", .copyright_vec = { { .owner = "IBM Corp.", .pub_first = 2001, .pub_last = 2017, }, UTIL_PRG_COPYRIGHT_END } }; /* * Test util_path_sysfs() */ static void test_util_path_sysfs(void) { const char * const subsys_vec[] = {"cpu", "memory"}; char *path; int i; /* Construct sysfs paths for the subsystems */ for (i = 0; i < 2; i++) { path = util_path_sysfs("devices/system/%s", subsys_vec[i]); printf("Path for %6s: \"%s\"\n", subsys_vec[i], path); free(path); } } /* * Test path */ static void test_path(const char *path) { printf("%-20s: ", path); if (util_path_exists(path)) printf("exists=yes "); else printf("exists=no "); if (util_path_is_readable(path)) printf("read=yes "); else printf("read=no "); if (util_path_is_writable(path)) printf("write=yes "); else printf("write=no "); if (util_path_is_reg_file(path)) printf("reg_file=yes "); else printf("reg_file=no "); if (util_path_is_dir(path)) printf("dir=yes"); else printf("dir=no "); printf("\n"); } /* * Test util_path_is_xxx() */ static void test_util_path_is_xxx(void) { test_path("util_path_example.c"); test_path("/tmp"); test_path("i_do_not_exist"); } /* * Usage: util_path_example [sysfs mount point] | "is_xxx" */ int main(int argc, char *argv[]) { util_prg_init(&prg); if (argc < 2) goto out_fail; if (strcmp(argv[1], "sysfs") == 0) { if (argc == 3) { /* Change sysfs path via SYSFS_ROOT env variable */ setenv("SYSFS_ROOT", argv[2], 1); } test_util_path_sysfs(); } else if (strcmp(argv[1], "is_xxx") == 0) { test_util_path_is_xxx(); } else { goto out_fail; } return EXIT_SUCCESS; out_fail: errx(EXIT_FAILURE, "Usage: %s sysfs | is_xxx", argv[0]); } //! [code] s390-tools-2.3.0/libutil/util_prg.c000066400000000000000000000060351323410431100170010ustar00rootroot00000000000000/* * util - Utility function library * * Print standard program messages * * Copyright IBM Corp. 2016, 2017 * * 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 #include #include "lib/util_base.h" #include "lib/util_prg.h" #include "lib/zt_common.h" /* * Private data */ static struct util_prg_l { const struct util_prg *prg; /* Command used for parsing */ const char *command; } l; struct util_prg_l *util_prg_l = &l; /** * Set the current command for command line option processing * * @param[in] command The current command or NULL for no command */ void util_prg_set_command(const char *command) { l.command = command; } /** * Print program usage information for the --help option */ void util_prg_print_help(void) { /* Print usage */ printf("Usage: %s", program_invocation_short_name); if (l.prg->command_args) printf(" %s", l.prg->command_args); printf(" [OPTIONS]"); if (l.prg->args) printf(" %s", l.prg->args); /* Print usage description */ printf("\n\n"); util_print_indented(l.prg->desc, 0); printf("\n"); } /** * Print program version information for the --version option */ void util_prg_print_version(void) { const struct util_prg_copyright *copyright; printf("%s version %s\n", program_invocation_short_name, RELEASE_STRING); copyright = l.prg->copyright_vec; while (copyright->owner) { if (copyright->pub_first == copyright->pub_last) printf("Copyright %s %d\n", copyright->owner, copyright->pub_first); else printf("Copyright %s %d, %d\n", copyright->owner, copyright->pub_first, copyright->pub_last); copyright++; } } /* * Ask user to use the --help option */ void util_prg_print_parse_error(void) { if (l.command) fprintf(stderr, "Try '%s %s --help' for more information.\n", program_invocation_short_name, l.command); else fprintf(stderr, "Try '%s --help' for more information.\n", program_invocation_short_name); } /** * An option has been specified that is not supported * * @param[in] option Option string (short or long) */ void util_prg_print_invalid_option(const char *opt_name) { fprintf(stderr, "%s: Invalid option '%s'\n", program_invocation_short_name, opt_name); util_prg_print_parse_error(); } /** * A required argument for an option is missing * * @param[in] option Option string */ void util_prg_print_required_arg(const char *opt_name) { fprintf(stderr, "%s: Option '%s' requires an argument\n", program_invocation_short_name, opt_name); util_prg_print_parse_error(); } /** * A superfluous invalid positional argument has been specified * * @param[in] arg_name Name of the invalid argument */ void util_prg_print_arg_error(const char *arg_name) { fprintf(stderr, "%s: Invalid argument '%s'\n", program_invocation_short_name, arg_name); util_prg_print_parse_error(); } /** * Initialize the program module * * @param[in] prg Program description */ void util_prg_init(const struct util_prg *prg) { l.prg = prg; } s390-tools-2.3.0/libutil/util_prg_example.c000066400000000000000000000036311323410431100205130ustar00rootroot00000000000000/** * util_prg_example - Example program for util_prg * * Copyright IBM Corp. 2016, 2017 * * s390-tools is free software; you can redistribute it and/or modify * it under the terms of the MIT license. See LICENSE for details. */ //! [code] #include #include #include "lib/util_prg.h" /* * Program description */ const struct util_prg prg = { .desc = "Sample for util_prg library.", .args = "[POS_ARGS]", .copyright_vec = { { .owner = "IBM Corp.", .pub_first = 2001, .pub_last = 2017, }, { .owner = "Another Corp.", .pub_first = 2017, .pub_last = 2017, }, UTIL_PRG_COPYRIGHT_END } }; /* * Demonstrate the util_prg_print() functions */ int main(int argc, char *argv[]) { const char *file_name = "i_do_not_exist"; char optopt_str[3]; FILE *fp; int opt; /* Disable the getopt_long() error messages */ opterr = 0; util_prg_init(&prg); while ((opt = getopt(argc, argv, "vhe")) != -1) { switch (opt) { case 'v': util_prg_print_version(); return EXIT_SUCCESS; case 'h': util_prg_print_help(); printf(" -e Try to open non-exisiting file\n"); printf(" -h Print this help, then exit\n"); printf(" -v Print version information, then exit\n"); return EXIT_SUCCESS; case 'e': fp = fopen(file_name, "r"); if (!fp) { err(EXIT_FAILURE, "Open of '%s' failed", file_name); } return EXIT_SUCCESS; case ':': /* Option requires an argument */ util_prg_print_required_arg(argv[optind - 1]); return EXIT_FAILURE; case '?': sprintf(optopt_str, "-%c", optopt); util_prg_print_invalid_option(optopt_str); return EXIT_FAILURE; } } if (argc > 2) { util_prg_print_arg_error(argv[2]); return EXIT_FAILURE; } if (argc == 2) { printf("Positional parameter specified: %s\n", argv[1]); return EXIT_SUCCESS; } errx(EXIT_FAILURE, "Specify either -h, -v, -e, or one positional " "parameter"); } //! [code] s390-tools-2.3.0/libutil/util_proc.c000066400000000000000000000252621323410431100171570ustar00rootroot00000000000000/* * s390-tools/zipl/src/proc.c * Scanner for the /proc/ files * * Copyright IBM Corp. 2001, 2017 * * 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 #include #include #include #include #include #include #include "lib/util_proc.h" static const char util_proc_part_filename[] = "/proc/partitions"; static const char util_proc_dev_filename[] = "/proc/devices"; struct file_buffer { char *buffer; off_t pos; size_t length; }; #define INITIAL_FILE_BUFFER_SIZE 1024 /* Read file into buffer without querying its size (necessary for reading files * from /proc). Upon success, return zero and set BUFFER to point to * the file buffer and SIZE (if non-null) to contain the file size. Return * non-zero otherwise. Add a null-byte at the end of the buffer if * NIL_TERMINATE is non-zero. */ static int util_proc_read_special_file(const char *filename, char **buffer, size_t *size, int nil_terminate) { FILE *file; char *data; char *new_data; size_t count; size_t current_size; int current; file = fopen(filename, "r"); if (file == NULL) { printf("Could not open %s\n", filename); return -1; } current_size = INITIAL_FILE_BUFFER_SIZE; count = 0; data = (char *) malloc(current_size); if (data == NULL) { printf("Could not allocate %zu bytes of memory", current_size); fclose(file); return -1; } current = fgetc(file); while (current != EOF || nil_terminate) { if (current == EOF) { current = 0; nil_terminate = 0; } data[count++] = (char) current; if (count >= current_size) { new_data = (char *) malloc(current_size * 2); if (new_data == NULL) { printf("Could not allocate %zu bytes of memory", current_size * 2); free(data); fclose(file); return -1; } memcpy(new_data, data, current_size); free(data); data = new_data; current_size *= 2; } current = fgetc(file); } fclose(file); *buffer = data; if (size) *size = count; return 0; } /* Get the contents of a file and fill in the respective fields of * FILE. Return 0 on success, non-zero otherwise. */ static int get_file_buffer(struct file_buffer *file, const char *filename) { int rc; rc = util_proc_read_special_file(filename, &file->buffer, &file->length, 0); file->pos = 0; return rc; } /* Free resources allocated for file buffer identified by * FILE. */ static void free_file_buffer(struct file_buffer *file) { if (file->buffer != NULL) { free(file->buffer); file->buffer = NULL; file->pos = 0; file->length = 0; } } /* Return character at current FILE buffer position or EOF if at end of * file. */ static int current_char(struct file_buffer *file) { if (file->buffer != NULL) if (file->pos < (off_t) file->length) return file->buffer[file->pos]; return EOF; } /* Advance the current file pointer of file buffer FILE until the current * character is no longer a whitespace or until the end of line or file is * reached. Return 0 if at least one whitespace character was encountered, * non-zero otherwise. */ static int skip_whitespaces(struct file_buffer *file) { int rc; rc = -1; while ((current_char(file) != '\n') && isspace(current_char(file))) { rc = 0; file->pos++; } return rc; } /* Scan a positive integer number at the current position of file buffer FILE * and advance the position respectively. Upon success, return zero and set * NUMBER to contain the scanned number. Return non-zero otherwise. */ static int scan_number(struct file_buffer *file, size_t *number) { int rc; size_t old_number; *number = 0; rc = -1; while (isdigit(current_char(file))) { rc = 0; old_number = *number; *number = *number * 10 + current_char(file) - '0'; /* Check for overflow */ if (old_number > *number) { rc = -1; break; } file->pos++; } return rc; } /* Scan a device node name at the current position of file buffer FILE and * advance the position respectively. Upon success, return zero and set * NAME to contain a copy of the scanned name. Return non-zero otherwise. */ static int scan_name(struct file_buffer *file, char **name) { off_t start_pos; start_pos = file->pos; while (!isspace(current_char(file)) && (current_char(file) != EOF)) file->pos++; if (file->pos > start_pos) { *name = (char *) malloc(file->pos - start_pos + 1); if (*name == NULL) return -1; memcpy((void *) *name, (void *) &file->buffer[start_pos], file->pos - start_pos); (*name)[file->pos - start_pos] = 0; return 0; } else return -1; } /* Scan for the specified STRING at the current position of file buffer FILE * and advance the position respectively. Upon success, return zero. Return * non-zero otherwise. */ static int scan_string(struct file_buffer *file, const char *string) { int i; i = 0; for (i = 0; string[i] && (current_char(file) == string[i]); i++, file->pos++) ; if (string[i] == '\0') return 0; return -1; } /* Advance the current file position to beginning of next line in file buffer * FILE or to end of file. */ static void skip_line(struct file_buffer *file) { while ((current_char(file) != '\n') && (current_char(file) != EOF)) file->pos++; if (current_char(file) == '\n') file->pos++; } /* Return non-zero if the current file position of file buffer FILE is at the * end of file. Return zero otherwise. */ static int eof(struct file_buffer *file) { return file->pos >= (off_t) file->length; } /* Scan a line of the specified /proc/partitions FILE buffer and advance the * current file position pointer respectively. If the current line matches * the correct pattern, fill in the corresponding data into ENTRY and return 0. * Return non-zero otherwise. */ static int scan_part_entry(struct file_buffer *file, struct util_proc_part_entry *entry) { int rc; size_t dev_major; size_t dev_minor; size_t blockcount; char *name; /* Scan for: (\s*)(\d+)(\s+)(\d+)(\s+)(\d+)(\s+)(\S+)(\.*)$ */ skip_whitespaces(file); rc = scan_number(file, &dev_major); if (rc) return rc; rc = skip_whitespaces(file); if (rc) return rc; rc = scan_number(file, &dev_minor); if (rc) return rc; rc = skip_whitespaces(file); if (rc) return rc; rc = scan_number(file, &blockcount); if (rc) return rc; rc = skip_whitespaces(file); if (rc) return rc; rc = scan_name(file, &name); if (rc) return rc; skip_line(file); entry->device = makedev(dev_major, dev_minor); entry->blockcount = blockcount; entry->name = name; return 0; } /* Release resources associated with ENTRY. */ void util_proc_part_free_entry(struct util_proc_part_entry *entry) { if (entry->name != NULL) { free(entry->name); entry->name = NULL; } } /* Scan a line of the specified /proc/devices FILE buffer and advance the * current file position pointer respectively. If the current line matches * the correct pattern, fill in the corresponding data into ENTRY and return 0. * Return non-zero otherwise. */ static int scan_dev_entry(struct file_buffer *file, struct util_proc_dev_entry *entry, int blockdev) { int rc; size_t dev_major; char *name; /* Scan for: (\s*)(\d+)(\s+)(\S+)(\.*)$ */ skip_whitespaces(file); rc = scan_number(file, &dev_major); if (rc) return rc; rc = skip_whitespaces(file); if (rc) return rc; rc = scan_name(file, &name); if (rc) return rc; skip_line(file); entry->device = makedev(dev_major, 0); entry->name = name; entry->blockdev = blockdev; return 0; } /* Release resources associated with ENTRY. */ void util_proc_dev_free_entry(struct util_proc_dev_entry *entry) { if (entry->name != NULL) { free(entry->name); entry->name = NULL; } } /* Parse one record. */ static int scan_mnt_entry(struct file_buffer *file, struct util_proc_mnt_entry *entry) { int rc; skip_whitespaces(file); rc = scan_name(file, &entry->spec); if (rc) return rc; skip_whitespaces(file); rc = scan_name(file, &entry->file); if (rc) return rc; skip_whitespaces(file); rc = scan_name(file, &entry->vfstype); if (rc) return rc; skip_whitespaces(file); rc = scan_name(file, &entry->mntOpts); if (rc) return rc; skip_whitespaces(file); rc = scan_name(file, &entry->dump); if (rc) return rc; skip_whitespaces(file); rc = scan_name(file, &entry->passno); if (rc) return rc; skip_line(file); return 0; } /* Free the memory allocated for one record. */ void util_proc_mnt_free_entry(struct util_proc_mnt_entry *entry) { free(entry->spec); free(entry->file); free(entry->vfstype); free(entry->mntOpts); free(entry->dump); free(entry->passno); memset(entry, 0, sizeof(*entry)); } /* Scan /proc/partitions for an entry matching DEVICE. When there is a match, * store entry data in ENTRY and return 0. Return non-zero otherwise. */ int util_proc_part_get_entry(dev_t device, struct util_proc_part_entry *entry) { struct file_buffer file; int rc; rc = get_file_buffer(&file, util_proc_part_filename); if (rc) return rc; rc = -1; while (!eof(&file)) { if (scan_part_entry(&file, entry) == 0) { if (entry->device == device) { rc = 0; break; } util_proc_part_free_entry(entry); } else skip_line(&file); } free_file_buffer(&file); return rc; } /* Scan /proc/devices for a blockdevice (BLOCKDEV is 1) or a character * device (BLOCKDEV is 0) with a major number matching the major number of DEV. * When there is a match, store entry data in ENTRY and return 0. Return * non-zero otherwise. */ int util_proc_dev_get_entry(dev_t device, int blockdev, struct util_proc_dev_entry *entry) { struct file_buffer file; int rc; int scan_blockdev = 0; rc = get_file_buffer(&file, util_proc_dev_filename); if (rc) return rc; rc = -1; while (!eof(&file)) { if (scan_string(&file, "Block") == 0) { skip_line(&file); scan_blockdev = 1; continue; } else if (scan_dev_entry(&file, entry, scan_blockdev) == 0) { if ((major(entry->device) == major(device)) && blockdev == scan_blockdev) { rc = 0; break; } util_proc_dev_free_entry(entry); } else skip_line(&file); } free_file_buffer(&file); return rc; } /* * Provide one record form a /proc/mounts like file * * The parameter file_name distinguishes the file form procfs which * is read, the parameter spec is the selector for the record. */ int util_proc_mnt_get_entry(const char *file_name, const char *spec, struct util_proc_mnt_entry *entry) { struct file_buffer file; int rc; rc = get_file_buffer(&file, file_name); if (rc) return rc; rc = -1; while (!eof(&file)) { rc = scan_mnt_entry(&file, entry); if (rc) goto out_free; if (!strcmp(entry->spec, spec)) { rc = 0; goto out_free; } util_proc_mnt_free_entry(entry); } out_free: free_file_buffer(&file); return rc; } s390-tools-2.3.0/libutil/util_rec.c000066400000000000000000000277451323410431100167750ustar00rootroot00000000000000/** * util - Utility function library * * Print records in different output formats * * Copyright IBM Corp. 2016, 2017 * * 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 #include #include #include #include #include #include "lib/util_base.h" #include "lib/util_libc.h" #include "lib/util_list.h" #include "lib/util_panic.h" #include "lib/util_prg.h" #include "lib/util_rec.h" /* * Field structure containing the string value and secondary information */ struct util_rec_fld { char *key; /* Name of the filed */ char *hdr; /* Content of the header */ size_t len; /* Length of string argz array */ char *val; /* The value of the field */ enum util_rec_align align; /* Alignment of the field */ int width; /* Field width */ struct util_list_node node; /* Pointers to previous and next field */ }; /* * Print formats */ struct rec_fmt { enum { REC_FMT_WIDE, REC_FMT_LONG, REC_FMT_CSV, } type; union { struct wide_p { char *hdr_sep; char *col_sep; int argz_sep; } wide_p; struct long_p { char *hdr_sep; char *col_sep; int argz_sep; char *key; int key_size; int val_size; } long_p; struct csv_p { char *col_sep; int argz_sep; } csv_p; } d; }; /* * Record structure (internal representation) */ /// @cond struct util_rec { struct util_list *list; /* List of the fields */ struct rec_fmt fmt; /* Output format */ }; /// @endcond struct util_list *__util_rec_get_list(struct util_rec *rec) { return rec->list; } /* * Get the field according to a distinct key */ static struct util_rec_fld *rec_get_fld(struct util_rec *rec, const char *key) { struct util_rec_fld *fld; util_list_iterate(rec->list, fld) { if (!strcmp(fld->key, key)) return fld; } return NULL; } /** * Return the key name of a field * * @param[in] fld Field for query * * @returns Pointer to key string */ const char *util_rec_fld_get_key(struct util_rec_fld *fld) { return fld->key; } /** * Create a new record with "wide" output format * * @param[in] hdr_sep Header separator * * @returns Pointer to the created record */ struct util_rec *util_rec_new_wide(const char *hdr_sep) { struct util_rec *rec = util_malloc(sizeof(struct util_rec)); rec->list = util_list_new(struct util_rec_fld, node); rec->fmt.type = REC_FMT_WIDE; rec->fmt.d.wide_p.hdr_sep = util_strdup(hdr_sep); rec->fmt.d.wide_p.argz_sep = ','; return rec; } /* * Print record header in "wide" output format */ static void rec_print_wide_hdr(struct util_rec *rec) { const char *hdr_sep = rec->fmt.d.wide_p.hdr_sep; int col_nr = 0, size = 0, field_count = 0; struct util_rec_fld *fld; char *buf; util_list_iterate(rec->list, fld) { if (col_nr) printf(" "); if (fld->hdr) { if (fld->align == UTIL_REC_ALIGN_LEFT) printf("%-*s", fld->width, fld->hdr); else printf("%*s", fld->width, fld->hdr); size += fld->width; field_count++; } col_nr++; } printf("\n"); if (!hdr_sep) return; size += field_count - 1; if (hdr_sep) { buf = util_malloc(size + 1); memset(buf, (int)hdr_sep[0], size); buf[size] = 0; printf("%s\n", buf); free(buf); } } /* * Print record field values in "wide" output format */ void rec_print_wide(struct util_rec *rec) { const char argz_sep = rec->fmt.d.wide_p.argz_sep; struct util_rec_fld *fld; char argz_str[PAGE_SIZE]; int fld_count = 0; char *entry; util_list_iterate(rec->list, fld) { if (!fld->hdr) continue; if (fld_count) printf(" "); entry = fld->val; if (argz_count(fld->val, fld->len) > 1) { strcpy(argz_str, entry); while ((entry = argz_next(fld->val, fld->len, entry))) strcat(strncat(argz_str, &argz_sep, 1), entry); entry = argz_str; } if (fld->align == UTIL_REC_ALIGN_LEFT) printf("%-*s", fld->width, entry); else printf("%*s", fld->width, entry); fld_count++; } printf("\n"); } /* * Free private memory of record */ static void rec_free_wide(struct util_rec *rec) { free(rec->fmt.d.wide_p.hdr_sep); } /** * Create a new record with "long" output format * * @param[in] hdr_sep Header separator * @param[in] col_sep Column separator * @param[in] key Primary key of record * @param[in] key_size Width of left column i.e. keys * @param[in] val_size Width of right column i.e. values * * @returns Pointer to the created record */ struct util_rec *util_rec_new_long(const char *hdr_sep, const char *col_sep, const char *key, int key_size, int val_size) { struct util_rec *rec = util_malloc(sizeof(struct util_rec)); rec->list = util_list_new(struct util_rec_fld, node); rec->fmt.type = REC_FMT_LONG; rec->fmt.d.long_p.hdr_sep = util_strdup(hdr_sep); rec->fmt.d.long_p.col_sep = util_strdup(col_sep); rec->fmt.d.long_p.key = util_strdup(key); rec->fmt.d.long_p.key_size = key_size; rec->fmt.d.long_p.val_size = val_size; rec->fmt.d.long_p.argz_sep = ' '; return rec; } /* * Print field header in "long" output format */ static void rec_print_long_hdr(struct util_rec *rec) { struct long_p *p = &rec->fmt.d.long_p; struct util_rec_fld *fld; int len = 0; char *buf; fld = rec_get_fld(rec, p->key); util_assert(fld != NULL, "Record not found\n"); util_assert(fld->hdr != NULL, "Header for field not found\n"); if (p->col_sep) { printf("%-*s %s %-*s\n", p->key_size, fld->hdr, p->col_sep, fld->width, fld->val); len = p->key_size + p->val_size + 3; } else { printf("%-*s %-*s\n", p->key_size, fld->hdr, fld->width, fld->val); len = p->key_size + p->val_size + 1; } if (!p->hdr_sep) return; buf = util_malloc(len + 1); memset(buf, p->hdr_sep[0], len); buf[len] = 0; printf("%s\n", buf); free(buf); } /* * Print record field values in "long" output format */ static void rec_print_long(struct util_rec *rec) { struct long_p *p = &rec->fmt.d.long_p; struct util_rec_fld *fld; char *item = NULL; rec_print_long_hdr(rec); util_list_iterate(rec->list, fld) { if (!fld->hdr) continue; if (!strcmp(p->key, fld->key)) continue; if (!fld->val) continue; item = argz_next(fld->val, fld->len, item); if (p->col_sep) { printf(" %-*s %s %s\n", p->key_size - 8, fld->hdr, p->col_sep, item); while ((item = argz_next(fld->val, fld->len, item))) printf(" %-*s %c %s\n", p->key_size - 8, "", p->argz_sep, item); } else { printf(" %-*s %s\n", p->key_size - 8, fld->hdr, fld->val); while ((item = argz_next(fld->val, fld->len, item))) printf(" %-*s %s\n", p->key_size - 8, "", item); } } printf("\n"); } /* * Free private memory of record */ static void rec_free_long(struct util_rec *rec) { free(rec->fmt.d.long_p.hdr_sep); free(rec->fmt.d.long_p.col_sep); free(rec->fmt.d.long_p.key); } /** * Create a new record with "csv" output format * * @param[in] col_sep Column separator * * @returns Pointer to the created record */ struct util_rec *util_rec_new_csv(const char *col_sep) { struct util_rec *rec = util_malloc(sizeof(struct util_rec)); rec->list = util_list_new(struct util_rec_fld, node); rec->fmt.type = REC_FMT_CSV; rec->fmt.d.csv_p.col_sep = util_strdup(col_sep); rec->fmt.d.csv_p.argz_sep = ' '; return rec; } /* * Print record header in "csv" output format */ void rec_print_csv_hdr(struct util_rec *rec) { const char *col_sep = rec->fmt.d.csv_p.col_sep; struct util_rec_fld *fld; int fld_count = 0; util_list_iterate(rec->list, fld) { if (fld_count) printf("%c", *col_sep); if (fld->hdr) { printf("%s", fld->hdr); fld_count++; } } printf("\n"); } /* * Print record field values in "csv" output format */ void rec_print_csv(struct util_rec *rec) { const char argz_sep = rec->fmt.d.csv_p.argz_sep; const char *col_sep = rec->fmt.d.csv_p.col_sep; struct util_rec_fld *fld; int fld_count = 0; char *item = NULL; util_list_iterate(rec->list, fld) { item = argz_next(fld->val, fld->len, item); if (fld_count) printf("%c", *col_sep); if (fld->hdr) { printf("%s", item); while ((item = argz_next(fld->val, fld->len, item))) printf("%c%s", argz_sep, item); fld_count++; } } printf("\n"); } /* * Free private memory of record */ static void rec_free_csv(struct util_rec *rec) { free(rec->fmt.d.csv_p.col_sep); } /** * Define a new field for the record * * @param[in] rec Pointer of the record * @param[in] key Key of the filed is to be created. It should be unique * within the record. * @param[in] align Alignment of field * @param[in] width Width of field * @param[in] hdr This information is printed in record headers. If it is * NULL, the field is prohibited form printing completely. */ void util_rec_def(struct util_rec *rec, const char *key, enum util_rec_align align, int width, const char *hdr) { struct util_rec_fld *fld = util_malloc(sizeof(struct util_rec_fld)); fld->key = util_strdup(key); fld->hdr = util_strdup(hdr); fld->val = NULL; fld->align = align; fld->width = width; util_list_add_tail(rec->list, fld); } /** * Free record and associated fields * * @param[in] rec Record pointer */ void util_rec_free(struct util_rec *rec) { struct util_rec_fld *fld, *tmp; util_list_iterate_safe(rec->list, fld, tmp) { util_list_remove(rec->list, fld); free(fld->key); free(fld->hdr); free(fld->val); free(fld); } util_list_free(rec->list); switch (rec->fmt.type) { case REC_FMT_WIDE: rec_free_wide(rec); break; case REC_FMT_LONG: rec_free_long(rec); break; case REC_FMT_CSV: rec_free_csv(rec); break; } free(rec); } /** * Print record field values according to output format * * @param[in] rec Record pointer */ void util_rec_print(struct util_rec *rec) { switch (rec->fmt.type) { case REC_FMT_WIDE: rec_print_wide(rec); break; case REC_FMT_LONG: rec_print_long(rec); break; case REC_FMT_CSV: rec_print_csv(rec); break; } } /** * Print record header according to output format * * @param[in] rec Record pointer */ void util_rec_print_hdr(struct util_rec *rec) { switch (rec->fmt.type) { case REC_FMT_WIDE: rec_print_wide_hdr(rec); break; case REC_FMT_LONG: break; case REC_FMT_CSV: rec_print_csv_hdr(rec); break; } } /** * Set a field value to an argz vector * * @param[in] rec Record pointer * @param[in] key Key of the desired field * @param[in] argz Pointer to the series of strings * @param[in] len Length of the argz buffer */ void util_rec_set_argz(struct util_rec *rec, const char *key, const char *argz, size_t len) { struct util_rec_fld *fld; char *val; fld = rec_get_fld(rec, key); if (!fld) return; val = util_malloc(len); val = memcpy(val, argz, len); free(fld->val); fld->val = val; fld->len = len; } /** * Set a field value to a formatted string * * @param[in] rec Record pointer * @param[in] key Key of the desired field * @param[in] fmt Format string for generation of value string * @param[in] ... Parameters for format string * * @returns Pointer to the field which was modified or NULL in the case of * any error. */ void util_rec_set(struct util_rec *rec, const char *key, const char *fmt, ...) { struct util_rec_fld *fld; va_list ap; char *str; util_assert(fmt != NULL, "Parameter 'fmt' pointer must not be NULL\n"); fld = rec_get_fld(rec, key); if (!fld) return; UTIL_VASPRINTF(&str, fmt, ap); free(fld->val); fld->val = str; fld->len = strlen(str) + 1; } /** * Return the string value of a desired field. If the field value stored in argz * format, pointer to the first argz element is returned. * * @param[in] rec Record pointer * @param[in] key Key of the field * * @returns If the desired field was found, the pointer to its value. * NULL in the case of any error or if the field is empty. */ const char *util_rec_get(struct util_rec *rec, const char *key) { struct util_rec_fld *fld = rec_get_fld(rec, key); return (fld != NULL) ? fld->val : NULL; } s390-tools-2.3.0/libutil/util_rec_example.c000066400000000000000000000037761323410431100205060ustar00rootroot00000000000000/** * util_rec_example - Example program for util_rec * * Copyright IBM Corp. 2016, 2017 * * s390-tools is free software; you can redistribute it and/or modify * it under the terms of the MIT license. See LICENSE for details. */ //! [code] #include #include #include #include "lib/util_rec.h" /* * Print three records in specified format */ static void print_records(const char *format, struct util_rec *rec) { static const char * const size_vec[] = {"small", "medium", "large"}; static const char * const name_vec[] = {"zero", "one", "two"}; int i; printf("###########################################################\n"); printf("# %s\n\n", format); /* Define fields of record */ util_rec_def(rec, "number", UTIL_REC_ALIGN_LEFT, 6, "Number"); util_rec_def(rec, "name", UTIL_REC_ALIGN_LEFT, 10, "Name"); util_rec_def(rec, "size", UTIL_REC_ALIGN_RIGHT, 15, "Size"); /* Print record header (is a nop for long format) */ util_rec_print_hdr(rec); for (i = 0; i < 3; i++) { /* Fill fields of record with values */ util_rec_set(rec, "number", "%d", i); util_rec_set(rec, "name", name_vec[i]); util_rec_set(rec, "size", size_vec[i]); /* Print the record */ util_rec_print(rec); } printf("\n"); } /* * Print keys for record fields */ static void print_fields(struct util_rec *rec) { struct util_rec_fld *fld; int i = 1; printf("###########################################################\n"); printf("# Keys of record fields\n"); util_rec_iterate(rec, fld) { printf("Field %d : %s\n", i++, util_rec_fld_get_key(fld)); } } /* * Print records in "wide", "long", and "csv" format */ int main(void) { struct util_rec *rec; rec = util_rec_new_wide("-"); print_records("Wide format", rec); util_rec_free(rec); rec = util_rec_new_long("-", ":", "number", 30, 20); print_records("Long format", rec); util_rec_free(rec); rec = util_rec_new_csv(","); print_records("CSV format", rec); print_fields(rec); util_rec_free(rec); return EXIT_SUCCESS; } //! [code] s390-tools-2.3.0/libutil/util_scandir.c000066400000000000000000000107541323410431100176370ustar00rootroot00000000000000/* * util - Utility function library * * Scan a directory for matching entries * * Copyright IBM Corp. 2016, 2017 * * 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 #include #include #include #include #include #include #include #include #include "lib/util_base.h" #include "lib/util_libc.h" #include "lib/util_panic.h" #include "lib/util_scandir.h" #include "lib/zt_common.h" /// @cond struct util_scandir_filter { regex_t reg_buf; }; /// @endcond /* * Check directory entry */ static int filter_regexp(const struct dirent *de, void *data) { struct util_scandir_filter *filter = data; regmatch_t pmatch[1]; if (regexec(&filter->reg_buf, de->d_name, (size_t) 1, pmatch, 0) == 0) return 1; return 0; } typedef int (*__compar_fn_t) (const void *, const void *); /* * Return sorted "struct dirent" array for entries that match "filter_fn" */ static int __scandir(struct dirent ***de_vec, const char *path, int (*filter_fn)(const struct dirent *, void *), void *filter_data, int (*compar_fn)(const struct dirent **, const struct dirent **)) { struct dirent *de, *de_new, **de_vec_new = NULL; int count = 0; DIR *dirp; dirp = opendir(path); if (!dirp) return -1; while ((de = readdir(dirp))) { if (filter_fn(de, filter_data) == 0) continue; de_new = util_malloc(sizeof(*de_new)); *de_new = *de; de_vec_new = realloc(de_vec_new, sizeof(void *) * (count + 1)); de_vec_new[count++] = de_new; } closedir(dirp); if (compar_fn) qsort(de_vec_new, count, sizeof(void *), (__compar_fn_t) compar_fn); *de_vec = de_vec_new; return count; } /* * Return sorted "struct dirent" array for entries that match "pattern" */ static int scandir_regexp(struct dirent ***de_vec, const char *path, const char *pattern, int compar_fn(const struct dirent **, const struct dirent **)) { struct util_scandir_filter filter; char err_buf[256]; int count, rc; rc = regcomp(&filter.reg_buf, pattern, REG_EXTENDED); if (rc) { regerror(rc, &filter.reg_buf, err_buf, sizeof(err_buf)); util_panic("Function regcomp(%s) failed: %s\n", pattern, err_buf); } count = __scandir(de_vec, path, filter_regexp, &filter, compar_fn); regfree(&filter.reg_buf); return count; } /** * Compare two hexadecimal string dirents numerically * * @param[in] de1 First directory entry * @param[in] de2 Second directory entry * * @retval -1 de1 < de2 * @retval 0 de1 = de2 * @retval 1 de1 > de2 */ int util_scandir_hexsort(const struct dirent **de1, const struct dirent **de2) { unsigned long val1 = strtoul((*de1)->d_name, NULL, 16); unsigned long val2 = strtoul((*de2)->d_name, NULL, 16); if (val1 < val2) return -1; if (val1 == val2) return 0; return 1; } /** * Construct a list of direcotry entries using POSIX regular expressions * * A desired directory in sysfs is scanned for entries of a given name * pattern. The name pattern is constructed with sprintf() using a format * string and variable argument list. After constructing the pattern it * is used with regcomp() and regexec() to find matching entries. * The returned list of matches consist of an array of pointers to * directory entries. The entries as well as the pointer array itself are * allocated by the function and has to be released by the user via free. * * @param[out] de_vec Vector of matched directory entries * @param[in] compar_fn Callback function for sorting the entry list * @param[in] path Path to the directory to scan * @param[in] fmt Format string, describes the search pattern as POSIX regex * @param[in] ... Values for format string * * @returns Number of returned directory entries */ int util_scandir(struct dirent ***de_vec, int compar_fn(const struct dirent **first, const struct dirent **second), const char *path, const char *fmt, ...) { char *pattern; va_list ap; int rc; va_start(ap, fmt); rc = vasprintf(&pattern, fmt, ap); va_end(ap); if (rc < 0) return -1; rc = scandir_regexp(de_vec, path, pattern, compar_fn); free(pattern); return rc; } /** * Free list of directory entries * * @param[in] de_vec Vector of directory entries * @param[in] count Count of directory entries */ void util_scandir_free(struct dirent **de_vec, int count) { util_ptr_vec_free((void **) de_vec, count); } s390-tools-2.3.0/libutil/util_scandir_example.c000066400000000000000000000026421323410431100213470ustar00rootroot00000000000000/** * util_dir_example - Example program for util_dir * * Copyright IBM Corp. 2016, 2017 * * s390-tools is free software; you can redistribute it and/or modify * it under the terms of the MIT license. See LICENSE for details. */ //! [code] #include #include #include "lib/util_scandir.h" /* * Print real sysfs cpu directory contents */ static void show_cpu_dir(const char *sys_cpu_path) { char cmd_ls[256]; sprintf(cmd_ls, "ls -l %s", sys_cpu_path); printf("$ %s\n", cmd_ls); fflush(stdout); if (system(cmd_ls)) { perror("system() failed"); exit(EXIT_FAILURE); } printf("\n"); } /* * Show all CPUs on Linux system */ int main(void) { struct dirent **de_vec; int count, i; const char *path = "/sys/devices/system/cpu"; const char *prefix = "cpu"; show_cpu_dir(path); /* * Process all files that match regular expression "cpu[0-9]+" * and sort them alphabetically. Note that the regular expression * is constructed with a variable argument list. */ count = util_scandir(&de_vec, alphasort, path, "%s[0-9]+", prefix); if (count == -1) { perror("util_dir_scan failed"); return EXIT_FAILURE; } /* Print all directories */ printf("Found cpus:\n\n"); for (i = 0; i < count; i++) { if (de_vec[i]->d_type != DT_DIR) continue; printf(" - %s\n", de_vec[i]->d_name); } /* Free directory entries */ util_scandir_free(de_vec, count); return EXIT_SUCCESS; } //! [code] s390-tools-2.3.0/libvmdump/000077500000000000000000000000001323410431100153375ustar00rootroot00000000000000s390-tools-2.3.0/libvmdump/Makefile000066400000000000000000000003261323410431100170000ustar00rootroot00000000000000include ../common.mak lib = libvmdump.a all: $(lib) objects = register_content.o dump.o lkcd_dump.o register_content.o \ vmdump_convert.o vm_dump.o $(lib): $(objects) install: all clean: rm -f *.o $(lib) s390-tools-2.3.0/libvmdump/dump.cpp000066400000000000000000000026571323410431100170220ustar00rootroot00000000000000/* * vmdump - z/VM dump conversion library * * Dump base class * * Copyright IBM Corp. 2004, 2017 * * 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 "dump.h" int debug = 0; void s390TodToTimeval(uint64_t todval, struct timeval *xtime) { /* adjust todclock to 1970 */ todval -= 0x8126d60e46000000LL - (0x3c26700LL * 1000000 * 4096); todval >>= 12; xtime->tv_sec = todval / 1000000; xtime->tv_usec = todval % 1000000; } Dump::Dump(const char *filename, const char *mode) { fh = fopen(filename, mode); if(!fh){ throw(DumpErrnoException("Open dump file failed!")); } } Dump::~Dump(void) { if(fh){ fclose(fh); } } void ProgressBar::initProgress(void) { progressPercentage = -1; } void ProgressBar::displayProgress(uint64_t value, uint64_t maxValue) { char progress_bar[51]; int j; if (progressPercentage == (int) (value * 100 / maxValue)) fprintf(stderr, "%6lld of %6lld |\r", (long long) value, (long long) maxValue); else { /* percent value has changed */ progressPercentage = (value * 100 / maxValue); for (j = 0; j < progressPercentage / 2; j++) progress_bar[j] = '#'; for (j = progressPercentage / 2; j < 50; j++) progress_bar[j] = '-'; progress_bar[50] = 0; fprintf(stderr, "%6lld of %6lld |%s| %3d%% \r", (long long) value, (long long) maxValue, progress_bar, progressPercentage); } } s390-tools-2.3.0/libvmdump/dump.h000066400000000000000000000037511323410431100164630ustar00rootroot00000000000000/* * vmdump - z/VM dump conversion library * * Dump base class * * Copyright IBM Corp. 2004, 2017 * * s390-tools is free software; you can redistribute it and/or modify * it under the terms of the MIT license. See LICENSE for details. */ #ifndef DUMP_H #define DUMP_H #include #include #include #include #include extern int debug; class DumpException { public: DumpException(void) { msg[0] = 0; errorCode = 0; } DumpException(const char *m) { sprintf(msg, "%s", m); errorCode = 0; } const char *what(void) const { return msg; } int code(void) const { return errorCode; } protected: char msg[200]; int errorCode; }; class DumpErrnoException : public DumpException { public: DumpErrnoException(const char *m) { sprintf(msg, "%s (%s)", m, strerror(errno)); errorCode = errno; } }; class Dump { public: Dump(const char *filename, const char *mode); Dump(void) : fh(0) {} virtual ~Dump(void); typedef enum {DT_VM32, DT_VM64, DT_VM64_BIG, DT_LKCD32, DT_LKCD64, DT_UNKNOWN} DumpType; virtual void readMem(char *buf, int size) = 0; virtual int seekMem(uint64_t offset) = 0; virtual uint64_t getMemSize(void) const = 0; virtual struct timeval getDumpTime(void) const = 0; protected: FILE *fh; }; class ProgressBar { public: ProgressBar(void) { progressPercentage = -1; } void initProgress(void); void displayProgress(uint64_t value, uint64_t maxValue); private: int progressPercentage; }; void s390TodToTimeval(uint64_t todval, struct timeval *xtime); int vm_convert(const char *inputFileName, const char *outputFileName, const char *progName); static inline void dump_read(void *ptr, size_t size, size_t nmemb, FILE *stream) { if (fread(ptr, size, nmemb, stream) != nmemb) throw(DumpErrnoException("fread failed")); } static inline void dump_seek(FILE *stream, long offset, int whence) { if (fseek(stream, offset, whence) == -1) throw(DumpErrnoException("fseek failed")); } #endif /* DUMP_H */ s390-tools-2.3.0/libvmdump/lkcd_dump.cpp000066400000000000000000000165601323410431100200150ustar00rootroot00000000000000/* * vmdump - z/VM dump conversion library * * LKCD dump classes: LKCDDump, LKCDDump32, LKCDDump64 * * Copyright IBM Corp. 2004, 2017 * * 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 #include #include #include #include "lkcd_dump.h" LKCDDump::LKCDDump(Dump* dump, const char* arch) { referenceDump = dump; dumpHeader.magic_number = DUMP_MAGIC_NUMBER; dumpHeader.version = DUMP_VERSION_NUMBER; dumpHeader.header_size = sizeof(struct _lkcd_dump_header); dumpHeader.time.tv_sec = dump->getDumpTime().tv_sec; dumpHeader.time.tv_usec = dump->getDumpTime().tv_usec; strcpy(dumpHeader.utsname_machine, arch); dumpHeader.memory_size = dump->getMemSize(); dumpHeader.memory_start = 0; dumpHeader.memory_end = dump->getMemSize(); dumpHeader.num_dump_pages = dump->getMemSize()/0x1000; dumpHeader.page_size = 0x1000; dumpHeader.dump_compress = DUMP_COMPRESS_GZIP; dumpHeader.dump_level = DUMP_LEVEL_ALL; dumpHeaderAsm.magic_number = DUMP_ASM_MAGIC_NUMBER; dumpHeaderAsm.version = 1; dumpHeaderAsm.header_size = sizeof(dumpHeaderAsm); } int LKCDDump::compressGZIP(const char *old, uint32_t old_size, char *n, uint32_t new_size) { unsigned long len = old_size; int rc; rc = compress((Bytef*)n, &len, (const Bytef*)old, new_size); switch(rc) { case Z_OK: rc = len; break; case Z_BUF_ERROR: /* In this case the compressed output is bigger than */ /* the uncompressed */ rc = GZIP_NOT_COMPRESSED; break; case Z_MEM_ERROR: throw(DumpException("gzip call failed: out of memory")); case Z_DATA_ERROR: throw(DumpException("gzip call failed: input data " \ "corrupted!")); default: throw(DumpException("gzip call failed: unknown error")); } return rc; } void LKCDDump::writeDump(const char* fileName) { char dump_header_buf[DUMP_HEADER_SIZE] = {}; char dump_page_buf[DUMP_BUFFER_SIZE]; char dpcpage[DUMP_PAGE_SIZE]; uint32_t dp_size,dp_flags; ProgressBar progressBar; char buf[DUMP_PAGE_SIZE]; struct _dump_page dp; uint64_t mem_loc = 0; ssize_t buf_loc = 0; int size, fd; if (fileName == NULL) { fd = STDOUT_FILENO; } else { fd = open(fileName, O_WRONLY | O_CREAT | O_TRUNC, S_IRUSR | S_IWUSR); if (fd == -1) { char msg[1024]; sprintf(msg, "Open of dump '%s' failed.", fileName); throw(DumpErrnoException(msg)); } } /* write dump header */ memcpy(dump_header_buf, &dumpHeader, sizeof(dumpHeader)); memcpy(&dump_header_buf[sizeof(dumpHeader)], &dumpHeaderAsm, sizeof(dumpHeaderAsm)); if (write(fd, dump_header_buf, sizeof(dump_header_buf)) != sizeof(dump_header_buf)) { throw(DumpErrnoException("write failed")); } /* write memory */ referenceDump->seekMem(0); while (mem_loc < dumpHeader.memory_size) { referenceDump->readMem(buf, DUMP_PAGE_SIZE); copyRegsToPage(mem_loc,buf); memset(dpcpage, 0, DUMP_PAGE_SIZE); /* * Get the new compressed page size */ size = compressGZIP((char *)buf, DUMP_PAGE_SIZE, (char *)dpcpage, DUMP_PAGE_SIZE); /* * If compression failed or compressed was ineffective, * we write an uncompressed page */ if (size == GZIP_NOT_COMPRESSED) { dp_flags = DUMP_DH_RAW; dp_size = DUMP_PAGE_SIZE; } else { dp_flags = DUMP_DH_COMPRESSED; dp_size = size; } dp.address = mem_loc; dp.size = dp_size; dp.flags = dp_flags; memcpy((void *)(dump_page_buf + buf_loc), (const void *)&dp, sizeof(dp)); buf_loc += sizeof(dp); /* * Copy the page of memory */ if (dp_flags & DUMP_DH_COMPRESSED) { /* Copy the compressed page */ memcpy((void *)(dump_page_buf + buf_loc), (const void *)dpcpage, dp_size); } else { /* Copy directly from memory */ memcpy((void *)(dump_page_buf + buf_loc), (const void *)buf, dp_size); } buf_loc += dp_size; if(write(fd, dump_page_buf, buf_loc) != buf_loc){ throw(DumpErrnoException("write failed")); } buf_loc = 0; mem_loc += DUMP_PAGE_SIZE; progressBar.displayProgress(mem_loc/(1024*1024), dumpHeader.memory_size/(1024*1024)); } /* * Write end marker */ dp.address = 0x0; dp.size = 0x0; dp.flags = DUMP_DH_END; if(write(fd, &dp, sizeof(dp)) != sizeof(dp)){ throw(DumpErrnoException("write failed")); } fprintf(stderr, "\n"); if (fd != STDOUT_FILENO) close(fd); } struct timeval LKCDDump::getDumpTime(void) const { struct timeval rc; rc.tv_sec = dumpHeader.time.tv_sec; rc.tv_usec = dumpHeader.time.tv_usec; return rc; } LKCDDump32::LKCDDump32(Dump* dump, const RegisterContent32& r) : LKCDDump(dump,"s390") { unsigned int i; dumpHeaderAsm.real_cpu_cnt = (uint32_t) r.getNumCpus(); for (i = 0; i < dumpHeaderAsm.real_cpu_cnt; i++) { if (!r.regSets[i].prefix) continue; dumpHeaderAsm.lc_vec[i] = r.regSets[i].prefix; dumpHeaderAsm.cpu_cnt++; } registerContent = r; } void LKCDDump32::copyRegsToPage(uint64_t offset, char *buf) { int cpu; for(cpu = 0; cpu < registerContent.getNumCpus(); cpu++){ if(offset == registerContent.regSets[cpu].prefix){ memcpy(buf+0xd8,®isterContent.regSets[cpu].cpuTimer, sizeof(registerContent.regSets[cpu].cpuTimer)); memcpy(buf+0xe0,®isterContent.regSets[cpu].clkCmp, sizeof(registerContent.regSets[cpu].clkCmp)); memcpy(buf+0x100,®isterContent.regSets[cpu].psw, sizeof(registerContent.regSets[cpu].psw)); memcpy(buf+0x108,®isterContent.regSets[cpu].prefix, sizeof(registerContent.regSets[cpu].prefix)); memcpy(buf+0x120,®isterContent.regSets[cpu].acrs, sizeof(registerContent.regSets[cpu].acrs)); memcpy(buf+0x160,®isterContent.regSets[cpu].fprs, sizeof(registerContent.regSets[cpu].fprs)); memcpy(buf+0x180,®isterContent.regSets[cpu].gprs, sizeof(registerContent.regSets[cpu].gprs)); memcpy(buf+0x1c0,®isterContent.regSets[cpu].crs, sizeof(registerContent.regSets[cpu].crs)); } } } LKCDDump64::LKCDDump64(Dump* dump, const RegisterContent64& r) : LKCDDump(dump,"s390x") { unsigned int i; dumpHeaderAsm.real_cpu_cnt = (uint32_t) r.getNumCpus(); for (i = 0; i < dumpHeaderAsm.real_cpu_cnt; i++) { if (!r.regSets[i].prefix) continue; dumpHeaderAsm.lc_vec[i] = r.regSets[i].prefix; dumpHeaderAsm.cpu_cnt++; } registerContent = r; } void LKCDDump64::copyRegsToPage(uint64_t offset, char *buf) { int cpu; for(cpu = 0; cpu < registerContent.getNumCpus(); cpu++){ if(offset == (registerContent.regSets[cpu].prefix + 0x1000)){ memcpy(buf+0x328,®isterContent.regSets[cpu].cpuTimer, sizeof(registerContent.regSets[cpu].cpuTimer)); memcpy(buf+0x330,®isterContent.regSets[cpu].clkCmp, sizeof(registerContent.regSets[cpu].clkCmp)); memcpy(buf+0x300,®isterContent.regSets[cpu].psw, sizeof(registerContent.regSets[cpu].psw)); memcpy(buf+0x318,®isterContent.regSets[cpu].prefix, sizeof(registerContent.regSets[cpu].prefix)); memcpy(buf+0x340,®isterContent.regSets[cpu].acrs, sizeof(registerContent.regSets[cpu].acrs)); memcpy(buf+0x200,®isterContent.regSets[cpu].fprs, sizeof(registerContent.regSets[cpu].fprs)); memcpy(buf+0x280,®isterContent.regSets[cpu].gprs, sizeof(registerContent.regSets[cpu].gprs)); memcpy(buf+0x380,®isterContent.regSets[cpu].crs, sizeof(registerContent.regSets[cpu].crs)); memcpy(buf+0x31c,®isterContent.regSets[cpu].fpCr, sizeof(registerContent.regSets[cpu].fpCr)); } } } s390-tools-2.3.0/libvmdump/lkcd_dump.h000066400000000000000000000107311323410431100174540ustar00rootroot00000000000000/* * vmdump - z/VM dump conversion library * * LKCD dump classes: LKCDDump, LKCDDump32, LKCDDump64 * * Copyright IBM Corp. 2004, 2017 * * s390-tools is free software; you can redistribute it and/or modify * it under the terms of the MIT license. See LICENSE for details. */ #ifndef LKCD_DUMP_H #define LKCD_DUMP_H #include "lib/zt_common.h" #include "dump.h" #include "register_content.h" #define UTS_LEN 65 #define DUMP_BUFFER_SIZE 0x2000 /* Size of dump buffer */ /* Standard header definitions */ #define DUMP_HEADER_SIZE 0x10000 #define DUMP_MAGIC_NUMBER 0xa8190173618f23edULL /* Dump magic number */ #define DUMP_ASM_MAGIC_NUMBER 0x733339302d64756dULL /* asm magic number */ #define DUMP_VERSION_NUMBER 0x8 /* Dump version number */ #define DUMP_PANIC_LEN 0x100 /* Dump panic string length */ /* Dump levels - type specific stuff added later */ #define DUMP_LEVEL_ALL 0x10 /* Dump all memory RAM and firmware */ /* Dump compression options */ #define DUMP_COMPRESS_GZIP 0x2 /* Use GZIP compression */ /* Dump header flags */ #define DUMP_DH_RAW 0x1 /* Raw page (no compression) */ #define DUMP_DH_COMPRESSED 0x2 /* Page is compressed */ #define DUMP_DH_END 0x4 /* End marker on a full dump */ /* Dump page defines */ #define DUMP_PAGE_SHIFT 12ULL #define DUMP_PAGE_SIZE (1ULL << DUMP_PAGE_SHIFT) #define GZIP_NOT_COMPRESSED -1 class LKCDDump : public Dump { public: LKCDDump(Dump*, const char *); virtual ~LKCDDump(void) {} inline virtual void readMem(char *UNUSED(buf), int UNUSED(size)) { throw(DumpException("LKCDDump::readMem() not implemented!")); } inline int seekMem(uint64_t UNUSED(offset)) { throw(DumpException("LKCDDump::seekMem() not implemented!")); } inline virtual uint64_t getMemSize() const { return dumpHeader.memory_size; } virtual struct timeval getDumpTime(void) const; virtual void writeDump(const char *fileName); virtual void copyRegsToPage(uint64_t offset, char *buf) = 0; protected: struct _lkcd_dump_header { uint64_t magic_number; /* dump magic number,unique to verify */ /* dump */ uint32_t version; /* version number of this dump */ uint32_t header_size; /* size of this header */ uint32_t dump_level; /* level of this dump */ uint32_t page_size; /* page size (e.g. 4K, 8K, 16K, etc.) */ uint64_t memory_size; /* size of entire physical memory */ uint64_t memory_start; /* start of physical memory */ uint64_t memory_end; /* end of physical memory */ /* the number of dump pages in this dump specifically */ uint32_t num_dump_pages; char panic_string[DUMP_PANIC_LEN]; /* timeval depends on machine, two long values */ struct {uint64_t tv_sec; uint64_t tv_usec; } time; /* the time of the system crash */ /* the NEW utsname (uname) information -- in character form */ /* we do this so we don't have to include utsname.h */ /* plus it helps us be more architecture independent */ char utsname_sysname[UTS_LEN]; char utsname_nodename[UTS_LEN]; char utsname_release[UTS_LEN]; char utsname_version[UTS_LEN]; char utsname_machine[UTS_LEN]; char utsname_domainname[UTS_LEN]; uint64_t current_task; uint32_t dump_compress; /* compression type used in this dump */ uint32_t dump_flags; /* any additional flags */ uint32_t dump_device; /* any additional flags */ uint64_t s390_asm_magic; uint16_t cpu_cnt; uint32_t lowcore_ptr[512]; } __packed; struct _lkcd_dump_header_asm { uint64_t magic_number; uint32_t version; uint32_t header_size; uint16_t cpu_cnt; uint16_t real_cpu_cnt; uint32_t lc_vec[512]; } __packed; struct _dump_page { uint64_t address; /* the address of this dump page */ uint32_t size; /* the size of this dump page */ uint32_t flags; /* flags (DUMP_COMPRESSED, DUMP_RAW */ /* or DUMP_END) */ } __packed; struct _lkcd_dump_header dumpHeader; struct _lkcd_dump_header_asm dumpHeaderAsm; private: int compressGZIP(const char *old, uint32_t old_size, char *n, uint32_t new_size); Dump *referenceDump; }; class LKCDDump32 : public LKCDDump { public: LKCDDump32(Dump *dump, const RegisterContent32 &rc); virtual void copyRegsToPage(uint64_t offset, char *buf); private: RegisterContent32 registerContent; }; class LKCDDump64 : public LKCDDump { public: LKCDDump64(Dump *dump, const RegisterContent64 &rc); virtual void copyRegsToPage(uint64_t offset, char *buf); private: RegisterContent64 registerContent; }; #endif /* LKCD_DUMP_H */ s390-tools-2.3.0/libvmdump/register_content.cpp000066400000000000000000000031711323410431100214230ustar00rootroot00000000000000/* * vmdump - z/VM dump conversion library * * Register content classes * * Copyright IBM Corp. 2004, 2017 * * 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 #include "register_content.h" #include "dump.h" RegisterContent32::RegisterContent32(void) : regSets(), nrCpus(0) { } RegisterContent32::RegisterContent32(const RegisterContent32& r) { nrCpus = r.nrCpus; memcpy(®Sets,&r.regSets,sizeof(regSets)); } void RegisterContent32::addRegisterSet(const RegisterSet32& rs) { if(nrCpus < MAX_CPUS){ regSets[nrCpus++] = rs; } else { throw(DumpException("RegisterContent32::addRegisterSet - " \ "No more register sets available")); } } RegisterSet32 RegisterContent32::getRegisterSet(int cpu){ if(cpu <= nrCpus){ return regSets[cpu]; } else { throw(DumpException("RegisterContent32::getRegisterSet - " \ "No register set for cpu")); } } RegisterContent64::RegisterContent64(void) : regSets(), nrCpus(0) { } RegisterContent64::RegisterContent64(const RegisterContent64& r) { nrCpus = r.nrCpus; memcpy(®Sets,&r.regSets,sizeof(regSets)); } void RegisterContent64::addRegisterSet(const RegisterSet64& rs) { if(nrCpus < MAX_CPUS){ regSets[nrCpus++] = rs; } else { throw(DumpException("RegisterContent64::addRegisterSet - " \ "No more register sets available")); } } RegisterSet64 RegisterContent64::getRegisterSet(int cpu) { if(cpu <= nrCpus){ return regSets[cpu]; } else { throw(DumpException("RegisterContent64::getRegisterSet - " \ "No register set for cpu")); } } s390-tools-2.3.0/libvmdump/register_content.h000066400000000000000000000025511323410431100210710ustar00rootroot00000000000000/* * vmdump - z/VM dump conversion library * * Register content classes * * Copyright IBM Corp. 2004, 2017 * * s390-tools is free software; you can redistribute it and/or modify * it under the terms of the MIT license. See LICENSE for details. */ #ifndef REGISTER_CONTENT_H #define REGISTER_CONTENT_H #include #define MAX_CPUS 512 class RegisterSet64 { public: uint64_t gprs[16]; uint64_t crs[16]; uint32_t acrs[16]; uint64_t fprs[16]; uint32_t fpCr; uint64_t psw[2]; uint32_t prefix; uint64_t cpuTimer; uint64_t clkCmp; }; class RegisterSet32 { public: uint32_t gprs[16]; uint32_t crs[16]; uint32_t acrs[16]; uint64_t fprs[4]; uint32_t psw[2]; uint32_t prefix; uint64_t cpuTimer; uint64_t clkCmp; }; class RegisterContent64{ public: RegisterContent64(void); RegisterContent64(const RegisterContent64&); RegisterSet64 getRegisterSet(int cpu); void addRegisterSet(const RegisterSet64&); inline int getNumCpus(void) const { return nrCpus; } RegisterSet64 regSets[MAX_CPUS]; private: int nrCpus; }; class RegisterContent32{ public: RegisterContent32(void); RegisterContent32(const RegisterContent32&); RegisterSet32 getRegisterSet(int cpu); void addRegisterSet(const RegisterSet32&); inline int getNumCpus(void) const { return nrCpus; } RegisterSet32 regSets[MAX_CPUS]; private: int nrCpus; }; #endif /* REGISTER_CONTENT_H */ s390-tools-2.3.0/libvmdump/vm_dump.cpp000066400000000000000000000412761323410431100175240ustar00rootroot00000000000000/* * vmdump - z/VM dump conversion library * * Register content classes: * VMDump, VMDumpClassic, VMDump64, VMDump64Big, VMDump32 * * Copyright IBM Corp. 2004, 2017 * * 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 #include #include #include "vm_dump.h" Dump::DumpType VMDump::getDumpType(const char* inputFileName) { uint8_t fmbk_id[8] = {0xc8, 0xc3, 0xd7, 0xc4, 0xc6, 0xd4, 0xc2, 0xd2}; struct _fir_basic fir; struct _fmbk fmbk; struct _adsr adsr; char msg[200]; FILE* fh; fh = fopen(inputFileName,"r"); if (!fh) { sprintf(msg,"Could not open '%s'",inputFileName); throw DumpErrnoException(msg); } /* Record 1: adsr */ dump_read(&adsr, sizeof(adsr), 1, fh); /* Record 2: fmbk */ dump_seek(fh, 0x1000, SEEK_SET); if (fread(&fmbk, sizeof(fmbk), 1, fh) != 1) { if(ferror(fh)) { sprintf(msg,"Could not read header of vmdump '%s'", inputFileName); fclose(fh); throw DumpErrnoException(msg); } else{ sprintf(msg,"Input file '%s' is not a vmdump", inputFileName); fclose(fh); throw DumpException(msg); } } /* Check if this is a vmdump */ if (memcmp(fmbk.id, fmbk_id, 8) != 0 || adsr.dump_type != 0xe5d4c4e4d4d74040ULL) { fclose(fh); sprintf(msg, "Input file '%s' is not a vmdump", inputFileName); throw DumpException(msg); } /* Record 3-7: fir */ dump_seek(fh, (fmbk.rec_nr_fir - 1) * 0x1000, SEEK_SET); if(fread(&fir, sizeof(fir), 1, fh) != 1) { if (ferror(fh)) { sprintf(msg,"Could not read header of vmdump '%s'", inputFileName); fclose(fh); throw DumpErrnoException(msg); } else{ sprintf(msg, "Could not read header of vmdump '%s'", inputFileName); fclose(fh); throw DumpException(msg); } } fclose(fh); if (fir.fir_format == 0) { return DT_VM32; } else if (fir.fir_format == 0x02) {/*XXX && (fir.dump_format == 0x1))*/ return DT_VM64_BIG; } else if (fir.fir_format == 0x82) { return DT_VM64; } else { return DT_UNKNOWN; } } VMDump::VMDump(const char *fileName) : Dump(fileName, "rb") { uint8_t fmbk_id[8] = {0xc8, 0xc3, 0xd7, 0xc4, 0xc6, 0xd4, 0xc2, 0xd2}; ebcdicAsciiConv = iconv_open("ISO-8859-1", "EBCDIC-US"); /* Record 1: adsrRecord */ dump_seek(fh,0,SEEK_SET); dump_read(&adsrRecord,sizeof(adsrRecord),1,fh); if(debug) { char buf[1024]; int i; fprintf(stderr, "off=%d\n", adsrRecord.sec5_offset); dump_seek(fh, adsrRecord.sec5_offset, SEEK_SET); dump_read(buf, adsrRecord.sec5_len, 1, fh); ebcAsc(buf, adsrRecord.sec5_len); for (i=0; i < adsrRecord.sec5_len; i++) { if ((buf[i]==0) || iscntrl(buf[i])) buf[i]=' '; } buf[adsrRecord.sec5_len] = 0; printf("symptom string1: %s\n",buf); } /* Record 2: fmbk */ dump_seek(fh,0x1000,SEEK_SET); dump_read(&fmbkRecord,sizeof(fmbkRecord),1,fh); /* Check if this is a vmdump */ if(memcmp(fmbkRecord.id, fmbk_id, 8) != 0) { throw DumpException("Input file is not a vmdump"); } /* Record 3-7: fir records read by subclasses */ /* Record 8: albk */ dump_seek(fh,(fmbkRecord.rec_nr_access-1)*0x1000 ,SEEK_SET); dump_read(&albkRecord,sizeof(albkRecord),1,fh); } struct timeval VMDump::getDumpTime(void) const { struct timeval rc; s390TodToTimeval(adsrRecord.tod,&rc); return rc; } void VMDump::printDebug(void) { struct timeval time; char buf[1024]; s390TodToTimeval(adsrRecord.tod, &time); /* adsr */ printf("time : %s\n", ctime(&time.tv_sec)); printf("stat1 : %x\n", adsrRecord.record_status_flag1); printf("stat2 : %x\n", adsrRecord.record_status_flag2); printf("sec 2 len: %i\n", adsrRecord.sec2_len); printf("sec 2.1 len: %i/%i\n", adsrRecord.sec2_1_len, adsrRecord.sec2_1_offset); printf("sec 3 len: %i/%i\n", adsrRecord.sec3_len, adsrRecord.sec3_offset); printf("sec 4 len: %i/%i\n", adsrRecord.sec4_len, adsrRecord.sec4_offset); printf("sec 5 len: %i/%i\n", adsrRecord.sec5_len, adsrRecord.sec5_offset); printf("sec 6 len: %i/%i\n", adsrRecord.sec6_len, adsrRecord.sec6_offset); /* fmbk */ ebcAsc(fmbkRecord.id, sizeof(fmbkRecord.id)); fmbkRecord.id[7] = 0; printf("id : %s\n", fmbkRecord.id); printf("fir rec nr: %i\n", fmbkRecord.rec_nr_fir); printf("vec rec nr: %i\n", fmbkRecord.rec_nr_vector); printf("access rec nr: %i\n", fmbkRecord.rec_nr_access); /* albk */ memcpy(buf, albkRecord.id, sizeof(albkRecord.id)); ebcAsc(buf, sizeof(albkRecord.id)); buf[8]=0; printf("ALBK id : %s\n",buf); /* asibk */ /* XXX memcpy(buf,asibkRecord.id,sizeof(asibkRecord.id)); ebcAsc(buf,sizeof(asibkRecord.id)); asibkRecord.id[8]=0; printf("ASIBK id : %s\n",buf); printf("storage : %x\n",asibkRecord.storage_size_2GB); printf("bitmapsrecs : %i\n",asibkRecord.nr_of_recs_of_first_bit_map); */ } void VMDump::printInfo(void) { struct timeval time; s390TodToTimeval(adsrRecord.tod,&time); fprintf(stderr, " date........: %s",ctime(&time.tv_sec)); } int VMDump::seekMem(uint64_t offset) { if (offset != 0) { return -1; } pageOffset = 0; return 0; } void VMDump::readMem(char* buf, int size) { int i; if (pageOffset == 0) dump_seek(fh, memoryStartRecord, SEEK_SET); if (size % 0x1000 != 0) { throw(DumpException("internal error: VMDump::readMem() " \ "can only handle sizes which are multiples of page size")); } for(i = 0; i < size; i += 0x1000) { if(testPage(pageOffset)) { dump_read(buf + i, 0x1000, 1, fh); } else { memset(buf + i, 0, 0x1000); } pageOffset += 1; } } VMDump::~VMDump(void) { } /*****************************************************************************/ /* VMDumpClassic: traditional 32/64 bit vmdump (before z/VM 5.2) */ /*****************************************************************************/ VMDumpClassic::VMDumpClassic(const char *fileName) : VMDump(fileName) { int storageKeyPages,bitMapPages; pageOffset = 0; /* Record 9: asibk */ dump_seek(fh,fmbkRecord.rec_nr_access * 0x1000,SEEK_SET); dump_read(&asibkRecord,sizeof(asibkRecord),1,fh); /* Record 10: bitmaps */ dump_seek(fh,(fmbkRecord.rec_nr_access + 1)* 0x1000 ,SEEK_SET); bitmap = new char[asibkRecord.storage_size_2GB / (0x1000 * 8)]; dump_read(bitmap,asibkRecord.storage_size_2GB / (0x1000*8),1,fh); bitMapPages=asibkRecord.storage_size_2GB / (0x1000 * 8); if (bitMapPages % 0x1000 != 0) bitMapPages = bitMapPages/0x1000 + 1; else bitMapPages = bitMapPages/0x1000; storageKeyPages=asibkRecord.storage_size_2GB / 0x1000; if(storageKeyPages % 0x1000 != 0) { storageKeyPages = storageKeyPages/0x1000 + 1; } else { storageKeyPages = storageKeyPages/0x1000; } /* Skip storage keys */ memoryStartRecord = (fmbkRecord.rec_nr_access + 1) *0x1000 /* 0x9000 */ + (bitMapPages + storageKeyPages)*0x1000; if(debug) { printf("Mem Offset: %llx\n", (long long) memoryStartRecord); } } void VMDumpClassic::printInfo(void) { VMDump::printInfo(); fprintf(stderr, " storage.....: %i MB\n", asibkRecord.storage_size_2GB/(1024*1024)); } VMDumpClassic::~VMDumpClassic(void) { delete bitmap; } /*****************************************************************************/ /* VMDump32: 32 bit vmdump */ /*****************************************************************************/ VMDump32::VMDump32(const char* filename) : VMDumpClassic(filename) { int i; if(!fh) { return; } dump_seek(fh,(fmbkRecord.rec_nr_fir-1)* 0x1000 ,SEEK_SET); dump_read(&fir32Record,sizeof(fir32Record),1,fh); fir32OtherRecords = new _fir_other_32[fir32Record.online_cpus]; for(i=0; i < fir32Record.online_cpus; i++) { /* fir other */ dump_read(&fir32OtherRecords[i],sizeof(fir32OtherRecords[i]),1, fh); } if(debug) printDebug(); } RegisterContent32 VMDump32::getRegisterContent(void) { RegisterContent32 rc; RegisterSet32 rs; int cpu; /* First CPU */ memcpy(&rs.gprs, &fir32Record.gprs, sizeof(rs.gprs)); memcpy(&rs.crs, &fir32Record.crs, sizeof(rs.crs)); memcpy(&rs.acrs, &fir32Record.acrs, sizeof(rs.acrs)); memcpy(&rs.psw, &fir32Record.psw, sizeof(rs.psw)); memcpy(&rs.prefix, &fir32Record.prefix, sizeof(rs.prefix)); memcpy(&rs.fprs, &fir32Record.fprs, sizeof(rs.fprs)); memcpy(&rs.cpuTimer, &fir32Record.cpu_timer, sizeof(rs.cpuTimer)); memcpy(&rs.clkCmp, &fir32Record.clock_cmp, sizeof(rs.clkCmp)); rc.addRegisterSet(rs); /* Other online cpus */ for(cpu = 0; cpu < fir32Record.online_cpus; cpu++) { memcpy(&rs.gprs, &fir32OtherRecords[cpu].gprs, sizeof(rs.gprs)); memcpy(&rs.crs, &fir32OtherRecords[cpu].crs, sizeof(rs.crs)); memcpy(&rs.acrs, &fir32OtherRecords[cpu].acrs, sizeof(rs.acrs)); /* No psw for ESA vmdumps */ rs.psw[0] = 0xdeadbeef; rs.psw[1] = 0xdeadbeef; memcpy(&rs.prefix, &fir32OtherRecords[cpu].prefix, sizeof(rs.prefix)); memcpy(&rs.fprs, &fir32OtherRecords[cpu].fprs, sizeof(rs.fprs)); memcpy(&rs.cpuTimer, &fir32OtherRecords[cpu].cpu_timer, sizeof(rs.cpuTimer)); memcpy(&rs.clkCmp, &fir32OtherRecords[cpu].clock_cmp, sizeof(rs.clkCmp)); rc.addRegisterSet(rs); } return rc; } void VMDump32::printDebug(void) { int i; VMDump::printDebug(); printf("prefix: %x\n", fir32Record.prefix); printf("cpus: %x\n", fir32Record.online_cpus); printf("psw: %08x %08x\n", fir32Record.psw[0], fir32Record.psw[1]); for (i=0; i < fir32Record.online_cpus; i++) { /* fir other */ printf("prefix (%i): %x\n", i, fir32OtherRecords[i].prefix); } } void VMDump32::printInfo(void) { fprintf(stderr, "vmdump information:\n"); fprintf(stderr, " architecture: 32 bit\n"); VMDumpClassic::printInfo(); fprintf(stderr, " cpus........: %x\n", fir32Record.online_cpus + 1); } VMDump32::~VMDump32(void) { delete fir32OtherRecords; } /*****************************************************************************/ /* VMDump64: 64 bit vmdump for old vmdump format (z/VM < 5.2) */ /*****************************************************************************/ VMDump64::VMDump64(const char* filename) : VMDumpClassic(filename) { int i; if(!fh) { return; } dump_seek(fh,(fmbkRecord.rec_nr_fir-1)* 0x1000 ,SEEK_SET); dump_read(&fir64Record,sizeof(fir64Record),1,fh); fir64OtherRecords = new _fir_other_64[fir64Record.online_cpus]; for (i=0; i < fir64Record.online_cpus; i++) { /* fir other */ dump_read(&fir64OtherRecords[i], sizeof(fir64OtherRecords[i]), 1, fh); } if(debug) printDebug(); } void VMDump64::printDebug(void) { int i; VMDump::printDebug(); printf("prefix: %x\n", fir64Record.prefix); printf("cpus: %x\n", fir64Record.online_cpus); printf("psw: %016llx %016llx\n", (long long)fir64Record.psw[0], (long long)fir64Record.psw[1]); for (i=0; i < fir64Record.online_cpus; i++) { /* fir other */ printf("prefix (%i): %x\n", i, fir64OtherRecords[i].prefix); } } void VMDump64::printInfo(void) { fprintf(stderr, "vmdump information:\n"); fprintf(stderr, " architecture: 64 bit\n"); VMDumpClassic::printInfo(); fprintf(stderr, " cpus........: %x\n",fir64Record.online_cpus + 1); } RegisterContent64 VMDump64::getRegisterContent(void) { RegisterContent64 rc; RegisterSet64 rs; int cpu; /* First CPU */ memcpy(&rs.gprs, &fir64Record.gprs, sizeof(rs.gprs)); memcpy(&rs.crs, &fir64Record.crs, sizeof(rs.crs)); memcpy(&rs.acrs, &fir64Record.acrs, sizeof(rs.acrs)); memcpy(&rs.psw, &fir64Record.psw, sizeof(rs.psw)); memcpy(&rs.prefix, &fir64Record.prefix, sizeof(rs.prefix)); memcpy(&rs.fprs, &fir64Record.fprs, sizeof(rs.fprs)); memcpy(&rs.cpuTimer, &fir64Record.cpu_timer, sizeof(rs.cpuTimer)); memcpy(&rs.clkCmp, &fir64Record.clock_cmp, sizeof(rs.clkCmp)); memcpy(&rs.fpCr, &fir64Record.fp_cntrl_reg, sizeof(rs.fpCr)); rc.addRegisterSet(rs); /* other online cpus */ for (cpu = 0; cpu < fir64Record.online_cpus; cpu++) { memcpy(&rs.gprs, &fir64OtherRecords[cpu].gprs, sizeof(rs.gprs)); memcpy(&rs.crs, &fir64OtherRecords[cpu].crs, sizeof(rs.crs)); memcpy(&rs.acrs, &fir64OtherRecords[cpu].acrs, sizeof(rs.acrs)); memcpy(&rs.psw, &fir64OtherRecords[cpu].psw, sizeof(rs.psw)); memcpy(&rs.prefix, &fir64OtherRecords[cpu].prefix, sizeof(rs.prefix)); memcpy(&rs.fprs, &fir64OtherRecords[cpu].fprs, sizeof(rs.fprs)); memcpy(&rs.cpuTimer, &fir64OtherRecords[cpu].cpu_timer, sizeof(rs.cpuTimer)); memcpy(&rs.clkCmp, &fir64OtherRecords[cpu].clock_cmp, sizeof(rs.clkCmp)); memcpy(&rs.fpCr, &fir64OtherRecords[cpu].fp_cntrl_reg, sizeof(rs.fpCr)); rc.addRegisterSet(rs); } return rc; } VMDump64::~VMDump64(void) { delete fir64OtherRecords; } /*****************************************************************************/ /* VMDump64Big: 64 bit vmdump with new big storage dump format */ /*****************************************************************************/ VMDump64Big::VMDump64Big(const char* filename) : VMDump(filename) { uint64_t pageNum, nrDumpedPages; int i, j; if(!fh) { return; } /* Record 9: asibk */ dump_seek(fh, fmbkRecord.rec_nr_access * 0x1000,SEEK_SET); dump_read(&asibkRecordNew, sizeof(asibkRecordNew), 1, fh); /* Record 10: bitmaps: */ /* Read all bitmap pages and setup bitmap array */ pageNum = 0; nrDumpedPages = asibkRecordNew.storage_size_def_store / 0x1000; memoryStartRecord = (fmbkRecord.rec_nr_access + 1) * 0x1000; bitmap = new char[asibkRecordNew.storage_size_def_store/(0x1000 * 8)]; if(!bitmap) { throw(DumpErrnoException("out of memory")); } memset(bitmap,0,asibkRecordNew.storage_size_def_store/(0x1000 * 8)); dump_seek(fh,(fmbkRecord.rec_nr_access + 1)* 0x1000 ,SEEK_SET); do { char bmIndexPage[0x1000]; dump_read(bmIndexPage, sizeof(bmIndexPage), 1, fh); memoryStartRecord += 0x1000; for (i=0; i < 0x1000; i++) { if(testBitmapPage(bmIndexPage, i)) { char bmPage[0x1000]; dump_read(bmPage,sizeof(bmPage),1,fh); memoryStartRecord += 0x1000; for(j = 0; j < 0x1000; j++) { if(testBitmapKeyPage(bmPage, j)) { setPageBit(pageNum); } pageNum++; if(pageNum == nrDumpedPages) { goto all_bitmaps_read; } } } else { pageNum += 0x1000; /* Empty pages */ } } } while (pageNum < nrDumpedPages); all_bitmaps_read: if(debug) printf("Mem Offset: %llx\n", (long long)memoryStartRecord); dump_seek(fh, (fmbkRecord.rec_nr_fir-1)* 0x1000, SEEK_SET); dump_read(&fir64Record, sizeof(fir64Record), 1, fh); fir64OtherRecords = new _fir_other_64[fir64Record.online_cpus]; for (i=0; i < fir64Record.online_cpus; i++) { /* fir other */ dump_read(&fir64OtherRecords[i], sizeof(fir64OtherRecords[i]), 1, fh); } if(debug) printDebug(); } void VMDump64Big::printDebug(void) { int i; VMDump::printDebug(); printf("prefix: %x\n", fir64Record.prefix); printf("cpus: %x\n", fir64Record.online_cpus); printf("psw: %016llx %016llx\n", (long long)fir64Record.psw[0], (long long)fir64Record.psw[1]); for (i=0; i < fir64Record.online_cpus; i++) { /* fir other */ printf("prefix (%i): %x\n", i, fir64OtherRecords[i].prefix); } } void VMDump64Big::printInfo(void) { fprintf(stderr, "vmdump information:\n"); fprintf(stderr, " architecture: 64 bit (big)\n"); fprintf(stderr, " storage.....: %lli MB\n", (long long)asibkRecordNew.storage_size_def_store / (1024*1024)); VMDump::printInfo(); fprintf(stderr, " cpus........: %x\n", fir64Record.online_cpus + 1); } RegisterContent64 VMDump64Big::getRegisterContent(void) { RegisterContent64 rc; RegisterSet64 rs; int cpu; /* First CPU */ memcpy(&rs.gprs, &fir64Record.gprs, sizeof(rs.gprs)); memcpy(&rs.crs, &fir64Record.crs, sizeof(rs.crs)); memcpy(&rs.acrs, &fir64Record.acrs, sizeof(rs.acrs)); memcpy(&rs.psw, &fir64Record.psw, sizeof(rs.psw)); memcpy(&rs.prefix, &fir64Record.prefix, sizeof(rs.prefix)); memcpy(&rs.fprs, &fir64Record.fprs, sizeof(rs.fprs)); memcpy(&rs.cpuTimer, &fir64Record.cpu_timer, sizeof(rs.cpuTimer)); memcpy(&rs.clkCmp, &fir64Record.clock_cmp, sizeof(rs.clkCmp)); memcpy(&rs.fpCr, &fir64Record.fp_cntrl_reg, sizeof(rs.fpCr)); rc.addRegisterSet(rs); /* other online cpus */ for(cpu = 0; cpu < fir64Record.online_cpus; cpu++) { memcpy(&rs.gprs, &fir64OtherRecords[cpu].gprs, sizeof(rs.gprs)); memcpy(&rs.crs, &fir64OtherRecords[cpu].crs, sizeof(rs.crs)); memcpy(&rs.acrs, &fir64OtherRecords[cpu].acrs, sizeof(rs.acrs)); memcpy(&rs.psw, &fir64OtherRecords[cpu].psw, sizeof(rs.psw)); memcpy(&rs.prefix, &fir64OtherRecords[cpu].prefix, sizeof(rs.prefix)); memcpy(&rs.fprs, &fir64OtherRecords[cpu].fprs, sizeof(rs.fprs)); memcpy(&rs.cpuTimer, &fir64OtherRecords[cpu].cpu_timer, sizeof(rs.cpuTimer)); memcpy(&rs.clkCmp, &fir64OtherRecords[cpu].clock_cmp, sizeof(rs.clkCmp)); memcpy(&rs.fpCr, &fir64OtherRecords[cpu].fp_cntrl_reg, sizeof(rs.fpCr)); rc.addRegisterSet(rs); } return rc; } VMDump64Big::~VMDump64Big(void) { delete bitmap; delete fir64OtherRecords; } s390-tools-2.3.0/libvmdump/vm_dump.h000066400000000000000000000255051323410431100171660ustar00rootroot00000000000000/* * vmdump - z/VM dump conversion library * * Register content classes: * VMDump, VMDumpClassic, VMDump64, VMDump64Big, VMDump32 * * Copyright IBM Corp. 2004, 2017 * * s390-tools is free software; you can redistribute it and/or modify * it under the terms of the MIT license. See LICENSE for details. */ /*===========================* * The format of an vmdump: * *===========================* *---------------------------* * Symptom Record * * (ADSR COPY) Record 1 * *---------------------------* * Dump File Map Record * * (HCPDFMBK COPY) Record 2 * *---------------------------* * Dump File Info Record * * (HCPDFIR COPY) Records 3-7* *---------------------------* * Vector Registers * * (optional) * *---------------------------* * Access Lists (HCPDALBK) * *---------------------------* * Address Space A * * Information and Map * * Record (HCPASIBK) * *---------------------------* * Address Space A * * Bit Maps * *---------------------------* * Address Space A * * Key Maps * *---------------------------* * Address Space A * * Guest Storage * *---------------------------* * Additional Address Spaces * * ASIBK * * Bit Maps * * Key Maps * * Guest Storage * * *NOTE* These probably * * aren't in a Linux guest. * *---------------------------* */ #ifndef VMDUMP_H #define VMDUMP_H #include #include #include "lib/zt_common.h" #include "dump.h" #include "register_content.h" class VMDump : public Dump { public: VMDump(const char *filename); virtual ~VMDump(void); virtual void readMem(char *buf, int size); virtual int seekMem(uint64_t offset); virtual struct timeval getDumpTime(void) const; void printDebug(void); void printInfo(void); static DumpType getDumpType(const char *); inline int testPage(uint64_t bit) const { return (bitmap[bit/8] & (1 << (7-(bit % 8)))); } inline void setPageBit(uint64_t bit) const { bitmap[bit/8] |= (1 << (7-(bit % 8))); } protected: /* Types */ struct _adsr { /* Section 1*/ uint16_t sr; uint32_t cpu_model; char cpu_serial[6]; uint32_t time_zone_conversion_factor; uint64_t tod; char time_stamp_str[4]; char date_str[6]; char node_name[8]; char product_id[4]; char feature_level[8]; uint8_t record_status_flag1; uint8_t record_status_flag2; uint64_t dump_type; /* Section 2*/ char arch_level[2]; uint16_t sec2_len; uint16_t sec2_1_len; uint16_t sec2_1_offset; uint16_t sec3_len; uint16_t sec3_offset; uint16_t sec4_len; uint16_t sec4_offset; uint16_t sec5_len; uint16_t sec5_offset; uint16_t sec6_len; uint16_t sec6_offset; } __packed; struct _fmbk { char id[8]; uint32_t rec_nr_fir; uint32_t rec_nr_vector; uint32_t rec_nr_access; uint32_t num_acc_recs; uint32_t num_addr_spaces; uint32_t rec_nr_asibk; } __packed; struct _fir_basic { char filler1[15]; uint8_t dump_format;/* 0x1 for big storage dump, 0x2 for cp */ /* hard abend, 0x3 for cp soft abend */ char filler2[171]; uint8_t fir_format; /* 0x2 for big esame, 0x82 for esame, */ /* 0x00 for esa */ } __packed; struct _albk { char id[8]; } __packed; /* Methods */ inline void ebcAsc(char *inout, size_t len) const { iconv(ebcdicAsciiConv, &inout, &len, &inout, &len); } /* Members */ struct _adsr adsrRecord; struct _fmbk fmbkRecord; struct _albk albkRecord; uint64_t memoryStartRecord; char *bitmap; uint64_t pageOffset; private: iconv_t ebcdicAsciiConv; }; class VMDumpClassic : public VMDump { public: VMDumpClassic(const char *filename); virtual ~VMDumpClassic(void); void printInfo(void); inline virtual uint64_t getMemSize(void) const { return (uint64_t)asibkRecord.storage_size_2GB; } protected: /* Types */ struct _asibk { char id[8]; char as_token[8]; char spaceid[33]; char reserved1[3]; uint32_t storage_size_2GB; uint32_t dcss_bitmap_first_rec; uint32_t byte_past_highest_defined_byte; uint64_t format_of_as; char dump_id[100]; uint32_t nr_of_recs_of_first_bit_map; } __packed; /* Members */ struct _asibk asibkRecord; }; #define MAX_BKEY_PAGES 2 class VMDump64Big : public VMDump { public: VMDump64Big(const char *filename); virtual ~VMDump64Big(void); RegisterContent64 getRegisterContent(void); void printDebug(void); void printInfo(void); inline virtual uint64_t getMemSize(void) const { return (uint64_t)asibkRecordNew.storage_size_def_store; } private: inline int testBitmapPage(char *page, uint64_t bit) const { return (page[bit/8] & (1 << (7-(bit % 8)))); } inline int testBitmapKeyPage(char *page, uint64_t bit) const { return (page[bit] & 0x01); } /* types */ struct _asibk_64_new { char id[8]; char as_token[8]; char spaceid[33]; char reserved1[2]; uint8_t asibk_format; char filler1[12]; uint64_t storage_size_with_dcss; uint64_t storage_size_def_store; char filler2[136]; uint64_t online_storage_table[8]; /* for "def store config" */ uint64_t fence1; uint64_t requested_range_table[8]; uint64_t fence2; uint32_t record_number_of_first_bit_map; /* XXX */ } __packed; struct _fir_64 { char id[8]; uint64_t reserved1; uint64_t gprs[16]; uint32_t prefix; char reserved2[5]; uint64_t tod; char reserved3[8]; uint64_t cpu_timer; char reserved4[7]; uint8_t flag; uint8_t type; uint8_t complete; uint8_t fir_format; /* 0x82 for esame - 0x00 for esa */ uint8_t cont_flags; uint8_t crypto_domain_index_reg; uint8_t virt_cpu_info; uint8_t arch_mode_id; uint64_t psw[2]; uint64_t crs[16]; uint64_t fprs[16]; uint8_t reserved5; uint64_t clock_cmp; char reserved6[3]; uint32_t tod_programmable_reg; uint32_t reserved_for_dvf[20]; uint32_t acrs[16]; uint32_t storage_size_2GB; uint32_t reserved7; uint32_t hcpsys_addr; uint32_t reserved8; uint64_t storage_size; uint32_t snap_area_map_blk; uint32_t reserved9; char loc_mem[256]; uint16_t online_cpus; uint16_t cpu_addr; uint16_t section_size_vector; uint16_t reserved10; char asit_primary[8]; char space_id_primary[33]; char reserved12[3]; uint16_t crypto_domain_index_mask; uint16_t reserved11; uint32_t fp_cntrl_reg; uint32_t reserved13; uint64_t reserved14[16]; } __packed; struct _fir_other_64 { uint16_t cpu_addr; uint16_t vector_sec_size; uint8_t crypto_index_reg; uint8_t virt_cpu_info; uint16_t crypto_index_mask; uint32_t reserved1[2]; uint64_t fprs[16]; uint64_t gprs[16]; uint64_t psw[2]; uint32_t reserved2[2]; uint32_t prefix; uint32_t fp_cntrl_reg; uint32_t reserved3; uint32_t tod; uint64_t cpu_timer; uint64_t clock_cmp; uint32_t reserved4[2]; uint32_t acrs[16]; uint64_t crs[16]; uint64_t mc_interrupt_code; uint32_t reserved5; uint32_t external_damage_code; uint64_t mc_failing_storage_addr; } __packed; /* Members */ struct _asibk_64_new asibkRecordNew; struct _fir_64 fir64Record; struct _fir_other_64 *fir64OtherRecords; }; class VMDump64 : public VMDumpClassic { public: VMDump64(const char *filename); virtual ~VMDump64(void); RegisterContent64 getRegisterContent(void); void printDebug(void); void printInfo(void); private: /* Types */ struct _fir_64 { char id[8]; uint64_t reserved1; uint64_t gprs[16]; uint32_t prefix; char reserved2[5]; uint64_t tod; char reserved3[8]; uint64_t cpu_timer; char reserved4[7]; uint8_t flag; uint8_t type; uint8_t complete; uint8_t fir_format; /* 0x82 for esame - 0x00 for esa */ uint8_t cont_flags; uint8_t crypto_domain_index_reg; uint8_t virt_cpu_info; uint8_t arch_mode_id; uint64_t psw[2]; uint64_t crs[16]; uint64_t fprs[16]; uint8_t reserved5; uint64_t clock_cmp; char reserved6[3]; uint32_t tod_programmable_reg; uint32_t reserved_for_dvf[20]; uint32_t acrs[16]; uint32_t storage_size_2GB; uint32_t reserved7; uint32_t hcpsys_addr; uint32_t reserved8; uint64_t storage_size; uint32_t snap_area_map_blk; uint32_t reserved9; char loc_mem[256]; uint16_t online_cpus; uint16_t cpu_addr; uint16_t section_size_vector; uint16_t reserved10; char asit_primary[8]; char space_id_primary[33]; char reserved12[3]; uint16_t crypto_domain_index_mask; uint16_t reserved11; uint32_t fp_cntrl_reg; uint32_t reserved13; uint64_t reserved14[16]; } __packed; struct _fir_other_64 { uint16_t cpu_addr; uint16_t vector_sec_size; uint8_t crypto_index_reg; uint8_t virt_cpu_info; uint16_t crypto_index_mask; uint32_t reserved1[2]; uint64_t fprs[16]; uint64_t gprs[16]; uint64_t psw[2]; uint32_t reserved2[2]; uint32_t prefix; uint32_t fp_cntrl_reg; uint32_t reserved3; uint32_t tod; uint64_t cpu_timer; uint64_t clock_cmp; uint32_t reserved4[2]; uint32_t acrs[16]; uint64_t crs[16]; uint64_t mc_interrupt_code; uint32_t reserved5; uint32_t external_damage_code; uint64_t mc_failing_storage_addr; } __packed; /* Members */ struct _fir_64 fir64Record; struct _fir_other_64 *fir64OtherRecords; }; class VMDump32 : public VMDumpClassic { public: VMDump32(const char *filename); virtual ~VMDump32(void); RegisterContent32 getRegisterContent(void); void printDebug(void); void printInfo(void); private: /* Types */ struct _fir_32 { uint32_t gprs[16]; uint32_t crs[16]; uint64_t fprs[4]; uint64_t tod; uint64_t cpu_timer; uint64_t clock_cmp; uint8_t flag; uint8_t type; uint8_t complete; uint8_t fir_format; uint32_t storage_size_2GB; char loc_mem[256]; uint32_t prefix; uint16_t online_cpus; uint8_t cont_flags; uint8_t crypto_domain_index_reg; uint8_t virt_cpu_info; uint8_t arch_mode_id; uint16_t crypto_domain_index_mask; uint32_t reserved1; uint32_t snap_area_map_blk; uint64_t reserved2; uint32_t hcpsys_addr; uint32_t reserved3[20]; uint32_t psw[2]; uint16_t cpu_addr; uint16_t section_size_vector; uint32_t acrs[16]; char asit_primary[8]; char space_id_primary[33]; char reserved4[131]; } __packed; struct _fir_other_32 { uint16_t cpu_addr; uint16_t vector_sec_size; uint32_t prefix; uint8_t crypto_index_reg; uint8_t virt_cpu_info; uint16_t crypto_index_mask; uint32_t reserved1; uint64_t cpu_timer; uint64_t clock_cmp; uint64_t mc_interrupt_code; uint64_t reserved2; uint32_t mc_failing_storage_addr; uint32_t machine_dependent_region_code; uint32_t lixed_logout_area[4]; char reserved3[16]; uint32_t acrs[16]; uint64_t fprs[4]; uint32_t gprs[16]; uint32_t crs[16]; } __packed; /* Members */ struct _fir_32 fir32Record; struct _fir_other_32 *fir32OtherRecords; }; #endif /* VMDUMP_H */ s390-tools-2.3.0/libvmdump/vmdump_convert.cpp000066400000000000000000000031671323410431100211220ustar00rootroot00000000000000/* * vmdump - z/VM dump conversion library * * Dump convert function: Converts VMDUMP to LKCD dump * * Copyright IBM Corp. 2004, 2017 * * 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 "lkcd_dump.h" #include "vm_dump.h" int vmdump_convert(const char* inputFileName, const char* outputFileName, const char* progName) { /* Do the conversion */ try { switch(VMDump::getDumpType(inputFileName)){ case Dump::DT_VM64_BIG: { LKCDDump64* lkcddump; VMDump64Big* vmdump; vmdump = new VMDump64Big(inputFileName); vmdump->printInfo(); lkcddump = new LKCDDump64(vmdump, vmdump->getRegisterContent()); lkcddump->writeDump(outputFileName); delete vmdump; delete lkcddump; break; } case Dump::DT_VM64: { LKCDDump64* lkcddump; VMDump64* vmdump; vmdump = new VMDump64(inputFileName); vmdump->printInfo(); lkcddump = new LKCDDump64(vmdump, vmdump->getRegisterContent()); lkcddump->writeDump(outputFileName); delete vmdump; delete lkcddump; break; } case Dump::DT_VM32: { LKCDDump32* lkcddump; VMDump32* vmdump; vmdump = new VMDump32(inputFileName); vmdump->printInfo(); lkcddump = new LKCDDump32(vmdump, vmdump->getRegisterContent()); lkcddump->writeDump(outputFileName); delete vmdump; delete lkcddump; break; } default: throw DumpException("This is not a vmdump"); } } catch (DumpException ex) { printf("%s: %s\n", progName, ex.what()); fflush(stdout); return 1; } return 0; } s390-tools-2.3.0/libvtoc/000077500000000000000000000000001323410431100150025ustar00rootroot00000000000000s390-tools-2.3.0/libvtoc/Makefile000066400000000000000000000002021323410431100164340ustar00rootroot00000000000000include ../common.mak lib = libvtoc.a all: $(lib) objects = vtoc.o $(lib): $(objects) install: all clean: rm -f *.o $(lib) s390-tools-2.3.0/libvtoc/vtoc.c000066400000000000000000000761651323410431100161400ustar00rootroot00000000000000/* * Copyright IBM Corp. 2001, 2017 * * 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 "lib/vtoc.h" static unsigned char EBCtoASC[256] = { /* 0x00 NUL SOH STX ETX *SEL HT *RNL DEL */ 0x00, 0x01, 0x02, 0x03, 0x07, 0x09, 0x07, 0x7F, /* 0x08 -GE -SPS -RPT VT FF CR SO SI */ 0x07, 0x07, 0x07, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, /* 0x10 DLE DC1 DC2 DC3 -RES -NL BS -POC -ENP ->LF */ 0x10, 0x11, 0x12, 0x13, 0x07, 0x0A, 0x08, 0x07, /* 0x18 CAN EM -UBS -CU1 -IFS -IGS -IRS -ITB -IUS */ 0x18, 0x19, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, /* 0x20 -DS -SOS FS -WUS -BYP LF ETB ESC -INP */ 0x07, 0x07, 0x1C, 0x07, 0x07, 0x0A, 0x17, 0x1B, /* 0x28 -SA -SFE -SM -CSP -MFA ENQ ACK BEL -SW */ 0x07, 0x07, 0x07, 0x07, 0x07, 0x05, 0x06, 0x07, /* 0x30 ---- ---- SYN -IR -PP -TRN -NBS EOT */ 0x07, 0x07, 0x16, 0x07, 0x07, 0x07, 0x07, 0x04, /* 0x38 -SBS -IT -RFF -CU3 DC4 NAK ---- SUB */ 0x07, 0x07, 0x07, 0x07, 0x14, 0x15, 0x07, 0x1A, /* 0x40 SP RSP ä ---- */ 0x20, 0xFF, 0x83, 0x84, 0x85, 0xA0, 0x07, 0x86, /* 0x48 . < ( + | */ 0x87, 0xA4, 0x9B, 0x2E, 0x3C, 0x28, 0x2B, 0x7C, /* 0x50 & ---- */ 0x26, 0x82, 0x88, 0x89, 0x8A, 0xA1, 0x8C, 0x07, /* 0x58 ß ! $ * ) ; */ 0x8D, 0xE1, 0x21, 0x24, 0x2A, 0x29, 0x3B, 0xAA, /* 0x60 - / ---- Ä ---- ---- ---- */ 0x2D, 0x2F, 0x07, 0x8E, 0x07, 0x07, 0x07, 0x8F, /* 0x68 ---- , % _ > ? */ 0x80, 0xA5, 0x07, 0x2C, 0x25, 0x5F, 0x3E, 0x3F, /* 0x70 --- ---- ---- ---- ---- ---- ---- */ 0x07, 0x90, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, /* 0x78 * ` : # @ ' = " */ 0x70, 0x60, 0x3A, 0x23, 0x40, 0x27, 0x3D, 0x22, /* 0x80 * a b c d e f g */ 0x07, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, /* 0x88 h i ---- ---- ---- */ 0x68, 0x69, 0xAE, 0xAF, 0x07, 0x07, 0x07, 0xF1, /* 0x90 ° j k l m n o p */ 0xF8, 0x6A, 0x6B, 0x6C, 0x6D, 0x6E, 0x6F, 0x70, /* 0x98 q r ---- ---- */ 0x71, 0x72, 0xA6, 0xA7, 0x91, 0x07, 0x92, 0x07, /* 0xA0 ~ s t u v w x */ 0xE6, 0x7E, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, /* 0xA8 y z ---- ---- ---- ---- */ 0x79, 0x7A, 0xAD, 0xAB, 0x07, 0x07, 0x07, 0x07, /* 0xB0 ^ ---- § ---- */ 0x5E, 0x9C, 0x9D, 0xFA, 0x07, 0x07, 0x07, 0xAC, /* 0xB8 ---- [ ] ---- ---- ---- ---- */ 0xAB, 0x07, 0x5B, 0x5D, 0x07, 0x07, 0x07, 0x07, /* 0xC0 { A B C D E F G */ 0x7B, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, /* 0xC8 H I ---- ö ---- */ 0x48, 0x49, 0x07, 0x93, 0x94, 0x95, 0xA2, 0x07, /* 0xD0 } J K L M N O P */ 0x7D, 0x4A, 0x4B, 0x4C, 0x4D, 0x4E, 0x4F, 0x50, /* 0xD8 Q R ---- ü */ 0x51, 0x52, 0x07, 0x96, 0x81, 0x97, 0xA3, 0x98, /* 0xE0 \ S T U V W X */ 0x5C, 0xF6, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, /* 0xE8 Y Z ---- Ö ---- ---- ---- */ 0x59, 0x5A, 0xFD, 0x07, 0x99, 0x07, 0x07, 0x07, /* 0xF0 0 1 2 3 4 5 6 7 */ 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, /* 0xF8 8 9 ---- ---- Ü ---- ---- ---- */ 0x38, 0x39, 0x07, 0x07, 0x9A, 0x07, 0x07, 0x07 }; static unsigned char ASCtoEBC[256] = { /*00 NL SH SX EX ET NQ AK BL */ 0x00, 0x01, 0x02, 0x03, 0x37, 0x2D, 0x2E, 0x2F, /*08 BS HT LF VT FF CR SO SI */ 0x16, 0x05, 0x15, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, /*10 DL D1 D2 D3 D4 NK SN EB */ 0x10, 0x11, 0x12, 0x13, 0x3C, 0x15, 0x32, 0x26, /*18 CN EM SB EC FS GS RS US */ 0x18, 0x19, 0x3F, 0x27, 0x1C, 0x1D, 0x1E, 0x1F, /*20 SP ! " # $ % & ' */ 0x40, 0x5A, 0x7F, 0x7B, 0x5B, 0x6C, 0x50, 0x7D, /*28 ( ) * + , - . / */ 0x4D, 0x5D, 0x5C, 0x4E, 0x6B, 0x60, 0x4B, 0x61, /*30 0 1 2 3 4 5 6 7 */ 0xF0, 0xF1, 0xF2, 0xF3, 0xF4, 0xF5, 0xF6, 0xF7, /*38 8 9 : ; < = > ? */ 0xF8, 0xF9, 0x7A, 0x5E, 0x4C, 0x7E, 0x6E, 0x6F, /*40 @ A B C D E F G */ 0x7C, 0xC1, 0xC2, 0xC3, 0xC4, 0xC5, 0xC6, 0xC7, /*48 H I J K L M N O */ 0xC8, 0xC9, 0xD1, 0xD2, 0xD3, 0xD4, 0xD5, 0xD6, /*50 P Q R S T U V W */ 0xD7, 0xD8, 0xD9, 0xE2, 0xE3, 0xE4, 0xE5, 0xE6, /*58 X Y Z [ \ ] ^ _ */ 0xE7, 0xE8, 0xE9, 0xAD, 0xE0, 0xBD, 0x5F, 0x6D, /*60 ` a b c d e f g */ 0x79, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, /*68 h i j k l m n o */ 0x88, 0x89, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, /*70 p q r s t u v w */ 0x97, 0x98, 0x99, 0xA2, 0xA3, 0xA4, 0xA5, 0xA6, /*78 x y z { | } ~ DL */ 0xA7, 0xA8, 0xA9, 0xC0, 0x4F, 0xD0, 0xA1, 0x07, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0xFF }; enum failure {unable_to_open, unable_to_seek, unable_to_write, unable_to_read}; static char buffer[85]; /* * */ static void vtoc_error(enum failure why, char *s1, char *s2) { switch (why) { case unable_to_open: fprintf(stderr, "\n%s opening device '%s' failed.\n%s\n", VTOC_ERROR, s1, s2); break; case unable_to_seek: fprintf(stderr, "\n%s seeking device '%s' failed.\n%s\n", VTOC_ERROR, s1, s2); break; case unable_to_write: fprintf(stderr, "\n%s writing to device '%s' failed,\n%s\n", VTOC_ERROR, s1, s2); break; case unable_to_read: fprintf(stderr, "\n%s reading from device '%s' failed.\n%s\n", VTOC_ERROR, s1, s2); break; default: fprintf(stderr, "\nFatal error\n"); } exit(1); } /* * */ char * vtoc_ebcdic_enc (char *source, char *target, int l) { int i; for (i = 0; i < l; i++) target[i]=ASCtoEBC[(unsigned char)(source[i])]; return target; } /* * */ char * vtoc_ebcdic_dec (char *source, char *target, int l) { int i; for (i = 0; i < l; i++) target[i]=EBCtoASC[(unsigned char)(source[i])]; return target; } /* * */ void vtoc_set_extent (extent_t *ext, u_int8_t typeind, u_int8_t seqno, cchh_t *lower, cchh_t *upper) { ext->typeind = typeind; ext->seqno = seqno; memcpy(&ext->llimit,lower,sizeof(cchh_t)); memcpy(&ext->ulimit,upper,sizeof(cchh_t)); } /* * */ void vtoc_set_cchh (cchh_t *addr, u_int32_t cc, u_int16_t hh) { addr->cc = (u_int16_t) cc; addr->hh = cc >> 16; addr->hh <<= 4; addr->hh |= hh; } u_int32_t vtoc_get_cyl_from_cchh(cchh_t *addr) { u_int32_t cyl; /*decode cylinder for large volumes */ cyl = addr->hh & 0xFFF0; cyl <<= 12; cyl |= addr->cc; return cyl; } u_int16_t vtoc_get_head_from_cchh(cchh_t *addr) { /* decode heads for large volumes */ return addr->hh & 0x000F; } /* * */ static void vtoc_set_ttr (ttr_t *addr, u_int16_t tt, u_int8_t r) { addr->tt = tt; addr->r = r; } /* * */ void vtoc_set_cchhb (cchhb_t *addr, u_int32_t cc, u_int16_t hh, u_int8_t b) { addr->cc = (u_int16_t) cc; addr->hh = cc >> 16; addr->hh <<= 4; addr->hh |= hh; addr->b = b; } u_int32_t vtoc_get_cyl_from_cchhb(cchhb_t *addr) { u_int32_t cyl; /* decode cylinder for large volumes */ cyl = addr->hh & 0xFFF0; cyl <<= 12; cyl |= addr->cc; return cyl; } u_int16_t vtoc_get_head_from_cchhb(cchhb_t *addr) { /* decode heads for large volumes */ return addr->hh & 0x000F; } /* * some functions to convert cyl-cyl-head-head addresses to * block or track numbers * Note: Record zero is special, so first block on a track is * in record 1! */ u_int64_t cchhb2blk(cchhb_t *p, struct hd_geometry *geo) { return (u_int64_t) vtoc_get_cyl_from_cchhb(p) * geo->heads * geo->sectors + vtoc_get_head_from_cchhb(p) * geo->sectors + p->b; } u_int64_t cchh2blk (cchh_t *p, struct hd_geometry *geo) { return (u_int64_t) vtoc_get_cyl_from_cchh(p) * geo->heads * geo->sectors + vtoc_get_head_from_cchh(p) * geo->sectors; } u_int32_t cchh2trk (cchh_t *p, struct hd_geometry *geo) { return vtoc_get_cyl_from_cchh(p) * geo->heads + vtoc_get_head_from_cchh(p); } /* * */ void vtoc_set_date (labeldate_t * d, u_int8_t year, u_int16_t day) { d->year = year; d->day = day; } /* * initializes the volume label with EBCDIC blanks */ void vtoc_volume_label_init (volume_label_t *vlabel) { sprintf(buffer, "%84s", " "); vtoc_ebcdic_enc(buffer, buffer, 84); memcpy(vlabel, buffer, 84); } /* * reads the volume label from dasd */ int vtoc_read_volume_label (char *device, unsigned long vlabel_start, volume_label_t *vlabel) { int f, rc; if ((f = open(device, O_RDONLY)) < 0) { vtoc_error(unable_to_open, device, "Could not read volume label."); } if (lseek(f, vlabel_start, SEEK_SET) < 0) { close(f); vtoc_error(unable_to_seek, device, "Could not read volume label."); } rc = read(f, vlabel, sizeof(volume_label_t)); if (rc != sizeof(volume_label_t)) { close(f); vtoc_error(unable_to_read, device, "Could not read volume label."); } close(f); return 0; } /* * writes the volume label to dasd */ int vtoc_write_volume_label (char *device, unsigned long vlabel_start, volume_label_t *vlabel) { int rc, f; if ((f = open(device, O_WRONLY)) < 0) { vtoc_error(unable_to_open, device, "Could not write volume label."); } if (lseek(f, vlabel_start, SEEK_SET) < 0) { close(f); vtoc_error(unable_to_seek, device, "Could not write volume label."); } rc = write(f, vlabel, sizeof(volume_label_t)); if (rc != sizeof(volume_label_t)) { close(f); vtoc_error(unable_to_write, device, "Could not write volume label."); } close(f); return 0; } /* * takes a string as input, converts it to uppercase, translates * it to EBCDIC and fills it up with spaces before it copies it * as volume serial to the volume label */ void vtoc_volume_label_set_volser (volume_label_t *vlabel, char *volser) { int j, i = strlen(volser); char s[VOLSER_LENGTH + 1]; strcpy(s, " "); vtoc_ebcdic_enc(s, s, VOLSER_LENGTH); strncpy(vlabel->volid, s, VOLSER_LENGTH); if (i > VOLSER_LENGTH) i = VOLSER_LENGTH; strncpy(s, volser, i); for (j=0; jvolid, s, i); return; } /* * returns the volume serial number right after it is translated * to ASCII */ char * vtoc_volume_label_get_volser (volume_label_t *vlabel, char *volser) { vtoc_ebcdic_dec(vlabel->volid, volser, VOLSER_LENGTH); volser[VOLSER_LENGTH] = '\0'; return volser; } /* * sets the volume label key right after * it has been translated to EBCDIC */ void vtoc_volume_label_set_key (volume_label_t *vlabel, char *key) { char s[4]; vtoc_ebcdic_enc(key, s, 4); strncpy(vlabel->volkey, s, 4); return; } /* * sets the volume label identifier right * after it has been translated to EBCDIC */ void vtoc_volume_label_set_label (volume_label_t *vlabel, char *lbl) { char s[4]; vtoc_ebcdic_enc(lbl, s, 4); strncpy(vlabel->vollbl, s, 4); return; } /* * returns the volume label key = the label identifier * right after it has been translated to ASCII */ char * vtoc_volume_label_get_label (volume_label_t *vlabel, char *lbl) { vtoc_ebcdic_dec(vlabel->vollbl, lbl, 4); lbl[4] = '\0'; return lbl; } /* * reads either a format4 label or a format1 label * from the specified position */ void vtoc_read_label (char *device, unsigned long position, format1_label_t *f1, format4_label_t *f4, format5_label_t *f5, format7_label_t *f7) { int f,t; if ((f = open(device, O_RDONLY)) < 0) vtoc_error(unable_to_open, device, "Could not read VTOC labels."); if (lseek(f, position, SEEK_SET) < 0) { close(f); vtoc_error(unable_to_seek, device, "Could not read VTOC labels."); } if (f1 != NULL) { t = sizeof(format1_label_t); if (read(f, f1, t) != t) { close(f); vtoc_error(unable_to_read, device, "Could not read VTOC FMT1 DSCB."); } } if (f4 != NULL) { t = sizeof(format4_label_t); if (read(f, f4, t) != t) { close(f); vtoc_error(unable_to_read, device, "Could not read VTOC FMT4 DSCB."); } } if (f5 != NULL) { t = sizeof(format5_label_t); if (read(f, f5, t) != t) { close(f); vtoc_error(unable_to_read, device, "Could not read VTOC FMT5 DSCB."); } } if (f7 != NULL) { t = sizeof(format7_label_t); if (read(f, f7, t) != t) { close(f); vtoc_error(unable_to_read, device, "Could not read VTOC FMT7 DSCB."); } } close(f); } /* * writes either a FMT1, FMT4 or FMT5 label * to the specified position */ void vtoc_write_label (char *device, unsigned long position, format1_label_t *f1, format4_label_t *f4, format5_label_t *f5, format7_label_t *f7, format9_label_t *f9) { int f,t; if ((f = open(device, O_WRONLY)) == -1) { vtoc_error(unable_to_open, device, "Could not write VTOC labels."); } if (lseek(f, position, SEEK_SET) == -1) { close(f); vtoc_error(unable_to_seek, device, "Could not write VTOC labels."); } if (f1 != NULL) { t = sizeof(format1_label_t); if (write(f, f1, t) != t) { close(f); vtoc_error(unable_to_write, device, "Could not write VTOC FMT1 DSCB."); } } if (f4 != NULL) { t = sizeof(format4_label_t); if (write(f, f4, t) != t) { close(f); vtoc_error(unable_to_write, device, "Could not write VTOC FMT4 DSCB."); } } if (f5 != NULL) { t = sizeof(format5_label_t); if (write(f, f5, t) != t) { close(f); vtoc_error(unable_to_write, device, "Could not write VTOC FMT5 DSCB."); } } if (f7 != NULL) { t = sizeof(format7_label_t); if (write(f, f7, t) != t) { close(f); vtoc_error(unable_to_write, device, "Could not write VTOC FMT7 DSCB."); } } if (f9 != NULL) { t = sizeof(format9_label_t); if (write(f, f9, t) != t) { close(f); vtoc_error(unable_to_write, device, "Could not write VTOC FMT9 DSCB."); } } close(f); } /* * initializes a format4 label */ void vtoc_init_format4_label ( format4_label_t *f4, unsigned int compat_cylinders, unsigned int real_cylinders, unsigned int tracks, unsigned int blocks, unsigned int blksize, u_int16_t dev_type) { int i; cchh_t lower = {VTOC_START_CC, VTOC_START_HH}; cchh_t upper = {VTOC_START_CC, VTOC_START_HH}; for (i=0; i<44; i++) f4->DS4KEYCD[i] = 0x04; f4->DS4IDFMT = 0xf4; vtoc_set_cchhb(&f4->DS4HPCHR, 0x0000, 0x0000, 0x00); f4->DS4DSREC = blocks - 2; /* free space starts right behind VTOC vtoc_set_cchh(&f4->DS4HCCHH, VTOC_START_CC, VTOC_START_HH + 1);*/ vtoc_set_cchh(&f4->DS4HCCHH, 0x0000, 0x0000); f4->DS4NOATK = 0x0000; f4->DS4VTOCI = 0x00; f4->DS4NOEXT = 0x01; f4->DS4SMSFG = 0x00; f4->DS4DEVAC = 0x00; /* -- begin f4->DS4DEVCT -- */ f4->DS4DEVCT.DS4DSCYL = compat_cylinders; f4->DS4DEVCT.DS4DSTRK = tracks; switch (dev_type) { case DASD_3380_TYPE: f4->DS4DEVCT.DS4DEVTK = DASD_3380_VALUE; break; case DASD_3390_TYPE: f4->DS4DEVCT.DS4DEVTK = DASD_3390_VALUE; break; case DASD_9345_TYPE: f4->DS4DEVCT.DS4DEVTK = DASD_9345_VALUE; break; default: f4->DS4DEVCT.DS4DEVTK = blocks * blksize;; } f4->DS4DEVCT.DS4DEVI = 0x00; f4->DS4DEVCT.DS4DEVL = 0x00; f4->DS4DEVCT.DS4DEVK = 0x00; f4->DS4DEVCT.DS4DEVFG = 0x30; f4->DS4DEVCT.DS4DEVTL = 0x0000; f4->DS4DEVCT.DS4DEVDT = blocks; f4->DS4DEVCT.DS4DEVDB = 0x00; /* -- end f4->DS4DEVCT -- */ bzero(f4->DS4AMTIM, sizeof(f4->DS4AMTIM)); bzero(f4->DS4AMCAT, sizeof(f4->DS4AMCAT)); bzero(f4->DS4R2TIM, sizeof(f4->DS4R2TIM)); bzero(f4->res1, sizeof(f4->res1)); bzero(f4->DS4F6PTR, sizeof(f4->DS4F6PTR)); /* -- begin f4lbl->DS4VTOCE -- */ vtoc_set_extent(&f4->DS4VTOCE, 0x01, 0x00, &lower, &upper); /* -- end f4lbl->DS4VTOCE -- */ bzero(f4->res2, sizeof(f4->res2)); f4->DS4EFLVL = 0x00; bzero(&f4->DS4EFPTR, sizeof(f4->DS4EFPTR)); bzero(&f4->res3, sizeof(f4->res3)); f4->DS4DCYL = real_cylinders; bzero(f4->res4, sizeof(f4->res4)); f4->DS4DEVF2 = 0x40; /* allow format 8 and 9 labels */ bzero(&f4->res5, sizeof(f4->res5)); } /* * initializes a format5 label */ void vtoc_init_format5_label (format5_label_t *f5) { int i; bzero(f5, sizeof(format5_label_t)); for (i=0; i<4; i++) f5->DS5KEYID[i] = 0x05; f5->DS5FMTID = 0xf5; } /* * initializes a format7 label */ void vtoc_init_format7_label (format7_label_t *f7) { int i; bzero(f7, sizeof(format7_label_t)); for (i=0; i<4; i++) f7->DS7KEYID[i] = 0x07; f7->DS7FMTID = 0xf7; } /* * helper function that initializes a most parts of a * format1 or format 8 label, all but the field DS1FMTID */ static void vtoc_init_format_1_8_label ( unsigned int blksize, extent_t *part_extent, format1_label_t *f1) { struct tm * creatime; time_t t; char str[80]; /* get actual date */ t = time(NULL); creatime = gmtime(&t); bzero(f1->DS1DSNAM, sizeof(f1->DS1DSNAM)); sprintf(str, "PART .NEW "); vtoc_ebcdic_enc(str, str, 44); strncpy(f1->DS1DSNAM, str, 44); strncpy((char *) f1->DS1DSSN, " ", 6); f1->DS1VOLSQ = 0x0001; vtoc_set_date(&f1->DS1CREDT, (u_int8_t) creatime->tm_year, (u_int16_t) creatime->tm_yday); /* expires never - 99 365 */ vtoc_set_date(&f1->DS1EXPDT, 0x63, 0x016D); f1->DS1NOEPV = 0x01; f1->DS1NOBDB = 0x00; f1->DS1FLAG1 = 0x00; vtoc_ebcdic_enc("IBM LINUX ", str, 13); strncpy((char *)f1->DS1SYSCD, str, 13); vtoc_set_date(&f1->DS1REFD, (u_int8_t) creatime->tm_year, (u_int16_t) creatime->tm_yday); f1->DS1SMSFG = 0x00; f1->DS1SCXTF = 0x00; f1->DS1SCXTV = 0x0000; f1->DS1DSRG1 = 0x00; f1->DS1DSRG2 = 0x00; f1->DS1RECFM = 0x88; f1->DS1OPTCD = 0x00; f1->DS1BLKL = blksize; f1->DS1LRECL = blksize; f1->DS1KEYL = 0x00; f1->DS1RKP = 0x0000; f1->DS1DSIND = 0x80; /* last volume for this dataset */ f1->DS1SCAL1 = 0x80; bzero(&f1->DS1SCAL3, sizeof(f1->DS1SCAL3)); vtoc_set_ttr(&f1->DS1LSTAR, 0x0000, 0x00); f1->DS1TRBAL = 0x00; bzero(&f1->res1, sizeof(f1->res1)); memcpy(&f1->DS1EXT1, part_extent, sizeof(extent_t)); bzero(&f1->DS1EXT2, sizeof(extent_t)); bzero(&f1->DS1EXT3, sizeof(extent_t)); vtoc_set_cchhb(&f1->DS1PTRDS, 0x0000, 0x0000, 0x00); } void vtoc_init_format1_label ( unsigned int blksize, extent_t *part_extent, format1_label_t *f1) { vtoc_init_format_1_8_label(blksize, part_extent, f1); f1->DS1FMTID = 0xf1; } void vtoc_init_format8_label ( unsigned int blksize, extent_t *part_extent, format1_label_t *f8) { vtoc_init_format_1_8_label(blksize, part_extent, f8); f8->DS1FMTID = 0xf8; } void vtoc_update_format8_label ( cchhb_t *associated_f9, format1_label_t *f8) { memcpy(&f8->DS1PTRDS, associated_f9, sizeof(*associated_f9)); } void vtoc_init_format9_label ( format9_label_t *f9) { f9->DS9KEYID = 0x09; f9->DS9SUBTY = 0x01; f9->DS9NUMF9 = 1; f9->DS9FMTID = 0xf9; } /* * do some updates to the VTOC format4 label */ void vtoc_update_format4_label ( format4_label_t *f4, cchhb_t *highest_f1, u_int16_t unused_update) { /* update highest address of a format 1 label */ memcpy(&f4->DS4HPCHR, highest_f1, sizeof(cchhb_t)); /* update unused DSCB count */ f4->DS4DSREC = unused_update; } /* * reorganizes all extents within a FMT5 label */ static void vtoc_reorganize_FMT5_extents (format5_label_t *f5) { ds5ext_t *ext, *last, tmp; int i, j; for (i=0; i<26; i++) { if (i==0) last = &f5->DS5AVEXT; else if ((i > 0) && (i < 8)) last = &f5->DS5EXTAV[i-1]; else last = &f5->DS5MAVET[i-8]; for (j=i; j<26; j++) { if (j==0) ext = &f5->DS5AVEXT; else if ((j > 0) && (j < 8)) ext = &f5->DS5EXTAV[j-1]; else ext = &f5->DS5MAVET[j-8]; if (((ext->t > 0) && (last->t == 0)) || ((ext->t > 0) && (ext->t < last->t))) { tmp.t = last->t; tmp.fc = last->fc; tmp.ft = last->ft; last->t = ext->t; last->fc = ext->fc; last->ft = ext->ft; ext->t = tmp.t; ext->fc = tmp.fc; ext->ft = tmp.ft; } } } } /* * add a free space extent description to the VTOC FMT5 DSCB */ void vtoc_update_format5_label_add (format5_label_t *f5, int verbose, int trk, u_int16_t a, u_int16_t b, u_int8_t c) { ds5ext_t *ext = NULL, *tmp = NULL; int i; for (i=0; i<26; i++) { if (i==0) ext = &f5->DS5AVEXT; else if ((i > 0) && (i < 8)) ext = &f5->DS5EXTAV[i-1]; else ext = &f5->DS5MAVET[i-8]; if (((a < ext->t) && (a + b*trk + c > ext->t)) || ((a > ext->t) && (ext->t + ext->fc*trk + ext->ft > a))) { printf("BUG: overlapping free space extents " \ "in FMT5 DSCB!\nexiting...\n"); exit(1); } if ((ext->t + ext->fc + ext->ft) == 0x0000) { ext->t = a; ext->fc = b; ext->ft = c; tmp = ext; if (verbose) printf("FMT5 add extent: " \ "add new extent\n"); break; } } if (tmp == NULL) { /* BUG: no free extent found */ printf("BUG: no free FMT5 DSCB extent found!\nexiting...\n"); exit(1); } for (i=0; i<26; i++) { if (i==0) ext = &f5->DS5AVEXT; else if ((i > 0) && (i < 8)) ext = &f5->DS5EXTAV[i-1]; else ext = &f5->DS5MAVET[i-8]; if ((ext->t + ext->fc + ext->ft) == 0x0000) continue; if ((ext->t + ext->fc*trk + ext->ft) == tmp->t) { /* this extent precedes the new one */ ext->fc += (tmp->fc + (tmp->ft + ext->ft)/trk); ext->ft = (tmp->ft + ext->ft) % trk; bzero(tmp, sizeof(ds5ext_t)); tmp = ext; if (verbose) printf("FMT5 add extent: " \ "merge with predecessor\n"); i = -1; continue; } if ((tmp->t + tmp->fc*trk + tmp->ft) == ext->t) { /* this extent succeeds the new one */ ext->t = tmp->t; ext->fc += (tmp->fc + (tmp->ft + ext->ft)/trk); ext->ft = (tmp->ft + ext->ft) % trk; bzero(tmp, sizeof(ds5ext_t)); tmp = ext; if (verbose) printf("FMT5 add extent: " \ "merge with successor\n"); i = -1; continue; } } } /* * remove a free space extent description from the VTOC FMT5 DSCB */ void vtoc_update_format5_label_del (format5_label_t *f5, int verbose, int trk, u_int16_t a, u_int16_t b, u_int8_t c) { ds5ext_t *ext; int i, counter=0; for (i=0; i<26; i++) { if (i==0) ext = &f5->DS5AVEXT; else if ((i > 0) && (i < 8)) ext = &f5->DS5EXTAV[i-1]; else ext = &f5->DS5MAVET[i-8]; if ((a == ext->t) && (b == ext->fc) && (c == ext->ft)) { /* fills up whole free space gap */ bzero(ext, sizeof(ds5ext_t)); if (verbose) printf("FMT5 del extent: fills whole gap\n"); counter++; break; } if ((a == ext->t) && ((b < ext->fc) || (c < ext->ft))) { /* left-bounded in free space gap */ ext->t = ext->t + b*trk + c; if (c > ext->ft) { ext->fc -= (b + 1); ext->ft -= (c - trk); } else { ext->fc -= b; ext->ft -= c; } if (verbose) printf("FMT5 del extent: left bounded\n"); counter++; break; } if ((ext->t < a) && ((ext->t + ext->fc*trk + ext->ft) == (a + b*trk + c))) { /* right-bounded in free space gap */ if (c > ext->ft) { ext->fc -= (b + 1); ext->ft -= (c - trk); } else { ext->fc -= b; ext->ft -= c; } if (verbose) printf("FMT5 del extent: right bounded\n"); counter++; break; } if ((a > ext->t) && ((ext->t + ext->fc*trk + ext->ft) > (a + b*trk + c))) { /* partition devides free space into 2 pieces */ u_int16_t x = a + b*trk + c; u_int16_t w,y; u_int8_t z; w = (ext->t + ext->fc*trk + ext->ft) - (a + b*trk + c); y = w / trk; z = w % trk; ext->fc = (a - ext->t) / trk; ext->ft = (a - ext->t) % trk; vtoc_update_format5_label_add(f5, verbose, trk, x, y, z); if (verbose) printf("FMT5 del extent: 2 pieces\n"); counter++; break; } if ((a < ext->t) && (a + b*trk + c > ext->t) && (a + b*trk + c < ext->t + ext->fc*trk + ext->ft)) { printf("BUG: corresponding free space extent " \ "doesn't match free space currently shown " \ "in FMT5 DSCB!\nexiting...\n"); exit(1); } if ((a > ext->t) && (a < ext->t + ext->fc*trk + ext->ft) && (a + b*trk + c > ext->t + ext->fc*trk + ext->ft)) { printf("BUG: specified free space extent for " \ "deleting doesn't match free space " \ "currently shown in FMT5 DSCB!\n" \ "exiting...\n"); exit(1); } } if (counter > 0) return; printf("BUG: specified free space extent for " \ "deleting not found in FMT5 DSCB!\n" \ "exiting...\n"); exit(1); } /* * reorganizes all extents within a FMT7 label */ static void vtoc_reorganize_FMT7_extents (format7_label_t *f7) { ds7ext_t *ext, *last, tmp; int i, j; for (i=0; i<16; i++) { if (i<5) last = &f7->DS7EXTNT[i]; else last = &f7->DS7ADEXT[i-5]; for (j=i; j<16; j++) { if (j<5) ext = &f7->DS7EXTNT[j]; else ext = &f7->DS7ADEXT[j-5]; if (((ext->a > 0) && (last->a == 0)) || ((ext->a > 0) && (ext->a < last->a))) { tmp.a = last->a; tmp.b = last->b; last->a = ext->a; last->b = ext->b; ext->a = tmp.a; ext->b = tmp.b; } } } } /* * add a free space extent description to the VTOC FMT7 DSCB */ void vtoc_update_format7_label_add (format7_label_t *f7, int verbose, u_int32_t a, u_int32_t b) { ds7ext_t *ext = NULL, *tmp = NULL; int i; for (i=0; i<16; i++) { if (i<5) ext = &f7->DS7EXTNT[i]; else ext = &f7->DS7ADEXT[i-5]; if (((a < ext->a) && (b > ext->a) && (b < ext->b)) || ((a > ext->a) && (a < ext->b) && (b > ext->b))) { printf("BUG: overlapping free space extents " "in FMT7 DSCB!\nexiting...\n"); exit(1); } if ((ext->a + ext->b) == 0x00000000) { ext->a = a; ext->b = b; tmp = ext; if (verbose) printf("FMT7 add extent: add new extent\n"); break; } } if (tmp == NULL) { printf("BUG: no free FMT7 DSCB extent found!\nexiting...\n"); exit(1); } for (i=0; i<16; i++) { if (i<5) ext = &f7->DS7EXTNT[i]; else ext = &f7->DS7ADEXT[i-5]; if ((ext->a + ext->b) == 0x00000000) continue; if ((ext->b) == tmp->a) { /* this extent precedes the new one */ ext->b = tmp->b; bzero(tmp, sizeof(ds7ext_t)); tmp = ext; if (verbose) printf("FMT7 add extent: " \ "merge with predecessor\n"); i = -1; continue; } if (ext->a == (tmp->b)) { /* this extent succeeds the new one */ ext->a = tmp->a; bzero(tmp, sizeof(ds7ext_t)); tmp = ext; if (verbose) printf("FMT7 add extent: " \ "merge with successor\n"); i = -1; continue; } } } /* * remove a free space extent description from the VTOC FMT7 DSCB */ void vtoc_update_format7_label_del (format7_label_t *f7, int verbose, u_int32_t a, u_int32_t b) { ds7ext_t *ext; int i, counter=0; for (i=0; i<16; i++) { if (i<5) ext = &f7->DS7EXTNT[i]; else ext = &f7->DS7ADEXT[i-5]; if ((a == ext->a) && (b == ext->b)) { /* fills up whole free space gap */ bzero(ext, sizeof(ds7ext_t)); if (verbose) printf("FMT7 del extent: fills whole gap\n"); counter++; break; } if ((a == ext->a) && (b < ext->b)) { /* left-bounded in free space gap */ ext->a = b; if (verbose) printf("FMT7 add extent: left-bounded\n"); counter++; break; } if ((a > ext->a) && (b == ext->b)) { /* right-bounded in free space gap */ ext->b = a; if (verbose) printf("FMT7 add extent: right-bounded\n"); counter++; break; } if ((a > ext->a) && (b < ext->b)) { /* partition devides free space into 2 pieces */ vtoc_update_format7_label_add(f7, verbose, b, ext->b); ext->b = a; if (verbose) printf("FMT7 add extent: 2 pieces\n"); counter++; break; } if (((a < ext->a) && (b > ext->a)) || ((a < ext->b) && (b > ext->b))) { printf("BUG: specified free space extent for deleting " "doesn't match free space currently shown in " "FMT7 DSCB!\nexiting...\n"); exit(1); } } if (counter > 0) return; printf("BUG: specified free space extent for deleting not found " "in FMT7 DSCB!\nexiting...\n"); exit(1); } /* * */ void vtoc_set_freespace(format4_label_t *f4, format5_label_t *f5, format7_label_t *f7, char ch, int verbose, u_int32_t start, u_int32_t stop, u_int32_t cyl, u_int32_t trk) { if ((cyl * trk) > BIG_DISK_SIZE) { if (ch == '+') { vtoc_update_format7_label_add(f7, verbose, start, /* ds7ext RTA + 1 */ stop + 1); } else if (ch == '-') { vtoc_update_format7_label_del(f7, verbose, start, /* ds7ext RTA + 1 */ stop + 1); } else { printf("BUG: syntax error in vtoc_set_freespace.\n"); } vtoc_reorganize_FMT7_extents (f7); f4->DS4VTOCI = 0xa0; f4->DS4EFLVL = 0x07; vtoc_set_cchhb(&f4->DS4EFPTR, 0x0000, 0x0001, 0x03); } else { u_int16_t x,y; u_int8_t z; x = (u_int16_t) start; y = (u_int16_t) ((stop - start + 1) / trk); z = (u_int8_t) ((stop - start + 1) % trk); if (ch == '+') { vtoc_update_format5_label_add(f5, verbose, trk, x, y, z); } else if (ch == '-') { vtoc_update_format5_label_del(f5, verbose, trk, x, y, z); } else { printf("BUG: syntax error in vtoc_set_freespace.\n"); } vtoc_reorganize_FMT5_extents (f5); } } s390-tools-2.3.0/libzds/000077500000000000000000000000001323410431100146275ustar00rootroot00000000000000s390-tools-2.3.0/libzds/Makefile000066400000000000000000000003041323410431100162640ustar00rootroot00000000000000include ../common.mak ALL_CFLAGS += -D_FILE_OFFSET_BITS=64 lib = libzds.a all: $(lib) objects = libzds.o $(lib): $(objects) install: all clean: rm -f *.o $(lib) .PHONY: all install clean s390-tools-2.3.0/libzds/libzds.c000066400000000000000000003447721323410431100163030ustar00rootroot00000000000000/** * @file libzds.c * This is the implementation of the internal library libzds. * Please note that this library should currently only be used * by programs in the s390-tools package. It is not yet meant * for external use as interfaces and definitions may change * without further notice. * * Copyright IBM Corp. 2013, 2017 * * 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 #include #include #include #include #include #include "lib/dasd_base.h" #include "lib/libzds.h" #include "lib/u2s.h" #include "lib/util_base.h" #include "lib/util_list.h" #include "lib/vtoc.h" /** @cond PRIVATE */ /******************************************************************************/ /* libzds structure definitions */ /******************************************************************************/ /** * @brief Maximum size of a volume serial string (NOT including one byte for * 0-termination) */ #define MAXVOLSER 6 /* * The following structures are declared in libzds.h but defined here in * the .c file to keep them opaque to the user of the library. */ struct errorlog { struct util_list *entries; }; /** * @brief Size of the message buffer in errormsg * */ #define ERRORMSG 240 #define BUSIDSIZE 8 /** * @brief An internal structure that represents an entry in the error log. */ struct errormsg { /** @brief List head to store a list of errormsg in struct errorlog */ struct util_list_node list; /** @brief error code that was associated with this message*/ int error; /** @brief a descriptive message text */ char text[ERRORMSG]; }; /** * As the VTOC is the data area on the DASD that describes all data sets, * this library will often have to refer to the various records in the VTOC. * To make this more efficiant, we will read the whole VTOC once and identify * all elements (DSCBs). The raw data of the VTOC tracks and the index to the * DSCBs is stored. */ struct raw_vtoc { /** @brief The raw track data */ char *rawdata; /** @brief This size of the raw track data in bytes */ unsigned long long rawdatasize; /** @brief An array with pointers to the various DSCBs in the rawdata */ char **vtocindex; /** @brief Number of entries in the index */ unsigned int vtocindexcount; /** @brief Number of records per VTOC track * * @note While the DS4DEVDT field in the format 4 DSCB names the number * if DSCBs per VTOC track, we count the records, which is DS4DEVDT + 1 * for record 0. */ unsigned int vtoc_rec_per_track; /** @brief The track number in which the vtoc begins on the DASD */ unsigned int vtoctrackoffset; /** @brief Start record of VTOC. * * The rawdata contains full tracks. This is the number of the first * record that actually belongs to the VTOC */ unsigned int vtocrecno; /** @brief The DASD this vtoc was read from */ struct dasd *dasd; /** @brief Detailed error messages in case of a problem */ struct errorlog *log; }; struct dscbiterator { /** @brief The raw_vtoc this iterator refers to */ struct raw_vtoc *rawvtoc; /** @brief Index to the vtocindex array in rawvtoc */ unsigned int i; }; struct dasd { /** @brief List head used to store a list of DASDs in struct zdsroot */ struct util_list_node list; /** @brief Name of the block device, e.g. /dev/dasde */ char *device; /** @brief File descriptor for the block device. * * The device is kept open for as along as the library uses it. * This lets the system know that the device is still in use. */ int inusefd; /* @brief where to find the volume label */ unsigned int label_block; /** @brief Device geometry. How many cylinders does the DASD have. */ unsigned int cylinders; /** @brief Device geometry. How many heads does the DASD have. */ unsigned int heads; /** @brief The VTOC data that has been read from this device */ struct raw_vtoc *rawvtoc; /** @brief The volume label that has been read from this device */ volume_label_t *vlabel; /** @brief Detailed error messages in case of a problem */ struct errorlog *log; }; struct dasdhandle { /** @brief The struct dasd this context relates to */ struct dasd *dasd; /** @brief File descriptor for the block device. * Should be -1 when the device not open */ int fd; /** @brief Detailed error messages in case of a problem */ struct errorlog *log; }; struct pdsmember { /** @brief List head that is used to store a list of members in * struct dataset */ struct util_list_node list; /** @brief Member name, converted from EBCDIC to ASCII */ char name[MEMBERNAMELENGTH]; /** @brief The track the member starts in, relative to the data set. * * @note This number is relative to the data set, with track 0 * being the first track of the data set. It is independent * of the DASD geometry or extent location. */ unsigned short track; /** @brief First record of the member starts in, relative to the * start track.*/ unsigned char record; /** @brief Marks if pdsmember is an alias (we make no distinction * between a regular member and an alias). */ unsigned char is_alias; /** @brief Detailed error messages in case of a problem */ struct errorlog *log; }; /** * @brief An internal structure that represents part of a multi volume data set * * Data sets can be spread over several DASD devices (multi volume data set), * and this structure represents one such part. Each data set has at least one * datasetpart. */ struct datasetpart { /** @brief The dasd that this part resides on */ struct dasd *dasdi; /** @brief Pointer to the respective format 1 DSCB in the raw_vtoc * of that dasd */ format1_label_t *f1; /** @brief Each part can consist of up to MAXEXTENTS (16) extents */ extent_t ext[MAXEXTENTS]; }; struct dataset { /** @brief List head that is used to store a list of data sets in * struct zdsroot */ struct util_list_node list; /** @brief Data set name, translated from EBCDIC to ASCII, 0-terminated * and with any blank padding removed */ char name[MAXDSNAMELENGTH]; /** @brief Array of data set parts this data set consists of. * * We use just an regular array as the number of parts is limited. * Each part has a specific position, as defined by the DS1VOLSQ * value in the parts format 1 label. */ struct datasetpart *dsp[MAXVOLUMESPERDS]; /** @brief Number of parts this data set has * * @note This is the number of data set parts we have already found. * As long as there are still gaps in the dsp array, dspcount may be * smaller than the largest index of an element in the dsp array. */ int dspcount; /** @brief Flag that is set to 1 if we have all parts * * @note: In cases where a dataset consists of only one part, the * the fist part should be flagged as last part as well in DS1DSIND, * but this seems not to be reliable. So, as long as we only have only * found one part in position 0, we may set iscomplete, even if we * have no 'last part' marker found. */ int iscomplete; /** @brief If a data set is a partitioned data set (PDS), then this * contains a list of members, otherwise the list is empty */ struct util_list *memberlist; /** @brief Detailed error messages in case of a problem */ struct errorlog *log; }; struct memberiterator { /** @brief Data set that holds the members */ struct dataset *ds; /** @brief The last selected member. */ struct pdsmember *memberi; }; struct dsiterator { /** @brief zdsroot that holds the data sets */ struct zdsroot *zdsroot; /** @brief The last selected data set */ struct dataset *dsi; }; struct dasditerator { /** @brief zdsroot that holds the dasds */ struct zdsroot *zdsroot; /** @brief The last selected dasd */ struct dasd *dasdi; }; struct zdsroot { /** @brief list of dasds */ struct util_list *dasdlist; /** @brief list of data sets */ struct util_list *datasetlist; /** @brief Detailed error messages in case of a problem */ struct errorlog *log; }; /** * @brief Internal structure to keep track of offsets in the data set */ struct seekelement { /** @brief Data set part this element refers to */ unsigned char dsp_no; /** @brief The extent on that part/dasd */ unsigned char ext_seq_no; /** @brief The starting track on that part/dasd */ unsigned int bufstarttrk; /** @brief The absolute offset in the data set */ long long databufoffset; }; /** * @brief Default value for the tracks value in dshandle */ #define TRACK_BUFFER_DEFAULT 128 struct dshandle { /** @brief Data set this context relates to */ struct dataset *ds; /** @brief Pointer to member, only applicable to PDS */ struct pdsmember *member; /** @brief One dasdhandle per data set part * * The dshandle functions do not read directly from the devices, * instead they use the dasdhandle interfacesw. */ struct dasdhandle *dasdhandle[MAXVOLUMESPERDS]; /** @brief A multiplier that is used to determine the various buffer sizes. Number of tracks in one track frame. */ unsigned int tracks_per_frame; /** @brief Flag: While interpreting the data, keep the record * descriptor words in the data stream */ int keepRDW; /** @brief Flag that is set between open and close */ int is_open; /** @brief This flag is set when during interpretation of the track * buffer the end of the data is found */ int eof_reached; /* The following values describe our current position within the data * set */ /** @brief Index number of the current data set part */ int dsp_no; /** @brief The sequence number of the current extent in the current * data set part */ int ext_seq_no; /** @brief Start buffer interpretation at this record. * * Data set members may start in the middle of a track. So we need * to know with which record to start. */ unsigned char startrecord; /** @brief The first track of the extent that dsp_no and ext_seq_no * point to */ unsigned int extstarttrk; /** @brief The last track of the extent that dsp_no and ext_seq_no * point to */ unsigned int extendtrk; /** @brief Start of the area that is currently in the rawbuffer */ unsigned int bufstarttrk; /** @brief End of the area that is currently in the rawbuffer */ unsigned int bufendtrk; /** @brief Running number of the current track frame */ long long frameno; /** @brief Buffer for the raw track images */ char *rawbuffer; /** @brief Buffer for the extracted user data */ char *databuffer; /** @brief Size of the rawbuffer */ long long rawbufmax; /** @brief Size of the databuffer */ long long databufmax; /** @brief Size of the currently used part of the rawbuffer */ long long rawbufsize; /** @brief Size of the currently used part of the databuffer */ long long databufsize; /** @brief Current position of the databuffer relative to the begin * of the data set */ long long databufoffset; /** @brief Current position in the databuffer */ long long bufpos; /** @brief Buffer for seek data points */ struct seekelement *seekbuf; /** @brief Total number of elements in seekbuf */ unsigned long long seek_count; /** @brief Number of used elements in seekbuf */ unsigned long long seek_current; /** @brief Modulo that determines which track frame is stored in the * seek buffer * * Example: If skip is 2, then every 2'nd frame is stored. */ unsigned long long skip; /** @brief Detailed error messages in case of a problem */ struct errorlog *log; }; /** @endcond */ /******************************************************************************/ /* BASIC level functions */ /******************************************************************************/ static void dasd_free(struct dasd *dasd); static void dataset_free_memberlist(struct dataset *ds); static void errorlog_free(struct errorlog *log); static void errorlog_clear(struct errorlog *log); static int errorlog_add_message(struct errorlog **log, struct errorlog *oldlog, int error_code, const char *message_format, ...) __attribute__ ((format (printf, 4, 5))); /** * Since the zdsroot is the root for all the other data structures, * this should be one of the first functions to call. * @param[out] root Reference to a pointer variable in which the newly * allocated structure will be returned. * @return 0 on success, otherwise one of the following error codes: * - ENOMEM Could not allocate structure due to lack of memory. */ int lzds_zdsroot_alloc(struct zdsroot **root) { struct zdsroot *tmproot; *root = NULL; tmproot = malloc(sizeof(*tmproot)); if (!tmproot) return ENOMEM; memset(tmproot, 0, sizeof(*tmproot)); tmproot->dasdlist = util_list_new(struct dasd, list); tmproot->datasetlist = util_list_new(struct dataset, list); *root = tmproot; return 0; } /** * It should be noted that this frees all structures that are owned by the * root structure as well. For example, a pointer to a struct dasd that * has been returned by lzds_zdsroot_add_device is not valid anymore. * * @param[in] root Reference to the zdsroot structure that is to be freed. */ void lzds_zdsroot_free(struct zdsroot *root) { struct dasd *dasd, *nextdasd; struct dataset *ds, *nextds; int i; if (!root) return; util_list_iterate_safe(root->dasdlist, dasd, nextdasd) { util_list_remove(root->dasdlist, dasd); dasd_free(dasd); } util_list_free(root->dasdlist); util_list_iterate_safe(root->datasetlist, ds, nextds) { util_list_remove(root->datasetlist, ds); dataset_free_memberlist(ds); for (i = 0; i < MAXVOLUMESPERDS; ++i) free(ds->dsp[i]); errorlog_free(ds->log); free(ds); } util_list_free(root->datasetlist); errorlog_free(root->log); free(root); } /** * @brief Subroutine of lzds_zdsroot_add_device * * This function determines some basic DASD geometry information and stores * it in the struct dasd for later use. * * @param[in] dasd Reference to the dasd to work on. * @return 0 on success, otherwise one of the following error codes: * - EIO Some error prevented us from gaining this information * */ static int dasd_read_geometry(struct dasd *dasd) { unsigned long long size_in_bytes; errorlog_clear(dasd->log); if (dasd_get_blocksize_in_bytes(dasd->device, &size_in_bytes) != 0) return errorlog_add_message( &dasd->log, NULL, EIO, "read geometry: could not get size from device %s\n", dasd->device); /* label_block and heads are simply hard coded with the correct values * for ECKD DASDs. This makes us independent from any DASD specific * ioctls like BIODASDINFO and allows us to work on DASD images via * loopback device. */ dasd->label_block = 2; dasd->heads = 15; dasd->cylinders = (size_in_bytes / (dasd->heads * RAWTRACKSIZE)); return 0; } /** * @brief Subroutine of lzds_zdsroot_add_device * * This function goes through the list of dasds in root and verifies that * the a dasd with the given device name is not yet present. * @param[in] root Reference to the zdsroot structure the new struct dasd * is to be added to. * @param[in] devnode String that holds the name of the device node, * e.g. "/dev/dasdb". * @return true if matching dasd has been found, false if not */ static int zdsroot_is_duplicate_device(struct zdsroot *root, const char *devnode) { struct dasd *dasd; dasd = NULL; lzds_zdsroot_get_dasd_by_node_name(root, devnode, &dasd); return !(dasd == NULL); } /** * This function creates a new struct dasd and adds it to the root. * It can later be traversed using the dasditerator functions. * * @param[in] root Reference to the zdsroot structure the new struct dasd * is to be added to. * @param[in] devnode String that holds the name of the device node, * e.g. "/dev/dasdb". * @param[out] dasd Reference to a pointer variable in which the newly * allocated structure will be returned. * This pointer is returned for the convenience of the user, * to be used with follow on calls, e.g to lzds_dasd_read_vlabel. * * @return 0 on success, otherwise one of the following error codes: * - ENOMEM Could not allocate structure due to lack of memory. * - ENOTTY The used ioctl is not supported by the device (i.e. the * device is not a DASD.) * - EIO Some other error prevented us from gaining this information * * @note It is not guaranteed that ENOTTY is returned when the device is * not a DASD. It depends on the device whether ENOTTY or EIO is returned. */ int lzds_zdsroot_add_device(struct zdsroot *root, const char *devnode, struct dasd **dasd) { struct dasd *dasdtmp; int rc; errorlog_clear(root->log); if (zdsroot_is_duplicate_device(root, devnode)) { return errorlog_add_message( &root->log, NULL, EINVAL, "add device: duplicate device %s\n", devnode); } dasdtmp = malloc(sizeof(*dasdtmp)); if (!dasdtmp) return ENOMEM; memset(dasdtmp, 0, sizeof(*dasdtmp)); dasdtmp->device = strdup(devnode); dasdtmp->inusefd = open(dasdtmp->device, O_RDONLY); if (dasdtmp->inusefd < 0) { errorlog_add_message( &root->log, dasdtmp->log, EIO, "add device: could open device %s\n", dasdtmp->device); dasd_free(dasdtmp); return EIO; } rc = dasd_read_geometry(dasdtmp); if (rc) { errorlog_add_message( &root->log, dasdtmp->log, EIO, "add device: could not read device data from %s\n", dasdtmp->device); close(dasdtmp->inusefd); dasd_free(dasdtmp); return EIO; } util_list_add_tail(root->dasdlist, dasdtmp); if (dasd) *dasd = dasdtmp; return 0; } /** * @param[in] dasd A dasd on which an error occurred. * @param[out] log Reference to a variable in which the errorlog * is returned. */ void lzds_dasd_get_errorlog(struct dasd *dasd, struct errorlog **log) { *log = dasd->log; } /** * @brief Subroutine of lzds_zdsroot_free. Frees the struct dasd and everything * that belogns to it. * * @param[in] dasd Pointer to the struct dasd that is to be freed. */ static void dasd_free(struct dasd *dasd) { free(dasd->device); free(dasd->vlabel); if (dasd->rawvtoc) { free(dasd->rawvtoc->rawdata); free(dasd->rawvtoc->vtocindex); errorlog_free(dasd->rawvtoc->log); free(dasd->rawvtoc); } errorlog_free(dasd->log); close(dasd->inusefd); free(dasd); } /** * @param[in] zdsroot Reference to struct zdsroot that the iterator will be * bound to. The iterator will traverse the dasds stored * in this zdsroot. * @param[out] it Reference to a pointer variable in which the newly allocated * structure will be returned. * @return 0 on success, otherwise one of the following error codes: * - ENOMEM Could not allocate structure due to lack of memory. */ int lzds_zdsroot_alloc_dasditerator(struct zdsroot *zdsroot, struct dasditerator **it) { *it = malloc(sizeof(struct dasditerator)); if (*it) { (*it)->dasdi = NULL; (*it)->zdsroot = zdsroot; return 0; } return ENOMEM; } /** * @param[in] it Pointer to the struct dasditerator that is to be freed. */ void lzds_dasditerator_free(struct dasditerator *it) { free(it); } /** * @param[out] it Reference to the struct dasditerator we use to traverse the * dasd list. * @param[out] dasd Reference to a pointer variable in which the next dasd in * the sequence will be returned. If there is no next DASD, * this variable will be set to NULL. * @return 0 on success, otherwise one of the following error codes: * - EPERM The end of the list has been reached. There is no further dasd. */ int lzds_dasditerator_get_next_dasd(struct dasditerator *it, struct dasd **dasd) { struct dasd *dasdtmp; if (!it->dasdi) dasdtmp = util_list_start(it->zdsroot->dasdlist); else dasdtmp = util_list_next(it->zdsroot->dasdlist, it->dasdi); *dasd = dasdtmp; if (!dasdtmp) return EPERM; it->dasdi = dasdtmp; return 0; } /** * @param[in] dasd The struct dasd we want to know the device of. * @param[out] device Reference to a pointer variable in which the device * string will be returned. This string holds the device * name as it was given to lzds_zdsroot_add_device. */ void lzds_dasd_get_device(struct dasd *dasd, char **device) { *device = dasd->device; } /** * @param[in] root Reference to the zdsroot that holds the dasd. * @param[in] device Pointer to a character string that holds the device node * name that we are looking for. It must be the same name as * previously given to lzds_zdsroot_add_device * @param[out] dasd Reference to a pointer variable in which the found struct * dasd will be returned. If no dasd was found, * this will be set to NULL * @return 0 on success, otherwise one of the following error codes: * - ENOMEM Could not allocate internal structures due to lack of memory. * - ENODEV No matching struct dasd was found. */ int lzds_zdsroot_get_dasd_by_node_name(struct zdsroot *root, const char *device, struct dasd **dasd) { struct dasditerator *dasdit; int rc; struct dasd *tempdasd; char *dasddev; errorlog_clear(root->log); rc = lzds_zdsroot_alloc_dasditerator(root, &dasdit); if (rc) return ENOMEM; rc = ENODEV; *dasd = NULL; while (!lzds_dasditerator_get_next_dasd(dasdit, &tempdasd)) { lzds_dasd_get_device(tempdasd, &dasddev); if (!strcmp(device, dasddev)) { rc = 0; *dasd = tempdasd; break; } } lzds_dasditerator_free(dasdit); return rc; } /** * @param[in] root A zdsroot on which an error occurred. * @param[out] log Reference to a variable in which the errorlog * is returned. */ void lzds_zdsroot_get_errorlog(struct zdsroot *root, struct errorlog **log) { *log = root->log; } /** * @brief free storage for a single error message * * @param[in] msg The message to be freed */ static void errormsg_free(struct errormsg *msg) { free(msg); } /** * @brief allocate storage for a single error message * * @param[out] msg Reference to a pointer variable in which the newly allocated * structure will be returned. * @return 0 on success, otherwise one of the following error codes: * - ENOMEM Could not allocate structure due to lack of memory. */ static int errormsg_alloc(struct errormsg **msg) { struct errormsg *tmpmsg; *msg = NULL; tmpmsg = malloc(sizeof(*tmpmsg)); if (!tmpmsg) return ENOMEM; memset(tmpmsg, 0, sizeof(*tmpmsg)); *msg = tmpmsg; return 0; } /** * @brief remove and free all messages from a given errolog * * After this operation new messages can be added to the log. * * @param[in] log The message log to be cleared. This may be NULL. */ static void errorlog_clear(struct errorlog *log) { struct errormsg *msg, *nextmsg; if (!log) return; util_list_iterate_safe(log->entries, msg, nextmsg) { util_list_remove(log->entries, msg); errormsg_free(msg); } } /** * @brief free storage for an error log, including all messages * * @param[in] log The error log to be freed. This may be NULL. */ static void errorlog_free(struct errorlog *log) { if (!log) return; errorlog_clear(log); util_list_free(log->entries); free(log); } /** * @brief allocate storage for an error log * * @param[out] log Reference to a pointer variable in which the newly allocated * structure will be returned. * @return 0 on success, otherwise one of the following error codes: * - ENOMEM Could not allocate structure due to lack of memory. */ static int errorlog_alloc(struct errorlog **log) { struct errorlog *tmplog; *log = NULL; tmplog = malloc(sizeof(*tmplog)); if (!tmplog) return ENOMEM; memset(tmplog, 0, sizeof(*tmplog)); tmplog->entries = util_list_new(struct errormsg, list); *log = tmplog; return 0; } /** * @brief add a new message to the front of a log. * * @param[out] log A reference to a errorlog pointer variable. If a log already * exists, old messages are cleared, otherwise a new log will * be created. * @param[in] oldlog A log that already contains messages, usually from a call * to a subordinate function. This may be the same errorlog as * referenced by log, in which case the existing messages * are retained. This may also be NULL. * @param[in] error_code The error code that will be stored in the new errormsg. * This is also the return value. * @param[in] message_format A format string for the message string * (see vsnprintf man page). * @param[in] ... A variable number of further parameters. * Must match the message_format string. */ static int errorlog_add_message(struct errorlog **log, struct errorlog *oldlog, int error_code, const char *message_format, ...) { struct errormsg *msg, *nextmsg; struct errorlog *tmplog; va_list ap; int rc; if (!log) return error_code; if (log && !*log) { errorlog_alloc(&tmplog); if (!tmplog) return error_code; *log = tmplog; } else { tmplog = *log; } if (tmplog != oldlog) { errorlog_clear(tmplog); if (oldlog) { util_list_iterate_safe(oldlog->entries, msg, nextmsg) { util_list_remove(oldlog->entries, msg); util_list_add_tail(tmplog->entries, msg); } } } if (!message_format) return error_code; rc = errormsg_alloc(&msg); if (rc) return error_code; va_start(ap, message_format); vsnprintf(msg->text, ERRORMSG - 1, message_format, ap); va_end(ap); msg->error = error_code; util_list_add_head(tmplog->entries, msg); return error_code; } /** * This is pretty a very simple implementation that just goes through * the list of messages in the log and for each message it prints * "rc : " * * @param[in] log A log that contains messages. * @param[in] stream The stream that these messages will be printed to. */ int lzds_errorlog_fprint(struct errorlog *log, FILE *stream) { struct errormsg *msg; int rc; if (!log) return 0; util_list_iterate(log->entries, msg) { rc = fprintf(stream, "rc %d: %s", msg->error, msg->text); if (rc < 0) return -rc; } return 0; } /******************************************************************************/ /* LOW level functions */ /******************************************************************************/ /** * @param[in] dasd The DASD to whose geometry we refer to. * @param[in] p Cylinder and head address * @param[out] track The sequential track number for the given * cylinder and head address. */ void lzds_dasd_cchh2trk(struct dasd *dasd, cchh_t *p, unsigned int *track) { *track = vtoc_get_cyl_from_cchh(p) * dasd->heads + vtoc_get_head_from_cchh(p); } /** * @param[in] dasd The DASD to whose geometry we refer to. * @param[out] cylinders The number of cylinders that DASD has */ void lzds_dasd_get_cylinders(struct dasd *dasd, unsigned int *cylinders) { *cylinders = dasd->cylinders; } /** * @param[in] dasd The DASD to whose geometry we refer to * @param[out] heads The number of heads that DASD has */ void lzds_dasd_get_heads(struct dasd *dasd, unsigned int *heads) { *heads = dasd->heads; } /** * @param[in] dasd Reference to struct dasd that represents * the DASD that we want to read from. * @param[out] dasdh Reference to a pointer variable in which the newly * allocated structure will be returned. * @return 0 on success, otherwise one of the following error codes: * - ENOMEM Could not allocate structure due to lack of memory. */ int lzds_dasd_alloc_dasdhandle(struct dasd *dasd, struct dasdhandle **dasdh) { struct dasdhandle *dasdhtmp; dasdhtmp = malloc(sizeof(*dasdhtmp)); if (!dasdhtmp) return ENOMEM; memset(dasdhtmp, 0, sizeof(*dasdhtmp)); dasdhtmp->fd = -1; dasdhtmp->dasd = dasd; *dasdh = dasdhtmp; return 0; } /** * @param[in] dasdh Pointer to the struct dasdhandle that is to be freed. */ void lzds_dasdhandle_free(struct dasdhandle *dasdh) { if (!dasdh) return; /* we close the file descriptor in case it wasn't done properly */ lzds_dasdhandle_close(dasdh); errorlog_free(dasdh->log); free(dasdh); } /** * @param[in] dasdh The dasd handle for the dasd that is to be opened. * @return 0 on success, otherwise one of the following error codes: * - EIO Could not open underlying device. */ int lzds_dasdhandle_open(struct dasdhandle *dasdh) { errorlog_clear(dasdh->log); dasdh->fd = open(dasdh->dasd->device, O_RDONLY | O_DIRECT); if (dasdh->fd < 0) { dasdh->fd = -1; return errorlog_add_message( &dasdh->log, NULL, EIO, "dasdhandle: could not open %s, errno %d\n", dasdh->dasd->device, errno); } return 0; } /** * @param[in] dasdh The dasdhandle that has to be closed * @return 0 on success, otherwise one of the following error codes: * - EIO Error when closing underlying dasd device. */ int lzds_dasdhandle_close(struct dasdhandle *dasdh) { int rc; errorlog_clear(dasdh->log); rc = 0; if (dasdh->fd >= 0) rc = close(dasdh->fd); dasdh->fd = -1; if (rc) return errorlog_add_message( &dasdh->log, NULL, EIO, "dasdhandle: could not close %s\n", dasdh->dasd->device); return 0; } /** * @param[in] dasdh The dasdhandle we are reading from * @param[in] starttrck First track to read * @param[in] endtrck Last track to read * @param[out] trackdata Target buffer we read into, must have at least the * size (endtrk - starttrk + 1) * RAWTRACKSIZE * @return 0 on success, otherwise one of the following error codes: * - EINVAL starttrck or endtrck are not within the boundaries of the * underlying DASD device. * - EPROTO Could not read a full track image * - EIO Other I/O error */ int lzds_dasdhandle_read_tracks_to_buffer(struct dasdhandle *dasdh, unsigned int starttrck, unsigned int endtrck, char *trackdata) { off_t trckseek; ssize_t residual; off_t rc; ssize_t count; unsigned int cylinders; unsigned int heads; errorlog_clear(dasdh->log); /* verify that endtrck is not beyond the end of the dasd */ lzds_dasd_get_cylinders(dasdh->dasd, &cylinders); lzds_dasd_get_heads(dasdh->dasd, &heads); if (starttrck > endtrck || endtrck >= cylinders * heads) return errorlog_add_message( &dasdh->log, NULL, EINVAL, "dasdhandle read tracks: start %u, end %u is" " out of bounds for device %s\n", starttrck, endtrck, dasdh->dasd->device); /* * Compute seek address of the first track and number of tracks * to be read. Please note that geo.sectors does not match our raw * track size of 16*4KB, so we use the RAWTRACKSIZE explicitly */ trckseek = (off_t)starttrck * RAWTRACKSIZE; /* residual is the number of bytes we still have to read */ residual = (off_t)(endtrck - starttrck + 1) * RAWTRACKSIZE; rc = lseek(dasdh->fd, trckseek, SEEK_SET); if (rc < 0) return errorlog_add_message( &dasdh->log, NULL, EINVAL, "dasdhandle read tracks: seek to %llu, failed" " for device %s\n", (unsigned long long)trckseek, dasdh->dasd->device); while (residual) { count = read(dasdh->fd, trackdata, residual); if (count < 0) return errorlog_add_message( &dasdh->log, NULL, EIO, "dasdhandle read tracks: read failed" " for device %s, start %u, end %u\n", dasdh->dasd->device, starttrck, endtrck); if (count % RAWTRACKSIZE) /* No full track read */ return errorlog_add_message( &dasdh->log, NULL, EPROTO, "dasdhandle read tracks: read returned " "unaligned data for device %s," "start %u, end %u\n", dasdh->dasd->device, starttrck, endtrck); residual -= count; trackdata += count; } return 0; } /******************************************************************************/ /* MID level functions */ /******************************************************************************/ /** * @brief Helper function that iterates through the records in a track buffer. * * @param[in] buffer Address of the track buffer * @param[in] size Size of the buffer * @param[in,out] record Pointer that has the current record pointer as input * and gets a pointer to the next record as output. * If it the current record pointer is null, then the * pointer to the first record is returned. * @return 0 on success, otherwise one of the following error codes: * - ENOENT If we have reached the end of the buffer and there are no * further records */ static int buffer_get_next_record(char *buffer, size_t size, char **record) { char *data, *next_record; unsigned long offset; unsigned int record_size; struct eckd_count *ecount; /* If *record contains no record yet, then we return the first record */ if (!*record) { *record = buffer; return 0; } data = *record; ecount = (struct eckd_count *)data; record_size = sizeof(*ecount) + ecount->kl + ecount->dl; data += record_size; next_record = NULL; while (!next_record) { /* check if we have reached the end of the buffer */ if (data >= buffer + size) { *record = NULL; return ENOENT; } /* If the 'next' record is the pseudo record, then we have * reached the end of data in this track and we have to jump * to the start of the next track to find the next record. */ if ((*(unsigned long long *)data) == ENDTOKEN) { offset = (unsigned long)data - (unsigned long)buffer; offset &= ~(RAWTRACKSIZE - 1); offset += RAWTRACKSIZE; data = buffer + offset; continue; } next_record = data; } *record = next_record; return 0; } /** * @brief Helper function that does the whole open/read/close cycle in one go. * * @param[in] dasd Pointer to struct dasd that represents * the DASD that we want to read from. * @param[in] starttrck First track to read * @param[in] endtrck Last track to read * @param[out] trackdata Target buffer we read into, must have at least the * size (endtrk - starttrk + 1) * RAWTRACKSIZE * @return 0 on success, otherwise one of the following error codes: * - ENOMEM Could not allocate internal structure due to lack of memory. * - EINVAL starttrck or endtrck are not within the boundaries of the * underlying DASD device. * - EPROTO Could not read a full track image * - EIO Other I/O error */ static int dasd_read_tracks(struct dasd *dasd, unsigned int starttrck, unsigned int endtrck, char *trackdata) { struct dasdhandle *dasdh; int rc, rc2; rc = lzds_dasd_alloc_dasdhandle(dasd, &dasdh); if (rc) return errorlog_add_message( &dasd->log, dasd->log, rc, "dasd read tracks: could not allocate dasdhandle\n"); rc = lzds_dasdhandle_open(dasdh); if (rc) { errorlog_add_message( &dasd->log, dasdh->log, rc, "dasd read tracks: could not open dasdhandle\n"); lzds_dasdhandle_free(dasdh); return rc; } rc = lzds_dasdhandle_read_tracks_to_buffer(dasdh, starttrck, endtrck, trackdata); if (rc) errorlog_add_message( &dasd->log, dasdh->log, rc, "dasd read tracks: read error\n"); rc2 = lzds_dasdhandle_close(dasdh); /* report close error only if we had no read error */ if (rc2 && !rc) { errorlog_add_message( &dasd->log, dasdh->log, rc, "dasd read tracks: could not close dasdhandle\n"); rc = rc2; } lzds_dasdhandle_free(dasdh); return rc; } /** * @brief Helper function that reads a volume label from a DASD. * * @param[in] dasd Pointer to struct dasd that represents * the DASD that we want to read from. * @param[out] vlabel Buffer to read the label into. * @return 0 on success, otherwise one of the following error codes: * - ENOMEM Could not allocate internal structure due to lack of memory. * - EIO Other I/O error */ static int dasd_read_vlabel_to_buffer(struct dasd *dasd, struct volume_label *vlabel) { int rc; unsigned int i; char *trackdata, *record; struct volume_label *label; struct eckd_count *ecount; unsigned long labelend, trackend; size_t label_size; trackdata = memalign(4096, RAWTRACKSIZE); /* page align for O_DIRECT */ if (!trackdata) return ENOMEM; rc = dasd_read_tracks(dasd, 0, 0, trackdata); if (rc) { free(trackdata); return errorlog_add_message( &dasd->log, dasd->log, EIO, "read vlabel: could not read track 0\n"); } /* fist step, find label record */ record = NULL; label = NULL; ecount = NULL; i = 0; while (!buffer_get_next_record(trackdata, RAWTRACKSIZE, &record)) { if (i == (dasd->label_block + 1)) { ecount = (struct eckd_count *)record; label = (struct volume_label *)(ecount + 1); break; } ++i; } if (!ecount || !label) { free(trackdata); return errorlog_add_message( &dasd->log, dasd->log, EPROTO, "read vlabel: could not find label record\n"); } /* verify record layout */ memset(vlabel, 0, sizeof(*vlabel)); labelend = (unsigned long)label + ecount->kl + ecount->dl; trackend = (unsigned long)trackdata + RAWTRACKSIZE; if ((ecount->kl + ecount->dl == 84) && (labelend <= trackend)) { /* VOL1 label */ memcpy(vlabel, label, ecount->kl + ecount->dl); } else if ((ecount->kl == 0) && (labelend <= trackend)) { /* LNX1 / CMS1 label */ label_size = MIN(ecount->dl, sizeof(*vlabel) - 4); memcpy(&vlabel->vollbl, label, label_size); } else { free(trackdata); return errorlog_add_message( &dasd->log, dasd->log, EPROTO, "read vlabel: record layout does not match VOL1" " label\n"); } free(trackdata); return 0; } /** * @param[in] dasd Pointer to struct dasd that represents * the DASD that we want to read from. * @return 0 on success, otherwise one of the following error codes: * - ENOMEM Could not allocate internal structure due to lack of memory. * - EIO Other I/O error */ int lzds_dasd_read_vlabel(struct dasd *dasd) { struct volume_label *vlabel; int rc; errorlog_clear(dasd->log); free(dasd->vlabel); dasd->vlabel = NULL; vlabel = malloc(sizeof(*vlabel)); if (!vlabel) return ENOMEM; rc = dasd_read_vlabel_to_buffer(dasd, vlabel); if (rc) free(vlabel); else dasd->vlabel = vlabel; return rc; } /** * @param[in] dasd Reference to struct dasd that we want to get the label * from. * @param[out] vlabel Reference to a pointer variable in which the struct * volume_label will be returned. * @return 0 on success, otherwise one of the following error codes: * - EINVAL The volume lable has not yet been read from the device. */ int lzds_dasd_get_vlabel(struct dasd *dasd, struct volume_label **vlabel) { *vlabel = dasd->vlabel; if (*vlabel) return 0; else return EINVAL; } /** * @param[in] rawvtoc Reference to struct raw_vtoc that the iterator will be * bound to. The iterator will traverse the DSCBs stored * in this raw_vtoc. * @param[out] it Reference to a pointer variable in which the newly allocated * structure will be returned. * @return 0 on success, otherwise one of the following error codes: * - ENOMEM Could not allocate structure due to lack of memory. */ int lzds_raw_vtoc_alloc_dscbiterator(struct raw_vtoc *rawvtoc, struct dscbiterator **it) { *it = malloc(sizeof(**it)); if (*it) { (*it)->i = rawvtoc->vtocrecno - 1; (*it)->rawvtoc = rawvtoc; return 0; } return ENOMEM; } /** * @param[in] it Pointer to the struct dscbiterator that is to be freed. */ void lzds_dscbiterator_free(struct dscbiterator *it) { free(it); } /** * @param[out] it Reference to the struct dscb iterator we use to traverse * the VTOC. * @param[out] dscb Reference to a pointer variable in which the next dscb in * the sequence will be returned. If there is no next dscb, * this variable will be set to NULL. * @return 0 on success, otherwise one of the following error codes: * - EPERM There is no further DSCB in the VTOC. */ int lzds_dscbiterator_get_next_dscb(struct dscbiterator *it, struct dscb **dscb) { struct eckd_count *ecount; unsigned int i; i = it->i + 1; while (i < it->rawvtoc->vtocindexcount) { ecount = (struct eckd_count *)(it->rawvtoc->vtocindex[i]); if (ecount && (ecount->kl == 44) && (ecount->dl == 96)) break; else ++i; } if (i < it->rawvtoc->vtocindexcount) { it->i = i; *dscb = (struct dscb *)(it->rawvtoc->vtocindex[it->i] + sizeof(*ecount)); return 0; } else { *dscb = NULL; return EPERM; } } /** * @brief Subroutine of lzds_raw_vtoc_get_dscb_from_cchhb * * This function takes a cylinder, head, block address as it can be * found in DSCBs and returns an index to the matching entry in the * raw_vtoc vtocindex. * * The cchhb2blk function of the libvtoc does not work for raw devices * as the 'sectors per track' value in the geo structure has no meaning * for a raw DASD. We need to take this value from the context, * e.g. from the format 4 label of the VTOC. * Since this computation is very specialized, we can go all the way and * just compute the index to the vtoc array. * * @param[in] rv The raw_vtoc we refer to. * @param[in] p The cylinder, head, block address structure. * @return index to the vtocindex array */ static long long vtocindex_from_cchhb(struct raw_vtoc *rv, cchhb_t *p) { long long recno; recno = (long long) vtoc_get_cyl_from_cchhb(p) * rv->dasd->heads * rv->vtoc_rec_per_track + vtoc_get_head_from_cchhb(p) * rv->vtoc_rec_per_track + p->b; return recno - (rv->vtoctrackoffset * rv->vtoc_rec_per_track); } /** * @note A cchhb address within a VTOC dscb is often set to zero to * indicate that this entry does not point anywhere. For example this * is the case at the end of a format 3 dscb chain. This special case * is handled by setting the dscb pointer to NULL and having a return * value of 0 (no error). * * @param[in] rv The raw_vtoc we refer to. * @param[in] p The cylinder, head, block address of the DSCB. * @param[out] dscb Reference to a pointer variable in which a pointer to * the respective dscb in the raw_vtoc will be returned. * @return 0 on success, otherwise one of the following error codes: * - EINVAL The address in *p refers to a record that is not a valid DSCB. * - ERANGE The cylinder, head, block address lies not within the VTOC. */ int lzds_raw_vtoc_get_dscb_from_cchhb(struct raw_vtoc *rv, cchhb_t *p, struct dscb **dscb) { long long index; char *record; errorlog_clear(rv->log); index = vtocindex_from_cchhb(rv, p); *dscb = NULL; if (!p->cc && !p->hh && !p->b) return 0; /* record zero is part of the track image, but not a dscb */ if (!p->b) return errorlog_add_message( &rv->log, NULL, EINVAL, "raw vtoc: DSCB address is empty\n"); if (index < rv->vtocrecno || index >= rv->vtocindexcount) return errorlog_add_message( &rv->log, NULL, ERANGE, "raw vtoc: DSCB address is outside VTOC\n"); record = rv->vtocindex[vtocindex_from_cchhb(rv, p)]; if (!record) return errorlog_add_message( &rv->log, NULL, EINVAL, "raw vtoc: DSCB address points to nonexistent DSCB\n"); *dscb = (struct dscb *)(record + sizeof(struct eckd_count)); return 0; } /** * @param[in] dasd The struct dasd that represents the device we want to read * the VTOC from. * @return 0 on success, otherwise one of the following error codes: * - ENOMEM Could not allocate internal structure due to lack of memory. * - EINVAL The volume label has not yet been read or it is not valid. * - EPROTO The VTOC data is not in a valid format. * - EIO Other I/O error */ int lzds_dasd_read_rawvtoc(struct dasd *dasd) { unsigned long long vtoctrckno, vtocrecno; unsigned int vtoctrack_start, vtoctrack_end, vtocindexsize; unsigned int vtoc_rec_per_track; unsigned int i; int rc; char *record; struct eckd_count *ecount; format4_label_t *f4; unsigned long long rawvtocsize; struct raw_vtoc *rawvtoc = NULL; volume_label_t *vlabel = NULL; char *trackdata = NULL; char vol1[] = {0xe5, 0xd6, 0xd3, 0xf1, 0x00}; /* "VOL1" in EBCDIC */ errorlog_clear(dasd->log); /* cleanup the old rawvtoc structures before we read new ones */ rawvtoc = dasd->rawvtoc; dasd->rawvtoc = NULL; if (rawvtoc) { free(rawvtoc->rawdata); free(rawvtoc->vtocindex); free(rawvtoc); } rawvtoc = malloc(sizeof(*rawvtoc)); if (!rawvtoc) return ENOMEM; memset(rawvtoc, 0, sizeof(*rawvtoc)); rawvtoc->dasd = dasd; rc = lzds_dasd_get_vlabel(dasd, &vlabel); if (rc) { errorlog_add_message( &dasd->log, NULL, rc, "read VTOC: there is no volume label data available\n"); goto cleanup; } /* verify that we have a proper VOL1 label */ if (strncmp(vlabel->volkey, vol1, 4) || strncmp(vlabel->vollbl, vol1, 4)) { rc = EINVAL; errorlog_add_message( &dasd->log, NULL, rc, "read VTOC: volume label is not a VOL1 label\n"); goto cleanup; } /* The label contains the address of the first block of the vtoc. */ vtoctrckno = (unsigned long long) vtoc_get_cyl_from_cchhb(&vlabel->vtoc) * dasd->heads + vtoc_get_head_from_cchhb(&vlabel->vtoc); vtocrecno = vlabel->vtoc.b; /* We do not know how large the VTOC is, yet. So first, we read only * one track of the VTOC to access the format 4 DSCB in the first record * of the VTOC. */ trackdata = memalign(4096, RAWTRACKSIZE); /* page align for O_DIRECT */ if (!trackdata) { rc = ENOMEM; goto cleanup; } rc = dasd_read_tracks(dasd, vtoctrckno, vtoctrckno, trackdata); if (rc) { errorlog_add_message( &dasd->log, dasd->log, rc, "read VTOC: error when reading VTOC start\n"); goto cleanup; } record = NULL; f4 = NULL; i = 0; while (!buffer_get_next_record(trackdata, RAWTRACKSIZE, &record)) { if (i == vtocrecno) { f4 = (format4_label_t *)(record + 8); ecount = (struct eckd_count *)record; break; } ++i; } /* verify that the found record has the expected format */ if (!(f4 && (ecount->kl == 44) && (ecount->dl == 96) && (f4->DS4KEYCD[0] == 0x04) && (f4->DS4KEYCD[43] == 0x04) && (f4->DS4IDFMT == 0xf4))) { rc = EPROTO; errorlog_add_message( &dasd->log, NULL, rc, "read VTOC: could not find format 4 DSCB\n"); goto cleanup; } /* We have found a format 4 label at the position indicated by the * label. * How to determine the size of the VTOC: * - DS4VTOCE contains the VTOC extent, or in other words, lower and * uper boundary of the VTOC * * Searching through the VTOC tracks record by record is tedious, so * we build an array of pointers to the DSCBs, our VTOC index: * Number of entries in the index is the number of tracks times the * number of DSCBS per track plus one for record zero */ lzds_dasd_cchh2trk(dasd, &f4->DS4VTOCE.llimit, &vtoctrack_start); lzds_dasd_cchh2trk(dasd, &f4->DS4VTOCE.ulimit, &vtoctrack_end); vtoc_rec_per_track = (f4->DS4DEVCT.DS4DEVDT + 1); /* A VTOC consists of whole tracks, so the index size is number of * tracks multiplied by records per track */ vtocindexsize = (vtoctrack_end - vtoctrack_start + 1) * vtoc_rec_per_track; rawvtocsize = ((unsigned long long)vtoctrack_end - vtoctrack_start + 1) * RAWTRACKSIZE; f4 = NULL; record = NULL; free(trackdata); trackdata = memalign(4096, rawvtocsize); /* page align for O_DIRECT */ if (!trackdata) { rc = ENOMEM; goto cleanup; } /* read in the full VTOC from disk into memory */ rc = dasd_read_tracks(dasd, vtoctrack_start, vtoctrack_end, trackdata); if (rc) { errorlog_add_message( &dasd->log, dasd->log, rc, "read VTOC: error when reading VTOC\n"); goto cleanup; } rawvtoc->rawdata = trackdata; rawvtoc->rawdatasize = rawvtocsize; rawvtoc->vtoc_rec_per_track = vtoc_rec_per_track; rawvtoc->vtoctrackoffset = vtoctrack_start; rawvtoc->vtocrecno = vtocrecno; rawvtoc->vtocindexcount = vtocindexsize; /* Now parse all VTOC tracks in memory and create an index of * all records (including record 0) */ rawvtoc->vtocindex = malloc(sizeof(char *) * vtocindexsize); if (!rawvtoc->vtocindex) { rc = ENOMEM; goto cleanup; } memset(rawvtoc->vtocindex, 0, (sizeof(char *) * vtocindexsize)); record = NULL; f4 = NULL; i = 0; while (!buffer_get_next_record(trackdata, rawvtocsize, &record)) { /* verify that we do not get too many records */ if (i >= vtocindexsize) { rc = EPROTO; errorlog_add_message( &dasd->log, NULL, rc, "read VTOC: too many records in VTOC\n"); goto cleanup; } rawvtoc->vtocindex[i] = record; ++i; } dasd->rawvtoc = rawvtoc; return 0; cleanup: free(rawvtoc->vtocindex); free(trackdata); free(rawvtoc); return rc; } /** * @param[in] dasd Pointer to the struct dasd we want to get the raw_vtoc from. * @param[out] vtoc Reference to a pointer variable in which a pointer to * the previously read struct raw_vtoc will be returned. * @return 0 on success, otherwise one of the following error codes: * - EINVAL The VTOC has not yet been read. */ int lzds_dasd_get_rawvtoc(struct dasd *dasd, struct raw_vtoc **vtoc) { errorlog_clear(dasd->log); *vtoc = dasd->rawvtoc; if (!*vtoc) return EINVAL; else return 0; } /******************************************************************************/ /* HIGH level functions */ /******************************************************************************/ /** * @param[in] zdsroot Reference to struct zdsroot that the iterator will be * bound to. The iterator will traverse the data sets stored * in this zdsroot. * @param[out] it Reference to a pointer variable in which the newly allocated * structure will be returned. * @return 0 on success, otherwise one of the following error codes: * - ENOMEM Could not allocate structure due to lack of memory. */ int lzds_zdsroot_alloc_dsiterator(struct zdsroot *zdsroot, struct dsiterator **it) { *it = malloc(sizeof(struct dsiterator)); if (*it) { (*it)->dsi = NULL; (*it)->zdsroot = zdsroot; return 0; } return ENOMEM; } /** * @param[in] it Pointer to the struct dsiterator that is to be freed. */ void lzds_dsiterator_free(struct dsiterator *it) { free(it); } /** * @param[in] it Reference to the struct dsiterator we use to traverse the * data set list. * @param[out] ds Reference to a pointer variable in which the next * data set in the sequence will be returned. If there * is no next data set, this variable will be set to NULL. * @return 0 on success, otherwise one of the following error codes: * - EPERM The end of the list has been reached. There is no further dataset. */ int lzds_dsiterator_get_next_dataset(struct dsiterator *it, struct dataset **ds) { struct dataset *dstmp; if (!it->dsi) dstmp = util_list_start(it->zdsroot->datasetlist); else dstmp = util_list_next(it->zdsroot->datasetlist, it->dsi); *ds = dstmp; if (!dstmp) return EPERM; it->dsi = dstmp; return 0; } /** * @param[in] root Reference to struct zdsroot that holds the list of data * sets that this function shall search through. * @param[in] name Name of the data set. * @param[out] ds Reference to a pointer variable in which the found dataset * structure will be returned. If no data set was found, this * variable will be set to NULL * @return 0 on success, otherwise one of the following error codes: * - ENOMEM Could not internal structure due to lack of memory. * - ENOENT A dataset with the given name was not found. */ int lzds_zdsroot_find_dataset(struct zdsroot *root, const char *name, struct dataset **ds) { struct dsiterator *dsit; struct dataset *tempds; int rc; errorlog_clear(root->log); *ds = NULL; rc = lzds_zdsroot_alloc_dsiterator(root, &dsit); if (rc) return ENOMEM; while (!lzds_dsiterator_get_next_dataset(dsit, &tempds)) { if (!strcmp(tempds->name, name)) { *ds = tempds; break; } } lzds_dsiterator_free(dsit); if (!*ds) return ENOENT; return 0; } /** * @param[in] ds Reference to the struct dataset that the iterator will be * bound to. The iterator will traverse the members stored * in this data set. * @param[out] it Reference to a pointer variable in which the newly allocated * structure will be returned. * @return 0 on success, otherwise one of the following error codes: * - ENOMEM Could not allocate structure due to lack of memory. * - EINVAL Failed to allocate a memberiterator because the data set does * not support members (is not a PDS). */ int lzds_dataset_alloc_memberiterator(struct dataset *ds, struct memberiterator **it) { if (!ds->memberlist) { *it = NULL; return errorlog_add_message( &ds->log, NULL, EINVAL, "alloc memberiterator: this data set has no members\n"); } *it = malloc(sizeof(struct memberiterator)); if (*it) { (*it)->memberi = NULL; (*it)->ds = ds; return 0; } return ENOMEM; } /** * @param[in] it Pointer to the struct meberiterator that is to be freed. */ void lzds_memberiterator_free(struct memberiterator *it) { free(it); } /** * @param[out] it Reference to the struct memberiterator we use to traverse * the member list. * @param[out] member Reference to a pointer variable in which the next member * in the sequence will be returned. If there is no next * member, this variable will be set to NULL. * @return 0 on success, otherwise one of the following error codes: * - EPERM The end of the list has been reached. There is no further dasd. */ int lzds_memberiterator_get_next_member(struct memberiterator *it, struct pdsmember **member) { struct pdsmember *memtmp; if (!it->memberi) memtmp = util_list_start(it->ds->memberlist); else memtmp = util_list_next(it->ds->memberlist, it->memberi); *member = memtmp; if (!memtmp) return EPERM; it->memberi = memtmp; return 0; } /** * @brief Subroutine of raw_vtoc_get_datasetpart_from_dscb * * Check the validity of the extent and copy it to the extent array in the * datasetpart. * @param[in] extent Pointer to the extent that is to be copied. * @param[in] dsp The target datasetpart. * @return 0 on success, otherwise one of the following error codes: * - EPROTO The extent is not valid. */ static int copy_extent_to_datasetpart(extent_t *extent, struct datasetpart *dsp) { /* sanity check: if the extent is valid then make sure that seqno * will not cause us to go beyond the array limits */ if (extent->typeind && extent->seqno >= MAXEXTENTS) return EPROTO; if (extent->typeind) dsp->ext[extent->seqno] = *extent; return 0; } /** * @brief Subroutine of raw_vtoc_get_datasetpart_from_dscb */ static int raw_vtoc_add_extent_error_message(struct raw_vtoc *rv) { return errorlog_add_message( &rv->log, NULL, EPROTO, "vtoc: an extent descriptor is not valid \n"); } /** * @brief Subroutine of create_dataset_from_dscb * * This function copies the necessary data from a format 1/8 DSCB * into a given datasetpart structure. * @param[in] rv The raw_vtoc that f1 belongs to. * @param[in] f1 The f1/f8 DSCB that the datasetpart is based on. * @param[in] dsp The target datasetpart. * @return 0 on success, otherwise one of the following error codes: * - EPROTO Invalid data in the DSCB or dependent DSCBs. */ static int raw_vtoc_get_datasetpart_from_dscb(struct raw_vtoc *rv, format1_label_t *f1, struct datasetpart *dsp) { format3_label_t *f3; format9_label_t *f9; struct dscb *dscb; int rc, j; errorlog_clear(rv->log); memset(dsp, 0, sizeof(*dsp)); dsp->f1 = f1; /* Find the first format 3 DSCB that is chained format 1 or 8 DSCB. * In a format 8 dscb we will first have one or more format 9 * DSCBs that we need to pass over. */ rc = lzds_raw_vtoc_get_dscb_from_cchhb(rv, &f1->DS1PTRDS, &dscb); while (!rc && dscb && dscb->fmtid == 0xf9) { f9 = (format9_label_t *)dscb; rc = lzds_raw_vtoc_get_dscb_from_cchhb(rv, &f9->DS9PTRDS, &dscb); } if (rc) return errorlog_add_message( &rv->log, rv->log, EPROTO, "vtoc: format 9 DSCB chain not valid \n"); /* We may or may not have a format 3 DSCB */ f3 = (dscb && dscb->fmtid == 0xf3) ? (format3_label_t *)dscb : NULL; /* In any case we have three extents in the f1/8 label itself */ rc = copy_extent_to_datasetpart(&f1->DS1EXT1, dsp); if (rc) return raw_vtoc_add_extent_error_message(rv); rc = copy_extent_to_datasetpart(&f1->DS1EXT2, dsp); if (rc) return raw_vtoc_add_extent_error_message(rv); rc = copy_extent_to_datasetpart(&f1->DS1EXT3, dsp); if (rc) return raw_vtoc_add_extent_error_message(rv); /* now follow the f3 chain */ while (f3) { if (f3->DS3FMTID != 0xf3) return errorlog_add_message( &rv->log, rv->log, EPROTO, "vtoc: format 3 DSCB not valid \n"); for (j = 0; j < 4; ++j) { rc = copy_extent_to_datasetpart(&f3->DS3EXTNT[j], dsp); if (rc) return raw_vtoc_add_extent_error_message(rv); } for (j = 0; j < 9; ++j) { rc = copy_extent_to_datasetpart(&f3->DS3ADEXT[j], dsp); if (rc) return raw_vtoc_add_extent_error_message(rv); } rc = lzds_raw_vtoc_get_dscb_from_cchhb(rv, &f3->DS3PTRDS, (struct dscb **)&f3); if (rc) return errorlog_add_message( &rv->log, rv->log, EPROTO, "vtoc: format 3 DSCB reference not valid\n"); } return 0; } /** * @brief Subroutine of lzds_zdsroot_extract_datasets_from_dasd * * This functions takes the data of a format 1/8 label, fills in * a given struct dataset and creates exactly one dataset part. * In case of a multi volume data set this part may not be the the * first in the ds->dsp array, but is placed according to its * volume sequence number! * @param[in] dasd The dasd the data set belongs to. * @param[in] f1 The f1/f8 DSCB that the dataset(part) is based on. * @param[in] ds A dataset structure that will be filled with data, * in particular a data set part. * @return 0 on success, otherwise one of the following error codes: * - ENOMEM Could not allocate structure due to lack of memory. * - EPROTO Invalid data in the DSCB: An extent is not valid. */ static int create_dataset_from_dscb(struct dasd *dasd, format1_label_t *f1, struct dataset *ds) { struct datasetpart *dsp; char *end; int rc; int dspindex; errorlog_clear(dasd->log); memset(ds, 0, sizeof(*ds)); dsp = malloc(sizeof(*dsp)); if (!dsp) return ENOMEM; /* convert EBCDIC fixed length name into ascii 0-terminated string */ strncpy(ds->name, f1->DS1DSNAM, MAXDSNAMELENGTH - 1); vtoc_ebcdic_dec(ds->name, ds->name, MAXDSNAMELENGTH - 1); end = strchr(ds->name, ' '); if (end) *end = 0; rc = raw_vtoc_get_datasetpart_from_dscb(dasd->rawvtoc, f1, dsp); if (rc) { free(dsp); return errorlog_add_message( &dasd->log, dasd->rawvtoc->log, rc, "create data sets: get data set part failed for %s\n", ds->name); } dsp->dasdi = dasd; dspindex = f1->DS1VOLSQ - 1; if (dspindex < 0 || dspindex >= MAXVOLUMESPERDS) { free(dsp); return errorlog_add_message( &dasd->log, NULL, EPROTO, "create data sets: data set sequence number " "out of bounds failed for %s\n", ds->name); } ds->dsp[dspindex] = dsp; ds->dspcount = 1; /* Note: we cannot tell the difference between the first volume of * a multi volume data set and a single volume data set, * so the following is just a first assumption */ if (dspindex == 0) ds->iscomplete = 1; else ds->iscomplete = 0; return 0; } /** * @brief Subroutine of extract_members_from_track * * Take the information from a pds_member_entry, create a new pdsmember * and add it to the datasets memberlist * @param[in] ds The dataset that the new struct pdsmember will be added to. * @param[in] memberentry The PDS directory entry that describes the member. * @return 0 on success, otherwise one of the following error codes: * - ENOMEM Could not allocate structure due to lack of memory. */ static int dataset_add_member(struct dataset *ds, struct pds_member_entry *memberentry) { char name[9]; char *end; struct pdsmember *member; /* convert name to ascii and truncate trailing spaces */ strncpy(name, memberentry->name, 8); name[8] = 0; vtoc_ebcdic_dec(name, name, 8); end = strchr(name, ' '); if (end) *end = 0; member = malloc(sizeof(*member)); if (!member) return ENOMEM; memset(member, 0, sizeof(*member)); strcpy(member->name, name); member->track = memberentry->track; member->record = memberentry->record; member->is_alias = memberentry->is_alias; util_list_add_tail(ds->memberlist, member); return 0; } /** * @brief Helper function that removes and frees all elements in the * member list in a struct dataset. * * @param[in] ds The dataset whose memberlist is to be freed. */ static void dataset_free_memberlist(struct dataset *ds) { struct pdsmember *member, *next; if (!ds->memberlist) return; util_list_iterate_safe(ds->memberlist, member, next) { util_list_remove(ds->memberlist, member); errorlog_free(member->log); free(member); } util_list_free(ds->memberlist); ds->memberlist = NULL; } /** * @brief Helper function that just checks if the type of an extend * indicates that it contains user data or not. * * @param[in] ext The extent that gets evaluated. * @return 1 if the extent contains user data, 0 otherwise. */ static int extent_contains_userdata(extent_t *ext) { return ((ext->typeind == 0x01) || (ext->typeind == 0x81)); } /** * @brief Subroutine of dataset_member_analysis. * * This function parses one track of a PDS directory and adds all found * members to the dataset. A PDS directory may span more than one track. * The variable dirend is used to indicate the end of the directory. * * @note In case of an error there is no cleanup done for the data set. * * @param[in] trackdata The raw track that contains the PDS directory. * @param[in] ds The dataset the found members will be added to. * @param[out] dirend If the end of the directory is found, dirend is * set to 1, else it is 0. * @return 0 on success, otherwise one of the following error codes: * - ENOMEM Could not allocate structure due to lack of memory. * - EPROTO The track layout is not valid. */ static int extract_members_from_track(char *trackdata, struct dataset *ds, int *dirend) { char *record, *data; int r; struct eckd_count *ecount; int used_bytes, residual, user_data_size; struct pds_member_entry *member; int rc; *dirend = 0; record = NULL; r = 0; while (!buffer_get_next_record(trackdata, RAWTRACKSIZE, &record)) { /* jump over record zero */ if (r == 0) { ++r; continue; } data = record; ecount = (struct eckd_count *)data; /* sanity check: do key and data length match the format of * a directory record? */ if ((ecount->kl != PDS_DIR_KL) || (ecount->dl != PDS_DIR_DL)) return errorlog_add_message( &ds->log, NULL, EPROTO, "member analysis: directory record layout" " not valid, offset %lu\n", (unsigned long)ecount - (unsigned long)trackdata); data += sizeof(*ecount); /* compare key to directory end token */ if ((*(unsigned long long *)data) == ENDTOKEN) *dirend = 1; data += ecount->kl; /* First element in the data area are two bytes that denote how * may bytes of the data area are used for directory entries. * This number includes the first two bytes. */ used_bytes = (*(unsigned short *)data); residual = used_bytes - sizeof(unsigned short); data += sizeof(unsigned short); /* Loop over directory entries in record */ while (residual > 0) { /* A pseudo directory entry marks directory end */ if ((*(unsigned long long *)data) == ENDTOKEN) { *dirend = 1; /* should already be set */ break; } member = (struct pds_member_entry *)data; rc = dataset_add_member(ds, member); if (rc) return rc; /* A directory entry may contain a user data part * that follows the pds_member_entry structure. */ user_data_size = 2 * member->user_data_count; data += sizeof(*member) + user_data_size; residual -= (sizeof(*member) + user_data_size); } ++r; if (*dirend) break; } return 0; } /** * @brief Subroutine of lzds_zdsroot_extract_datasets_from_dasd. * * This function checks if a data set is a PDS, analyzes the PDS directory * and creates a corresponding list of struct pdsmember in the dataset. * * @param[in] ds The dataset that is to be analyzed and the found * members will be added to. * @return 0 on success, otherwise one of the following error codes: * - ENOMEM Could not allocate structure due to lack of memory. * - EPROTO The track layout is not valid. * - EINVAL An internal error happened. * - EIO An error happened while reading data from disk. */ static int dataset_member_analysis(struct dataset *ds) { char *trackdata; unsigned int extstarttrk, extendtrk, currenttrack; int j; int dirend; struct datasetpart *dsp; struct dasd *dasd; struct dasdhandle *dasdh; int rc, rc2; int issupported; errorlog_clear(ds->log); rc2 = 0; /* a partitioned data set has only one volume, so we only need dsp[0] */ dsp = ds->dsp[0]; /* if it is not a partitioned data set, do nothing */ if (!dsp || !(dsp->f1->DS1DSRG1 & 0x02)) return 0; /* do not do member analysis if we do not support the format (PDSE) */ lzds_dataset_get_is_supported(ds, &issupported); if (!issupported) return 0; dasd = dsp->dasdi; dataset_free_memberlist(ds); ds->memberlist = util_list_new(struct pdsmember, list); /* track buffer must be page aligned for O_DIRECT */ trackdata = memalign(4096, RAWTRACKSIZE); if (!trackdata) return ENOMEM; rc = lzds_dasd_alloc_dasdhandle(dasd, &dasdh); if (rc) goto out1; rc = lzds_dasdhandle_open(dasdh); if (rc) { errorlog_add_message( &ds->log, dasdh->log, rc, "member analysis: could not open dasdhandle\n"); goto out2; } dirend = 0; /* loop over all extents in dataset*/ for (j = 0; j < MAXEXTENTS; ++j) { if (!extent_contains_userdata(&dsp->ext[j])) continue; lzds_dasd_cchh2trk(dasd, &dsp->ext[j].llimit, &extstarttrk); lzds_dasd_cchh2trk(dasd, &dsp->ext[j].ulimit, &extendtrk); currenttrack = extstarttrk; /* loop over tracks in extent */ while (currenttrack <= extendtrk) { rc = lzds_dasdhandle_read_tracks_to_buffer( dasdh, currenttrack, currenttrack, trackdata); if (rc) { errorlog_add_message( &ds->log, dasdh->log, rc, "member analysis: read error\n"); goto out4; } rc = extract_members_from_track(trackdata, ds, &dirend); if (rc) { errorlog_add_message( &ds->log, ds->log, rc, "member analysis: error " "extracting members from track %u\n", currenttrack); goto out4; } currenttrack++; if (dirend) break; } if (dirend) break; } rc = 0; goto out3; out4: dataset_free_memberlist(ds); out3: rc2 = lzds_dasdhandle_close(dasdh); /* report close error only if we had no read error */ if (rc2 && !rc) { errorlog_add_message( &ds->log, dasdh->log, rc, "member analysis: could not close dasdhandle\n"); rc = rc2; } out2: lzds_dasdhandle_free(dasdh); out1: free(trackdata); rc = rc ? rc : rc2; return rc; } /** * @brief Subroutine of zdsroot_merge_dataset * * Merge two dataset structures that are two halves of a multi volume data set. * All datasetparts of the second dataset are copied to the first dataset. * * @param[in] baseds The dataset that the data will be merged into. * @param[in] newds The dataset that will be merged with baseds. * This strucure can be freed after the merge, but do not * free the data set parts it contained, as those belong * to baseds now. * @return 0 on success, otherwise one of the following error codes: * - EPROTO The data is not mergable because of conflicting entries. */ static int dataset_merge_dataset(struct dataset *baseds, struct dataset *newds) { int k, l, dspcount; for (k = 0; k < MAXVOLUMESPERDS; ++k) { /* if both datasets have a part in position k, * then something is wrong */ if (baseds->dsp[k] && newds->dsp[k]) return errorlog_add_message( &baseds->log, NULL, EPROTO, "merge dataset: " "part %d was previously found on device %s\n", k, baseds->dsp[k]->dasdi->device); /* if the new data set has a part that is not present in the * base data set, than copy the dsp pointer to the base */ if (!baseds->dsp[k] && newds->dsp[k]) { /* Each format 1/8 DSCB of a part in a multi volume data * set has a reference to the volume serial of the first * volume. Need to verify that the new data set parts * refer to the correct volume serial in f1->DS1DSSN. * Since dsp[0] may not be set yet, we loop over the * base dsp array until we find an entry. */ for (l = 0; l < MAXVOLUMESPERDS; ++l) if (baseds->dsp[l]) { if (memcmp(baseds->dsp[l]->f1->DS1DSSN, newds->dsp[k]->f1->DS1DSSN, MAXVOLSER)) return errorlog_add_message( &baseds->log, NULL, EPROTO, "merge dataset: part %d has incompatible" " base volume serial\n", k); else break; } baseds->dsp[k] = newds->dsp[k]; baseds->dspcount++; } } /* check for completeness: * If element (dspcount - 1) exists and is the last part in a multi * volume data set, then all other parts must have been found as well. */ dspcount = baseds->dspcount; if (baseds->dsp[dspcount - 1] && (baseds->dsp[dspcount - 1]->f1->DS1DSIND & 0x80)) baseds->iscomplete = 1; else baseds->iscomplete = 0; /* The last statement is only true for a correct multi volume data set. * Since the data on the DASDs may be incorrect and we will rely later * on the fact that the first dspcount elements of the dsp array are * valid, we must make sure that they are all filled. */ if (baseds->iscomplete) for (l = 0; l < baseds->dspcount; ++l) if (!baseds->dsp[l]) { baseds->iscomplete = 0; return errorlog_add_message( &baseds->log, NULL, EPROTO, "merge dataset: inconsistent data set" " part list at index %d\n", l); } return 0; } /** * @brief Subroutine of lzds_zdsroot_extract_datasets_from_dasd * * Takes the data from newds and merges it with a matching dataset in * root. If no matching dataset exists yet, a new struct dataset is * created, so that the caller of this function can release newds in * any case. * It is important to note that while newds is just a temporary * structure that can be released after the function returns, the * elements and structures that are contained by newds (e.g the * datasetparts) are transferred to the struct dataset in root and must * not be released. * * @param[in] root The zdsroot that the dataset will be merged into. * @param[in] newds The dataset that will be merged. * @return 0 on success, otherwise one of the following error codes: * - ENOMEM Could not allocate internal structure due to lack of memory. * - EPROTO The data is not mergable because of conflicting entries. */ static int zdsroot_merge_dataset(struct zdsroot *root, struct dataset *newds) { struct dataset *rootds; int rc; /* first, try to find a matching data set in the old list */ rc = lzds_zdsroot_find_dataset(root, newds->name, &rootds); if (!rc) { /* match found */ rc = dataset_merge_dataset(rootds, newds); if (rc) return errorlog_add_message( &root->log, rootds->log, rc, "merge dataset: " "merge with existing data set failed\n"); } else if (rc == ENOENT) { /* no match found */ rootds = malloc(sizeof(*rootds)); if (!rootds) return ENOMEM; memcpy(rootds, newds, sizeof(*rootds)); util_list_add_tail(root->datasetlist, rootds); } else return rc; return 0; } /** * This function finds all data set descriptions in the VTOC of the * dasd and creates respective struct dataset representations. These * struct dataset are stored in the zdsroot and can later be traversed * using a dsiterator. In case that it finds a dataset that is * already present in the zdsroot, it verifies that both are parts of * the same multivolume data set and then merges the new data with the * existing struct dataset. If the conflicting data sets are indeed * individual data sets and not parts of a single one, the function * returns an error. * * @param[in] root The zdsroot that the dataset will be merged into. * @param[in] dasd The datasets found in this dasd will be merged. * @return 0 on success, otherwise one of the following error codes: * - ENOMEM Could not allocate structure due to lack of memory. * - EPROTO The data is not mergable because of conflicting entries, * or invalid data in the VTOC of the dasd. */ int lzds_zdsroot_extract_datasets_from_dasd(struct zdsroot *root, struct dasd *dasd) { format1_label_t *f1; struct dscb *dscb; struct dscbiterator *it; int rc; struct dataset tmpds; int i; errorlog_clear(root->log); memset(&tmpds, 0, sizeof(tmpds)); rc = lzds_raw_vtoc_alloc_dscbiterator(dasd->rawvtoc, &it); if (rc) return ENOMEM; while (!lzds_dscbiterator_get_next_dscb(it, &dscb)) { if (dscb->fmtid == 0xf1 || dscb->fmtid == 0xf8) { f1 = (format1_label_t *)dscb; rc = create_dataset_from_dscb(dasd, f1, &tmpds); if (rc) { errorlog_add_message( &root->log, dasd->log, rc, "extract data sets: " "creating dataset failed for %s\n", dasd->device); break; } rc = dataset_member_analysis(&tmpds); if (rc) { errorlog_add_message( &root->log, tmpds.log, rc, "extract data sets: " "member analysis failed for %s\n", tmpds.name); break; } rc = zdsroot_merge_dataset(root, &tmpds); if (rc) { errorlog_add_message( &root->log, root->log, rc, "extract data sets: " "merge dataset failed for %s\n", tmpds.name); break; } } } if (rc) { dataset_free_memberlist(&tmpds); for (i = 0; i < MAXVOLUMESPERDS; ++i) free(tmpds.dsp[i]); errorlog_free(tmpds.log); } lzds_dscbiterator_free(it); return rc; } /** * @brief Subroutine of lzds_dataset_get_size_in_tracks * * Computes the number of tracks in a given extent. * Returns 0 for anything but user data. * @param[in] ext The extent we want to know the size of. * @param[in] dasd The dasd that the extent is located on. * @return Number of tracks the extent contains */ static unsigned int get_extent_size_in_tracks(extent_t *ext, struct dasd *dasd) { unsigned int starttrck, endtrck; if (!extent_contains_userdata(ext)) return 0; lzds_dasd_cchh2trk(dasd, &ext->llimit, &starttrck); lzds_dasd_cchh2trk(dasd, &ext->ulimit, &endtrck); return endtrck - starttrck + 1; } /** * @param[in] ds The dataset we we want to know the size of. * @param[out] tracks Reference to a return buffer for the number of tracks. */ void lzds_dataset_get_size_in_tracks(struct dataset *ds, unsigned long long *tracks) { unsigned long long sumtracks; int i, j; *tracks = 0; sumtracks = 0; for (i = 0; i < MAXVOLUMESPERDS; ++i) if (ds->dsp[i]) for (j = 0; j < MAXEXTENTS; ++j) sumtracks += get_extent_size_in_tracks( &ds->dsp[i]->ext[j], ds->dsp[i]->dasdi); *tracks = sumtracks; } /** * @param[in] member The PDS member we want to know the name of. * @param[out] name Reference to a pointer variable in which a pointer to * the name string will be returned. */ void lzds_pdsmember_get_name(struct pdsmember *member, char **name) { *name = member->name; } /** * @param[in] ds The dataset we want to know the name of. * @param[out] name Reference to a pointer variable in which a pointer to * the name string will be returned. */ void lzds_dataset_get_name(struct dataset *ds, char **name) { *name = ds->name; } /** * @param[in] ds Is this dataset a PDS? * @param[out] ispds Reference to a pointer variable in which * 1 (true) or 0 (false) is returned. */ void lzds_dataset_get_is_PDS(struct dataset *ds, int *ispds) { if (ds->dsp[0]->f1->DS1DSRG1 & 0x02) /* is PDS */ *ispds = 1; else *ispds = 0; } /** * The returned DSCB belongs always to the first volume of a data set. * * @param[in] ds The dataset we want to know the DSCB of. * @param[out] f1 Reference to a pointer variable in which a pointer to * the format 1 DSCB will be returned. */ void lzds_dataset_get_format1_dscb(struct dataset *ds, format1_label_t **f1) { *f1 = ds->dsp[0]->f1; } /** * @param[in] ds Is this dataset complete? * @param[out] iscomplete Reference to a pointer variable in which * 1 (true) or 0 (false) is returned. */ void lzds_dataset_get_is_complete(struct dataset *ds, int *iscomplete) { *iscomplete = ds->iscomplete; } /** * @param[in] ds Is this dataset supported? * @param[out] issupported Reference to a pointer variable in which * 1 (true) or 0 (false) is returned. */ void lzds_dataset_get_is_supported(struct dataset *ds, int *issupported) { int complete, org_supported, format_supported, not_ext_fmt; char DS1RECFM; if (!ds->dsp[0]) { *issupported = 0; return; } /* do we have all parts of the data set? */ lzds_dataset_get_is_complete(ds, &complete); /* is this a supported organisation (PS or PDS)?*/ org_supported = 0; if ((ds->dsp[0]->f1->DS1DSRG1 & 0x40) || /* PS */ (ds->dsp[0]->f1->DS1DSRG1 & 0x02)) /* PDS */ org_supported = 1; /* extended format datasets are not supported */ not_ext_fmt = 0; if (!(ds->dsp[0]->f1->DS1SMSFG & 0x0C)) not_ext_fmt = 1; /* fixed, variable or undefined length records are supported */ DS1RECFM = ds->dsp[0]->f1->DS1RECFM; format_supported = 0; if (DS1RECFM & 0xC0) format_supported = 1; /* track overflow (legacy) is not supported */ if ((DS1RECFM & 0x20)) format_supported = 0; /* all other RECFM flags are modifiers of the above and are supported */ *issupported = complete && org_supported && format_supported && not_ext_fmt; return; } /** * @param[in] ds The dataset that is searched for the member. * @param[in] membername The name of the member (ASCII string). * @param[out] member Reference to a pointer variable in which the found * pdsmember is returned. If no member is found, this * is set to NULL. * @return 0 on success, otherwise one of the following error codes: * - ENOMEM Could not allocate internal structure due to lack of memory. * - ENOENT No matching member was found. */ int lzds_dataset_get_member_by_name(struct dataset *ds, char *membername, struct pdsmember **member) { struct memberiterator *it; struct pdsmember *tmpmember; int rc; errorlog_clear(ds->log); *member = NULL; rc = lzds_dataset_alloc_memberiterator(ds, &it); if (rc) return ENOMEM; while (!lzds_memberiterator_get_next_member(it, &tmpmember)) { if (!strcmp(tmpmember->name, membername)) { *member = tmpmember; break; } } lzds_memberiterator_free(it); if (!*member) return ENOENT; return 0; } /** * @param[in] dsh Pointer to structure that is to be freed. */ void lzds_dshandle_free(struct dshandle *dsh) { int i; if (!dsh) return; for (i = 0; i < MAXVOLUMESPERDS; ++i) if (dsh->dasdhandle[i]) lzds_dasdhandle_free(dsh->dasdhandle[i]); free(dsh->databuffer); free(dsh->rawbuffer); if (dsh->seekbuf) free(dsh->seekbuf); errorlog_free(dsh->log); free(dsh); } /** * @param[in] ds The dataset we want to read from. * @param[in] tracks_per_frame The number of tracks that the internal buffers * can hold. If 0, then the default value 128 is used. * @param[out] dsh Reference to a pointer variable which will be used * to store the new dshandle. * @return 0 on success, otherwise one of the following error codes: * - ENOMEM Could not allocate structure due to lack of memory. */ int lzds_dataset_alloc_dshandle(struct dataset *ds, unsigned int tracks_per_frame, struct dshandle **dsh) { struct dshandle *dshtmp; int i, rc; dshtmp = malloc(sizeof(*dshtmp)); if (!dshtmp) return ENOMEM; memset(dshtmp, 0, sizeof(*dshtmp)); for (i = 0; i < ds->dspcount; ++i) { rc = lzds_dasd_alloc_dasdhandle(ds->dsp[i]->dasdi, &dshtmp->dasdhandle[i]); if (rc) { lzds_dshandle_free(dshtmp); return rc; } } if (tracks_per_frame) dshtmp->tracks_per_frame = tracks_per_frame; else dshtmp->tracks_per_frame = TRACK_BUFFER_DEFAULT; dshtmp->rawbufmax = dshtmp->tracks_per_frame * RAWTRACKSIZE; /* track buffer must be page aligned for O_DIRECT */ dshtmp->rawbuffer = memalign(4096, dshtmp->rawbufmax); if (!dshtmp->rawbuffer) { lzds_dshandle_free(dshtmp); return ENOMEM; } dshtmp->databufmax = dshtmp->tracks_per_frame * MAXRECSIZE; dshtmp->databuffer = malloc(dshtmp->databufmax); if (!dshtmp->databuffer) { lzds_dshandle_free(dshtmp); return ENOMEM; } dshtmp->ds = ds; *dsh = dshtmp; return 0; } /** * The number of user data bytes per track is not predictable as record * sizes and number of records per track may vary. Seeking forward will * always require us to read all the data between the current position * and the seek target. To improve performance of seeking backwards * we can buffer previous positions in the data set. * For a given seek buffer size and the known number of tracks of the * data set, we can compute how many track frames we need to skip if * we and to store track frames in regular intervals. * * @param[in] dsh The dshandle we want to modify. * @param[in] seek_buffer_size The maximum number of bytes to be allocated * for the seek buffer. * @return 0 on success, otherwise one of the following error codes: * - ENOMEM Could not allocate structure due to lack of memory. */ int lzds_dshandle_set_seekbuffer(struct dshandle *dsh, unsigned long long seek_buffer_size) { unsigned long long totaltracks; size_t entries, frames; unsigned int extents, skip; struct dataset *ds; int i, j; unsigned long long buf_count; errorlog_clear(dsh->log); if (dsh->seekbuf) free(dsh->seekbuf); dsh->seekbuf = NULL; dsh->seek_count = 0; dsh->seek_current = 0; dsh->skip = 0; if (!seek_buffer_size) return 0; ds = dsh->ds; lzds_dataset_get_size_in_tracks(ds, &totaltracks); /* compute the total number of extents */ extents = 0; for (i = 0; i < ds->dspcount; ++i) for (j = 0; j < MAXEXTENTS; ++j) if (ds->dsp[i]->ext[j].typeind != 0x00) ++extents; entries = seek_buffer_size / sizeof(struct seekelement); /* track frames at the end of an extent may be shorter, * increasing the maximum number of frames we need to read */ frames = (totaltracks / dsh->tracks_per_frame) + 1 + extents; skip = (frames / entries) + 1; buf_count = (frames / skip) + 1; dsh->seekbuf = malloc(buf_count * sizeof(struct seekelement)); if (!dsh->seekbuf) return ENOMEM; memset(dsh->seekbuf, 0, buf_count * sizeof(struct seekelement)); dsh->seek_count = buf_count; dsh->skip = skip; return 0; } /** * If dsh points to a partitioned data set, the library needs to know * which member of that PDS should be read. So this function must be * called before lzds_dshandle_open. This setting cannot be changed * for open dsh, so this function must not be used after * lzds_dshandle_open, unless the dsh has been closed with * lzds_dsh_close again. * * @pre The dsh must not be open when this function is called. * * @param[in] dsh The dshandle we want to modify. * @param[in] membername The name of the member that shall be read via * this handle. * @return 0 on success, otherwise one of the following error codes: * - ENOMEM Could not allocate internal structure due to lack of memory. * - ENOENT No matching member was found. * - EBUSY The handle is already open. * - EINVAL The data set is not a PDS. */ int lzds_dshandle_set_member(struct dshandle *dsh, char *membername) { int ispds, rc; struct pdsmember *member; errorlog_clear(dsh->log); if (dsh->is_open) return errorlog_add_message( &dsh->log, NULL, EBUSY, "dshandle: cannot set member while handle is open\n"); dsh->member = NULL; lzds_dataset_get_is_PDS(dsh->ds, &ispds); if (!ispds) return errorlog_add_message( &dsh->log, NULL, EINVAL, "dshandle: cannot set member, not a PDS\n"); rc = lzds_dataset_get_member_by_name(dsh->ds, membername, &member); if (rc) return errorlog_add_message( &dsh->log, NULL, rc, "dshandle: could not find member %s in dataset %s\n", membername, dsh->ds->name); dsh->member = member; return 0; } /** * @param[in] dsh The dshandle that we want to know the member of. * @param[out] member Reference to a pointer variable in which the found * pdsmember is returned. If no member has been set * before, this is set to NULL. */ void lzds_dshandle_get_member(struct dshandle *dsh, struct pdsmember **member) { *member = dsh->member; } /** * @pre The dsh must not be open when this function is called. * * @param[in] dsh The dshandle we want to modify. * @param[in] keepRDW Set this to 1 to enable the keep RDW feature or * 0 to disable it. * @return 0 on success, otherwise one of the following error codes: * - EBUSY The handle is already open. */ int lzds_dshandle_set_keepRDW(struct dshandle *dsh, int keepRDW) { errorlog_clear(dsh->log); if (dsh->is_open) return errorlog_add_message( &dsh->log, NULL, EBUSY, "dshandle: cannot set RDW while handle is open\n"); dsh->keepRDW = keepRDW; return 0; } /** * @param[in] dsh The dshandle that we want to know the member of. * @param[out] keepRDW Reference to a variable in which the previously * set keepRDW value is returned. */ void lzds_dshandle_get_keepRDW(struct dshandle *dsh, int *keepRDW) { *keepRDW = dsh->keepRDW; } /** * @brief Helper function that initializes the given handle so that it * points to the beginning of the dataset or member. * * @param[in] dsh The dshandle that keeps track of the I/O operations. * @return 0 on success, otherwise one of the following error codes: * - EPROTO The dataset data is inconsistent. */ static int initialize_buffer_positions_for_first_read(struct dshandle *dsh) { unsigned long long tracksum, extentsize; unsigned int starttrck, endtrck; int j; /* make sure that read knows that we have no ready data in our buffer */ dsh->bufpos = 0; dsh->databufsize = 0; dsh->databufoffset = 0; dsh->eof_reached = 0; /* we need to set the bufendtrk and sequence number so, * that the current track buffer seems to end with the * track that comes before the first track of the * data set or member */ /* When we read the first track frame this will be incremented to 0 */ dsh->frameno = -1; /* We allways start with data set part 0. Partitioned * data sets have only one part, so this correct for * both partitioned and non partitioned data sets. */ dsh->dsp_no = 0; /* for a non partitioned data set we just need to set the * extentsequence number to -1 so read will start with the * first track of extent number 0 */ if (!dsh->member) { dsh->ext_seq_no = -1; dsh->bufstarttrk = 0; dsh->bufendtrk = 0; dsh->extstarttrk = 0; dsh->extendtrk = 0; return 0; } /* sanity check: a partitioned data set cannot be a multi volume data * set. */ if (dsh->ds->dspcount != 1) return errorlog_add_message( &dsh->log, NULL, EPROTO, "initialize read buffer: dataset %s is inconsistent," " PDS must not span more than one volume\n", dsh->ds->name); /* For a partitioned data set we need to find the correct start * track and point the current buffer just before it. * As we always need to read full tracks, any additional * record offset will be set explicitly and handled during * track interpretation. */ dsh->startrecord = dsh->member->record; /* member->track is an offset based on the start of the data set * I will have to add up extents until I have got the right number * of tracks */ tracksum = 0; /* Note: No need to loop over all data set parts, a PDS has only one */ for (j = 0; j < MAXEXTENTS; ++j) { if (!extent_contains_userdata(&dsh->ds->dsp[0]->ext[j])) continue; lzds_dasd_cchh2trk(dsh->ds->dsp[0]->dasdi, &dsh->ds->dsp[0]->ext[j].llimit, &starttrck); lzds_dasd_cchh2trk(dsh->ds->dsp[0]->dasdi, &dsh->ds->dsp[0]->ext[j].ulimit, &endtrck); extentsize = endtrck - starttrck + 1; /* If offset in the extent (member->track - tracksum) == 0, * then we must set the dsh buffer to the end of the previous * extent, so that rdf_read will start with the first track * of the next extent. * However, since rdf_read checks for bufendtrk < extendtrk * we can set both to 0 and do not need a special case for the * first extend. */ if (dsh->member->track == tracksum) { dsh->ext_seq_no = j - 1; dsh->bufendtrk = 0; dsh->extendtrk = 0; break; } /* If the offset is within the current extent an not the * special case above, then we can need to adjust the dsh so, * as if we have just already read data up to the track before * our target track */ if (dsh->member->track < tracksum + extentsize) { dsh->ext_seq_no = j; dsh->extstarttrk = starttrck; dsh->extendtrk = endtrck; dsh->bufstarttrk = dsh->extstarttrk; dsh->bufendtrk = dsh->bufstarttrk + (dsh->member->track - tracksum) - 1; break; } tracksum += extentsize; } return 0; } /** * @param[in] dsh The dshandle that keeps track of the I/O operations. */ void lzds_dshandle_close(struct dshandle *dsh) { int i; for (i = 0; i < MAXVOLUMESPERDS; ++i) if (dsh->dasdhandle[i]) lzds_dasdhandle_close(dsh->dasdhandle[i]); dsh->is_open = 0; } /** * This makes the data set context ready for read operations. * All settings on the dsh must be done before it is opened. * @pre For a partitioned data set a member must be set before * this function is called. * * @param[in] dsh The dshandle that keeps track of the I/O operations. * @return 0 on success, otherwise one of the following error codes: * - ENOTSUP The dataset is of a type that is not supported. * - EINVAL Tried to open a PDS without setting a member before.. * - EIO Could not open underlying device. */ int lzds_dshandle_open(struct dshandle *dsh) { int i, rc; int ispds, issupported; /* sanity check: Open will fail if the data set type is not supported. * We do this check here and not during dshandle creation, as it may * depend on settings on the dshandle that the user has to make * between creation and open. */ errorlog_clear(dsh->log); lzds_dataset_get_is_supported(dsh->ds, &issupported); if (!issupported) return errorlog_add_message( &dsh->log, NULL, ENOTSUP, "data set open: data set %s is not supported\n", dsh->ds->name); lzds_dataset_get_is_PDS(dsh->ds, &ispds); if (ispds && !dsh->member) return errorlog_add_message( &dsh->log, NULL, EINVAL, "data set open: a member must be set" " before PDS %s can be opened\n", dsh->ds->name); rc = initialize_buffer_positions_for_first_read(dsh); if (rc) return errorlog_add_message( &dsh->log, dsh->log, rc, "data set open: error when initializing buffers" " for data set %s\n", dsh->ds->name); for (i = 0; i < dsh->ds->dspcount; ++i) { rc = lzds_dasdhandle_open(dsh->dasdhandle[i]); if (rc) { errorlog_add_message( &dsh->log, dsh->dasdhandle[i]->log, rc, "data set open: error opening DASD " "for data set %s\n", dsh->ds->name); lzds_dshandle_close(dsh); return rc; } } dsh->is_open = 1; return 0; } /** * @brief subroutine of dshandle_extract_data_from_trackbuffer * * @param[in] dsh The dshandle that keeps track of the I/O operations. * @param[in] rec Pointer to the raw record. * @param[in] targetdata Pointer to the data buffer. * @return Number of copied data bytes on success, * otherwise one of the following (negative) error codes: * - -EPROTO The record is malformed. */ static ssize_t parse_fixed_record(struct dshandle *dsh, char *rec, char *targetdata) { struct eckd_count *ecount; ecount = (struct eckd_count *)rec; /* Make sure that we do not copy data beyond the end of * the data buffer */ if ((unsigned long)targetdata + ecount->dl > (unsigned long)dsh->databuffer + dsh->databufmax) return - errorlog_add_message( &dsh->log, NULL, EPROTO, "fixed record to long for target buffer\n"); memcpy(targetdata, (rec + sizeof(*ecount) + ecount->kl), ecount->dl); return ecount->dl; } /** * @brief subroutine of dshandle_extract_data_from_trackbuffer * * @param[in] dsh The dshandle that keeps track of the I/O operations. * @param[in] rec Pointer to the raw record. * @param[in] targetdata Pointer to the data buffer. * @param[in] keepRDW Flag that specifies if the RDW should be copied to * the data buffer or or not. * @return Number of copied data bytes on success, * otherwise one of the following (negative) error codes: * - -EPROTO The record is malformed. */ static ssize_t parse_variable_record(struct dshandle *dsh, char *rec, char *targetdata, int keepRDW) { struct eckd_count *ecount; unsigned int blocklength, segmentlength, residual; char *data; struct segment_header *blockhead; struct segment_header *seghead; size_t totaldatalength; /* We must not rely on the data in rec, as it was read from disk and * may be broken. Wherever we interprete the data we must have sanity * checks. */ ecount = (struct eckd_count *)rec; totaldatalength = 0; /* An empty record is expected at the end of dataset or member */ if (ecount->dl == 0) return 0; /* If the data area is not zero but to small to contain a segment header * then the record contents cannot be valid. */ if (ecount->dl < sizeof(struct segment_header)) return - errorlog_add_message( &dsh->log, NULL, EPROTO, "variable record parser: record length to small\n"); data = (rec + sizeof(*ecount) + ecount->kl); blockhead = (struct segment_header *)data; blocklength = blockhead->length; /* If the length in the block descriptor is 0, then the block contains * no data. Not sure if this is a valid case, but we tolerate it. */ if (!blocklength) return totaldatalength; /* If blocklength is to small to contain the block descriptor or to * large to fit in the data area, then the block descriptor is broken */ if ((blocklength < sizeof(*blockhead)) || (blocklength > ecount->dl)) return - errorlog_add_message( &dsh->log, NULL, EPROTO, "variable record parser: block length to small\n"); data += sizeof(*blockhead); residual = blocklength - sizeof(*blockhead); while (residual) { seghead = (struct segment_header *)data; segmentlength = seghead->length; if (seghead->nullsegment || !segmentlength) { /* null segment found -> end of data in block */ return totaldatalength; } /* If segmentlength is to small to contain the record descriptor * descriptor or to large to fit in the residual data area, then * the record descriptor is broken */ if ((residual < segmentlength) || (segmentlength < sizeof(*seghead))) return - errorlog_add_message( &dsh->log, NULL, EPROTO, "variable record parser: segment length %d " "inconsistent at offset %lu\n", segmentlength, (unsigned long)seghead - (unsigned long)rec); residual -= segmentlength; if (!keepRDW) { data += sizeof(*seghead); segmentlength -= sizeof(*seghead); } /* Make sure that we do not copy data beyond the end of * the data buffer */ if ((unsigned long)targetdata + segmentlength > (unsigned long)dsh->databuffer + dsh->databufmax) return - errorlog_add_message( &dsh->log, NULL, EPROTO, "variable record parser: " "record to long for target buffer\n"); memcpy(targetdata, data, segmentlength); targetdata += segmentlength; totaldatalength += segmentlength; data += segmentlength; } return totaldatalength; } /** * @brief subroutine of lzds_dshandle_read * * Parses the raw track buffer in dsh and copies the user data to * the databuffer in dsh. * * @param[in] dsh The dshandle that keeps track of the I/O operations. * @return 0 on success, otherwise one of the following error codes: * - EPROTO The raw track data is malformed. */ static int dshandle_extract_data_from_trackbuffer(struct dshandle *dsh) { char *track; size_t i, trckcount; struct eckd_count *ecount; char *rawdata, *targetdata; unsigned int record; char DS1RECFM; ssize_t tdsize; DS1RECFM = dsh->ds->dsp[0]->f1->DS1RECFM; trckcount = dsh->rawbufsize / RAWTRACKSIZE; track = dsh->rawbuffer; targetdata = dsh->databuffer; dsh->databufsize = 0; /* Record zero is not part of the regular data, so I must not copy its * data. In case of a PDS member, we may need to skip a few extra * records on the first track. In this case startrecord is already set * and will be reset to 1 after the first track has been read. */ if (!dsh->startrecord) dsh->startrecord = 1; for (i = 0; i < trckcount && !dsh->eof_reached; ++i) { record = 0; rawdata = track; while (!dsh->eof_reached) { tdsize = 0; if (record >= dsh->startrecord) { /* fixed or undefined record size */ if ((DS1RECFM & 0x80)) tdsize = parse_fixed_record(dsh, rawdata, targetdata); /* variable records */ if (!(DS1RECFM & 0x80) && (DS1RECFM & 0x40)) tdsize = parse_variable_record(dsh, rawdata, targetdata, dsh->keepRDW); if (tdsize < 0) return errorlog_add_message( &dsh->log, dsh->log, EPROTO, "data extraction: error at " "record %u, offset %lu\n", record, (unsigned long)rawdata - (unsigned long)dsh->rawbuffer); targetdata += tdsize; dsh->databufsize += tdsize; } ecount = (struct eckd_count *)rawdata; rawdata += sizeof(*ecount) + ecount->kl + ecount->dl; /* An empty record marks the end of a member / data set * We need to take startrecord into account or we might * find the end marker of the previous member. */ if ((record >= dsh->startrecord) && (!ecount->kl) && (!ecount->dl)) dsh->eof_reached = 1; ++record; if ((*(unsigned long long *)rawdata) == ENDTOKEN) break; if ((unsigned long)rawdata >= (unsigned long)track + RAWTRACKSIZE) return errorlog_add_message( &dsh->log, NULL, EPROTO, "data extraction: run over end of" " track buffer\n"); } dsh->startrecord = 1; track += RAWTRACKSIZE; } return 0; } /** * @brief subroutine of lzds_dshandle_read * * Find the next range of extents and prepare dsh for the next read. * The return value indicates whether there is more data to read or not. * * @pre: For the first call to this function, dsh should be set to the * last track before the first track to read. * If the first track to read is the first track in the dataset * then set dsh->ext_seq_no to -1. * * @param[in] dsh The dshandle that keeps track of the I/O operations. * * @return * 0 when there is no further raw data available, * 1 when there is more data available and dsh is prepared */ static int dshandle_prepare_for_next_read_tracks(struct dshandle *dsh) { int found, dsp_no, ext_seq_no; /* If there are still unread tracks in the current extent, we just need * to point dsh to the next range of tracks */ if (dsh->bufendtrk < dsh->extendtrk) { dsh->bufstarttrk = dsh->bufendtrk + 1; dsh->bufendtrk = dsh->bufstarttrk + (dsh->rawbufmax / RAWTRACKSIZE) - 1; dsh->bufendtrk = MIN(dsh->bufendtrk, dsh->extendtrk); dsh->rawbufsize = (dsh->bufendtrk - dsh->bufstarttrk + 1) * RAWTRACKSIZE; dsh->databufoffset = dsh->databufoffset + dsh->databufsize; dsh->databufsize = 0; dsh->bufpos = 0; dsh->frameno++; return 1; } /* There are no more tracks left in the current extent. * Loop over data set parts and extends in these parts until a valid * extent is found or the end of the data set is reached */ ext_seq_no = dsh->ext_seq_no; dsp_no = dsh->dsp_no; found = 0; while (!found) { ++ext_seq_no; if (ext_seq_no >= MAXEXTENTS) { ext_seq_no = 0; ++dsp_no; } if (dsp_no >= dsh->ds->dspcount) break; if (extent_contains_userdata( &dsh->ds->dsp[dsp_no]->ext[ext_seq_no])) found = 1; } if (!found) return 0; /* We have found the next valid extent. Get lower and upper track * limits and set dsh to the first range of tracks */ dsh->ext_seq_no = ext_seq_no; dsh->dsp_no = dsp_no; lzds_dasd_cchh2trk(dsh->ds->dsp[dsp_no]->dasdi, &dsh->ds->dsp[dsp_no]->ext[ext_seq_no].llimit, &dsh->extstarttrk); lzds_dasd_cchh2trk(dsh->ds->dsp[dsp_no]->dasdi, &dsh->ds->dsp[dsp_no]->ext[ext_seq_no].ulimit, &dsh->extendtrk); dsh->bufstarttrk = dsh->extstarttrk; dsh->bufendtrk = dsh->bufstarttrk + (dsh->rawbufmax / RAWTRACKSIZE) - 1; dsh->bufendtrk = MIN(dsh->bufendtrk, dsh->extendtrk); dsh->rawbufsize = (dsh->bufendtrk - dsh->bufstarttrk + 1) * RAWTRACKSIZE; dsh->databufoffset = dsh->databufoffset + dsh->databufsize; dsh->databufsize = 0; dsh->bufpos = 0; dsh->frameno++; return 1; } /** * @brief subroutine of lzds_dshandle_read * * As we progress in reading data from the dataset, we store * track/data offsets in the dshandle for late use by the * lzds_dshandle_lseek and related operations. * * @param[in] dsh The dshandle that keeps track of the I/O operations. * * @return 0 on success, otherwise one of the following error codes: * - EPROTO The existing seek buffer data is inconsistent. * - EINVAL The existing seek buffer data is inconsistent. * - ERANGE We try to add more elements than the prepared buffer can hold. */ static int dshandle_store_trackframe(struct dshandle *dsh) { unsigned long long index; /* if we have no skip or seekbuf we cannot store anything */ if (!dsh->skip || !dsh->seekbuf) return 0; /* if this is a frame we want to skip, just return 0 */ if (dsh->frameno % dsh->skip) return 0; /* make sure we do not access elements beyond the end of the buffer */ if (dsh->seek_current >= dsh->seek_count) return errorlog_add_message( &dsh->log, NULL, ERANGE, "store track frame: frame list size is inconsistent\n"); /* our seek code relies on the fact that element n refers to frame * n * skip, so we need to make sure we that we do not leave gaps */ index = dsh->frameno / dsh->skip; if (index > dsh->seek_current) return errorlog_add_message( &dsh->log, NULL, EPROTO, "store track frame: frame list inconsistent\n"); /* if we have visited this frame before, return */ if (index < dsh->seek_current) { if (dsh->seekbuf[index].dsp_no != dsh->dsp_no || dsh->seekbuf[index].ext_seq_no != dsh->ext_seq_no || dsh->seekbuf[index].bufstarttrk != dsh->bufstarttrk || dsh->seekbuf[index].databufoffset != dsh->databufoffset) return errorlog_add_message( &dsh->log, NULL, EINVAL, "store track frame: frame data inconsistent\n"); else return 0; } /* the seek_current = index case */ dsh->seekbuf[index].dsp_no = dsh->dsp_no; dsh->seekbuf[index].ext_seq_no = dsh->ext_seq_no; dsh->seekbuf[index].bufstarttrk = dsh->bufstarttrk; dsh->seekbuf[index].databufoffset = dsh->databufoffset; dsh->seek_current++; return 0; } /** * @param[in] dsh The dshandle that keeps track of the I/O operations. * @param[in] buf The target buffer for the read data. * @param[in] size The number of bytes that are to be read. * @param[out] rcsize Reference to a variable in which the actual number * of read bytes is returned. * If this is 0, the end of the file is reached. * @return 0 on success, otherwise one of the following error codes: * - EINVAL The data in dsh is inconsistent. * - ERANGE The data in dsh is inconsistent. * - EPROTO The data read from the disk does not conform to the * expected format. * - EIO I/O error when reading from device. */ int lzds_dshandle_read(struct dshandle *dsh, char *buf, size_t size, ssize_t *rcsize) { ssize_t copysize; int rc; errorlog_clear(dsh->log); if (!dsh->is_open) return errorlog_add_message( &dsh->log, NULL, EINVAL, "data set read: dshandle is not open\n"); *rcsize = 0; while (*rcsize < (long long)size) { if (dsh->bufpos >= dsh->databufsize) { /* need to fill dsh data buffer */ if (dsh->eof_reached) break; /* end of data in data set reached */ if (!dshandle_prepare_for_next_read_tracks(dsh)) break; /* end of data set extents reached */ rc = lzds_dasdhandle_read_tracks_to_buffer( dsh->dasdhandle[dsh->dsp_no], dsh->bufstarttrk, dsh->bufendtrk, dsh->rawbuffer); if (rc) return errorlog_add_message( &dsh->log, dsh->dasdhandle[dsh->dsp_no]->log, rc, "data set read: error reading data set" " %s\n", dsh->ds->name); rc = dshandle_extract_data_from_trackbuffer(dsh); if (rc) return errorlog_add_message( &dsh->log, dsh->log, rc, "data set read: extracting data set " "%s from %s, tracks %u to %u\n", dsh->ds->name, dsh->dasdhandle[dsh->dsp_no]->dasd->device, dsh->bufstarttrk, dsh->bufendtrk); rc = dshandle_store_trackframe(dsh); if (rc) return errorlog_add_message( &dsh->log, dsh->log, rc, "data set read: storing track frame " "%s\n", dsh->ds->name); } /* if databuf has data to copy */ if (dsh->bufpos < dsh->databufsize) { /* copy data from databuf to buf */ copysize = MIN(((long long)size - *rcsize), (dsh->databufsize - dsh->bufpos)); memcpy(buf, &dsh->databuffer[dsh->bufpos], copysize); buf += copysize; dsh->bufpos += copysize; *rcsize += copysize; } } return 0; } /** * @brief subroutine of lzds_dshandle_lseek * * Find the closest buffered seekelement that starts before offset * * @param[in] dsh The dshandle that keeps track of the I/O operations. * @param[in] offset The data offset in the dataset that we want to reach. * @param[out] se_index Reference to a variable in which the found index * to dsh->seekbuf is returned. * * @return 0 on success, otherwise one of the following error codes: * - EINVAL There is no seekbuffer available. */ static int dshandle_find_seekelement(struct dshandle *dsh, off_t offset, long long *se_index) { unsigned long long low, high, index; if (!dsh->seek_current) return EINVAL; /* special case for the last element in the list */ if (dsh->seekbuf[dsh->seek_current - 1].databufoffset <= offset) { *se_index = dsh->seek_current - 1; return 0; } /* search starts with 'high' set to the second to last element */ index = 0; high = dsh->seek_current - 2; low = 0; *se_index = 0; index = (high + low) / 2; while (low != high) { if (dsh->seekbuf[index].databufoffset <= offset) { low = index; index = (high + low + 1) / 2; } else { high = index - 1; index = (high + low) / 2; } } *se_index = low; return 0; } /** * @brief subroutine of lzds_dshandle_lseek * * Reset the internel buffers etc, so that the next read will read * the track frame pointed to by the seekelement. * * @param[in] dsh The dshandle that keeps track of the I/O operations. * @param[out] se_index Index to the seekelement in dsh->seekbuf. */ static void dshandle_reset_buffer_position_to_seekelement( struct dshandle *dsh, long long se_index) { /* make sure that read knows that we have no ready data in our buffer */ dsh->bufpos = 0; dsh->databufsize = 0; dsh->eof_reached = 0; /* we need to set the bufendtrk and sequence number so, * that the current track buffer seems to end with the * track that comes before the first track of the * data set or member */ /* framno will be incremented during read, so do a -1 here */ dsh->frameno = (se_index * dsh->skip) - 1; dsh->databufoffset = dsh->seekbuf[se_index].databufoffset; dsh->dsp_no = dsh->seekbuf[se_index].dsp_no; /* For a partitioned data set we need to find the correct start * track and point the current buffer just before it. * As we always need to read full tracks, any additional * record offset will be set explicitly and handled during * track interpretation. */ if (dsh->member && (dsh->frameno == -1)) dsh->startrecord = dsh->member->record; /* In most cases our track frame will be in the middle of the * disk, so we set bufendtrk to the last track before our track * frame. In the special case that the track frame begins * on track 0, we set the ext_seq_no to that of the frame -1, * so that the read code will advance to the next extend and * the first track of that extent */ if (!dsh->seekbuf[se_index].bufstarttrk) { dsh->ext_seq_no = dsh->seekbuf[se_index].ext_seq_no - 1; dsh->bufendtrk = 0; dsh->extendtrk = 0; return; } dsh->ext_seq_no = dsh->seekbuf[se_index].ext_seq_no; lzds_dasd_cchh2trk(dsh->ds->dsp[dsh->dsp_no]->dasdi, &dsh->ds->dsp[dsh->dsp_no]->ext[dsh->ext_seq_no].llimit, &dsh->extstarttrk); lzds_dasd_cchh2trk(dsh->ds->dsp[dsh->dsp_no]->dasdi, &dsh->ds->dsp[dsh->dsp_no]->ext[dsh->ext_seq_no].ulimit, &dsh->extendtrk); dsh->bufstarttrk = 0; dsh->bufendtrk = dsh->seekbuf[se_index].bufstarttrk - 1; dsh->rawbufsize = 0; dsh->databufoffset = dsh->seekbuf[se_index].databufoffset; dsh->databufsize = 0; dsh->bufpos = 0; return; } /** * It is not possible to seek beyond the end of the data, but an * attempt to do so is a common occurrence as we may not know the * actual data size beforehand. In this case, the returned rcoffset * is smaller than offset and points to the offset directly following * the last data byte. * * @param[in] dsh The dshandle that keeps track of the I/O operations. * @param[in] offset The data offset in the dataset that we want to reach. * @param[out] rcoffset Reference to a variable in which the actual offset * is returned. * * @return 0 on success, otherwise one of the following error codes: * - EINVAL The data in dsh is inconsistent. * - ERANGE The data in dsh is inconsistent. * - EPROTO The data read from the disk does not conform to the * expected format. * - EIO I/O error when reading from device. */ int lzds_dshandle_lseek(struct dshandle *dsh, long long offset, long long *rcoffset) { char foo; ssize_t rcsize; int rc; long long se_index; errorlog_clear(dsh->log); if (dsh->databufoffset <= offset && offset < dsh->databufoffset + dsh->databufsize) { /* offset is within the current track frame */ dsh->bufpos = offset - dsh->databufoffset; *rcoffset = offset; return 0; } /* need to seek to some other track frame */ if (!dshandle_find_seekelement(dsh, offset, &se_index)) { /* do not reset our context if we can seek forward from * our current position */ if (!(dsh->seekbuf[se_index].databufoffset < dsh->databufoffset && dsh->databufoffset <= offset)) { dshandle_reset_buffer_position_to_seekelement(dsh, se_index); } } else if (offset < dsh->databufoffset) { /* if we have no seekbuffer, we can only reset to the * start of the data set */ rc = initialize_buffer_positions_for_first_read(dsh); if (rc) return errorlog_add_message( &dsh->log, dsh->log, rc, "data set seek: error when initializing buffers" " for data set %s\n", dsh->ds->name); } /* from here on we just need to go forward by reading track * frames until we find a frame that contains the offset */ while (dsh->databufoffset + dsh->databufsize <= offset) { dsh->bufpos = dsh->databufsize; rc = lzds_dshandle_read(dsh, &foo, sizeof(foo), &rcsize); if (rc || !rcsize) { *rcoffset = dsh->databufoffset + dsh->databufsize; if (rc) errorlog_add_message( &dsh->log, dsh->log, rc, "data set seek: error reading data from" " data set %s\n", dsh->ds->name); return rc; } } dsh->bufpos = offset - dsh->databufoffset; *rcoffset = offset; return 0; } /** * @param[in] dsh The dshandle that keeps track of the I/O operations. * @param[out] offset Reference to a variable in which the current offset * is returned. */ void lzds_dshandle_get_offset(struct dshandle *dsh, long long *offset) { *offset = dsh->databufoffset + dsh->bufpos; } /** * @param[in] dsh The dshandle that keeps track of the I/O operations. * @param[out] log Reference to a variable in which the errorlog * is returned. */ void lzds_dshandle_get_errorlog(struct dshandle *dsh, struct errorlog **log) { *log = dsh->log; } /******************************************************************************/ /* libzds helper functions */ /******************************************************************************/ /** * This function takes the DS1RECFM byte as defined for the format 1 DSCB, and * creates a string of the usual characters F, V, U, T, B, S, A, and M. * * @param[in] DS1RECFM Input byte. * @param[out] buffer Buffer for the output string. * The buffer must be at least 7 characters long. */ void lzds_DS1RECFM_to_recfm(char DS1RECFM, char *buffer) { if ((DS1RECFM & 0x80) && !(DS1RECFM & 0x40)) *buffer++ = 'F'; /* fixed records */ if (!(DS1RECFM & 0x80) && (DS1RECFM & 0x40)) *buffer++ = 'V'; /* variable records */ if ((DS1RECFM & 0x80) && (DS1RECFM & 0x40)) *buffer++ = 'U'; /* undefined length records */ if ((DS1RECFM & 0x20)) *buffer++ = 'T'; /* track overflow (legacy) */ if ((DS1RECFM & 0x10)) *buffer++ = 'B'; /* blocked */ if ((DS1RECFM & 0x08)) *buffer++ = 'S'; /* standard records */ if ((DS1RECFM & 0x04) && !(DS1RECFM & 0x02)) *buffer++ = 'A'; /* ISO / ANSI control characters */ if (!(DS1RECFM & 0x04) && (DS1RECFM & 0x02)) *buffer++ = 'M'; /* machine control characters */ *buffer = 0; /* The combinations ((DS1RECFM & 0x04) && (DS1RECFM & 0x02)) * and (DS1RECFM & 0x01) are reserved * * If we count only one byte for the mutual exclusive F, V and U, * three bytes for T, B and S, * one byte for the mutual exclusive A and M, * one byte for a possible future definition of 0x01, and * one byte for the zero termination, * then we get a required buffer length of 7 bytes */ } int lzds_analyse_open_count(struct zdsroot *root, int warn) { struct dasd *dasd; int value; int rc = 0; util_list_iterate(root->dasdlist, dasd) { value = u2s_get_host_access_count(dasd->device); if (value < 0) { fprintf(stderr, "Hosts access information not available for disk %s.\n", dasd->device); rc = value; continue; } if (value == 1) continue; if (warn) fprintf(stderr, "\nWARNING:\n" "Disk %s is online on operating system instances in %d different LPARs.\n" "Ensure that the disk is not being used by a system outside your LPAR.\n" "Note: Your installation might include z/VM systems that are configured to\n" "automatically vary on disks, regardless of whether they are subsequently used.\n", dasd->device, value); else { fprintf(stderr, "\nERROR:\n" "Disk %s is online on operating system instances in %d different LPARs.\n" "Ensure that the disk is not being used by a system outside your LPAR.\n" "Note: Your installation might include z/VM systems that are configured to\n" "automatically vary on disks, regardless of whether they are subsequently used.\n", dasd->device, value); rc = -EACCES; } } return rc; } s390-tools-2.3.0/man/000077500000000000000000000000001323410431100141135ustar00rootroot00000000000000s390-tools-2.3.0/man/Makefile000066400000000000000000000006411323410431100155540ustar00rootroot00000000000000include ../common.mak MANS = dumpconf.8 prandom.4 af_iucv.7 all: clean: install: $(MANS) for man in $(MANS); do \ msection=`echo $$man |sed 's/.*\.\([1-8]\)$$/man\1/'` ; \ $(INSTALL) -g $(GROUP) -o $(OWNER) -m 644 -D $$man $(DESTDIR)$(MANDIR)/$$msection/$$man ; \ done pdf: $(MANS) for man in $(MANS); do \ man -t ./$$man |ps2pdf -sPAPERSIZE=a4 - $${man}.pdf ; \ done .PHONY: all install clean pdf s390-tools-2.3.0/man/af_iucv.7000066400000000000000000000374371323410431100156350ustar00rootroot00000000000000.\" af_iucv.7 .\" .\" .\" Copyright IBM Corp. 2008, 2017 .\" s390-tools is free software; you can redistribute it and/or modify .\" it under the terms of the MIT license. See LICENSE for details. .\" ---------------------------------------------------------------------- .TH AF_IUCV 7 "August 2011" "s390-tools" "Linux Programmer's Manual" .SH NAME AF_IUCV \- Sockets for z/VM IUCV and HiperSockets communication . . . .SH SYNOPSIS .B #include .br .B #include .PP .IB iucv_stream_socket " = socket(AF_IUCV, SOCK_STREAM, 0);" .br .IB iucv_packet_socket " = socket(AF_IUCV, SOCK_SEQPACKET, 0);" . . . .SH DESCRIPTION The AF_IUCV address family provides an addressing mode for communications between applications that run on System z mainframes. This addressing mode can be used for connections through real HiperSockets and through the z/VM Inter-User Communication Vehicle (IUCV). .PP HiperSockets facilitate connections between applications across LPARs within a System z mainframe. In particular, an application running on an instance of Linux on System z can communicate with: .RS 2 .IP "\(bu" 2 Itself .IP "\(bu" 2 Other applications running on the same Linux instance .IP "\(bu" 2 An application on an instance of Linux on System z in another LPAR .RE .PP IUCV facilitates connections between applications across z/VM guest virtual machines within a z/VM system. In particular, an application running on Linux on z/VM can communicate with: .RS 2 .IP "\(bu" 2 Itself .IP "\(bu" 2 Other applications running on the same Linux instance .IP "\(bu" 2 Applications running on other instances of Linux on z/VM within the same z/VM system .IP "\(bu" 2 Applications running on a z/VM guest other than Linux within the same z/VM system .IP "\(bu" 2 The z/VM control program (CP) .RE .PP The AF_IUCV address family supports stream-oriented sockets (\f(CWSOCK_STREAM\fP) and connection-oriented datagram sockets (\f(CWSOCK_SEQPACKET\fP). Stream-oriented sockets can fragment data over several packets. Sockets of type SOCK_SEQPACKET always map a particular socket write or read operation to a single packet. . . .SS Features For all instances of Linux on System z, the AF_IUCV address family provides: .RS 2 .IP "\(bu" 2 Multiple outgoing socket connections for real HiperSockets in layer3 mode .IP "\(bu" 2 Multiple incoming socket connections for real HiperSockets in layer3 mode .RE .PP For instances of Linux on z/VM, the AF_IUCV address family also provides: .RS 2 .IP "\(bu" 2 Multiple outgoing socket connections for IUCV .IP "\(bu" 2 Multiple incoming socket connections for IUCV .IP "\(bu" 2 Socket communication with applications utilizing CMS AF_IUCV support .RE . . . . .SH "ADDRESS FORMAT" An AF_IUCV socket is represented by the following format: .PP .RS 8 .ft CW .nf #define AF_IUCV 32 struct sockaddr_iucv { sa_family_t siucv_family; /* AF_IUCV */ unsigned short siucv_port; /* reserved */ unsigned int siucv_addr; /* reserved */ char siucv_nodeid[8]; /* reserved */ char siucv_user_id[8]; /* user id */ char siucv_name[8]; /* application name */ }; .fi .ft .RE .PP .TP .B siucv_family is set to .BR AF_IUCV (= 32) . .TP .B siucv_port, siucv_addr, siucv_nodeid are reserved for future use. The .B siucv_port and .B siucv_addr fields must be zero. The .B siucv_nodeid field must be set to exactly eight blanks. . .TP .B siucv_user_id specifies a HiperSockets device or a z/VM guest virtual machine. This specification implicitly sets the connection type for the socket to a HiperSockets connection or to a z/VM IUCV connection. This field must be eight characters long and, if necessary, padded with blanks on the right. For HiperSockets connections, the .B siucv_user_id field specifies the identifier that is set with the \fBhsuid\fP sysfs attribute of the HiperSockets device. For .BR bind (2) this is the identifier of a local device, and for .BR connect (2) this is the identifier of the HiperSockets device of the communication peer. For IUCV connections, the .B siucv_user_id field specifies a z/VM user ID. For .BR bind (2) this is the identifier of the local z/VM guest virtual machine, and for .BR connect (2) this is the identifier of the z/VM guest virtual machine for the communication peer. .RS .TP .B Tip: For .BR bind (2) you can also specify eight blanks. The AF_IUCV address family support then automatically substitutes the local z/VM user ID for you. .RE . .TP .B siucv_name is set to the application name by which the socket is known. Servers advertise application names and clients use these application names to connect to servers. This field must be eight characters long, and if necessary, padded with blanks on the right. Similar to TCP or UDP ports, application names distinguish distinct applications on the same operating system instance. Do not call .BR bind (2) for names beginning with \fBlnxhvc\fP. These names are reserved for the z/VM IUCV HVC device driver (see also .BR hvc_iucv (9)). . . . .SH "SOCKET OPTIONS" Socket options can be set with .BR setsockopt (2) and read with .BR getsockopt (2) by specifying \f(CWSOL_IUCV\fP as the socket level. .TP .B SO_IPRMDATA_MSG Enables the application to send up to seven bytes of socket data in the parameter list of an IUCV message. Use this option for IUCV connections to increase performance when transferring small amounts of data. For HiperSockets connections, this option has no effect. To send data in the parameter list, specify a non-zero integer value. .RS .TP .B Note: Use this option with care, older AF_IUCV versions do not support receiving socket data in the parameter list and shut down the socket on which a parameter list message has been received. .RE . .TP .B SO_MSGLIMIT Modifies the message limit for communication paths. The message limit specifies the maximum number of outstanding messages that are allowed for established connections. For IUCV connections this setting can be lowered by z/VM when a connection is established. The message limit is an integer value in range 1 to 65535. The default value is 65535 for IUCV connections and 128 for HiperSockets connections. The message limit must be set before .BR connect "(2) or " listen (2) is called for sockets. .br For sockets that are already connected or listening for connections, the message limit cannot be changed. .br New sockets created by .BR accept (2) inherit the message limit that has been set for the listening socket. .BR getsockopt (2) returns the default message limit or the limit that has been set. For connected sockets, the current message limit is returned. For IUCV connections, there are two parameters that specify the message limit: .BR getsockopt (2) and the z/VM IUCV MSGLIMIT parameter. If the two parameters specify different values for the message limit, the lower value is used. See the "SETUP FOR IUCV CONNECTIONS" section for setting IUCV MSGLIMIT authorizations. . .TP .B SO_MSGSIZE .BR getsockopt (2) returns the maximum message size a bound AF_IUCV socket can handle. The maximum message size for connections through HiperSockets depends on the MTU size of the underlying HiperSockets connection. .br For sockets that are not yet bound the maximum message size cannot be determined. . . .SH "ANCILLARY DATA" Ancillary data is sent and received using .BR sendmsg (2) and .BR recvmsg (2)\fR.\fP To send ancillary data, set the \fBcmsg_level\fP field of struct \fBcmsghdr\fP to \f(CWSOL_IUCV\fP and the \fBcmsg_type\fP field to a type of ancillary data that is supported by the AF_IUCV address family. .br For more information see .BR cmsg (3). Currently, the only supported type is: .TP .B SCM_IUCV_TRGCLS Send or receive IUCV target class information. The IUCV target class can be used to classify and identify an IUCV message at the IUCV protocol level. If the target class is not specified as ancillary data, it is set to zero. The target class is a number of type \fBuint32_t\fP. . . . .SH "SETUP FOR HIPERSOCKETS CONNECTIONS" This section applies to HiperSockets connections and explains the configuration of a HiperSockets device used for AF_IUCV address family support. .PP To run an AF_IUCV socket application using HiperSockets connections, the socket must be bound to a particular HiperSockets device configured with layer3 mode. Use the \f(CWhsuid\fP attribute of a HiperSockets device to identify it to the AF_IUCV address family support. .PP The identifier must adhere to these rules: .RS 2 .IP \(bu 2 It must be 1 to 8 characters. .IP \(bu 2 It must be unique across your environment. .IP \(bu 2 It must not match any z/VM user ID in your environment. .RE .PP To set an identifier, issue a command like this: .PP .RS 8 .ft CW echo \fIidentifier\fP > /sys/devices/qeth/\fI\fP/hsuid .ft .RE .PP You can then address this device by specifying the hsuid as the value for the \fBsiucv_user_id\fP field in the \fBsockaddr_iucv\fP addressing structure. .PP For example, to use "MYHOST01" to bind AF_IUCV sockets to the HiperSockets device with bus-ID 0.0.8000, run: .PP .RS 8 .ft CW .nf echo "MYHOST01" > /sys/devices/qeth/0.0.8000/hsuid .fi .ft .RE . . . .SH "SETUP FOR IUCV CONNECTIONS" This section applies to z/VM IUCV connections and provides an overview of the required IUCV statements for your z/VM guest virtual machines. For details and for general IUCV setup information for z/VM guest virtual machines see .I z/VM CP Programming Services and .IR "z/VM CP Planning and Administration" . . . .SS "Granting IUCV authorizations" Use the .B IUCV directory control statement to grant the necessary authorizations. . .TP .B IUCV ALLOW allows any other z/VM guest virtual machine to establish a communication path with this z/VM guest virtual machine. With this statement, no further authorization is required for the z/VM guest virtual machine that initiates the communication. . .TP .B IUCV ANY allows this z/VM guest virtual machine to establish a communication path with any other z/VM guest virtual machine. . .TP .B IUCV \fIuser_ID\fP allows this z/VM guest virtual machine to establish a communication path to the z/VM guest virtual machine with the z/VM user ID \fIuser_ID\fP. .PP You can specify multiple IUCV statements. To any of these IUCV statements you can append the .B MSGLIMIT \fIlimit\fP parameter. \fIlimit\fP specifies the maximum number of outstanding messages that are allowed for each connection authorized by this statement. If no value is specified for \fBMSGLIMIT\fP, the maximum, 65535, is used. . . .SS "Setting a connection limit" Use the \fBOPTION\fP statement to limit the number of concurrent connections. .TP .B OPTION MAXCONN \fImaxno\fP \fImaxno\fP specifies the maximum number of IUCV connections allowed for this virtual machine. The default is 64. The maximum is 65535. . . .SS "Example" These sample statements allow any z/VM guest virtual machine to connect to your z/VM guest virtual machine with a maximum of 10\^000 outstanding messages for each incoming connection. Your z/VM guest virtual machine is permitted to connect to all other z/VM guest virtual machines. The total number of connections for your z/VM guest virtual machine cannot exceed 100. .ft CW .in +0.25i .nf IUCV ALLOW MSGLIMIT 10000 IUCV ANY OPTION MAXCONN 100 .fi .in -0.25i .ft . . . . .SH ERRORS Several socket operations return error conditions that have a special meaning in the context of AF_IUCV. Those error conditions, and the respective descriptions are listed below. See the manual page of the respective socket operation for a complete list of errors. .TP .B ECONNREFUSED .BR connect (2) called but the target system is not listening on the application name. . .TP .B ENETUNREACH .BR connect (2) called but the target z/VM guest virtual machine is not logged on. Ensure that the z/VM guest virtual machine to which your application wants to connect is logged on. . .TP .B EAGAIN .BR connect (2) called but the maximum number of IUCV connections is exceeded for the calling or for the target z/VM guest virtual machine. This error can be temporary and the application might try again after some time. If the error occurs repeatedly, increase the maximum number of connections (for one or both z/VM guest virtual machines). See the "SETUP FOR IUCV CONNECTIONS" section about the required authorization statement. .B sendmsg (2) called but the maximum number of outstanding messages for the socket connection is reached, for example, if data is available that has not yet been received by the communication peer. If necessary, increase the message limit using the .BR setsockopt (2) function for HiperSockets and IUCV connections. In addition, increase the IUCV message limit as as explained in section "Granting IUCV authorizations". . .TP .B EACCES .BR connect (2) called but the calling z/VM guest virtual machine is missing IUCV authorization. See the "SETUP FOR IUCV CONNECTIONS" section about required IUCV authorizations. . .TP .B ENODEV .BR connect (2) or .BR sendmsg (2) called but the HiperSockets device bound to the AF_IUCV socket does not exist. . .TP .B ENETDOWN .BR connect (2) or .BR sendmsg (2) called but the HiperSockets device bound to the AF_IUCV socket is not activated. . .TP .B EBADFD .BR connect (2) called but for HiperSockets connections the AF_IUCV socket is not bound or, for IUCV connections, the socket is neither in open nor in bound state. .BR bind (2) called but the AF_IUCV socket is no longer in open state. .BR accept (2) called but the AF_IUCV socket is not listening. .BR getsockopt (2) called but the AF_IUCV socket is not bound. .TP .B EINVAL .BR connect (2) or .BR bind (2) called but the \fBsiucv_family\fP field of the specified \fBsockaddr_iucv\fP structure is not set to \fBAF_IUCV\fP. .BR listen (2) called but the AF_IUCV socket has not yet been bound to an address. Always call .BR bind (2) before .BR listen (2). .BR setsockopt (2) called with option \fBSO_MSGLIMIT\fP for sockets that are already connected. . .TP .B ENOPROTOOPT .BR setsockopt (2) or .BR getsockopt (2) called but the socket level has not been set to \f(CWSOL_IUCV\fP, or the specified socket option is not supported. . .TP .B EOPNOTSUPP .BR sendmsg (2) or .BR recvmsg (2) might have been called with the .I MSG_OOB flag set. AF_IUCV does not support sending or receiving \fIout-of-band\fP data on its sockets. For \f(CWSOCK_SEQPACKET\fP sockets, .BR sendmsg (2) called without the .I MSG_EOR flag set. AF_IUCV does not support segmentation, and thus, the "end-of-record" (\fIMSG_EOR\fP) flag must always be set. . .TP .B EPROTONOSUPPORT .BR socket (2) called with a protocol that is not supported. The socket protocol parameter must be either zero or \f(CWPF_IUCV\fP. . .TP .B EAFNOSUPPORT .BR socket (2) called with \f(CWAF_IUCV\fP but the AF_IUCV address family is not supported by the current Linux kernel. Ensure that your Linux kernel has been compiled with support for the latest version of the AF_IUCV address family. . .TP .B EADDRINUSE .BR bind (2) called with an \fBsiucv_name\fP already used for another AF_IUCV socket. . .PP Other errors can be generated by the generic socket layer. See the respective manual pages for more information. . . . .SH "SEE ALSO" .BR connect (2), .BR recvmsg (2), .BR sendmsg (2), .BR socket (2), .BR setsockopt (2), .BR getsockopt (2), .BR cmsg (3), .BR socket (7) .I "Linux on System z - Device Drivers, Features, and Commands" .br .I "z/VM CP Planning and Administration" .br .I "z/VM CP Programming Services" . . . .SH "HISTORY" .TP .B AF_IUCV, version 1.0 .RS 4 .IP "\(bu" 2 Initial version. .RE . .TP .B AF_IUCV, version 1.1 .RS 4 .IP "\(bu" 2 Support for sending socket data in the parameter list of an IUCV message (\f(CWSO_IPRMDATA_MSG\fP). .IP "\(bu" 2 Access the target class of an IUCV message as ancillary data using .BR sendmsg "(2) and " recvmsg (2). .IP "\(bu" 2 Support for \f(CWSOCK_SEQPACKET\fP sockets to facilitate development of native IUCV applications that interact with AF_IUCV. .RE . .TP .B AF_IUCV, version 1.2 .RS 4 .IP "\(bu" 2 Support for HiperSockets connections. .RE s390-tools-2.3.0/man/dumpconf.8000066400000000000000000000074641323410431100160320ustar00rootroot00000000000000.\" Copyright 2017 IBM Corp. .\" s390-tools is free software; you can redistribute it and/or modify .\" it under the terms of the MIT license. See LICENSE for details. .\" .TH DUMPCONF 8 "Sept 2011" "s390-tools" .SH NAME dumpconf \- Configure panic and PSW restart actions for Linux on System z .SH SYNOPSIS .br \fBdumpconf\fR [start|stop|status] .br \fBdumpconf\fR [-h|-v] .SH DESCRIPTION \fBdumpconf\fR reads the /etc/sysconfig/dumpconf file and establishes the action to be taken if a kernel panic occurs or PSW restart is triggered. The following keywords can be used in the dumpconf file: .TP \fB - ON_PANIC:\fR Shutdown action in case of a kernel panic or a PSW restart. Possible values are 'dump', 'reipl', 'dump_reipl', 'stop' and 'vmcmd': .br dump: Trigger dump according to the configuration in /etc/sysconfig/dumpconf. .br reipl: Trigger re-IPL according to the configuration under /sys/firmware/reipl. .br dump_reipl: First trigger dump according to the configuration in /etc/sysconfig/dumpconf, then trigger re-IPL according to the configuration under /sys/firmware/reipl. .br stop: Stop Linux and enter disabled wait (default). .br vmcmd: Trigger CP command according to the 'VMCMD_X' configuration in /etc/sysconfig/dumpconf. .TP \fB - DUMP_TYPE:\fR Type of dump device. Possible values are 'ccw' and 'fcp'. .TP \fB - DEVICE:\fR Device number of dump device. .TP \fB - WWPN\fR WWPN for SCSI dump device. .TP \fB - LUN\fR LUN for SCSI dump device. .TP \fB - BOOTPROG:\fR Boot program selector. .TP \fB - BR_LBA:\fR Boot record logical block address. .TP \fB - VMCMD_1, VMCMD_2 ... VMCMD_8:\fR Up to eight CP commands, which are executed in case of a kernel panic or PSW restart. .TP \fB - DELAY_MINUTES:\fR Number of minutes the activation of dumpconf is to be delayed. If this keyword is omitted, the default is zero, which means that dumpconf activates immediately during system startup. Specify a non-zero delay time only if you specified shutdown action "reipl" or "dump_reipl". These actions might cause a reboot loop if the Linux kernel crashes persistently during (or shortly after) each reboot. A non-zero delay time causes dumpconf to sleep in the background until the delay time has expired. In this case messages are written to /var/log/messages. By default (DELAY_MINUTES is omitted or zero) dumpconf runs in the foreground and informational messages are written to sysout, while error messages are written to syserr. Example: If you specified DELAY_MINUTES=10 and your Linux system crashes within 10 minutes after the reboot, then dumpconf is not yet active and the default action (stop) is triggered. .SH COMMANDS .TP \fBstart\fR Enable configuration defined in /etc/sysconfig/dumpconf. .TP \fBstop\fR Disable dump configuration. .TP \fBstatus\fR Show current configuration. .SH OPTIONS .TP \fB-h\fR or \fB--help\fR Print usage information, then exit. .TP \fB-v\fR or \fB--version\fR Print version information, then exit. .SH PSW Restart PSW Restart can be triggered by the operator under z/VM with the CP command "#cp system restart" and under LPAR on the HMC with "Recovery/PSW Restart". .SH EXAMPLES: The following are examples of the /etc/sysconfig/dumpconf file: .br # .br # Example configuration for a CCW dump device (DASD) .br # .br ON_PANIC=dump_reipl .br DUMP_TYPE=ccw .br DEVICE=0.0.1234 .br DELAY_MINUTES=5 .br # .br # Example configuration for an FCP dump device (SCSI Disk) .br # .br ON_PANIC=dump .br DUMP_TYPE=fcp .br DEVICE=0.0.2345 .br WWPN=0x5005076303004712 .br LUN=0x4713000000000000 .br BOOTPROG=0 .br BR_LBA=0 .br # .br # Example configuration for CP commands .br # .br ON_PANIC=vmcmd .br VMCMD_1="MESSAGE * Starting VMDUMP" .br VMCMD_2="VMDUMP" .br VMCMD_3="IPL 3456" # .br # Example config for re-IPL .br # .br ON_PANIC=reipl .br DELAY_MINUTES=5 .SH SEE ALSO Linux on System z: Using the Dump Tools s390-tools-2.3.0/man/prandom.4000066400000000000000000000026331323410431100156440ustar00rootroot00000000000000.\" Copyright IBM Corp. 2007, 2017 .\" s390-tools is free software; you can redistribute it and/or modify .\" it under the terms of the MIT license. See LICENSE for details. .\" .TH PRANDOM 4 "Jan 2007" "s390-tools" .SH NAME prandom \- kernel pseudo random number generator device for s390 .SH DESCRIPTION The character special file \fI/dev/prandom\fP provides an interface to the pseudo random number generator. The s390 pseudo random number generator uses the hardware accelerated cryptographic assist functions which are integrated in the CPU. \fI/dev/prandom\fP is available starting with the z9 processor. .LP Reading from \fI/dev/prandom\fP is non-blocking. Any amount of data could be read from the device. .LP The s390 pseudo random number generator provides cryptographically secure pseudo random numbers following the algorithm in ANSI X9.17. Entropy is added periodically to the generator to protect against a compromised key. .LP .SH CONFIGURATION The \fI/dev/prandom\fP device node is generated by udev while loading the corresponding kernel module or while booting a kernel with the generator built-in. By default \fI/dev/prandom\fP is readable only by root. If it should be readable by every user add the following to /etc/rules.d/50-udev.rules: .nf KERNEL=="prandom", MODE="0444", OPTIONS="last_rule" .fi .SH FILES /dev/prandom .SH SEE ALSO Linux on zSeries: Device Drivers, Features and Commands s390-tools-2.3.0/mon_tools/000077500000000000000000000000001323410431100153515ustar00rootroot00000000000000s390-tools-2.3.0/mon_tools/Makefile000066400000000000000000000010161323410431100170070ustar00rootroot00000000000000include ../common.mak all: mon_fsstatd mon_procd mon_fsstatd: mon_fsstatd.o mon_procd: mon_procd.o install: all $(INSTALL) -g $(GROUP) -o $(OWNER) -m 755 mon_fsstatd \ $(DESTDIR)$(USRSBINDIR) $(INSTALL) -g $(GROUP) -o $(OWNER) -m 644 mon_fsstatd.8 \ $(DESTDIR)$(MANDIR)/man8 $(INSTALL) -g $(GROUP) -o $(OWNER) -m 755 mon_procd \ $(DESTDIR)$(USRSBINDIR) $(INSTALL) -g $(GROUP) -o $(OWNER) -m 644 mon_procd.8 \ $(DESTDIR)$(MANDIR)/man8 clean: rm -f *.o *~ mon_fsstatd mon_procd core .PHONY: all install clean s390-tools-2.3.0/mon_tools/mon_fsstatd.8000066400000000000000000000015121323410431100177620ustar00rootroot00000000000000.\" Copyright 2017 IBM Corp. .\" s390-tools is free software; you can redistribute it and/or modify .\" it under the terms of the MIT license. See LICENSE for details. .\" .TH MON_FSSTATD 8 "Dec 2006" "s390-tools" .SH NAME mon_fsstatd \- Filesystem statistics monitor. .SH SYNOPSIS \fBmon_fsstatd\fR [-h] [-v] [-a] [-i \fI\fR] .SH DESCRIPTION \fBmon_fsstatd\fR is a daemon that writes filesystem utilization data to the z/VM monitor stream. .SH OPTIONS .TP \fB-h\fR or \fB--help\fR Print usage message and exit. .TP \fB-v\fR or \fB--version\fR Print Version information and exit. .TP \fB-a\fR or \fB--attach\fR Run in foreground. .TP \fB-i\fR \fI\fR or \fB--interval\fR=\fI\fR Set polling interval in seconds. .SH AUTHOR .nf This man-page was written by Melissa Howland . .fi s390-tools-2.3.0/mon_tools/mon_fsstatd.c000066400000000000000000000267771323410431100200610ustar00rootroot00000000000000/* * mon_fsstatd - Write file system utilization data to the z/VM monitor stream * * Copyright IBM Corp. 2006, 2017 * * 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "mon_fsstatd.h" static int attach; static int mw_dev; static char small_mon_record[SMALL_MON_RECORD_LEN]; static char large_mon_record[LARGE_MON_RECORD_LEN]; static long sample_interval = 60; static const char *pid_file = "/var/run/mon_fsstatd.pid"; struct mw_name_lens { __u16 mw_name_len; __u16 mw_dir_len; __u16 mw_type_len; __u16 mw_fsdata_len; __u16 mw_total; }; /* * Clean up when SIGTERM or SIGINT received */ void stop_fsstatd(int UNUSED(a)) { remove(pid_file); exit(0); } /* * Set up for handling SIGTERM or SIGINT */ static void fsstatd_handle_signals(void) { struct sigaction act; act.sa_flags = 0; sigemptyset(&act.sa_mask); act.sa_handler = stop_fsstatd; if (sigaction(SIGTERM, &act, NULL) < 0) { fprintf(stderr, "sigaction( SIGTERM, ... ) failed - " "reason %s\n", strerror(errno)); exit(1); } act.sa_handler = stop_fsstatd; if (sigaction(SIGINT, &act, NULL) < 0) { fprintf(stderr, "sigaction( SIGINT, ... ) failed - " "reason %s\n", strerror(errno)); exit(1); } } /* * Open /dev/monwriter */ static void fsstatd_open_monwriter(void) { mw_dev = open("/dev/monwriter", O_EXCL | O_RDWR); if (mw_dev == -1) { printf("cannot open /dev/monwriter: %s\n", strerror(errno)); exit(1); } } /* * Store daemon's pid so it can be stopped */ static int store_pid(void) { FILE *f = fopen(pid_file, "w"); if (!f) { syslog(LOG_ERR, "cannot open pid file %s: %s", pid_file, strerror(errno)); return -1; } fprintf(f, "%d\n", getpid()); fclose(f); return 0; } /* * Stop sampling of any buffers that are not longer needed */ static void stop_unused(int curr_max, int prev_max) { struct monwrite_hdr *mw_hdrp; int i; mw_hdrp = (struct monwrite_hdr *)small_mon_record; mw_hdrp->mon_function = MONWRITE_STOP_INTERVAL; mw_hdrp->applid = FSSTATD_APPLID; mw_hdrp->hdrlen = sizeof(struct monwrite_hdr); mw_hdrp->datalen = 0; for (i = 0; i < prev_max - curr_max; i += 2) { mw_hdrp->mod_level = curr_max + i; if (write(mw_dev, mw_hdrp, sizeof(struct monwrite_hdr)) == -1) syslog(LOG_ERR, "write error on STOP: %s\n", strerror(errno)); } } /* * Calculate lengths of data to be written to monitor stream */ static struct mw_name_lens fsstatd_get_lens(struct mntent *ent) { struct mw_name_lens name_lens; name_lens.mw_name_len = strlen(ent->mnt_fsname); name_lens.mw_dir_len = strlen(ent->mnt_dir); name_lens.mw_type_len = strlen(ent->mnt_type); /* if name & dir too long to fit both, truncate them */ if (name_lens.mw_name_len + name_lens.mw_dir_len + name_lens.mw_type_len > MAX_NAMES_LEN) { if (name_lens.mw_name_len > MAX_NAME_LEN) name_lens.mw_name_len = MAX_NAME_LEN; if (name_lens.mw_dir_len > MAX_DIR_LEN) name_lens.mw_dir_len = MAX_DIR_LEN; if (name_lens.mw_type_len > MAX_TYPE_LEN) name_lens.mw_type_len = MAX_TYPE_LEN; } /* total fs data to be written */ name_lens.mw_fsdata_len = sizeof(__u16) + name_lens.mw_name_len + sizeof(__u16) + name_lens.mw_dir_len + sizeof(__u16) + name_lens.mw_type_len + sizeof(struct fsstatd_data); /* total monitor data to be written in monitor record */ name_lens.mw_total = sizeof(struct fsstatd_hdr) + name_lens.mw_fsdata_len; return name_lens; } /* * Write fs data for ent to monitor stream */ static void fsstatd_write_ent(struct mntent *ent, time_t curr_time, int *small_maxp, int *big_maxp, struct statvfs buf) { struct monwrite_hdr *mw_hdrp; struct fsstatd_hdr *mw_fshdrp; struct fsstatd_data *mw_fsdatap; char *mw_tmpp; char *mw_bufp; struct mw_name_lens mw_lens; int write_len; mw_lens = fsstatd_get_lens(ent); if ((mw_lens.mw_total + sizeof(struct monwrite_hdr)) <= sizeof(small_mon_record)) { mw_bufp = small_mon_record; memset(&small_mon_record, 0, sizeof(small_mon_record)); mw_hdrp = (struct monwrite_hdr *)mw_bufp; mw_hdrp->datalen = sizeof(small_mon_record) - sizeof(struct monwrite_hdr); write_len = sizeof(small_mon_record); mw_hdrp->mod_level = *small_maxp; *small_maxp += 2; } else { mw_bufp = large_mon_record; memset(&large_mon_record, 0, sizeof(large_mon_record)); mw_hdrp = (struct monwrite_hdr *)mw_bufp; mw_hdrp->datalen = sizeof(large_mon_record) - sizeof(struct monwrite_hdr); write_len = sizeof(large_mon_record); mw_hdrp->mod_level = *big_maxp; *big_maxp += 2; } /* fill in rest of monwrite_hdr */ mw_tmpp = mw_bufp; mw_hdrp->applid = FSSTATD_APPLID; mw_hdrp->hdrlen = sizeof(struct monwrite_hdr); mw_hdrp->mon_function = MONWRITE_START_INTERVAL; /* fill in fsstatd_hdr */ mw_tmpp += sizeof(struct monwrite_hdr); mw_fshdrp = (struct fsstatd_hdr *)mw_tmpp; mw_fshdrp->time_stamp = (__u64) curr_time; mw_fshdrp->fsstat_data_len = (__u16) mw_lens.mw_fsdata_len; mw_fshdrp->fsstat_data_offset = (__u16) sizeof(struct fsstatd_hdr); /* fill in fs name, dir name and fs type and lengths */ mw_tmpp += sizeof(struct fsstatd_hdr); memcpy(mw_tmpp, &mw_lens.mw_name_len, sizeof(__u16)); mw_tmpp += sizeof(__u16); strncpy(mw_tmpp, ent->mnt_fsname, mw_lens.mw_name_len); mw_tmpp += mw_lens.mw_name_len; memcpy(mw_tmpp, &mw_lens.mw_dir_len, sizeof(__u16)); mw_tmpp += sizeof(__u16); strncpy(mw_tmpp, ent->mnt_dir, mw_lens.mw_dir_len); mw_tmpp += mw_lens.mw_dir_len; memcpy(mw_tmpp, &mw_lens.mw_type_len, sizeof(__u16)); mw_tmpp += sizeof(__u16); strncpy(mw_tmpp, ent->mnt_type, mw_lens.mw_type_len); /* fill in fsstatd_data */ mw_tmpp += mw_lens.mw_type_len; mw_fsdatap = (struct fsstatd_data *)mw_tmpp; mw_fsdatap->fs_bsize = (__u64)buf.f_bsize; mw_fsdatap->fs_frsize = (__u64) buf.f_frsize; mw_fsdatap->fs_blocks = (__u64) buf.f_blocks; mw_fsdatap->fs_bfree = (__u64) buf.f_bfree; mw_fsdatap->fs_bavail = (__u64) buf.f_bavail; mw_fsdatap->fs_files = (__u64) buf.f_files; mw_fsdatap->fs_ffree = (__u64) buf.f_ffree; mw_fsdatap->fs_favail = (__u64) buf.f_favail; mw_fsdatap->fs_flag = (__u64) buf.f_flag; if (write(mw_dev, mw_bufp, write_len) == -1) syslog(LOG_ERR, "write error: %s\n", strerror(errno)); } /* * Run as background process */ static void fsstatd_daemonize(void) { int pipe_fds[2], startup_rc = 1; pid_t pid; if (pipe(pipe_fds) == -1) { syslog(LOG_ERR, "pipe error: %s\n", strerror(errno)); exit(1); } /* Fork off the parent process */ pid = fork(); if (pid < 0) { syslog(LOG_ERR, "fork error: %s\n", strerror(errno)); exit(1); } if (pid > 0) { /* Wait for startup return code from daemon */ if (read(pipe_fds[0], &startup_rc, sizeof(startup_rc)) == -1) syslog(LOG_ERR, "pipe read error: %s\n", strerror(errno)); /* With startup_rc == 0, pid file was written at this point */ exit(startup_rc); } /* Change the file mode mask */ umask(0); /* Catch SIGINT and SIGTERM to clean up pid file on exit */ fsstatd_handle_signals(); /* Create a new SID for the child process */ if (setsid() < 0) { syslog(LOG_ERR, "setsid error: %s\n", strerror(errno)); goto notify_parent; } /* Change the current working directory */ if (chdir("/") < 0) { syslog(LOG_ERR, "chdir error: %s\n", strerror(errno)); goto notify_parent; } /* Close out the standard file descriptors */ close(STDIN_FILENO); close(STDOUT_FILENO); close(STDERR_FILENO); /* Store daemon pid */ if (store_pid() < 0) goto notify_parent; startup_rc = 0; notify_parent: /* Inform waiting parent about startup return code */ if (write(pipe_fds[1], &startup_rc, sizeof(startup_rc)) == -1) { syslog(LOG_ERR, "pipe write error: %s\n", strerror(errno)); exit(1); } if (startup_rc != 0) exit(startup_rc); } static int fsstatd_do_work(void) { FILE *mnttab; time_t curr_time; struct statvfs buf; struct mntent *ent; int result; int curr_small_max, prev_small_max; int curr_big_max, prev_big_max; /* * small buffers use even mod_levels, * big buffers use odd mod_levels */ prev_small_max = 0; prev_big_max = 1; syslog(LOG_INFO, "sample interval: %lu\n", sample_interval); while (1) { time(&curr_time); mnttab = fopen("/etc/mtab", "r"); if (mnttab == NULL) { syslog(LOG_ERR, "cannot open /etc/mtab: %s\n", strerror(errno)); break; } curr_small_max = 0; curr_big_max = 1; ent = getmntent(mnttab); if (ent == NULL) { syslog(LOG_ERR, "getmntent error: %s\n", strerror(errno)); fclose(mnttab); break; } while (ent) { /* Only sample physical filesystem size data */ if ((strncmp(ent->mnt_type, "autofs", 6) == 0 || strncmp(ent->mnt_type, "none", 4) == 0 || strncmp(ent->mnt_type, "proc", 4) == 0 || strncmp(ent->mnt_type, "subfs", 5) == 0 || strncmp(ent->mnt_type, "nfsd", 4) == 0 || strncmp(ent->mnt_type, "tmpfs", 5) == 0 || strncmp(ent->mnt_type, "sysfs", 5) == 0 || strncmp(ent->mnt_type, "pstore", 6) == 0 || strncmp(ent->mnt_type, "cgroup", 6) == 0 || strncmp(ent->mnt_type, "mqueue", 6) == 0 || strncmp(ent->mnt_type, "devpts", 6) == 0 || strncmp(ent->mnt_type, "debugfs", 7) == 0 || strncmp(ent->mnt_type, "devtmpfs", 8) == 0 || strncmp(ent->mnt_type, "configfs", 8) == 0 || strncmp(ent->mnt_type, "selinuxfs", 9) == 0 || strncmp(ent->mnt_type, "hugetlbfs", 9) == 0 || strncmp(ent->mnt_type, "securityfs", 10) == 0 || strncmp(ent->mnt_type, "rpc_pipefs", 10) == 0 || strncmp(ent->mnt_type, "binfmt_misc", 11) == 0 || strncmp(ent->mnt_type, "ignore", 6) == 0)) { ent = getmntent(mnttab); continue; } result = statvfs(ent->mnt_dir, &buf); if (result != 0) { syslog(LOG_ERR, "statvfs error on %s: %s\n", ent->mnt_dir, strerror(errno)); ent = getmntent(mnttab); continue; } if (buf.f_blocks > 0) fsstatd_write_ent(ent, curr_time, &curr_small_max, &curr_big_max, buf); ent = getmntent(mnttab); } if (curr_small_max < prev_small_max) stop_unused(curr_small_max, prev_small_max); if (curr_big_max < prev_big_max) stop_unused(curr_big_max, prev_big_max); prev_small_max = curr_small_max; prev_big_max = curr_big_max; fclose(mnttab); sleep(sample_interval); } return 1; } /* Parse options */ static int parse_options(int argc, char **argv) { int opt; do { opt = getopt_long(argc, argv, opt_string, options, NULL); switch (opt) { case -1: /* Reached end of parameter list. */ break; case 'h': printf("%s", help_text); exit(0); case 'v': printf("mon_fsstatd: version %s\n", RELEASE_STRING); printf("Copyright IBM Corp. 2006, 2017\n"); exit(0); case 'a': attach = 1; break; case 'i': sample_interval = strtol(optarg, NULL, 10); if (sample_interval <= 0) { fprintf(stderr, "Error: Invalid interval " "(needs to be greater than 0)\n"); return(1); } break; default: fprintf(stderr, "Try ' --help' for more" " information.\n"); return(1) ; } } while (opt != -1); return(0); } int main(int argc, char **argv) { int rc; rc = parse_options(argc, argv); if (rc > 0) return rc; fsstatd_open_monwriter(); openlog("mon_fsstatd", 0, LOG_DAEMON); if (!attach) fsstatd_daemonize(); rc = fsstatd_do_work(); close(mw_dev); return rc; } s390-tools-2.3.0/mon_tools/mon_fsstatd.h000066400000000000000000000041551323410431100200500ustar00rootroot00000000000000/* * mon_fsstatd - Write file system utilization data to the z/VM monitor stream * * Definitions used by mon_fsstatd * * Copyright IBM Corp. 2006, 2017 * * s390-tools is free software; you can redistribute it and/or modify * it under the terms of the MIT license. See LICENSE for details. */ #ifndef __mon_fsstatd_h__ #define __mon_fsstatd_h__ #include #include #include #include "lib/zt_common.h" /* mon_function values */ #define MONWRITE_START_INTERVAL 0x00 /* start interval recording */ #define MONWRITE_STOP_INTERVAL 0x01 /* stop interval or config recording */ #define MAX_REC_LEN 4010 #define MAX_NAMES_LEN 3900 #define MAX_NAME_LEN 1800 #define MAX_DIR_LEN 1800 #define MAX_TYPE_LEN 256 #define FSSTATD_APPLID 0x01 /* Assume usually lengths of name, dir and type <= 512 bytes total */ #define SMALL_MON_RECORD_LEN 602 #define LARGE_MON_RECORD_LEN 4010 struct monwrite_hdr { unsigned char mon_function; unsigned short applid; unsigned char record_num; unsigned short version; unsigned short release; unsigned short mod_level; unsigned short datalen; unsigned char hdrlen; } __attribute__((packed)); struct fsstatd_hdr { __u64 time_stamp; __u16 fsstat_data_len; __u16 fsstat_data_offset; } __attribute__((packed)); struct fsstatd_data { __u64 fs_bsize; __u64 fs_frsize; __u64 fs_blocks; __u64 fs_bfree; __u64 fs_bavail; __u64 fs_files; __u64 fs_ffree; __u64 fs_favail; __u64 fs_flag; }; static struct option options[] = { {"help", no_argument, NULL, 'h'}, {"version", no_argument, NULL, 'v'}, {"attach", no_argument, NULL, 'a'}, {"interval", required_argument, NULL, 'i'}, {NULL, 0, NULL, 0} }; static const char opt_string[] = "+hvai:"; static const char help_text[] = "mon_fsstatd: Daemon that writes file system utilization information\n" "to the z/VM monitor stream.\n" "\n" "Usage: mon_fstatd [OPTIONS]\n" "\n" "Options:\n" "-h, --help Print this help, then exit\n" "-v, --version Print version information, then exit\n" "-a, --attach Run in foreground\n" "-i, --interval= Sample interval\n"; #endif s390-tools-2.3.0/mon_tools/mon_procd.8000066400000000000000000000014371323410431100174270ustar00rootroot00000000000000.\" Copyright 2017 IBM Corp. .\" s390-tools is free software; you can redistribute it and/or modify .\" it under the terms of the MIT license. See LICENSE for details. .\" .TH MON_PROCD 8 "MAR 2007" "s390-tools" .SH NAME mon_procd \- Process data monitor. .SH SYNOPSIS \fBmon_procd\fR [-h] [-v] [-a] [-i \fI\fR] .SH DESCRIPTION \fBmon_procd\fR is a daemon that writes process data to the z/VM monitor stream. .SH OPTIONS .TP \fB-h\fR or \fB--help\fR Print usage message and exit. .TP \fB-v\fR or \fB--version\fR Print Version information and exit. .TP \fB-a\fR or \fB--attach\fR Run in foreground. .TP \fB-i\fR \fI\fR or \fB--interval\fR=\fI\fR Set polling interval in seconds. .SH AUTHOR .nf This man-page was written by Hongjie Yang . .fi s390-tools-2.3.0/mon_tools/mon_procd.c000066400000000000000000000626741323410431100175140ustar00rootroot00000000000000/* * mon_procd - Write process data to the z/VM monitor stream * * Copyright IBM Corp. 2007, 2017 * * 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "mon_procd.h" struct name_lens_t { __u16 ruser_len; __u16 euser_len; __u16 egroup_len; __u16 wchan_len; __u16 cmd_len; __u16 cmdline_len; }; static int num_cpus; static int curr_small_max, curr_big_max; static int prev_small_max, prev_big_max; static unsigned int pg_to_kb_shift, sort_tbl_size; static float e_time; static struct timeval prev_time, curr_time; static struct cpudata_t cpudata; static struct proc_sum_t proc_sum; static struct task_sort_t *prev_sort_tbl, *curr_sort_tbl; static struct name_lens_t name_lens; static int attach; static int mw_dev; static char *temp; static char fname[32]; static char buf[BUF_SIZE]; static char mon_record[MAX_REC_LEN]; static long sample_interval = 60; static const char *pid_file = "/var/run/mon_procd.pid"; /* * Clean up when SIGTERM or SIGINT received */ void stop_procd(int UNUSED(a)) { remove(pid_file); exit(0); } /* * Set up for handling SIGTERM or SIGINT */ static void procd_handle_signals(void) { struct sigaction act; act.sa_flags = 0; sigemptyset( &act.sa_mask ); act.sa_handler = stop_procd; if (sigaction(SIGTERM, &act, NULL) < 0) { fprintf(stderr, "sigaction( SIGTERM, ... ) failed - " "reason %s\n", strerror( errno ) ); exit(1); } act.sa_handler = stop_procd; if (sigaction(SIGINT, &act, NULL) < 0) { fprintf(stderr, "sigaction( SIGINT, ... ) failed - " "reason %s\n", strerror( errno ) ); exit(1); } } /* * Open /dev/monwriter */ static void procd_open_monwriter(void) { mw_dev = open("/dev/monwriter", O_EXCL | O_RDWR); if (mw_dev == -1) { printf("cannot open /dev/monwriter: %s\n", strerror(errno)); exit(1); } } /* * Store daemon's pid so it can be stopped */ static int store_pid(void) { FILE *f = fopen(pid_file, "w"); if (!f) { syslog(LOG_ERR, "cannot open pid file %s: %s", pid_file, strerror(errno)); return -1; } fprintf(f, "%d\n", getpid()); fclose(f); return 0; } /* * Stop sampling of any buffers that are not longer needed */ static void stop_unused(int curr_max, int prev_max) { struct monwrite_hdr *mw_hdrp; int i; mw_hdrp = (struct monwrite_hdr *)mon_record; mw_hdrp->applid = PROCD_APPLID; mw_hdrp->record_num = TASK_FLAG; mw_hdrp->hdrlen = sizeof(struct monwrite_hdr); mw_hdrp->mon_function = MONWRITE_STOP_INTERVAL; mw_hdrp->datalen = 0; for (i = 0; i < prev_max - curr_max; i += 2) { mw_hdrp->mod_level = curr_max + i; if (write(mw_dev, mw_hdrp, sizeof(struct monwrite_hdr)) == -1) syslog(LOG_ERR, "write error on STOP: %s\n", strerror(errno)); } } /* * Write process data to monitor stream */ static void procd_write_ent(void *entry, int size, char flag) { struct monwrite_hdr *mw_hdrp; struct procd_hdr *mw_pchdrp; char *mw_tmpp; char *mw_bufp; int write_len; mw_bufp = mon_record; mw_hdrp = (struct monwrite_hdr *)mw_bufp; write_len = size + sizeof(struct monwrite_hdr) + sizeof(struct procd_hdr); /* fill in monwrite_hdr */ if (flag == TASK_FLAG) { if (write_len <= SMALL_MON_RECORD_LEN) { write_len = SMALL_MON_RECORD_LEN; mw_hdrp->mod_level = curr_small_max; curr_small_max += 2; } else { write_len = MAX_REC_LEN; mw_hdrp->mod_level = curr_big_max; curr_big_max += 2; } } else { mw_hdrp->mod_level = 0; } mw_hdrp->datalen = write_len - sizeof(struct monwrite_hdr); mw_hdrp->applid = PROCD_APPLID; mw_hdrp->record_num = flag; mw_hdrp->hdrlen = sizeof(struct monwrite_hdr); mw_hdrp->mon_function = MONWRITE_START_INTERVAL; /* fill in procd_hdr */ mw_tmpp = mw_bufp + sizeof(struct monwrite_hdr); mw_pchdrp = (struct procd_hdr *)mw_tmpp; mw_pchdrp->time_stamp = (__u64) curr_time.tv_sec; mw_pchdrp->data_len = (__u16) size; mw_pchdrp->data_offset = (__u16) sizeof(struct procd_hdr); /* fill in entry information */ mw_tmpp += sizeof(struct procd_hdr); if (flag == SUM_FLAG) memcpy(mw_tmpp, entry, size); else memcpy(mw_tmpp, entry, sizeof(struct task_t)); if (write(mw_dev, mw_bufp, write_len) == -1) syslog(LOG_ERR, "write error: %s\n", strerror(errno)); } /* * Open and read a file into a buffer, terminated with buf[size/num] = '\0' */ static int read_file(char *fname, char *buf, int size) { int num, fp; fp = open(fname, O_RDONLY); if (!fp) return -1; num = read(fp, buf, size); if (num < 0) { close(fp); return -1; } buf[num] = '\0'; close(fp); return num; } /* * Get uptime */ static void read_uptime(void) { double uptm; if (read_file("/proc/uptime", buf, sizeof(buf) - 1) <= 0) return; if (sscanf(buf, "%lf ", &uptm) < 1) { syslog(LOG_ERR, "bad data in %s \n", fname); return; } proc_sum.uptime = (__u64)uptm; } /* * Get number of users */ static void count_users(void) { struct utmp *temp; proc_sum.users = 0; setutent(); while ((temp = getutent())) { if ((temp->ut_type == USER_PROCESS) && (temp->ut_name)) proc_sum.users++; } endutent(); } /* * Get load averages */ static void read_loadavg(void) { int ret_num; if (read_file("/proc/loadavg", buf, sizeof(buf) - 1) <= 0) return; ret_num = sscanf(buf, "%s %s %s", proc_sum.loadavg_1, proc_sum.loadavg_5, proc_sum.loadavg_15); if (ret_num < 3) syslog(LOG_ERR, "bad data in %s \n", fname); } /* * Calculate the state percentages for a CPU */ static void cal_cpu(struct cpudata_t *cpudt, struct cpu_t *cpu) { __u64 total_time; total_time = cpudt->usr - cpudt->usr_prev; total_time += cpudt->sys - cpudt->sys_prev; total_time += cpudt->nice - cpudt->nice_prev; if (cpudt->idle - cpudt->idle_prev > 0) total_time += cpudt->idle - cpudt->idle_prev; total_time += cpudt->iowt - cpudt->iowt_prev; total_time += cpudt->irq - cpudt->irq_prev; total_time += cpudt->sirq - cpudt->sirq_prev; total_time += cpudt->steal - cpudt->steal_prev; if (total_time < 1) total_time = 1; cpu->num_cpus = cpudt->id; cpu->puser = (__u16)((cpudt->usr - cpudt->usr_prev) * 10000 / total_time); cpu->psystem = (__u16)((cpudt->sys - cpudt->sys_prev) * 10000 / total_time); cpu->pnice = (__u16)((cpudt->nice - cpudt->nice_prev) * 10000 / total_time); if (cpudt->idle - cpudt->idle_prev > 0) cpu->pidle = (__u16)((cpudt->idle - cpudt->idle_prev) * 10000 / total_time); else cpu->pidle = 0; cpu->piowait = (__u16)((cpudt->iowt - cpudt->iowt_prev) * 10000 / total_time); cpu->pirq = (__u16)((cpudt->irq - cpudt->irq_prev) * 10000 / total_time); cpu->psoftirq = (__u16)((cpudt->sirq - cpudt->sirq_prev) * 10000 / total_time); cpu->psteal = (__u16)((cpudt->steal - cpudt->steal_prev) * 10000 / total_time); cpudt->usr_prev = cpudt->usr; cpudt->sys_prev = cpudt->sys; cpudt->nice_prev = cpudt->nice; cpudt->idle_prev = cpudt->idle; cpudt->iowt_prev = cpudt->iowt; cpudt->irq_prev = cpudt->irq; cpudt->sirq_prev = cpudt->sirq; cpudt->steal_prev = cpudt->steal; } /* * Get CPU summary information */ static void read_cpu(void) { unsigned long long u, n, s, i, w, x, y, z; if (read_file("/proc/stat", buf, sizeof(buf) - 1) <= 0) return; u = n = s = i = w = x = y = z = 0; temp = strstr(buf, "cpu"); if (temp) sscanf(temp, "cpu %Lu %Lu %Lu %Lu %Lu %Lu %Lu %Lu", &u, &n, &s, &i, &w, &x, &y, &z); else syslog(LOG_ERR, "no cpu in /proc/stat\n"); cpudata.usr = (__u64)u; cpudata.nice = (__u64)n; cpudata.sys = (__u64)s; cpudata.idle = (__u64)i; cpudata.iowt = (__u64)w; cpudata.irq = (__u64)x; cpudata.sirq = (__u64)y; cpudata.steal = (__u64)z; cpudata.id = (__u32)num_cpus; cal_cpu(&cpudata, &proc_sum.cpu); } /* * Get memory information */ static void read_mem(void) { unsigned long long mtotal, mfree, mbuf; unsigned long long stotal, sfree, scached; if (read_file("/proc/meminfo", buf, sizeof(buf) - 1) <= 0) return; mtotal = mfree = mbuf = stotal = sfree = scached = 0; temp = strstr(buf, "MemTotal"); if (temp) sscanf(temp, "MemTotal: %Lu kB", &mtotal); else syslog(LOG_ERR, "no MemTotal in /proc/meminfo\n"); temp = strstr(buf, "MemFree"); if (temp) sscanf(temp, "MemFree: %Lu kB", &mfree); else syslog(LOG_ERR, "no MemFree in /proc/meminfo\n"); temp = strstr(buf, "Buffers"); if (temp) sscanf(temp, "Buffers: %Lu kB", &mbuf); else syslog(LOG_ERR, "no Buffers in /proc/meminfo\n"); temp = strstr(buf, "SwapTotal"); if (temp) sscanf(temp, "SwapTotal: %Lu kB", &stotal); else syslog(LOG_ERR, "no SwapTotal in /proc/meminfo\n"); temp = strstr(buf, "SwapFree"); if (temp) sscanf(temp, "SwapFree: %Lu kB", &sfree); else syslog(LOG_ERR, "no SwapFree in /proc/meminfo\n"); temp = strstr(buf, "Cached"); if (temp) sscanf(temp, "Cached: %Lu kB", &scached); else syslog(LOG_ERR, "no Cached in /proc/meminfo\n"); proc_sum.mem.total = (__u64)mtotal; proc_sum.mem.free = (__u64)mfree; proc_sum.mem.buffers = (__u64)mbuf; proc_sum.swap.total = (__u64)stotal; proc_sum.swap.free = (__u64)sfree; proc_sum.swap.cached = (__u64)scached; proc_sum.mem.used = proc_sum.mem.total - proc_sum.mem.free; proc_sum.swap.used = proc_sum.swap.total - proc_sum.swap.free; } /* * Get virtual memory information */ static void read_vmem(void) { unsigned long long pgin, pgout, swpin, swpout; if (read_file("/proc/vmstat", buf, sizeof(buf) - 1) <= 0) return; pgin = pgout = swpin = swpout = 0; temp = strstr(buf, "pgpgin"); if (temp) sscanf(temp, "pgpgin %Lu", &pgin); else syslog(LOG_ERR, "no pgpgin in /proc/vmstat\n"); temp = strstr(buf, "pgpgout"); if (temp) sscanf(temp, "pgpgout %Lu", &pgout); else syslog(LOG_ERR, "no pgpgout in /proc/vmstat\n"); temp = strstr(buf, "pswpin"); if (temp) sscanf(temp, "pswpin %Lu", &swpin); else syslog(LOG_ERR, "no pswpin in /proc/vmstat\n"); temp = strstr(buf, "pswpout"); if (temp) sscanf(temp, "pswpout %Lu", &swpout); else syslog(LOG_ERR, "no pswpout in /proc/vmstat\n"); proc_sum.mem.pgpgin = (__u64)(pgin << pg_to_kb_shift); proc_sum.mem.pgpgout = (__u64)(pgout << pg_to_kb_shift); proc_sum.swap.pswpin = (__u64)swpin; proc_sum.swap.pswpout = (__u64)swpout; } /* * Get process summary information */ static void read_summary(void) { read_uptime(); count_users(); read_loadavg(); read_cpu(); read_mem(); read_vmem(); } /* * Get memory information for a task */ static int read_statm(struct task_t *task) { long size, res, sh, trs, lrs, drs, dt; snprintf(fname, sizeof(fname), "/proc/%u/statm", task->pid); if (read_file(fname, buf, sizeof(buf) - 1) == -1) return 0; sscanf(buf, "%ld %ld %ld %ld %ld %ld %ld", &size, &res, &sh, &trs, &lrs, &drs, &dt); task->size = (__u64)(size << pg_to_kb_shift); task->resident = (__u64)(res << pg_to_kb_shift); task->swap = task->size - task->resident; task->share = (__u64)(sh << pg_to_kb_shift); task->trs = (__u64)(trs << pg_to_kb_shift); task->drs = (__u64)(drs << pg_to_kb_shift); task->dt = (__u64)dt; task->pmem = (__u16)(task->resident * 10000 / proc_sum.mem.total); return 1; } /* * Get status information for a task from /proc/.../status */ static int read_status(struct task_t *task) { int ruid, euid, egid; char *lenp, *namep; struct passwd *pwd; struct group *grp; snprintf(fname, sizeof(fname), "/proc/%u/status", task->pid); if (read_file(fname, buf, sizeof(buf) - 1) == -1) return 0; ruid = euid = egid = 0; temp = strstr(buf, "Uid"); if (temp) sscanf(temp, "Uid: %d %d", &ruid, &euid); else syslog(LOG_ERR, "no Uid in /proc/%u/status\n", task->pid); temp = strstr(buf, "Gid"); if (temp) sscanf(temp, "Gid: %*d %d", &egid); else syslog(LOG_ERR, "no Gid in /proc/%u/status\n", task->pid); task->euid = (__u16)euid; lenp = mon_record + sizeof(struct monwrite_hdr); lenp += sizeof(struct procd_hdr); lenp += sizeof(struct task_t); namep = lenp + sizeof(__u16); pwd = getpwuid(ruid); if (!pwd) name_lens.ruser_len = sprintf(namep, "%u", ruid); else { name_lens.ruser_len = strlen(pwd->pw_name); if (name_lens.ruser_len > MAX_NAME_LEN) name_lens.ruser_len = MAX_NAME_LEN; memcpy(namep, pwd->pw_name, name_lens.ruser_len); } memcpy(lenp, &name_lens.ruser_len, sizeof(__u16)); lenp = namep + name_lens.ruser_len; namep = lenp + sizeof(__u16); pwd = getpwuid(task->euid); if (!pwd) name_lens.euser_len = sprintf(namep, "%u", task->euid); else { name_lens.euser_len = strlen(pwd->pw_name); if (name_lens.euser_len > MAX_NAME_LEN) name_lens.euser_len = MAX_NAME_LEN; memcpy(namep, pwd->pw_name, name_lens.euser_len); } memcpy(lenp, &name_lens.euser_len, sizeof(__u16)); lenp = namep + name_lens.euser_len; namep = lenp + sizeof(__u16); grp = getgrgid(egid); if (!grp) name_lens.egroup_len = sprintf(namep, "%u", egid); else { name_lens.egroup_len = strlen(grp->gr_name); if (name_lens.egroup_len > MAX_NAME_LEN) name_lens.egroup_len = MAX_NAME_LEN; memcpy(namep, grp->gr_name, name_lens.egroup_len); } memcpy(lenp, &name_lens.egroup_len, sizeof(__u16)); return 1; } /* * Calculate percentage of CPU used by a task since last sampling */ static void cal_task_pcpu(struct task_t *task, const unsigned long long tics) { unsigned int i, size; __u64 etics; if (proc_sum.task.total + 1 >= sort_tbl_size) { sort_tbl_size = sort_tbl_size * 5 / 4 + 100; size = sort_tbl_size * sizeof(struct task_sort_t); curr_sort_tbl = realloc(curr_sort_tbl, size); if (!curr_sort_tbl) { fprintf(stderr, "Allocating memory failed - " "reason %s\n", strerror(errno)); exit(1); } prev_sort_tbl = realloc(prev_sort_tbl, size); if (!prev_sort_tbl) { fprintf(stderr, "Allocating memory failed - " "reason %s\n", strerror(errno)); exit(1); } } curr_sort_tbl[proc_sum.task.total].pid = task->pid; curr_sort_tbl[proc_sum.task.total].tics = (__u64)tics; etics = (__u64)tics; for (i = 0; i < sort_tbl_size; i++) { if (prev_sort_tbl[i].pid == task->pid) { etics -= prev_sort_tbl[i].tics; break; } } task->pcpu = (__u16)((etics * 10000 / Hertz) / (e_time * num_cpus)); if (task->pcpu > 9999) task->pcpu = 9999; } /* * Get status information for a task from /proc/.../stat */ static int read_stat(struct task_t *task) { int ppid, tty, proc; unsigned long flags, pri, nice; unsigned long long maj_flt, utime, stime, cutime, cstime; char *cmd_start, *cmd_end, *cmdlenp, *cmdp; snprintf(fname, sizeof(fname), "/proc/%u/stat", task->pid); if (read_file(fname, buf, sizeof(buf) - 1) == -1) return 0; cmd_start = strchr(buf, '(') + 1; cmd_end = strchr(cmd_start, ')'); name_lens.cmd_len = cmd_end - cmd_start; cmdlenp = mon_record + sizeof(struct monwrite_hdr); cmdlenp += sizeof(struct procd_hdr); cmdlenp += sizeof(struct task_t); cmdlenp += sizeof(__u16) + name_lens.ruser_len; cmdlenp += sizeof(__u16) + name_lens.euser_len; cmdlenp += sizeof(__u16) + name_lens.egroup_len; cmdlenp += sizeof(__u16) + name_lens.wchan_len; if (name_lens.cmd_len <= 0) name_lens.cmd_len = 0; else { cmdp = cmdlenp + sizeof(__u16); if (name_lens.cmd_len > MAX_NAME_LEN) name_lens.cmd_len = MAX_NAME_LEN; memcpy(cmdp, cmd_start, name_lens.cmd_len); } memcpy(cmdlenp, &name_lens.cmd_len, sizeof(__u16)); cmd_end += 2; sscanf(cmd_end, "%c %d %*d %*d %d %*d " "%lu %*s %*s %Lu %*s " "%Lu %Lu %Lu %Lu " "%ld %ld " "%*d %*s " "%*s %*s %*s " "%*s %*s %*s %*s %*s %*s " "%*s %*s %*s %*s " "%*s %*s %*s " "%*d %d " "%*s %*s", &task->state, &ppid, &tty, &flags, &maj_flt, &utime, &stime, &cutime, &cstime, &pri, &nice, &proc); task->ppid = (__u32)ppid; task->tty = (__u16)tty; task->flags = (__u32)flags; task->maj_flt = (__u64)maj_flt; task->priority = (__s16)pri; task->nice = (__s16)nice; task->processor = (__u32)proc; task->total_time = (__u64)((utime + stime) * 100 / Hertz); task->ctotal_time = (__u64)((utime + stime + cutime + cstime) * 100 / Hertz); cal_task_pcpu(task, utime + stime); return 1; } /* * Get the sleeping in function of a task */ static int read_wchan(struct task_t *task) { int num; char *wchanlenp, *wchanp; snprintf(fname, sizeof(fname), "/proc/%u/wchan", task->pid); num = read_file(fname, buf, sizeof(buf) -1); if (num < 0) return 0; wchanlenp = mon_record + sizeof(struct monwrite_hdr); wchanlenp += sizeof(struct procd_hdr); wchanlenp += sizeof(struct task_t); wchanlenp += sizeof(__u16) + name_lens.ruser_len; wchanlenp += sizeof(__u16) + name_lens.euser_len; wchanlenp += sizeof(__u16) + name_lens.egroup_len; wchanp = wchanlenp + sizeof(__u16); name_lens.wchan_len = num; if (num == 0) { memcpy(wchanlenp, &name_lens.wchan_len, sizeof(__u16)); return 1; } buf[num] = '\0'; if (buf[0] == '0' && buf[1] == '\0') { memcpy(wchanlenp, &name_lens.wchan_len, sizeof(__u16)); wchanp[0] = '-'; return 1; } if (buf[0] == 's' && !strncmp(buf, "sys_", 4)) { name_lens.wchan_len -= 4; if (name_lens.wchan_len > MAX_NAME_LEN) name_lens.wchan_len = MAX_NAME_LEN; memcpy(wchanlenp, &name_lens.wchan_len, sizeof(__u16)); memcpy(wchanp, buf + 4, name_lens.wchan_len); return 1; } if (buf[0] == 'd' && !strncmp(buf, "do_", 3)) { name_lens.wchan_len -= 3; if (name_lens.wchan_len > MAX_NAME_LEN) name_lens.wchan_len = MAX_NAME_LEN; memcpy(wchanlenp, &name_lens.wchan_len, sizeof(__u16)); memcpy(wchanp, buf + 3, name_lens.wchan_len); return 1; } temp = buf; while (*temp == '_') { temp++; name_lens.wchan_len--; } if (name_lens.wchan_len > MAX_NAME_LEN) name_lens.wchan_len = MAX_NAME_LEN; memcpy(wchanlenp, &name_lens.wchan_len, sizeof(__u16)); memcpy(wchanp, temp, name_lens.wchan_len); return 1; } /* * Get command line of a task */ static int read_cmdline(struct task_t *task) { int i, num; char *cmdlnlenp, *cmdlinep; snprintf(fname, sizeof(fname), "/proc/%u/cmdline", task->pid); num = read_file(fname, buf, sizeof(buf) - 1); if (num == -1) return 0; for (i = 0; i < num; i++) { if (buf[i] < ' ' || buf[i] > '~') buf[i] = ' '; } name_lens.cmdline_len = num; cmdlnlenp = mon_record + sizeof(struct monwrite_hdr); cmdlnlenp += sizeof(struct procd_hdr); cmdlnlenp += sizeof(struct task_t); cmdlnlenp += sizeof(__u16) + name_lens.ruser_len; cmdlnlenp += sizeof(__u16) + name_lens.euser_len; cmdlnlenp += sizeof(__u16) + name_lens.egroup_len; cmdlnlenp += sizeof(__u16) + name_lens.wchan_len; cmdlnlenp += sizeof(__u16) + name_lens.cmd_len; cmdlinep = cmdlnlenp + sizeof(__u16); memcpy(cmdlnlenp, &name_lens.cmdline_len, sizeof(__u16)); if (name_lens.cmdline_len > 0) memcpy(cmdlinep, buf, name_lens.cmdline_len); return 1; } /* * Usage sorting help function */ static int sort_usage(const void *et1, const void *et2) { return ((struct task_sort_t *)et2)->cpu_mem_usage - ((struct task_sort_t *)et1)->cpu_mem_usage; } /* * Adjust task state counters */ static void task_count(char oldst, char newst) { if (oldst == newst) return; switch (oldst) { case 'R': proc_sum.task.running--; break; case 'S': case 'D': proc_sum.task.sleeping--; break; case 'T': proc_sum.task.stopped--; break; case 'Z': proc_sum.task.zombie--; break; } switch (newst) { case 'R': proc_sum.task.running++; break; case 'S': case 'D': proc_sum.task.sleeping++; break; case 'T': proc_sum.task.stopped++; break; case 'Z': proc_sum.task.zombie++; break; } } /* * Read and calculate memory and cpu usages of a task */ static void task_usage(struct task_t *task) { long res; unsigned long long utime, stime; snprintf(fname, sizeof(fname), "/proc/%u/statm", task->pid); if (read_file(fname, buf, sizeof(buf) - 1) == -1) return; sscanf(buf, "%*s %ld %*s %*s %*s %*s %*s", &res); task->resident = (__u64)(res << pg_to_kb_shift); task->pmem = (__u16)(task->resident * 10000 / proc_sum.mem.total); snprintf(fname, sizeof(fname), "/proc/%u/stat", task->pid); if (read_file(fname, buf, sizeof(buf) - 1) == -1) return; sscanf(buf, "%*s %*s " "%c %*s %*s %*s %*s %*s " "%*s %*s %*s %*s %*s " "%Lu %Lu ", &task->state, &utime, &stime); cal_task_pcpu(task, utime + stime); curr_sort_tbl[proc_sum.task.total].cpu_mem_usage = task->pcpu + task->pmem; curr_sort_tbl[proc_sum.task.total].state = task->state; task_count('\0', task->state); proc_sum.task.total++; } /* * read tasks information and write to monitor stream */ static void read_tasks(void) { int size; unsigned int i = 0, j = 0; DIR *proc_dir; struct direct *entry; struct task_t task; proc_dir = opendir("/proc"); if (!proc_dir) { syslog(LOG_ERR, "failed /proc open: %s ", strerror(errno)); return; } while ((entry = readdir(proc_dir))) { if (!entry->d_name) break; if (!isdigit(entry->d_name[0]) || !atoi(entry->d_name)) continue; memset(&task, 0, sizeof(struct task_t)); task.pid = atoi(entry->d_name); task_usage(&task); } closedir(proc_dir); if (proc_sum.task.total > MAX_TASK_REC) qsort(curr_sort_tbl, proc_sum.task.total, sizeof(struct task_sort_t), sort_usage); /* only write up to top 100 processes data to monitor stream */ while ((i < proc_sum.task.total) && (j < MAX_TASK_REC)) { memset(&task, 0, sizeof(struct task_t)); memset(mon_record, 0, sizeof(mon_record)); task.pid = curr_sort_tbl[i].pid; if (read_statm(&task) && read_status(&task) && read_wchan(&task) && read_stat(&task) && read_cmdline(&task)) { task_count(curr_sort_tbl[i].state, task.state); size = sizeof(struct task_t); size += sizeof(struct name_lens_t); size += name_lens.ruser_len + name_lens.euser_len; size += name_lens.egroup_len + name_lens.wchan_len; size += name_lens.cmd_len + name_lens.cmdline_len; procd_write_ent(&task, size, TASK_FLAG); j++; } else task_count(curr_sort_tbl[i].state, '\0'); i++; } proc_sum.task.total -= i - j; } /* * Run as background process */ static void procd_daemonize(void) { int pipe_fds[2], startup_rc = 1; pid_t pid; if (pipe(pipe_fds) == -1) { syslog(LOG_ERR, "pipe error: %s\n", strerror(errno)); exit(1); } /* Fork off the parent process */ pid = fork(); if (pid < 0) { syslog(LOG_ERR, "fork error: %s\n", strerror(errno)); exit(1); } if (pid > 0) { /* Wait for startup return code from daemon */ if (read(pipe_fds[0], &startup_rc, sizeof(startup_rc)) == -1) syslog(LOG_ERR, "pipe read error: %s\n", strerror(errno)); /* With startup_rc == 0, pid file was written at this point */ exit(startup_rc); } /* Change the file mode mask */ umask(0); /* Catch SIGINT and SIGTERM to clean up pid file on exit */ procd_handle_signals(); /* Create a new SID for the child process */ if (setsid() < 0) { syslog(LOG_ERR, "setsid error: %s\n", strerror(errno)); goto notify_parent; } /* Change the current working directory */ if (chdir("/") < 0) { syslog(LOG_ERR, "chdir error: %s\n", strerror(errno)); goto notify_parent; } /* Close out the standard file descriptors */ close(STDIN_FILENO); close(STDOUT_FILENO); close(STDERR_FILENO); /* Store daemon pid */ if (store_pid() < 0) goto notify_parent; startup_rc = 0; notify_parent: /* Inform waiting parent about startup return code */ if (write(pipe_fds[1], &startup_rc, sizeof(startup_rc)) == -1) { syslog(LOG_ERR, "pipe write error: %s\n", strerror(errno)); exit(1); } if (startup_rc != 0) exit(startup_rc); } static int procd_do_work(void) { int pgsize; struct timezone tz; struct task_sort_t *tmp; prev_small_max = 0; prev_big_max = 1; num_cpus = sysconf(_SC_NPROCESSORS_ONLN); if (num_cpus < 1) num_cpus = 1; pg_to_kb_shift = 0; pgsize = getpagesize(); while (pgsize > 1024) { pgsize >>= 1; pg_to_kb_shift++; } syslog(LOG_INFO, "procd sample interval: %lu\n", sample_interval); while (1) { gettimeofday(&curr_time, &tz); e_time = (curr_time.tv_sec - prev_time.tv_sec) + (float)(curr_time.tv_usec - prev_time.tv_usec) / 1000000.0; memset(&proc_sum, 0, sizeof(struct proc_sum_t)); tmp = prev_sort_tbl; prev_sort_tbl = curr_sort_tbl; curr_sort_tbl = tmp; curr_small_max = 0; curr_big_max = 1; read_summary(); read_tasks(); memset(mon_record, 0, sizeof(mon_record)); procd_write_ent(&proc_sum, sizeof(struct proc_sum_t), SUM_FLAG); if (curr_small_max < prev_small_max) stop_unused(curr_small_max, prev_small_max); if (curr_big_max < prev_big_max) stop_unused(curr_big_max, prev_big_max); prev_small_max = curr_small_max; prev_big_max = curr_big_max; prev_time.tv_sec = curr_time.tv_sec; prev_time.tv_usec = curr_time.tv_usec; sleep(sample_interval); } return 1; } /* Parse options */ static int parse_options(int argc, char **argv) { int opt; do { opt = getopt_long(argc, argv, opt_string, options, NULL); switch (opt) { case -1: /* Reached end of parameter list. */ break; case 'h': printf("%s", help_text); exit(0); case 'v': printf( "mon_procd: version %s\n", RELEASE_STRING); printf("Copyright IBM Corp. 2007, 2017\n"); exit(0); case 'a': attach = 1; break; case 'i': sample_interval = strtol(optarg, NULL, 10); if (sample_interval <= 0) { fprintf(stderr, "Error: Invalid interval " "(needs to be greater than 0)\n"); return(1); } break; default: fprintf(stderr, "Try ' --help' for more" " information.\n"); return(1); } } while (opt != -1); return(0); } int main(int argc, char **argv) { int rc; attach = 0; rc = parse_options(argc, argv); if (rc > 0) return rc; procd_open_monwriter(); openlog("mon_procd", 0, LOG_DAEMON); if (!attach) procd_daemonize(); rc = procd_do_work(); close(mw_dev); return rc; } s390-tools-2.3.0/mon_tools/mon_procd.h000066400000000000000000000063201323410431100175030ustar00rootroot00000000000000/* * mon_procd - Write process data to the z/VM monitor strea * * Definitions used by mon_procd * * Copyright IBM Corp. 2007, 2017 * * s390-tools is free software; you can redistribute it and/or modify * it under the terms of the MIT license. See LICENSE for details. */ #ifndef __mon_procd_h__ #define __mon_procd_h__ #include #include "lib/zt_common.h" /* mon_function values */ #define MONWRITE_START_INTERVAL 0x00 /* start interval recording */ #define MONWRITE_STOP_INTERVAL 0x01 /* stop interval or config recording */ #define SMALL_MON_RECORD_LEN 512 #define MAX_REC_LEN 1500 #define PROCD_APPLID 0x02 #define SUM_FLAG 0x00 #define TASK_FLAG 0x01 #define BUF_SIZE 4096 #define MAX_NAME_LEN 64 #define MAX_CMD_LEN 1024 #define MAX_TASK_REC 100 #define Hertz 100 struct monwrite_hdr { unsigned char mon_function; unsigned short applid; unsigned char record_num; unsigned short version; unsigned short release; unsigned short mod_level; unsigned short datalen; unsigned char hdrlen; } __attribute__((packed)); struct procd_hdr { __u64 time_stamp; __u16 data_len; __u16 data_offset; } __attribute__((packed)); struct task_sum_t { __u32 total; __u32 running; __u32 sleeping; __u32 stopped; __u32 zombie; }; struct cpu_t { __u32 num_cpus; __u16 puser; __u16 pnice; __u16 psystem; __u16 pidle; __u16 piowait; __u16 pirq; __u16 psoftirq; __u16 psteal; }; struct mem_t { __u64 total; __u64 used; __u64 free; __u64 buffers; __u64 pgpgin; __u64 pgpgout; }; struct swap_t { __u64 total; __u64 used; __u64 free; __u64 cached; __u64 pswpin; __u64 pswpout; }; struct proc_sum_t { __u64 uptime; __u32 users; char loadavg_1[6]; char loadavg_5[6]; char loadavg_15[6]; struct task_sum_t task; struct cpu_t cpu; struct mem_t mem; struct swap_t swap; } __attribute__((packed)); struct task_t { __u32 pid; __u32 ppid; __u32 euid; __u16 tty; __s16 priority; __s16 nice; __u32 processor; __u16 pcpu; __u16 pmem; __u64 total_time; __u64 ctotal_time; __u64 size; __u64 swap; __u64 resident; __u64 trs; __u64 drs; __u64 share; __u64 dt; __u64 maj_flt; char state; __u32 flags; } __attribute__((packed)); struct cpudata_t { __u32 id; __u64 usr; __u64 nice; __u64 sys; __u64 idle; __u64 iowt; __u64 irq; __u64 sirq; __u64 steal; __u64 usr_prev; __u64 nice_prev; __u64 sys_prev; __u64 idle_prev; __u64 iowt_prev; __u64 irq_prev; __u64 sirq_prev; __u64 steal_prev; }; struct task_sort_t { __u32 pid; __u64 tics; __u16 cpu_mem_usage; char state; }; static struct option options[] = { {"help", no_argument, NULL, 'h'}, {"version", no_argument, NULL, 'v'}, {"attach", no_argument, NULL, 'a'}, {"interval", required_argument, NULL, 'i'}, {NULL, 0, NULL, 0} }; static const char opt_string[] = "+hvai:"; static const char help_text[] = "mon_procd: Daemon that writes process data information\n" "to the z/VM monitor stream.\n" "\n" "Usage: mon_procd [OPTIONS]\n" "\n" "Options:\n" "-h, --help Print this help, then exit\n" "-v, --version Print version information, then exit\n" "-a, --attach Run in foreground\n" "-i, --interval= Sample interval\n" "\n" "Please report bugs to: linux390@de.ibm.com\n"; #endif s390-tools-2.3.0/netboot/000077500000000000000000000000001323410431100150125ustar00rootroot00000000000000s390-tools-2.3.0/netboot/Dockerfile000066400000000000000000000005671323410431100170140ustar00rootroot00000000000000# # Sample Dockerfile to build PXE-style boot image for KVM on s390 # FROM s390x/ubuntu:16.04 RUN apt-get update && apt-get install -y \ linux-image-4.4.0-78-generic \ make \ wget \ bzip2 \ linux-headers-4.4.0-78-generic \ gcc \ kexec-tools \ file RUN mkdir /netboot COPY . /netboot RUN cd /netboot && make -f Makefile.pxelinux.0 KERNEL_VERSION=4.4.0-78-generic s390-tools-2.3.0/netboot/Makefile000066400000000000000000000006201323410431100164500ustar00rootroot00000000000000# Install the netboot image build scripts as samples include ../common.mak NETBOOT_SAMPLEDIR=$(TOOLS_DATADIR)/netboot all: install: all $(INSTALL) -d -m 755 $(DESTDIR)$(NETBOOT_SAMPLEDIR) $(INSTALL) -m 755 mk-s390image mk-pxelinux-ramfs \ $(DESTDIR)$(NETBOOT_SAMPLEDIR) $(INSTALL) -m 644 Dockerfile Makefile.pxelinux.0 README.md \ $(DESTDIR)$(NETBOOT_SAMPLEDIR) .PHONY: all install clean s390-tools-2.3.0/netboot/Makefile.pxelinux.0000066400000000000000000000024151323410431100204650ustar00rootroot00000000000000# Sample Makefile to produce a pxe-style netboot image # matching the currently active kernel. # If the image is to be build for a different kernel version # the KERNEL_VERSION variable must be overridden # To use a local busybox installation, change BBINSTALL # to point to the local busybox install path ifeq ("$(KERNEL_VERSION)","") KERNEL_VERSION=$(shell uname -r) endif export KERNEL_VERSION filetype=$(shell file $1 | grep "Linux S390") s390_check=$(if $(call filetype,$1), $1) prefixes=image vmlinux vmlinuz kernel_images=$(foreach prefix, $(prefixes), \ $(call s390_check,/boot/$(prefix)-$(KERNEL_VERSION))) KERNEL_IMAGE=$(firstword $(kernel_images)) ifeq ($(KERNEL_IMAGE),) $(error Could not find a kernel image under /boot) endif BUSYBOX=busybox-1.27.1 BBINSTALL=$(BUSYBOX)/_install all: $(KERNEL_IMAGE) pxelinux.initramfs /bin/bash mk-s390image $(KERNEL_IMAGE) pxelinux.0 -r pxelinux.initramfs pxelinux.initramfs: $(BBINSTALL) /bin/bash mk-pxelinux-ramfs -b $< -k $(KERNEL_VERSION) $@ $(BUSYBOX)/_install: wget https://busybox.net/downloads/$(BUSYBOX).tar.bz2 tar xjf $(BUSYBOX).tar.bz2 make -C $(BUSYBOX) defconfig make -C $(BUSYBOX) install install: clean: $(RM) pxelinux.0 pxelinux.initramfs $(BUSYBOX).tar.bz2 $(RM) -r $(BUSYBOX) .PHONY: all install clean s390-tools-2.3.0/netboot/README.md000066400000000000000000000111201323410431100162640ustar00rootroot00000000000000# How to build a PXELINUX-style network boot image for KVM ## Synopsis To build a PXELINUX-style netboot image usable for KVM a s390 Linux system with access to the internet is required. Running the following command will generate the netboot image pxlinux.0: ` $ make -f Makefile.pxelinux.0` Alternatively you can use docker to build the image: ``` $ docker build -t pxelinux0 . $ docker run --rm -v $(pwd):/out pxelinux0 cp /netboot/pxelinux.0 /out $ docker rmi pxelinux0 ``` The resulting file pxelinux.0 must be copied to the system acting as DHCP/BOOTP server for the KVM installation. ## Full Description Starting with QEMU 2.10 it is possible to boot s390 virtual machines over a network interface using DHCP. As usual for DHCP/BOOTP a single bootable image is copied from the boot server, loaded into memory and booted. In order to boot a Linux Operating System, it is typically necessary to load a kernel together with an initial ramdisk (initramfs) and optionally specify some kernel command line parameters. Alternatively, on s390 it is possible to load a single file consisting of the kernel image followed by an initial ramdisk. Such single boot images can be provided by a Linux distributor, e.g. on the installation media. Single boot images can also easily be built from pre-existing kernel/initramfs pairs by concatenating these files. In order to allow the kernel to find the ramdisk, it is necessary to update the 8 bytes at location 0x10408 with the offset value of the ramdisk in the new binary, and the 8 bytes at location 0x10410 with the size of the ramdisk. Both values need to be updated in binary, big endian format. Since PXELINUX, the PXE boot implementation provided by the Syslinux project, has introduced a popular way to set up network boot servers for Linux, it is desirable that s390 network boot setups can be done in a similar way. A boot image simulating a PXELINUX-like boot for s390 can be easily constructed by combining a Linux kernel with a small fit-to-purpose initial ramdisk as described above. For practical purposes, using the host kernel is a reasonable way for this kind of approach. If possible, the initial ramdisk should be independent of the host, which is not always possible, as the kernel might require modules for e.g. virtio network and block devices. ### Example: Building a PXELINUX-style boot image The approach described below consists of bundling some shell scripts, busybox and the kexec binary bundled into the initial ramdisk. The init process can be a simple shell script that will mount a few essential file systems, like /dev, /proc, and /sys, start a DHCP client (e.g. busybox's udchpc) and then invoke another script to perform the network boot. udchpc will invoke the script /usr/share/udhcpc/default.script in response to DHCP server messages to perform configuration actions. The sample default.script delivered with busybox can be used for that purpose, but needs to be extended to evaluate the bootp specific DHCP options (most important the tftp server address) and store them for use by the boot script. The boot script itself has to retrieve the PXELINUX configuration from the tftp server according to the rules described [here][1] then retrieve the remote kernel and initial ramdisk and finally use kexec to boot the network kernel. In essence, the following steps are performed to produce the initial ramdisk: 1. Create a skeleton initramfs directory structure 2. Create the init script, the boot script and the DHCP default script 3. Copy kexec and it's dependencies from the host into the initramfs 4. Copy virtio network and block modules of the host's active kernel into the initramfs 5. Copy the busybox binaries into the initramfs. 6. Copy the DHCP configuration and PXE boot scripts to the initramfs 7. Build the ramdisk (in compressed CPIO format) 8. Concatenate the kernel image and the initial ramdisk, and adjust the ramdisk offset as described above. Steps 1 to 7 are performed by the sample script mk-pxelinux-ramfs, while step 8 is done with the help of mk-s390image. The binary resulting from the procedure described above can now be deployed to a DHCP/BOOTP server. This server should also act as a TFTP server for the PXELINUX configuration and binary files needed to complete the network boot. Alternatively, it is possible to use programs like [petitboot][2] or [pxe-kexec][3] in the initial ramdisk, as these provide more sophisticated and robust processing of PXELINUX-style configurations. [1]: http://www.syslinux.org/wiki/index.php?title=PXELINUX [2]: https://github.com/open-power/petitboot [3]: https://sourceforge.net/projects/pxe-kexec.berlios/ s390-tools-2.3.0/netboot/mk-pxelinux-ramfs000077500000000000000000000215161323410431100203340ustar00rootroot00000000000000#!/bin/bash # # netboot - PXE-style boot for KVM on s390 # # Sample script to build an initramfs image suitable for performing a # PXELINUX-style boot for KVM guests from a DHCP/BOOTP Server. # Has to be executed on the KVM host where it will be deployed and needs # to be re-run after kernel updates on the host, unless the virtio # drivers are statically built into the host kernel. # # The script requires a busybox install tree, e.g. resulting from a build # from source, after make defconfig && make install # # To keep things simple, we don't include udev but use devtmpfs # which means the host kernel must have been built with CONFIG_DEVTMPFS=y # # Sample invocation: # # ./mk-pxelinux-ramfs -b /downloads/busyboxdir pxelinux.initramfs # # Copyright IBM Corp. 2017 # # s390-tools is free software; you can redistribute it and/or modify # it under the terms of the MIT license. See LICENSE for details. # Variables cmd=$(basename $0) busyboxdir= builddir= initramfs= success=no # Cleanup on exit cleanup() { if [ -n $builddir ] then rm -rf $builddir fi } trap cleanup EXIT # Usage usage() { cat <<-EOF Usage: $cmd -b BUSYBOX_DIR [-k KERNEL_VERSION] INITRAMFS_FILE Build a PXELINUX style boot initramfs INITRAMFS_FILE using a busybox installed in BUSYBOX_DIR and kernel modules from the currently running kernel or from the kernel version specified with the '-k KERNEL_VERSION' option. OPTIONS -b Search installed busybox in directory BUSYBOX_DIR -k Use KERNEL_VERSION instead of currently running kernel -h Print this help, then exit EOF } # Get shared objects for binary sharedobjs() { ldd $1 | sed -e 's?[^/]*??' -e 's/(.*)//' } # Check args args=$(getopt b:k:h $*) if [ $? = 0 ] then set -- $args while [ -n $1 ] do case $1 in -b) busyboxdir=$2; shift 2;; -k) kernelversion=$2; shift 2;; -h) usage; exit 0;; --) shift; break;; *) echo "$cmd: Unexpected argument $1, exiting..." >&2; exit 1;; esac done fi if [ $# != 1 -o "$busyboxdir" = "" ] then usage >&2 exit 1 fi # Full output file path initramfs=$(readlink -m $(dirname $1))/$(basename $1) # Exit on error set -e # Module locations if [ -n $kernelversion ]; then moddir=/lib/modules/$kernelversion else moddir=/lib/modules/$(uname -r) fi netdir=$moddir/kernel/drivers/net blkdir=$moddir/kernel/drivers/block # Setup build directory builddir=$(mktemp -d) echo "$cmd: Building in $builddir" ramfsdirs="/bin /dev /etc /lib64 /lib /mnt /proc /run /sbin /sys /tmp /usr /var" for d in $ramfsdirs do mkdir -p $builddir/$d done # Kexec echo "$cmd: Copying kexec" # Install both binary and required shared libraries OLDPATH=$PATH PATH=$OLDPATH:/sbin:/usr/sbin kexec_bin=$(which kexec) kexec_sos=$(sharedobjs $kexec_bin) PATH=$OLDPATH cp $kexec_bin $builddir/sbin for so in $kexec_sos do mkdir -p $builddir/$(dirname $so) cp $so $builddir/$(dirname $so) done # virtio module(s), if present echo "$cmd: Copying virtio modules" mkdir -p $builddir/$netdir mkdir -p $builddir/$blkdir set +e cp $netdir/virtio_net.ko $builddir/$netdir 2> /dev/null cp $blkdir/virtio_blk.ko $builddir/$blkdir 2> /dev/null set -e # Busybox (+ dependencies) echo "$cmd: Copying busybox files" cp -a $busyboxdir/* $builddir busybox_sos=$(sharedobjs $busyboxdir/bin/busybox) for so in $busybox_sos do mkdir -p $builddir/$(dirname $so) cp $so $builddir/$(dirname $so) done # ad_packet module(s), if present echo "$cmd: Copying af_packet modules" packetdir=$moddir/kernel/net/packet mkdir -p $builddir/$packetdir set +e cp $packetdir/* $builddir/$packetdir 2> /dev/null set -e # Init script echo "$cmd: Making init script" # --- begin init script cat <<'EOF' > $builddir/init #!/bin/sh /bin/mount -t devtmpfs none /dev /bin/mount -t proc none /proc /bin/mount -t sysfs none /sys /bin/mount -t tmpfs none /run /sbin/modprobe virtio_net /sbin/udhcpc -O pxeconffile -O pxepathprefix & /sbin/pxeboot.script EOF # --- end init script chmod +x $builddir/init # udhcpc script echo "$cmd: Making DHCP script" mkdir -p $builddir/usr/share/udhcpc # -- begin dhcp script cat <<'EOF' > $builddir/usr/share/udhcpc/default.script #!/bin/sh # Setup name resolution and PXE boot configuration # called by udhcpc RESOLVCONF="/etc/resolv.conf" PXECONF="/etc/pxe.conf" ccidr() { # clumsy netmask to cidr transformation # with minimal sanity checking OLDIFS=$IFS IFS=. c=0 n=4 for i in $1 do n=$(/usr/bin/expr $n - 1) case $i in 255) c=$(/usr/bin/expr $c + 8);; 254) c=$(/usr/bin/expr $c + 7); break;; 252) c=$(/usr/bin/expr $c + 6); break;; 248) c=$(/usr/bin/expr $c + 5); break;; 240) c=$(/usr/bin/expr $c + 4); break;; 224) c=$(/usr/bin/expr $c + 3); break;; 192) c=$(/usr/bin/expr $c + 2); break;; 128) c=$(/usr/bin/expr $c + 1); break;; 0) break;; *) c=0; break;; esac if [ $n = 0 ] then break fi done IFS=$OLDIFS echo $c } echo "DHCP response $1: " case "$1" in deconfig) echo " interface: $interface" /sbin/ip route flush table all /sbin/ip addr flush $interface /sbin/ip link set $interface up /bin/rm -f $PXECONF $RESOLVCONF ;; renew|bound) echo " interface: $interface $ip $subnet" echo " router: $router" echo " domain: $domain $dns" echo " tftp: $siaddr" # flush routes /sbin/ip route flush table all # setup if link /sbin/ip addr flush $interface /sbin/ip link set $interface up # setup if addr if [ -n "$subnet" ] then maskedip="$ip"/$(ccidr $subnet) else maskedip="ip" fi /sbin/ip addr add $maskedip broadcast $broadcast dev $interface # setup default routes if [ -n "$router" ] then /sbin/ip route add default via $router fi # setup resolv.conf if [ -n "$domain" ] then echo "search $domain" > $RESOLVCONF for i in $dns do echo " nameserver $i" >> $RESOLVCONF done fi # pxe control if [ -n "$siaddr" ] then echo "siaddr=$siaddr" > $PXECONF echo "interface=$interface" >> $PXECONF echo "ip=$ip" >> $PXECONF fi ;; nak) ;; *) exit 1 ;; esac exit 0 EOF # -- end dhcp script chmod +x $builddir/usr/share/udhcpc/default.script # pxeboot script echo "$cmd: Making PXE boot script" # -- begin pxeboot script cat <<'EOF' > $builddir/sbin/pxeboot.script #!/bin/sh # Perform a PXE style boot using kexec # Supports only super-simple config files set -e # Check if a valid pxe conf is available # so far a non configurable 600 sec timeout PXE_CONF=/etc/pxe.conf TIMEOUT=600 WAITED=0 echo "waiting for pxe config from DHCP (max $TIMEOUT sec)" while [ ! -f $PXE_CONF ] do sleep 1 WAITED=$(($WAITED + 1)) if [ $WAITED -gt $TIMEOUT ] then echo Error waiting for PXE configuration from DHCP exit fi done # Source the DHCP-generated TFTP info # Currently we are just looking for siaddr . $PXE_CONF # Retrieve the config (default only for now) CONFIGS="" if [ -n "$siaddr" ]; then # Enable UUID based config on s390 if [ $(/bin/uname -m) = "s390x" ] then set +e uuid=$(/bin/grep UUID /proc/sysinfo | tail -1 | tr -d ' ' | cut -d ':' -f 2) 2> /dev/null set -e else # not caring for other arches right now uuid="" fi CONFIGS="$CONFIGS $uuid" # Enable MAC based config ifaddr=$(/bin/cat /sys/class/net/$interface/address | tr ':' '-') CONFIGS="$CONFIGS 01-$ifaddr" # Enable IP based config iphex=$(printf %02X $(echo $ip | tr '.' ' ')) for i in 8 7 6 5 4 3 2 1 do CONFIGS="$CONFIGS $(echo $iphex | cut -c 1-$i)" done # Finally enable default config CONFIGS="$CONFIGS default" set +e for c in $CONFIGS do echo "fetching config pxelinux.cfg/$c from $siaddr" if /usr/bin/tftp -g -l /tmp/config -r pxelinux.cfg/$c $siaddr then break fi done fi if [ ! -f /tmp/config ] then echo no config found exit fi # Simple config file parsing, only one entry allowed kernel=$(/bin/grep -i "^[[:space:]]*kernel" /tmp/config | sed "s/^[[:space:]]*kernel[[:space:]]*//I") initrd=$(/bin/grep -i "^[[:space:]]*initrd" /tmp/config | sed "s/^[[:space:]]*initrd[[:space:]]*//I") append=$(/bin/grep -i "^[[:space:]]*append" /tmp/config | sed "s/^[[:space:]]*append[[:space:]]*//I") if [ -z "$kernel" ] then echo no kernel statement found in config exit else echo fetch kernel $kernel from $siaddr /usr/bin/tftp -g -l /tmp/kernel -r $kernel $siaddr fi if [ -n "$initrd" ] then echo fetch initrd $initrd from $siaddr /usr/bin/tftp -g -l /tmp/initrd -r $initrd $siaddr INITRD="--initrd=/tmp/initrd" else INITRD="" fi if [ -z "$append" ]; then echo "Kexec load: kexec -l /tmp/kernel $INITRD" kexec -l /tmp/kernel $INITRD else echo "Kexec load: kexec -l /tmp/kernel $INITRD --append=\"$append\"" kexec -l /tmp/kernel $INITRD --append="$append" fi kexec -e EOF # -- end pxeboot script chmod +x $builddir/sbin/pxeboot.script # The final initramfs echo Building initramfs cd $builddir find . | cpio -o -Hnewc | gzip - > $initramfs cd $OLDPWD s390-tools-2.3.0/netboot/mk-s390image000077500000000000000000000067031323410431100170540ustar00rootroot00000000000000#!/bin/bash # # netboot - PXE-style boot for KVM on s390 # # Sample script to build a single s390 boot image consisting of # kernel, an initial ramdisk and kernel parameters from # individual components. Note that bash is required to run this script! # # Sample invocation: # # ./mk-s390image /boot/image -r /boot/initrd image # # The resulting image can be used to build a bootable # ISO or as firmware image for KVM. # # Copyright IBM Corp. 2017 # # s390-tools is free software; you can redistribute it and/or modify # it under the terms of the MIT license. See LICENSE for details. # Offsets OFFS_INITRD_START_BYTES=66568 OFFS_INITRD_SIZE_BYTES=66576 OFFS_COMMANDLINE_BYTES=66688 MAX_PARMFILE_SIZE=896 # Variables cmd=$(basename $0) kernel= ramdisk= parmfile= image= binval= success=no # Cleanup on exit cleanup() { if [ -n "$binval" ] then rm -f $binval fi if [ -n "$image" -a $success = no ] then rm $image fi } trap cleanup EXIT # Usage usage() { cat <<-EOF Usage: $cmd KERNEL BOOT_IMAGE [-r RAMDISK] [-p PARMFILE] Build an s390 image BOOT_IMAGE suitable for CD/tape/network boot or as a KVM firmware image using a stripped Linux kernel file KERNEL. OPTIONS -p Use PARMFILE with kernel parameters in the image -r Include RAMDISK in the image -h Print this help, then exit EOF } # Convert decimal number to big endian doubleword dec2be64() { local num=$1 local b local i for i in $(seq 1 8) do b="\\x$(printf '%x' $(expr $num % 256))$b" num=$(expr $num / 256) || true done printf $b } # Do the image build dobuild() { local i local kernel_size local ramdisk_size local ramdisk_offset local parmfile_size # check whether all specified files exist for i in $kernel $ramdisk $parmfile do if [ ! -f $i ] then echo "$cmd: File $i not found" >&2 return 1 fi done if ! file -b $(readlink -f $kernel) | grep "Linux S390" > /dev/null then echo "$cmd: Unrecognized file format for $kernel" >&2 return 1 fi # from now on we SHOULD only fail on disk shortage # or file permissions, let the shell handle that set -e # copy over kernel padded with zeroes to page boundary dd if=$kernel of=$image bs=4096 conv=sync status=none # append ramdisk if specified if [ "$ramdisk" != "" ] then ramdisk_size=$(du -b $ramdisk | cut -f1) kernel_size=$(du -b $kernel | cut -f1) ramdisk_offset=$(du -b $image | cut -f1) cat $ramdisk >> $image binval=$(mktemp) dec2be64 $ramdisk_offset > $binval dd seek=$OFFS_INITRD_START_BYTES if=$binval of=$image bs=1 \ count=8 conv=notrunc status=none dec2be64 $ramdisk_size > $binval dd seek=$OFFS_INITRD_SIZE_BYTES if=$binval of=$image bs=1 \ count=8 conv=notrunc status=none fi # set cmdline if [ "$parmfile" != "" ] then parmfile_size=$(du -b $parmfile | cut -f1) if [ $parmfile_size -le $MAX_PARMFILE_SIZE ] then dd seek=$OFFS_COMMANDLINE_BYTES bs=1 if=$parmfile \ of=$image conv=notrunc status=none else echo "$cmd: Size $parmfile_size of $parmfile exceeds command line limit of $MAX_PARMFILE_SIZE" >&2 return 1 fi fi # we've done it success=yes } # check args and build args=$(getopt "r:p:h" $*) if [ $? = 0 ] then set -- $args while [ $1 != "" ] do case $1 in -r) ramdisk=$2; shift 2;; -p) parmfile=$2; shift 2;; -h) usage; exit 0;; --) shift; break;; *) echo "$cmd: Unexpected argument $1, exiting..." >&2; exit 1;; esac done fi if [ $# = 2 ] then kernel=$1 image=$2 dobuild exit 0 fi # something wasn't right usage >&2 exit 1 s390-tools-2.3.0/osasnmpd/000077500000000000000000000000001323410431100151645ustar00rootroot00000000000000s390-tools-2.3.0/osasnmpd/Makefile000066400000000000000000000015551323410431100166320ustar00rootroot00000000000000include ../common.mak LDLIBS = `net-snmp-config --agent-libs` # On some Linux systems `net-snmp-config --agent-libs` introduces -pie, # therefore add -fPIC to prevent link failures. ALL_CFLAGS += -fPIC ALL_CFLAGS += `net-snmp-config --cflags` OBJS = ibmOSAMib.o ibmOSAMibUtil.o osasnmpd.o ifeq (${HAVE_SNMP},0) all: $(SKIP) HAVE_SNMP=0 install: $(SKIP) HAVE_SNMP=0 else check_dep: $(call check_dep, \ "osasnmpd", \ "net-snmp/net-snmp-config.h", \ "net-snmp-devel or libsnmp-dev", \ "HAVE_SNMP=0") all: check_dep osasnmpd osasnmpd: $(OBJS) install: all $(INSTALL) -d -m 755 $(DESTDIR)$(USRSBINDIR) $(DESTDIR)$(MANDIR)/man8 $(INSTALL) -g $(GROUP) -o $(OWNER) -m 755 osasnmpd \ $(DESTDIR)$(USRSBINDIR) $(INSTALL) -g $(GROUP) -o $(OWNER) -m 644 osasnmpd.8 \ $(DESTDIR)$(MANDIR)/man8 endif clean: rm -f $(OBJS) osasnmpd core .PHONY: all install clean s390-tools-2.3.0/osasnmpd/ibmOSAMib.c000066400000000000000000000747071323410431100171110ustar00rootroot00000000000000/* * osasnmpd - IBM OSA-Express network card SNMP subagent * * Basic MIB implementation module for the OSA-E subagent * * The code in this module is typical for a net-snmp MIB implementation * information on how this works because the MIB layout is retrieved during * startup of the subagent, the magic identifier is not used within this * implementation. The var_ function uses the vp->type instead to distinct * the OIDs. * * Copyright 2017 IBM Corp. * * s390-tools is free software; you can redistribute it and/or modify * it under the terms of the MIT license. See LICENSE for details. */ #include "lib/zt_common.h" #include "ibmOSAMibUtil.h" #include "ibmOSAMib.h" /* ptr to OSA Express MIB information stored in linked lists */ TABLE_OID* oid_list_head; /* ptr to interface information on this system */ IF_LIST* if_list; int ifNumber; /********************************************************************** * init_ibmOSAMib(): * Initialization routine. This function is called when the agent * starts up. * parameters: * IN uses global MIB data * OUT none * returns: none *********************************************************************/ void init_ibmOSAMib(void) { int i, sd, /* socket descriptor */ error_code, /* holds errno value */ osaexp_num, /* number of OSA Express devices */ retc; /* return code from register_tables */ struct ifreq ifr; /* request structure for ioctl */ IPA_CMD_REG* ipa_reg_mib; /* structure for IPA REGISTER MIB command header */ char* buffer; /* a data buffer */ char time_buf[TIME_BUF_SIZE]; /* date/time buffer */ /* init head for Toplevel OID linked list */ oid_list_head = init_oid_list(); if ( oid_list_head == NULL ) { fprintf( stderr, "init_ibmOSAMib(): " "malloc() for OID list head failed\n" "Cannot start subagent...exiting...\n"); exit(1); } /* GET net-snmp ifNumber/ifIndex/ifDescr from IF-MIB for all interfaces */ /* on this system */ ifNumber = query_IF_MIB( &if_list ); if ( ifNumber < 0 ) { fprintf( stderr, "init_ibmOSAMib(): " "could not obtain interface info from IF-MIB\n" "check if: snmpd daemon is started and subagent " "access control is correct\n" "see agent log file for more details\n" "Cannot start subagent...exiting...\n"); exit(1); } else if ( ifNumber == 0 ) { get_time( time_buf ); snmp_log( LOG_ERR, "%s init_ibmOSAMib(): " "SNMP reports no devices within IF-MIB" " - starting subagent anyway\n", time_buf ); return; } /* end if */ /* query OSA-E device driver for OSA-E devices and mark them in IF-MIB interface list */ osaexp_num = query_OSA_EXP ( &if_list, ifNumber ); if ( osaexp_num < 0 ) { fprintf( stderr, "init_ibmOSAMib(): " "OSA-E device driver query interface ioctl() failed\n" "check agent log file for more details\n" "Cannot start subagent...exiting...\n"); exit(1); } else if ( osaexp_num == 0 ) { fprintf( stderr, "init_ibmOSAMib(): bad or no OSA-E devices reported\n" "check agent log file for more details\n" "Cannot start subagent...exiting...\n"); exit(1); } /* end if */ /* allocate area, that should contain retrieved MIB data for a single interface */ buffer = (char*) malloc ( MIB_AREA_LEN ); if ( buffer == NULL ) { fprintf( stderr, "init_ibmOSAMib(): " "malloc() for REGISTER MIB data buffer " "failed\ninit_ibmOSAMib(): requested %d bytes\n" "Cannot start subagent...exiting...\n", MIB_AREA_LEN ); exit(1); } /* end if */ /* open socket for ioctl */ sd = socket( AF_INET, SOCK_STREAM, 0 ); if ( sd < 0 ) { error_code = errno; fprintf( stderr, "init_ibmOSAMIB(): " "error opening socket() - reason %s\n" "Cannot start subagent...exiting...\n", strerror( error_code ) ); exit(1); } /* end if */ /* walk through interface list and query MIB data for all OSA-E devices */ /* register MIB data with subagent driving code afterwards */ for ( i=0; i < ifNumber; i++ ) { if ( if_list[i].is_OSAEXP == TRUE ) { /* clear buffer */ memset( buffer, 0, MIB_AREA_LEN ); /* setup ioctl buffer with request and input parameters */ ipa_reg_mib = (IPA_CMD_REG*) buffer; /* map command structure */ ipa_reg_mib->ioctl_cmd.data_len = /* length of IPA data area */ MIB_AREA_LEN - offsetof( IOCTL_CMD_HDR, ipa_cmd_hdr ); ipa_reg_mib->ioctl_cmd.req_len = /* length of IPA subcommand */ sizeof( ipa_reg_mib->ioctl_cmd ); ipa_reg_mib->ioctl_cmd.ipa_cmd_hdr.request = IPA_REG_MIB; /* IPA subcommand code */ ipa_reg_mib->ioctl_cmd.ipa_cmd_hdr.ifIndex = if_list[i].ifIndex; /* assign IF-MIB ifIndex */ ipa_reg_mib->ioctl_cmd.ipa_cmd_hdr.ret_code = 0; ipa_reg_mib->ioctl_cmd.ipa_cmd_hdr.seq_num = 0; /* sequence number not used */ /* do ioctl */ strcpy( ifr.ifr_name, if_list[i].if_Name ); /* add interface name */ ifr.ifr_ifru.ifru_data = (char*) buffer; /* add data buffer */ if ( ioctl( sd, SIOC_QETH_ADP_SET_SNMP_CONTROL, &ifr ) < 0 ) { error_code = errno; /* see if we got a common I/O error */ if ( error_code == -EIO ) { get_time( time_buf ); snmp_log( LOG_ERR, "%s init_ibmOSAMib(): " "ioctl() failed - reason %s for interface %s\n" "init_ibmOSAMib(): start subagent anyway\n", time_buf, strerror( error_code ), if_list[i].if_Name ); close( sd ); free( buffer ); return; break; } /* end if */ /* let's see, if we got a return code from IPAssists */ /* or if MIB buffer is exhausted */ switch ( ipa_reg_mib->ioctl_cmd.ipa_cmd_hdr.ret_code ) { case IPA_FAILED: fprintf( stderr, "init_ibmOSAMib(): " "ioctl() failed - IPA command failed " "init_ibmOSAMib(): " "Can't get MIB information for network interface %s\n" "Cannot start subagent...exiting...\n", if_list[i].if_Name ); break; case IPA_NOT_SUPP: fprintf( stderr, "init_ibmOSAMib(): " "ioctl() failed - IPA command not supported " "init_ibmOSAMib(): " "Can't get MIB information for network interface %s\n" "Cannot start subagent...exiting...\n", if_list[i].if_Name ); break; case IPA_NO_DATA: get_time( time_buf ); snmp_log( LOG_ERR, "%s init_ibmOSAMib(): " "ioctl() failed - valid IPA command, but no" " SNMP data is available for interface %s\n" "init_ibmOSAMib(): start subagent anyway\n", time_buf, if_list[i].if_Name ); close( sd ); free( buffer ); return; break; case -ENOMEM: /* should not happen in the near future ;-) */ fprintf( stderr, "init_ibmOSAMib(): " "ioctl() failed - MIB data size > " "constant MIB_AREA_LEN\n" "init_ibmOSAMib(): " "Enlarge constant for MIB_AREA_LEN within " "ibmOSAMibDefs.h and recompile the subagent\n" "init_ibmOSAMib(): " "Can't get MIB information for network interfaces\n" "Cannot start subagent...exiting...\n" ); break; default: fprintf( stderr, "init_ibmOSAMib(): " "ioctl() failed - reason %s\n" "init_ibmOSAMib(): " "Can't get MIB information for network interface %s\n" "Cannot start subagent...exiting...\n", strerror( error_code ), if_list[i].if_Name ); break; } /* end switch */ exit(1); } else if( ipa_reg_mib->ioctl_cmd.ipa_cmd_hdr.ret_code != 0 ) { /* now check IPA SNMP subcommand return code */ switch ( ipa_reg_mib->ioctl_cmd.ipa_cmd_hdr.ret_code ) { case IPA_SNMP_INV_TOPOID: case IPA_SNMP_INV_GROUP: case IPA_SNMP_INV_SUFFIX: case IPA_SNMP_INV_INST: case IPA_SNMP_OID_NREAD: case IPA_SNMP_OID_NWRIT: get_time( time_buf ); snmp_log( LOG_ERR, "%s init_ibmOSAMib(): " "IPA SNMP subcommand failed\n" "init_ibmOSAMib(): " "IPA SNMP subcommand return code 0x%x\n" "init_ibmOSAMib(): " "Can't get MIB information for network interface %s\n" "Cannot start subagent...exiting...\n", time_buf, ipa_reg_mib->ioctl_cmd.ipa_cmd_hdr.ret_code, if_list[i].if_Name ); break; case IPA_SNMP_NOT_SUPP: get_time( time_buf ); snmp_log( LOG_ERR, "%s init_ibmOSAMib(): " "IPA SNMP subcommand failed - subcommand 0x%x " "not supported\ninit_ibmOSAMib(): " "Can't get MIB information for network interface %s\n" "Cannot start subagent...exiting...\n", time_buf, ipa_reg_mib->ioctl_cmd.ipa_cmd_hdr.request, if_list[i].if_Name ); break; case IPA_SNMP_NO_DATA: get_time( time_buf ); snmp_log( LOG_ERR, "%s init_ibmOSAMib(): " "IPA SNMP subcommand failed - no data available\n" "init_ibmOSAMib(): " "Can't get MIB information for network interface %s\n" "Cannot start subagent...exiting...\n", time_buf, if_list[i].if_Name ); break; default: get_time( time_buf ); snmp_log( LOG_ERR, "%s init_ibmOSAMib(): " "IPA SNMP subcommand failed - undefined return code" " 0x%x\ninit_ibmOSAMib(): " "Can't get MIB information for network interface %s\n" "Cannot start subagent...exiting...\n", time_buf, ipa_reg_mib->ioctl_cmd.ipa_cmd_hdr.ret_code, if_list[i].if_Name ); break; } /* end switch */ exit(1); } /* end if */ /* save microcode level */ if_list[i].ipa_ver = ipa_reg_mib->ioctl_cmd.ipa_cmd_hdr.ipa_ver; /* register initial table information, that we got from IPAssists */ retc = register_tables ( buffer, oid_list_head ); if ( retc != 0 ) { fprintf( stderr, "init_ibmOSAMib(): " "register MIB data with subagent driving " "code failed\ninit_ibmOSAMib(): for ifIndex %d ifDescr %s\n" "check agent log file for more details\n" "Cannot start subagent...exiting...\n", if_list[i].ifIndex, if_list[i].if_Name ); exit(1); } /* end if */ } /* end if */ } /* end for */ /* log IPA microcode level per interface */ for ( i=0; i < ifNumber; i++ ) { if ( if_list[i].is_OSAEXP == TRUE ) snmp_log( LOG_INFO, "OSA-E microcode level is %x for interface %s\n", if_list[i].ipa_ver, if_list[i].if_Name ); } /* end for */ /* free resources */ close( sd ); free( buffer ); } /* end init_ibmOSAMib */ /********************************************************************** * var_ibmOSAMib(): * This function is called every time the agent gets a request for * any MIB data for the IBM OSA express MIB. It's up to this function * to return the appropriate data back to the subagent driving code. * This function supports all standard SNMIv2 data types. * parameters: * IN variable vp - entry in variableN array * INOUT oid *name - OID from original request/OID being returned * INOUT size_t *length - length of orig. OID/length of ret. OID * IN int exact - exact/inexact request * OUT size_t *var_len - length of answer being returned * OUT WriteMethod **write_method - unused * returns: NULL - vp entry does not match or instance wrong * - something within ioctl handling failed * else data returned as answer *********************************************************************/ unsigned char* var_ibmOSAMib( struct variable *vp, oid *name, size_t *length, int exact, size_t *var_len, WriteMethod **write_method) { /* variables for returning data back to the subagent driving code */ static long long_ret; static unsigned char octetstr_buf[MAX_GET_DATA]; static oid objid[MAX_OID_LEN]; static struct counter64 osa_counter64; long *ptr_long; int *ptr_int; unsigned char *ptr_uchar; int ifIndex; /* IF-MIB ifIndex of the OSA device that is queried for data */ int offset; /* offset to returned data portion within GET command area */ char time_buf[TIME_BUF_SIZE]; /* date/time buffer */ IPA_CMD_GET *get_cmd; /* area for GET command */ IPA_GET_DATA *get_res; /* pointer to offset where data portion starts */ void *tmp_ptr; /* * This function compares the full OID that is passed in to the registered * OIDs from this subagent. * It is the IBM OSA Express specific version of the default * header_simple_table() function, that is normally used in case of a simple * table. Place a mutual exlusion lock around this operation to avoid * interfering threads, when updating the internal MIB table thru thread * update_mib_info() */ ifIndex = header_osa_table( vp, name, length, exact, var_len, write_method, oid_list_head ); if ( ifIndex == MATCH_FAILED ) return NULL; /* issue ioctl to query Get/Getnext request data */ offset = do_GET_ioctl ( ifIndex, name, *length, &get_cmd ); if ( offset < 0 ) { return NULL; } /* * return the result to subagent driving code */ /* map data portion returned by IPAssists */ /* # ptr GET command area + offset returned data portion */ /* # align PTR to 4 byte bdy where data portion starts */ tmp_ptr = (char*) get_cmd; tmp_ptr += offset; get_res = (IPA_GET_DATA*) (PTR_ALIGN4( tmp_ptr )); switch( vp->type ) { case ASN_INTEGER: case ASN_COUNTER: case ASN_GAUGE: case ASN_TIMETICKS: /* ASN_UNSIGNED is same as ASN_GAUGE (RFC1902) */ if ( get_res->len == sizeof(int) ) { ptr_int = (int*) get_res->data; long_ret = (long) *ptr_int; } else { ptr_long = (long*) get_res->data; long_ret = (long) *ptr_long; } /* end if */ free( get_cmd ); return (unsigned char *) &long_ret; break; case ASN_COUNTER64: if ( get_res->len > 8 ) { get_time( time_buf ); snmp_log( LOG_ERR, "%s var_ibmOSAMib(): " "IPA data length for ASN_COUNTER64 > 8 bytes\n" "var_ibmOSAMib(): rejected Get/Getnext request\n", time_buf ); free( get_cmd ); return NULL; } /* end if */ /* IPA returns 8 bytes for COUNTER64 */ ptr_int = (int*) get_res->data; osa_counter64.high = (int) *ptr_int; ptr_int++; osa_counter64.low = (int) *ptr_int; *var_len = sizeof( osa_counter64 ); free( get_cmd ); return (unsigned char *) &osa_counter64; break; case ASN_OPAQUE: /* old v1 type/included for compatibility */ case ASN_OCTET_STR: /* used for Binary data */ /* case Display String is handled within var_DisplayStr() */ if ( get_res->len > MAX_GET_DATA ) { get_time( time_buf ); snmp_log( LOG_ERR, "%s var_ibmOSAMib(): " "IPA data length %d for ASN_OCTET_STR > " "MAX_GET_DATA (%d bytes)\n" "var_ibmOSAMib(): rejected Get/Getnext request\n", time_buf, get_res->len, MAX_GET_DATA ); free( get_cmd ); return NULL; } /* end if */ *var_len = get_res->len; ptr_uchar = (unsigned char*) get_res->data; memcpy( octetstr_buf, ptr_uchar, *var_len ); free( get_cmd ); return (unsigned char *) octetstr_buf; break; case ASN_IPADDRESS: /* IPA IpAddress within 4 bytes hex data */ ptr_int = (int*) get_res->data; long_ret = (long) *ptr_int; free( get_cmd ); return (unsigned char *) &long_ret; break; case ASN_OBJECT_ID: /* IPA returned ObjectId as character string, have to convert... */ *var_len = str_to_oid_conv ( get_res->data, objid ); if ( *var_len == 0 ) { get_time( time_buf ); snmp_log( LOG_ERR, "%s var_ibmOSAMib(): IPA returned bad ObjectId - " "cannot convert net-snmp oid type\n" "var_ibmOSAMib(): rejected Get/Getnext request\n", time_buf ); free( get_cmd ); return NULL; } /* end if */ *var_len = (*var_len) * sizeof( oid ); free( get_cmd ); return (unsigned char *) &objid; break; default: get_time( time_buf ); snmp_log( LOG_ERR, "%s var_ibmOSAMib(): " "got a not known ASN data type %x\n" "var_ibmOSAMib(): " "rejected Get/Getnext request\n", time_buf, vp->type ); } /* end switch */ free( get_cmd ); return NULL; } /* end var_ibmOSAMib */ /********************************************************************** * var_DisplayStr(): * This function handles the special case for Display Strings, which are * a textual convention to Octet Strings. The binary data case for * Octet Strings is handled within var_ibmOSAMib(). * It's up to this function to return the appropriate data back to the * subagent driving code. * parameters: * IN variable vp - entry in variableN array * INOUT oid *name - OID from original request/OID being returned * INOUT size_t *length - length of orig. OID/length of ret. OID * IN int exact - exact/inexact request * OUT size_t *var_len - length of answer being returned * OUT WriteMethod **write_method - unused * returns: NULL - vp entry does not match or instance wrong * - something within ioctl handling failed * else data returned as answer *********************************************************************/ unsigned char* var_DisplayStr( struct variable *vp, oid *name, size_t *length, int exact, size_t *var_len, WriteMethod **write_method) { /* variables for returning a display string to the subagent driving code */ static char string[SPRINT_MAX_LEN]; char time_buf[TIME_BUF_SIZE]; /* date/time buffer */ int ifIndex; /* IF-MIB ifIndex of the OSA device that is queried for data */ int offset; /* offset to returned data portion within GET command area */ IPA_CMD_GET *get_cmd; /* area for GET command */ IPA_GET_DATA *get_res; /* pointer to offset where data portion starts */ void *tmp_ptr; /* * This function compares the full OID that is passed in to the registered * OIDs from this subagent. * It is the IBM OSA Express specific version of the default * header_simple_table() function, that is normally used in case of a simple * table. Place a mutual exlusion lock around this operation to avoid * interfering threads, when updating the internal MIB table thru thread * update_mib_info() */ ifIndex = header_osa_table( vp, name, length, exact, var_len, write_method, oid_list_head ); if ( ifIndex == MATCH_FAILED ) return NULL; /* issue ioctl to query Get/Getnext request data */ offset = do_GET_ioctl ( ifIndex, name, *length, &get_cmd ); if ( offset < 0 ) { return NULL; } /* * return the result to subagent driving code */ /* map data portion returned by IPAssists */ /* # ptr GET command area + offset returned data portion */ /* # align PTR to 4 byte bdy where data portion starts */ tmp_ptr = (char*) get_cmd; tmp_ptr += offset; get_res = (IPA_GET_DATA*) (PTR_ALIGN4( tmp_ptr )); if ( vp->type == ASN_OCTET_STR) { if ( get_res->len >= SPRINT_MAX_LEN ) { get_time( time_buf ); snmp_log( LOG_ERR, "%s var_DisplayStr(): " "IPA data length %d for Display " "String >= SPRINT_MAX_LEN (%d bytes)\n" "var_ibmOSAMib(): rejected Get/Getnext request\n" ,time_buf, get_res->len, SPRINT_MAX_LEN ); free( get_cmd ); return NULL; } /* end if */ strncpy( string, get_res->data, get_res->len ); string[ get_res->len ] = '\0'; *var_len = strlen( string ); free( get_cmd ); return (unsigned char *) string; } else { get_time( time_buf ); snmp_log( LOG_ERR, "%s var_DisplayStr(): " "expected a Display String here, " "but got a different ASN data type: %x\n" "var_DisplayStr(): " "rejected Get/Getnext request\n", time_buf, vp->type ); } /* end if */ free( get_cmd ); return NULL; } /* end var_DisplayStr */ /********************************************************************** * do_GET_ioctl() * This function handles the communication with an OSA Express Card * to query the appropriate MIB information from IPAssists. * An ioctl is used in order to qet the appropriate information. * parameters: * IN int ifIndex - IF-MIB interface index * IN oid *name - OID being returned * IN size_t len - length of ret. OID * INOUT IPA_CMD_GET** cmd - GET command area * returns: cmd_len - return offset to returned data * -1 - ioctl() was not successful *********************************************************************/ int do_GET_ioctl ( int ifIndex, oid *name, size_t len, IPA_CMD_GET **cmd ) { int sd; /* socket descriptor */ int i, error_code; char oid_str[MAX_OID_STR_LEN]; /* may hold an OID as string */ char time_buf[TIME_BUF_SIZE]; /* date/time buffer */ char device[IFNAME_MAXLEN] = "not_found"; /* device name for ioctl */ struct ifreq ifr; /* request structure for ioctl */ /* search device name in in global interface list for ifIndex */ for ( i=0; i < ifNumber; i++ ) { if ( if_list[i].ifIndex == ifIndex ) { strcpy( device, if_list[i].if_Name ); break; } } /* end for */ if ( strcmp( device, "not_found" ) == 0 ) { get_time( time_buf ); snmp_log( LOG_ERR, "%s do_GET_ioctl(): " "ifIndex %d is not recorded in " "interface list\n" "OSA Subagent MIB information may be incomplete!\n" ,time_buf, ifIndex ); return -1; } /* * query IPAssists for data appropriate to the OID that we just validated */ /* convert Get/GetNext OID to a string used by IPA */ if( oid_to_str_conv ( name, len, oid_str ) == FALSE ) { get_time( time_buf ); snmp_log( LOG_ERR, "%s do_GET_ioctl(): " "cannot convert OID to string object\n" "do_GET_ioctl(): rejected request\n", time_buf ); return -1; } /* allocate memory for Get/GetNext command area */ *cmd = ( IPA_CMD_GET* ) malloc( GET_AREA_LEN ); if ( *cmd == NULL ) { get_time( time_buf ); snmp_log( LOG_ERR, "%s do_GET_ioctl(): " "malloc() for GET command area failed\n" "do_GET_ioctl(): rejected request for .%s\n", time_buf, oid_str ); return -1; } /* end if */ /* set up input parameters in Get/GetNext command area */ /* size of IPA data area */ (*cmd)->ioctl_cmd.data_len = GET_AREA_LEN - offsetof( IOCTL_CMD_HDR, ipa_cmd_hdr ); /* size of IPA GET subcommand padded to 4-byte bdy */ (*cmd)->ioctl_cmd.req_len = (sizeof((*cmd)->ioctl_cmd) + strlen( oid_str ) + 1 + 3)&(~3); /* set up input parameters in Get/GetNext command area */ (*cmd)->ioctl_cmd.ipa_cmd_hdr.request = IPA_GET_OID; /* IPA subcommand code */ (*cmd)->ioctl_cmd.ipa_cmd_hdr.ifIndex = ifIndex; /* assign IF-MIB ifIndex */ (*cmd)->ioctl_cmd.ipa_cmd_hdr.ret_code = 0; (*cmd)->ioctl_cmd.ipa_cmd_hdr.seq_num = 0; /* sequence# is not used */ strcpy( (*cmd)->full_oid, oid_str ); /* requested OID */ /* (fully qualified) */ /* * issue Get/GetNext command against IPAssists */ /* create socket for ioctl */ sd = socket( AF_INET, SOCK_STREAM, 0 ); if ( sd < 0 ) { error_code = errno; get_time( time_buf ); snmp_log(LOG_ERR, "%s do_GET_ioctl(): " "error opening socket() - reason %s\n" "do_GET_ioctl(): rejected request for .%s\n", time_buf, strerror( error_code ), oid_str ); free( *cmd ); return -1; } /* end if */ /* do ioctl */ strcpy( ifr.ifr_name, device ); ifr.ifr_ifru.ifru_data = (char*) (*cmd); if ( ioctl( sd, SIOC_QETH_ADP_SET_SNMP_CONTROL, &ifr ) < 0 ) { error_code = errno; get_time( time_buf ); /* see if we got a common I/O error */ if ( error_code == -EIO ) { snmp_log( LOG_ERR, "%s do_GET_ioctl(): " "ioctl() failed - reason %s\n" "do_GET_ioctl(): rejected request for .%s\n", time_buf, strerror( error_code ), oid_str ); close( sd ); free( *cmd ); return -1; } /* end if */ /* let's see, if we got a return code from IPAssists */ /* or if MIB buffer is exhausted */ switch ( (*cmd)->ioctl_cmd.ipa_cmd_hdr.ret_code ) { case IPA_FAILED: snmp_log( LOG_ERR, "%s do_GET_ioctl(): " "ioctl() failed - IPA command failed\n" "do_GET_ioctl(): rejected request for .%s\n", time_buf, oid_str ); break; case IPA_NOT_SUPP: snmp_log( LOG_ERR, "%s do_GET_ioctl(): " "ioctl() failed - IPA command not supported\n" "do_GET_ioctl(): rejected request for .%s\n", time_buf, oid_str ); break; case IPA_NO_DATA: snmp_log( LOG_ERR, "%s do_GET_ioctl(): " "ioctl() failed - valid IPA command, but no " "SNMP data is available\n" "do_GET_ioctl(): rejected request for .%s\n", time_buf, oid_str ); break; case -ENOMEM: snmp_log( LOG_ERR, "%s do_GET_ioctl(): " "ioctl() failed - response data > " "constant MAX_GET_DATA %d\n" "do_GET_ioctl(): rejected request for .%s\n", time_buf, MAX_GET_DATA, oid_str ); break; default: snmp_log(LOG_ERR, "%s do_GET_ioctl(): " "ioctl() failed - reason %s\n" "do_GET_ioctl(): rejected request for .%s\n", time_buf, strerror( error_code ), oid_str ); break; } /* end switch */ close( sd ); free( *cmd ); return -1; } /* end if */ /* close socket */ close( sd ); /* now check IPA SNMP subcommand return code */ switch ( (*cmd)->ioctl_cmd.ipa_cmd_hdr.ret_code ) { case IPA_SNMP_SUCCESS: /* return offset to data portion */ return ( sizeof( IPA_CMD_GET ) + strlen( oid_str ) + 1 ); break; case IPA_SNMP_INV_TOPOID: case IPA_SNMP_INV_GROUP: case IPA_SNMP_INV_SUFFIX: case IPA_SNMP_INV_INST: case IPA_SNMP_OID_NREAD: case IPA_SNMP_OID_NWRIT: get_time( time_buf ); snmp_log( LOG_ERR, "%s do_GET_ioctl(): " "IPA SNMP subcommand failed - cannot handle OID\n" "do_GET_ioctl(): IPA SNMP subcommand return code 0x%x\n" "do_GET_ioctl(): rejected request for .%s\n", time_buf, (*cmd)->ioctl_cmd.ipa_cmd_hdr.ret_code, oid_str ); break; case IPA_SNMP_NOT_SUPP: get_time( time_buf ); snmp_log( LOG_ERR, "%s do_GET_ioctl(): " "IPA SNMP subcommand failed - subcommand 0x%x not supported\n" "do_GET_ioctl(): rejected request for .%s\n", time_buf, (*cmd)->ioctl_cmd.ipa_cmd_hdr.request, oid_str ); break; case IPA_SNMP_NO_DATA: get_time( time_buf ); snmp_log( LOG_ERR, "%s do_GET_ioctl(): " "IPA SNMP subcommand failed - no data available\n" "do_GET_ioctl(): rejected request for .%s\n", time_buf, oid_str ); break; default: get_time( time_buf ); snmp_log( LOG_ERR, "%s do_GET_ioctl(): " "IPA SNMP subcommand failed - undefined return code 0x%x\n" "do_GET_ioctl(): rejected request for .%s\n", time_buf, (*cmd)->ioctl_cmd.ipa_cmd_hdr.ret_code, oid_str ); break; } /* end switch */ /* return error */ free( *cmd ); return -1; } /* end do_GET_ioctl */ /********************************************************************** * write_ibmOSAMib(): * !!! Set processing is not supported in version 1.0.0 !!! * !!! Function is defind as a skeleton for later use !!! * This function handles any SET requests raised against the * ibmOSAMib. * The flow of actions is to preserve proper transaction handling * with other transactions in the same set request. * parameters: * IN int action - current action state * IN u_char *var_val - new variable value * IN u_char var_val_type - data type of above variable * IN size_t var_val_len - length of variable value * IN u_char *statP - value that a GET request would return * for this variable * IN oid *name - OID to be set * IN size_t name_len - len of OID to be set * returns: SNMP_ERR_WRONGTYPE - wrong data type passed in * SNMP_ERR_GENERR - general error occurred * SNMP_ERR_UNDOFAILED - undo operation failed * SNMP_ERR_NOERROR - variable set successful *********************************************************************/ int write_ibmOSAMib( int action, u_char *UNUSED(var_val), u_char UNUSED(var_val_type), size_t UNUSED(var_val_len), u_char *UNUSED(statP), oid *UNUSED(name), size_t UNUSED(name_len) ) { /* static unsigned char string[SPRINT_MAX_LEN]; */ /* int size; */ switch ( action ) { case RESERVE1: /* check to see that everything is possible */ break; case RESERVE2: /* allocate needed memory here */ break; case FREE: /* Release any resources that have been allocated */ break; case ACTION: /* Actually make the change requested. Note that anything done here must be reversible in the UNDO case */ break; case UNDO: /* Back out any changes made in the ACTION case */ break; case COMMIT: /* Things are working well, so it's now safe to make the change permanently. Make sure that anything done here can't fail! */ break; } return SNMP_ERR_NOERROR; } /* end write_ibmOSAMib */ s390-tools-2.3.0/osasnmpd/ibmOSAMib.h000066400000000000000000000021271323410431100171010ustar00rootroot00000000000000/* * osasnmpd - IBM OSA-Express network card SNMP subagent * * Include file for the OSA-E subagent MIB implementaton module. * Defines function prototypes of the basic functions in the MIB * implementation module. * * Copyright IBM Corp. 2002, 2017 * * s390-tools is free software; you can redistribute it and/or modify * it under the terms of the MIT license. See LICENSE for details. */ #ifndef _MIBGROUP_IBMOSAMIB_H #define _MIBGROUP_IBMOSAMIB_H /* we may use header_generic and header_simple_table from the util_funcs module */ config_require(util_funcs) /* function prototypes */ void init_ibmOSAMib(void); /* register MIB data */ FindVarMethod var_ibmOSAMib; /* handle GET and GETNEXT requests */ /* for all SMIv2 standard types */ FindVarMethod var_DisplayStr; /* handle special case Display String */ WriteMethod write_ibmOSAMib; /* handle SET requests */ /* ioctl for Get/Getnext processing */ int do_GET_ioctl ( int, oid*, size_t, IPA_CMD_GET** ); #endif /* _MIBGROUP_IBMOSAMIB_H */ s390-tools-2.3.0/osasnmpd/ibmOSAMibDefs.h000066400000000000000000000172341323410431100177100ustar00rootroot00000000000000/* * osasnmpd - IBM OSA-Express network card SNMP subagent * * Defines constants and data structures used by the OSA-E subagent. * * Copyright IBM Corp. 2002, 2017 * * 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 #include #include #ifndef NETSNMP_DS_APPLICATION_ID #define NETSNMP_DS_APPLICATION_ID DS_APPLICATION_ID #endif #ifndef NETSNMP_DS_AGENT_ROLE #define NETSNMP_DS_AGENT_ROLE DS_AGENT_ROLE #endif #ifndef NETSNMP_DS_AGENT_X_SOCKET #define NETSNMP_DS_AGENT_X_SOCKET DS_AGENT_X_SOCKET #endif /* version number of this agent */ /* default log file - don't change it here, use parameter -l */ #define OSAE_LOGFILE "/var/log/osasnmpd.log" /* definitions for subagent to master agent definition */ #define NET_SNMP_PEERNAME "localhost" #define NET_SNMP_COMMUNITY "public" /* need this for OSA Express ioctl's */ #define QETH_PROCFILE "/proc/qeth" #define QETH_IOC_MAGIC 'Z' #define QETH_IOCPROC_REGISTER _IOW(QETH_IOC_MAGIC, 1, int) #define QETH_UPDATE_MIB_SIGNAL SIGUSR1 #define QETH_QUERY_IPA_DATA _IOWR(QETH_IOC_MAGIC, 7, int ) #define QETH_CHECK_OSA_DEVICE _IOWR(QETH_IOC_MAGIC, 8, int ) #define IFNAME_MAXLEN 16 /* max length for linux interface names */ #define SUFFIX_MAXLEN 13 /* max length of suffix length for net-snmp */ #define MIB_AREA_LEN 25000 /* default size for register MIB data */ #define MAX_GET_DATA 4094 /* maximum GET response data length */ #define GET_AREA_LEN MAX_GET_DATA + 512 /* size for GET command area length */ #define TIME_BUF_SIZE 128 /* buffer size for date and time string */ #define MAX_OID_STR_LEN MAX_OID_LEN * 5 /* max OID string size */ /* definitions for 2.6 qeth */ #define QETH_SYSFILE "/sys/bus/ccwgroup/drivers/qeth/notifier_register" #define SIOC_QETH_ADP_SET_SNMP_CONTROL (SIOCDEVPRIVATE + 5) #define SIOC_QETH_GET_CARD_TYPE (SIOCDEVPRIVATE + 6) /* some definitions for the linked lists compare and delete functions */ #define OID_FOUND 0 #define OID_NOT_FOUND 1 #define UNEXP_ERROR -1 #define INDEX_FOUND 0 #define INDEX_NOT_FOUND 1 #define IF_ENTRY 0 #define IND_LIST 1 /* additional access types and data types used by IPAssists */ #define IPA_WRONLY 0xF2 #define IPA_DISPLAYSTR ((u_char)0x09) /* IPAssists SNMP subcommand codes */ #define IPA_REG_MIB 0x04 #define IPA_GET_OID 0x10 #define IPA_SET_OID 0x11 /*#define IPA_QUERY_ALERT 0x20*/ /*#define IPA_SET_TRAP 0x21*/ /* IPAssists command return codes */ #define IPA_SUCCESS 0x00 #define IPA_FAILED 0x01 #define IPA_NOT_SUPP 0x04 #define IPA_NO_DATA 0x08 /* IPAssists SNMP subcommand return codes */ #define IPA_SNMP_SUCCESS 0x00 #define IPA_SNMP_INV_TOPOID 0x01 #define IPA_SNMP_INV_GROUP 0x02 #define IPA_SNMP_INV_SUFFIX 0x04 #define IPA_SNMP_INV_INST 0x08 #define IPA_SNMP_OID_NREAD 0x10 #define IPA_SNMP_OID_NWRIT 0x20 #define IPA_SNMP_NOT_SUPP 0x40 #define IPA_SNMP_NO_DATA 0x80 #define PTR_ALIGN4(addr) ((long)((addr))+3)&(~3) /* align ptr 4-byte bdy */ /***************************************************************/ /* structure used for getting OSA-Express interfaces via ioctl */ /***************************************************************/ #define NAME_FILLED_IN 0x00000001 #define IFINDEX_FILLED_IN 0x00000002 /* version 0 */ typedef struct dev_list { char device_name[IFNAME_MAXLEN]; /* OSA-Exp device name (e.g. eth0) */ int if_index; /* interface index from kernel */ __u32 flags; /* device charateristics */ } __attribute__((packed)) DEV_LIST; typedef struct osaexp_dev_ver0 { __u32 version; /* structure version */ __u32 valid_fields; /* bitmask of fields that are really filled */ __u32 qeth_version; /* qeth driver version */ __u32 number_of_devices; /* number of OSA Express devices */ struct dev_list devices[0]; /* list of OSA Express devices */ } __attribute__((packed)) OSAEXP_DEV_VER0; /***************************************************************/ /* ioctl data structure for IPAssists SNMP processing */ /***************************************************************/ typedef struct ioctl_cmd_hdr { int data_len; /* total length of buffer passed to ioctl */ /* following the first 16 bytes */ /* in this structure (i.e. starts at token) */ int req_len; /* length of IPAssists SNMP request */ int reserved1; /* unused */ int reserved2; /* unused */ struct { char token[16]; /* not used */ int request; /* IPA subcommand code */ int ifIndex; /* IF-MIB ifIndex value for interface */ int ret_code; /* IPA return code */ int ipa_ver; /* IPA microcode level (4 hex digits to be shown as xx.yy) */ int seq_num; /* sequence number (currently not used) */ } ipa_cmd_hdr; } __attribute__((packed)) IOCTL_CMD_HDR; /***************************************************************/ /* structures for GET/GETNEXT IPAssists processing */ /***************************************************************/ typedef struct ipa_cmd_get { IOCTL_CMD_HDR ioctl_cmd; /* IOCTL command header */ char full_oid[0]; /* fully qualified OID for GET/GETNEXT */ } __attribute__((packed)) IPA_CMD_GET; typedef struct ipa_get_data { int len; /* length of returned data from IPA */ char data[0]; /* data returned by IPA */ } __attribute__((packed)) IPA_GET_DATA; /******************************************************************/ /* struct for IPAssists register MIB data processing */ /******************************************************************/ typedef struct ipa_cmd_reg { IOCTL_CMD_HDR ioctl_cmd; /* IPA subcommand header */ int table_cnt; /* number of table toplevel OIDs */ } __attribute__((packed)) IPA_CMD_REG; /***************************************************************/ /* linked list for table OID housekeeping */ /***************************************************************/ typedef struct table_oid { oid *pObjid; /* registered table OID */ size_t length; /* length of subtree OID */ struct variable13 *var13ptr; /* ptr to variable_x list */ struct reg_indices *ind_list; /* ptr to registered indices */ struct table_oid *next; /* ptr to next entry in list */ } TABLE_OID; /***************************************************************/ /* linked list for OSA Express interfaces housekeeping */ /***************************************************************/ typedef struct reg_indices { char *full_index; /* full index portion from IPA */ int ifIndex; /* ifIndex from IF-MIB */ struct reg_indices *next; /* ptr to next entry in list */ } REG_INDICES; /*******************************************************************/ /* this list keeps information queried from the IF-MIB */ /*******************************************************************/ typedef struct if_List { int kerIndex; /* Linux kernel ifIndex */ int ifIndex; /* IF-MIB ifIndex */ short is_OSAEXP; /* TRUE if an OSA Express device */ char if_Name[IFNAME_MAXLEN]; /* interface name (e.g. eth0) */ int ipa_ver; /* IPA microcode level */ } IF_LIST; s390-tools-2.3.0/osasnmpd/ibmOSAMibUtil.c000066400000000000000000001653161323410431100177440ustar00rootroot00000000000000/* * osasnmpd - IBM OSA-Express network card SNMP subagent * * Collection of utility functions used by the MIB implementation * module. * * Copyright IBM Corp. 2002, 2017 * * 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 #include #include #include #include #include #include #include "ibmOSAMibUtil.h" #include "ibmOSAMib.h" /* ptr to interface information on this system */ extern IF_LIST* if_list; extern int ifNumber; extern TABLE_OID* oid_list_head; /* proc file filedescriptor. opened in osasnmpd.c */ extern int proc_fd; /********************************************************************** * str_to_oid_conv(): * This function converts an OID string in an OID u_long array used * by net-snmp. * parameters: * IN char* uc_oid: OID string separated by dots * OUT oid* ul_oid: OID u_long array * returns: int count - number of OID identifiers found within OID * string; 0 if OID string was invalid *********************************************************************/ int str_to_oid_conv ( char* uc_oid, oid* ul_oid ) { short valid = TRUE; int count = 0; char* pos_strt = uc_oid; char* pos_end; /* got non-empty oid string */ if ( strlen( uc_oid ) > 0 ) { do { /* found a dot, skip it */ if ( *pos_strt == '.' ) { pos_strt++; if ( *pos_strt == '\0') /* found ending dot - valid - */ break; } /* found no dot and but expected one, indicate invalid OID */ else if (count > 0) { valid = FALSE; break; } /* end if */ /* convert oid digit into data type oid (unsigned long) */ ul_oid[count] = (oid) strtoul( pos_strt, &pos_end, 10); /* check result from conversion */ if (pos_strt == pos_end || ul_oid[count] == ULONG_MAX) { valid = FALSE; break; } /* adjust to next OID digit */ pos_strt = pos_end; count++; } while ( *pos_end != '\0' && count < MAX_OID_LEN ); } else /* indicate invalid OID */ valid = FALSE; /* if OID was valid, return number of OID identifiers */ if (!valid) return 0; else return count; } /* end str_to_oid_conv() */ /********************************************************************** * oid_to_str_conv(): * This function converts a net-snmp OID u_long array into an OID * string separated by dots. * parameters: * IN oid* ul_oid: OID u_long array * IN size_t length: OID length * OUT char* uc_oid: OID string including dots (no leading dot) * returns: TRUE - conversion was successful * FALSE - conversion failed *********************************************************************/ int oid_to_str_conv (oid* ul_oid, size_t length, char* uc_oid ) { #define MAX_CHARS 50 /* size of buffer */ int i; short valid = TRUE; char buffer[MAX_CHARS]; /* buffer used for conversion */ /* got invalid OID length */ if ( length != 0 && length <= MAX_OID_LEN ) { /* init return string */ uc_oid[0] = '\0'; for ( i=0; i < (int)length; i++) { /* convert and append OID digit to return string */ if (i == 0) sprintf( buffer, "%lu", ul_oid[i] ); else sprintf( buffer, ".%lu", ul_oid[i] ); strcat( uc_oid, buffer ); } /* end for */ } else /* indicate invalid OID */ valid = FALSE; /* if OID conversion was successful, indicate success */ return valid; } /* end oid_to_str_conv() */ /********************************************************************** * init_oid_list(): * This function initializes the head of the linked list structure to * maintain the IPAssists MIB information. It is called at subagent * startup. * * parameters: * INOUT none * returns: TABLE_OID* - head of linked list (pseudo node) * NULL - if malloc() for head failed * *********************************************************************/ TABLE_OID* init_oid_list () { TABLE_OID* head; /* allocate list head */ head = (TABLE_OID*) malloc( sizeof *head ); if ( head != NULL ) head->next = NULL; return head; } /* init_oid_list() */ /********************************************************************** * search_oid(): * This function searches the linked OID list for a given OID and * returns a ptr to the element if it is an exact match, otherwise the * previous (smaller) OID in the list is returned. * * parameters: * IN oid* s_oid - Toplevel OID to search for * IN size_t len - length of this OID * IN TABLE_OID* lhead - ptr to list head * OUT TABLE_OID** curr - ptr to entry in list * returns: 0 - exact match - OID exists in list * 1 - not found, curr point to possible insertion point * -1 - got unexpected return code from snmp_oid_compare() * *********************************************************************/ int search_oid ( oid* s_oid, size_t len, TABLE_OID* lhead, TABLE_OID** curr ) { /* loop through list and compare OIDs */ /* fyi - snmp_oid_compare() is a function from the net-snmp agent extension API */ for( *curr=lhead; (*curr)->next != NULL; *curr=(*curr)->next ) { switch ( snmp_oid_compare( s_oid, len, (*curr)->next->pObjid, (*curr)->next->length )) { case 0: /* exact OID match - curr->next points to entry */ *curr = (*curr)->next; return OID_FOUND; break; case 1: /* search OID still greater - goto next entry */ break; case -1: /* next entry is greater than search OID */ return OID_NOT_FOUND; break; default: /* unexpected return code from snmp_oid_compare */ return UNEXP_ERROR; } /* end switch */ } /* end for */ /* we're at the head or the end of linked list */ /* curr points to head or last element in list, good insertion points */ return OID_NOT_FOUND; } /* search_oid() */ /********************************************************************** * search_top_oid(): * This function searches for a fully qualified OID a matching Toplevel * OID from the linked list. * It returns a pointer to the element if it is an exact match. * Otherwise the return OID is set to NULL. * * parameters: * IN oid* s_oid - Fully qualified OID * IN size_t len - length of this OID * IN TABLE_OID* lhead - ptr to list head * OUT TABLE_OID** curr - ptr to entry in list * returns: 0 - exact match - appropriate Toplevel OID found * 1 - not found, curr set to NULL * -1 - got unexpected return code from snmp_oid_compare() * *********************************************************************/ int search_top_oid ( oid* s_oid, size_t len, TABLE_OID* lhead, TABLE_OID** curr ) { /* loop through list and compare OIDs */ /* snmp_oid_compare() is a taken from the net-snmp agent extension API */ for( *curr=lhead; (*curr)->next != NULL; *curr=(*curr)->next ) { /* fully qualified OID must be greater than our Toplevel OID */ if ( len > (*curr)->next->length ) { switch ( snmp_oid_compare( s_oid, (*curr)->next->length , (*curr)->next->pObjid, (*curr)->next->length )) { case 0: /* exact OID match - curr->next points to entry */ *curr = (*curr)->next; return OID_FOUND; break; case 1: /* search OID still greater - goto next entry */ break; case -1: /* next entry is greater than search OID */ *curr = NULL; return OID_NOT_FOUND; break; default: /* unexpected return code from snmp_oid_compare */ *curr = NULL; return UNEXP_ERROR; } /* end switch */ } /* end if */ } /* end for */ /* we're at the head or the end of linked list */ /* curr points to head or last element in list */ *curr = NULL; return OID_NOT_FOUND; } /* search_top_oid() */ /********************************************************************** * oid_insert_after(): * This function initializes and adds a new entry to the OID linked * list after entry pre_oid. * * parameters: * IN oid* i_oid - Toplevel OID to add * IN size_t len - length of this OID * IN TABLE_OID* pre_oid - add Toplevel OID after this entry * returns: TABLE_OID* - PTR to newly inserted entry * NULL - if malloc() for new entry failed *********************************************************************/ TABLE_OID* oid_insert_after ( oid* i_oid, size_t len, TABLE_OID* pre_oid ) { TABLE_OID *new_entry; new_entry = (TABLE_OID*) malloc( sizeof *new_entry ); if ( new_entry == NULL ) return NULL; /* allocate head for index list */ new_entry->ind_list = init_ind_list(); if ( new_entry->ind_list == NULL ) { free ( new_entry ); return NULL; } /* end if */ /* assign OID and length */ new_entry->pObjid = i_oid; new_entry->length = len; new_entry->var13ptr = NULL; /* insert */ new_entry->next = pre_oid->next; pre_oid->next = new_entry; return new_entry; } /* oid_insert_after()*/ /********************************************************************** * delete_oid(): * This function deletes a Toplevel OID entry from the Toplevel OID * linked list. * * parameters: * IN oid *d_oid - Toplevel OID to delete * IN size_t len - length of this OID * IN TABLE_OID *lhead - head of Toplevel OID linked list * returns: none * *********************************************************************/ int delete_oid( oid* d_oid, size_t len, TABLE_OID* lhead ) { TABLE_OID *curr, *del_entry; /* loop through list and compare OIDs */ /* snmp_oid_compare() is taken from the net-snmp agent extension API */ for( curr=lhead; curr->next != NULL; curr=curr->next ) { if ( snmp_oid_compare( d_oid, len, curr->next->pObjid, curr->next->length ) == 0 ) { del_entry = curr->next; curr->next = curr->next->next; free( del_entry->pObjid ); free( del_entry->var13ptr ); /* index list not empty, free index list first */ if ( del_entry->ind_list->next != NULL ) delete_index( del_entry->ind_list, 0, IND_LIST ); /* free index list head */ free( del_entry->ind_list ); free( del_entry ); break; } /* end if */ } /* end for */ return 0; } /* delete_oid() */ /********************************************************************** * clear_oid_list(): * This function removes all index entries from the Toplevel OID * linked list. * * parameters: * IN TABLE_OID *lhead - head of Toplevel OID linked list * returns: none * *********************************************************************/ int clear_oid_list( TABLE_OID* lhead ) { TABLE_OID *curr, *clr_entry; /* loop through list and clean entries */ for( curr=lhead; curr->next != NULL; curr=curr->next ) { clr_entry = curr->next; /* index list not empty, free index list first */ if ( clr_entry->ind_list->next != NULL ) delete_index( clr_entry->ind_list, 0, IND_LIST ); } /* end for */ return 0; } /* clear_oid_list() */ /********************************************************************** * init_ind_list(): * This function initializes the head of the linked list structure to * maintain index portion information. This is called when a new * Toplevel OID is inserted in the Toplevel OID linked list. * * parameters: * INOUT none * returns: TABLE_OID* - head of linked list (pseudo node) * NULL - if malloc() for head failed * *********************************************************************/ REG_INDICES* init_ind_list ( ) { REG_INDICES* ihead; /* allocate list head */ ihead = (REG_INDICES*) malloc( sizeof *ihead ); if ( ihead != NULL ) ihead->next = NULL; return ihead; } /* init_ind_list() */ /********************************************************************** * index_insert_after(): * This function adds a new index entry to the index linked list * * parameters: * IN char* i_index - index to add * IN int ifIndex - appropriate IF-MIB ifIndex * IN REG_INDICES* pre_ind - add index after this entry * returns: REG_INDICES* - PTR to newly inserted entry * NULL - if malloc() for new entry failed *********************************************************************/ REG_INDICES* index_insert_after ( char* i_index, int ifIndex, REG_INDICES* pre_ind ) { REG_INDICES *new_entry; new_entry = (REG_INDICES*) malloc( sizeof *new_entry ); if ( new_entry == NULL ) return NULL; /* assign index and ifIndex */ new_entry->full_index = i_index; new_entry->ifIndex = ifIndex; /* insert */ new_entry->next = pre_ind->next; pre_ind->next = new_entry; return new_entry; } /* index_insert_after()*/ /********************************************************************** * delete_index(): * This function deletes an entry in the index linked list indexed by * ifIndex or removes the whole index linked list if desired. * * parameters: * IN REG_INDICES *lhead - head of index linked list * IN int ifIndex - ifIndex to remove from list * IN int del_type - deletion type (IF_ENTRY/IND_LIST) * returns: none * *********************************************************************/ int delete_index( REG_INDICES* lhead, int ifIndex, int del_type ) { REG_INDICES *curr, *del_entry; if (del_type == IF_ENTRY ) { /* loop through list and compare ifIndex */ curr = lhead; while ( curr->next != NULL ) { if ( curr->next->ifIndex == ifIndex ) { del_entry = curr->next; curr->next = curr->next->next; free( del_entry->full_index ); free( del_entry ); } /* end if */ else curr=curr->next; } /* end while */ } else if ( del_type == IND_LIST ) { /* loop through list delete one entry after another */ curr = lhead; while ( curr->next !=NULL ) { del_entry = curr->next; curr->next = curr->next->next; free( del_entry->full_index ); free( del_entry ); } /* end while */ } /* end if */ return 0; } /* delete_index() */ /********************************************************************** * search_index(): * This function searches the linked index list for a given index and * returns a ptr to the element if it is an exact match, otherwise the * previous (smaller) index in the list is returned. * * parameters: * IN index* s_index - index to search for * IN REG_INDICES* lhead - ptr to list head * OUT REG_INDICES** curr - ptr to entry in list * returns: 0 - exact match - index exists in list * 1 - not found, curr point to possible insertion point * *********************************************************************/ int search_index ( char* s_index, REG_INDICES* lhead, REG_INDICES** curr ) { int oid_len1, oid_len2; oid ind_oid1[MAX_OID_LEN]; /* temporary net-snmp oids */ oid ind_oid2[MAX_OID_LEN]; /* loop through list and compare indices */ for( *curr=lhead; (*curr)->next != NULL; *curr=(*curr)->next ) { oid_len1 = str_to_oid_conv( (char*) s_index, ind_oid1 ); oid_len2 = str_to_oid_conv( (char*) (*curr)->next->full_index, ind_oid2 ); switch ( snmp_oid_compare( ind_oid1, oid_len1, ind_oid2, oid_len2 ) ) { case 0: /* exact index match - curr->next points to entry */ /* exact index match - curr-> points to entry */ *curr = (*curr)->next; return INDEX_FOUND; break; case 1: /* search index still greater - goto next entry */ continue; break; case -1: /* next entry is greater than search index */ /* next entry is greater than search index */ return INDEX_NOT_FOUND; break; default: /* unexpected return code from snmp_oid_compare */ return UNEXP_ERROR; } /* end switch */ } /* end for */ /* we're at the head or the end of linked list */ /* curr points to head or last element in list, good insertion points */ return INDEX_NOT_FOUND; } /* search_index() */ /********************************************************************** * register_tables() * Parses MIB information returned by IPAssists and registers OID * suffixes with net-snmp subagent driving code using the REGISTER_MIB * macro. Also puts data from the MIB information returned by IPAssists * into local data structures to maintain index information. * * This functions only maps the header of the IPAssists MIB info. * The table data is parsed through the void mib_data_ptr. * * IPAssists MIB information layout: * * header information: * INOUT int request * INOUT int interface number/ifIndex * INOUT int return code from IPAssists * INOUT int IPAssists version * INOUT int sequence number of this response * OUT int number of returned table information for that interface * * Repeated information: * OUT string Toplevel OID for a table (including dots) * OUT int number of OID suffixes for that table * * Repeated OID suffix information for a single table and every index: * OUT int object access type * OUT int object data type * OUT string OID suffix for object * OUT string OID index portion * * parameters: * IN struct table: MIB information returned by IPAssists * IN TABLE_OID* lhead: head of toplevel OID linked list * returns: 0 - SUCCESS * -1 - Registration not performed due to error; * an appropriate log entry is made * *********************************************************************/ int register_tables ( void* mib_data, TABLE_OID* lhead ) { int i, j, suf_def, suf_cnt, src_oid, src_ind; int oid_len, oid_acc, oid_typ; oid t_oid[MAX_OID_LEN]; /* temporary net-snmp oid */ oid *top_oid; char *new_index; char toid_str[MAX_OID_STR_LEN]; char time_buf[TIME_BUF_SIZE]; /* date/time buffer */ IPA_CMD_REG *mib_data_hdr; /* ptr to IPAssists hdr information */ char *last_suffix; /* last suffix in suffix list */ void *mib_data_ptr; /* ptr to parse IPAssists MIB info */ TABLE_OID *oid_ptr, *ins_oid = NULL; /* ptr into Toplevel OID linked list */ REG_INDICES *ind_ptr; /* ptr into index linked list */ /* ptr to net-snmp variable13 structs that will contain the suffix information registered with subagent driving code */ struct variable13 *table_vars, *new_area; /* point to beginning of MIB information (header) */ mib_data_hdr = ( IPA_CMD_REG* ) mib_data; /* set parse ptr behind header information; first Toplevel OID */ mib_data_ptr = ( char* ) mib_data_hdr + sizeof( IPA_CMD_REG ); /************************************/ /* loop through returned table data */ /************************************/ for ( i=0; i < mib_data_hdr->table_cnt; i++ ) { /****************************************************/ /* get Toplevel oid that we want to register under */ /****************************************************/ /* adjust parse ptr to next Toplevel OID (skip padding) */ mib_data_ptr = (int*) (PTR_ALIGN4(mib_data_ptr)); /* convert string OID to net-snmp oid type */ strncpy( toid_str, (char*) mib_data_ptr, MAX_OID_STR_LEN ); toid_str[ MAX_OID_STR_LEN-1 ] = '\0'; oid_len = str_to_oid_conv( (char*) mib_data_ptr, t_oid ); if ( oid_len == 0 ) { get_time( time_buf ); snmp_log( LOG_ERR, "%s register_tables(): str to oid conversion failed " "(Toplvl OID) .%s\nOSA Subagent MIB information may be incomplete!\n" ,time_buf, toid_str ); return -1; } /* end if */ /* save Toplevel OID in order to register it later */ top_oid = (oid*) malloc ( oid_len * sizeof(oid) ); if ( top_oid == NULL ) { get_time( time_buf ); snmp_log( LOG_ERR, "%s register_tables(): malloc() for variable top_oid failed\n" "register_tables(): Toplevel OID .%s\n" "OSA Subagent MIB information may be incomplete!\n", time_buf, toid_str ); return -1; } /* end if */ memcpy( top_oid, t_oid, oid_len * sizeof(oid) ); /* is retrieved Toplevel OID already in linked list? */ src_oid = search_oid( top_oid, oid_len, lhead, &oid_ptr ); if ( src_oid == UNEXP_ERROR ) { get_time( time_buf ); snmp_log( LOG_ERR, "%s register_tables(): Unexpected return code from" " function search_oid() for Toplevel OID .%s\n" "OSA Subagent MIB information may be incomplete!\n", time_buf, toid_str ); free( top_oid ); return -1; } /* put this Toplevel OID into the linked list, if OID was not found */ else if ( src_oid == OID_NOT_FOUND ) { ins_oid = oid_insert_after( top_oid, oid_len, oid_ptr ); if ( ins_oid == NULL ) { get_time( time_buf ); snmp_log( LOG_ERR, "%s register_tables(): malloc() for new entry in " "OID list failed for Toplevel OID .%s\nOSA Subagent " "MIB information may be incomplete!\n", time_buf, toid_str ); free( top_oid ); return -1; } /* end if */ } /* end if */ /* if OID was found, set insert OID to exact match OID */ else /* src_oid == OID_FOUND */ ins_oid = oid_ptr; /****************************************************************/ /* add suffixes to variable_x structure for macro REGISTER_MIB */ /****************************************************************/ last_suffix = NULL; /* initialize compare ptr */ table_vars = NULL; /* initialize variable_x ptr */ suf_def = 0; /* number of suffixes */ /* point to suffix entries */ mib_data_ptr += (strlen(mib_data_ptr)+1); mib_data_ptr = (int*) (PTR_ALIGN4(mib_data_ptr)); /* get number of attached suffix OIDs */ suf_cnt = *((int*) mib_data_ptr); /* loop through suffix list */ for ( j=0;j < suf_cnt; j++ ) { /* adjust parse ptr to access type (align pointer if it follows a string) */ if ( j == 0 ) mib_data_ptr += sizeof( int ); else { mib_data_ptr += (strlen(mib_data_ptr)+1); mib_data_ptr = (int*) (PTR_ALIGN4(mib_data_ptr)); } /* end if */ /* save object access type for registering later */ oid_acc = *((int*) mib_data_ptr); /* adjust ptr and save object data type for registering later */ mib_data_ptr += sizeof( int ); oid_typ = *((int*) mib_data_ptr); /* adjust ptr to OID suffix string */ mib_data_ptr += sizeof( int ); /* need to register suffixes only, if I have a new Toplevel OID */ if ( src_oid == OID_NOT_FOUND ) { /* init last_suffix here, if loop entered the first time */ if ( j==0 ) last_suffix = (char*) mib_data_ptr; /* check wether last and current OID suffixes match */ /* yes-don't need to register suffix again, goto register index portion */ /* no -add suffix to variable_x list */ if ( (strcmp( last_suffix, (char*) mib_data_ptr ) !=0) || j==0 ) { suf_def++; /* increase count of suffix definitions */ last_suffix = (char*) mib_data_ptr; /* save suffix */ /* allocate one entry within variable_x struct */ new_area = (struct variable13*) realloc( table_vars, suf_def * sizeof( struct variable13)); if ( new_area == NULL ) { get_time( time_buf ); snmp_log( LOG_ERR, "%s register_tables(): " "realloc() for variable_x structure failed\n" "register_tables(): for Toplevel OID .%s\n" "OSA Subagent MIB information may be incomplete!\n", time_buf, toid_str ); /* remove all memory for this Toplevel OID allocated so far */ free(table_vars); delete_oid( ins_oid->pObjid, ins_oid->length, lhead ); return -1; } /* end if */ else /* reassign table_vars */ table_vars = new_area; /* convert suffix OID string to net-snmp oid type */ oid_len = str_to_oid_conv( (char*) mib_data_ptr, t_oid ); if ( oid_len == 0 ) { get_time( time_buf ); snmp_log( LOG_ERR, "%s register_tables(): " "suffix str to oid conversion failed " "for Toplevel OID .%s\nOSA Subagent MIB information may " "be incomplete!\n", time_buf, toid_str ); /* remove all memory for this Toplevel OID allocated so far */ free(table_vars); delete_oid( ins_oid->pObjid, ins_oid->length, lhead ); return -1; } /* end if */ /*******************************/ /* set up variable_x structure */ /*******************************/ table_vars[suf_def-1].magic = 0; /* magic is not used */ if ( oid_typ == IPA_DISPLAYSTR ) /* set object type */ table_vars[suf_def-1].type = ASN_OCTET_STR; else table_vars[suf_def-1].type = oid_typ; if ( oid_acc == IPA_WRONLY ) /* have no WRITE ONLY */ table_vars[suf_def-1].acl = RWRITE; /* set to type RWRITE */ else table_vars[suf_def-1].acl = oid_acc; /* set given object type */ if ( oid_typ == IPA_DISPLAYSTR ) /* set callback functions */ table_vars[suf_def-1].findVar = var_DisplayStr; else table_vars[suf_def-1].findVar = var_ibmOSAMib; table_vars[suf_def-1].namelen = oid_len; /* OID suffix len */ if ( oid_len <= SUFFIX_MAXLEN ) /* set OID suffix */ memcpy( table_vars[suf_def-1].name, t_oid, oid_len * sizeof( oid )); else { get_time( time_buf ); snmp_log( LOG_ERR, "%s register_tables(): OID suffix length exceeded " "for Toplevel OID .%s\nSuffix OID length %d\n" "OSA Subagent MIB information may be incomplete!\n", time_buf, toid_str, oid_len ); /* remove all memory for this Toplevel OID allocated so far */ free(table_vars); delete_oid( ins_oid->pObjid, ins_oid->length, lhead ); return -1; } /* end if */ } /* end if */ } /* end if register_suffix part */ /****************************************************************/ /* add index to index linked list under this Toplevel OID entry */ /****************************************************************/ /* adjust ptr to index portion */ mib_data_ptr = (char*) (mib_data_ptr + (strlen(mib_data_ptr)+1)); /* search index list for this index */ src_ind = search_index( (char*) mib_data_ptr, ins_oid->ind_list, &ind_ptr ); /* if index is not found in the linked list, add index */ if ( src_ind == INDEX_NOT_FOUND ) { new_index = (char*) malloc( strlen( (char*) mib_data_ptr ) + 1 ); if ( new_index == NULL ) { get_time( time_buf ); snmp_log( LOG_ERR, "%s register_tables(): " "malloc() for new index entry failed\n" "register_tables(): for Toplevel OID .%s\n" "OSA Subagent MIB information may be incomplete!\n", time_buf, toid_str); if ( src_oid == OID_NOT_FOUND ) { free(table_vars); delete_oid( ins_oid->pObjid, ins_oid->length, lhead ); } return -1; } /* end if */ strcpy( new_index, (char*) mib_data_ptr ); index_insert_after( new_index, mib_data_hdr->ioctl_cmd.ipa_cmd_hdr.ifIndex, ind_ptr ); } /* end if */ } /* end for (j) */ /* new Toplevel OID?: yes, then register with agent to add this table */ /* note - register_mib() is taken from the net-snmp agent extension API */ if ( src_oid == OID_NOT_FOUND ) { if ( register_mib( "ibmOSAMib",(struct variable*) table_vars, sizeof(struct variable13), suf_def, top_oid, ins_oid->length ) != MIB_REGISTERED_OK ) { get_time( time_buf ); snmp_log( LOG_ERR, "%s register_tables(): " "API function register_mib() failed\n" "register_tables(): for Toplevel OID .%s\n" "OSA Subagent MIB information may be incomplete!\n", time_buf, toid_str ); /* remove all memory for this Toplevel OID allocated so far */ free(table_vars); delete_oid( ins_oid->pObjid, ins_oid->length, lhead ); return -1; } /* end if */ else { get_time( time_buf ); snmp_log( LOG_INFO, "%s registered Toplevel OID .%s\n", time_buf, toid_str ); } /* end if */ } /* end if */ /* save variable_x ptr to free allocated memory later, if needed */ ins_oid->var13ptr = table_vars; /* adjust ptr to next Toplevel OID */ mib_data_ptr = (char*) (mib_data_ptr + (strlen(mib_data_ptr)+1)); } /* end for (i) */ return 0; } /* end register_tables() */ /************************************************************************ * header_osa_table() * Compare 'name' to vp->name for the best match or an exact match. * Store result OID in 'name', including the index that matches best. * Return matching net-snmp ifIndex to caller. * * parameters: * IN struct variable *vp - ptr to registered subagent MIB data * INOUT oid *name - fully instantiated OID name * INOUT size_t *length - length of this OID * IN int exact - TRUE if an GET match is desired * OUT size_t *var_len - hook for size of returned data type * IN WriteMethod **write_method - hook for write method (UNUSED) * IN TABLE_OID *lhead - ptr to Toplevel OID list head * * returns: * ifIndex - ifIndex in this OID * -1 (MATCH_FAILED) - no suitable match found * ************************************************************************/ int header_osa_table( struct variable *vp, oid *name, size_t *length, int exact, size_t *var_len, WriteMethod **write_method, TABLE_OID *lhead ) { int interface = -1; int i, res, conv; size_t index_len; /* length of index portion */ oid newname[MAX_OID_LEN]; /* temporary return OID */ oid buffer[MAX_OID_LEN]; /* temporary converion buffer */ char name_index[MAX_OID_STR_LEN]; char time_buf[TIME_BUF_SIZE]; /* date/time buffer */ /* ptr into OID and index linked lists */ TABLE_OID *ptr_oid; REG_INDICES *ptr_ind; int found_OID = FALSE; /* some debugging calls */ DEBUGMSGTL(("ibmOSAMib-Subagent", "header_osa_table: ")); DEBUGMSGOID(("ibmOSAMib-Subagent", name, *length)); DEBUGMSG(("ibmOSAMib-Subagent"," exact=%d\n", exact)); /* OID compare */ /* set 'res' to -1 name < vp->name */ /* 1 name > vp->name */ /* 0 exact match */ for ( i=0, res=0; (i < (int) vp->namelen) && (i < (int) (*length)) && !res; i++ ) { if ( name[i] != vp->name[i] ) { if ( name[i] < vp->name[i] ) res = -1; else res = 1; } /* end if */ } /* end for */ DEBUGMSG(("ibmOSAMib-Subagent", " snmp_oid_compare: %d\n", res)); /* (GETNEXT AND search OID still greater) OR (GET AND not-exact OIDs) */ /* yes - indicate match failed */ if ( (!exact && (res > 0)) || (exact && (res != 0)) ) { if (var_len) *var_len = 0; return MATCH_FAILED; } /* end if */ /* init temporary return OID */ memset(newname, 0, sizeof(newname)); /******************************************/ /* handle GET requests with matching OIDs */ /******************************************/ if ( exact && res == 0 ) { /* got a too short OID */ if ( (*length) <= vp->namelen ) { if (var_len) *var_len = 0; return MATCH_FAILED; } else /* determine length of attached index */ index_len = (int) (*length) - (int) vp->namelen; DEBUGMSG(("ibmOSAMib-Subagent", " header_osa_table - GET index length: %zd\n", index_len)); /* search our internal linked list for a matching Toplevel OID */ res = search_top_oid ( (oid*) vp->name, (size_t) vp->namelen, lhead, &ptr_oid ); /* found a matching Toplevel OID, now look for index portion */ if ( res == OID_FOUND ) { DEBUGMSGOID(("ibmOSAMib-Subagent:header_osa_table - Toplevel OID found", ptr_oid->pObjid, ptr_oid->length)); /* convert index portion to string */ conv = oid_to_str_conv ( (oid*) &name[vp->namelen], index_len, name_index ); DEBUGMSG(("ibmOSAMib-Subagent"," index portion=%s\n", name_index)); if (conv == TRUE) { res = search_index ( name_index, (REG_INDICES*) ptr_oid->ind_list, &ptr_ind ); /* found a matching index, OID for GET request exists! */ if ( res == INDEX_FOUND ) { DEBUGMSG(("ibmOSAMib-Subagent"," index found in linked list\n")); /* return appropriate ifIndex responsible for that OID */ interface = ptr_ind->ifIndex; /* set up return OID */ memmove( newname, name, (*length) * sizeof(oid) ); found_OID = TRUE; } /* end if */ } /* end if */ } /* end if */ } /* end if */ /***************************/ /* handle GETNEXT requests */ /***************************/ else if ( !exact && res <= 0 ) { /* OID too short or switched to next suffix */ /* adjust length and attach first index */ if ( (int) (*length) <= (int) vp->namelen || res < 0 ) { DEBUGMSG(("ibmOSAMib-Subagent", " GETNEXT - length <= vp->namelen OR snmp_oid_compare < 0")); /* search our internal linked list for a matching Toplevel OID */ res = search_top_oid ( (oid*) vp->name, vp->namelen, lhead, &ptr_oid ); /* found a matching Toplevel OID, now look for index portion */ if ( res == OID_FOUND ) { DEBUGMSGOID(("ibmOSAMib-Subagent:header_osa_table - Toplevel OID found", ptr_oid->pObjid, ptr_oid->length)); /* set up return OID */ memmove( newname, vp->name, (int) vp->namelen * sizeof(oid) ); *length = vp->namelen; /* retrieve first index under this Toplevel OID and attach to newname */ if ( ptr_oid->ind_list->next != NULL ) { index_len = str_to_oid_conv ( ptr_oid->ind_list->next->full_index, buffer ); if ( index_len != 0 && ( (vp->namelen + index_len) <= MAX_OID_LEN ) ) { memmove ( &newname[vp->namelen], buffer, index_len * sizeof(oid) ); *length = *length + index_len; DEBUGMSG(("ibmOSAMib-Subagent"," index portion to attach=%s\n" ,ptr_oid->ind_list->next->full_index )); /* return appropriate ifIndex responsible for that OID */ interface = ptr_oid->ind_list->next->ifIndex; found_OID = TRUE; } else { get_time( time_buf ); snmp_log( LOG_ERR, "%s header_osa_table(): " "(GETNEXT-1) index list corrupted\n" "OSA Subagent MIB information may be incomplete!\n", time_buf ); } /* end if */ } /* end if */ } /* end if */ } /* 'true' GETNEXT case */ else { DEBUGMSG(("ibmOSAMib-Subagent", " GETNEXT - length > vp->namelen")); /* search our internal linked list for a matching Toplevel OID */ res = search_top_oid ( (oid*) name, (int) (*length), lhead, &ptr_oid ); /* found a matching Toplevel OID, now look for index portion */ if ( res == OID_FOUND ) { DEBUGMSGOID(("ibmOSAMib-Subagent: " "header_osa_table - Toplevel OID found", ptr_oid->pObjid, ptr_oid->length)); /* search the index attached to 'name' in the index list */ /* determine length of attached index and convert to a string */ index_len = (int) (*length) - (int) vp->namelen; conv = oid_to_str_conv ( (oid*) &name[vp->namelen], index_len, name_index ); if (conv == TRUE) { res = search_index ( name_index, (REG_INDICES*) ptr_oid->ind_list, &ptr_ind ); /* next index is the one we're looking for */ /* if next index is NULL; return MATCH FAILED(goto next suffix) */ if( ptr_ind->next != NULL ) { index_len = str_to_oid_conv( ptr_ind->next->full_index, buffer); if ( index_len != 0 && ( (vp->namelen + index_len) <= MAX_OID_LEN ) ) { DEBUGMSG(("ibmOSAMib-Subagent"," index portion to attach=%s\n" ,ptr_ind->next->full_index )); /* set up return OID */ *length = vp->namelen; memmove( newname, name, vp->namelen * sizeof(oid) ); memmove( &newname[vp->namelen], buffer, index_len * sizeof(oid) ); *length = *length + index_len; /* return appropriate ifIndex responsible for that OID */ interface = ptr_ind->next->ifIndex; found_OID = TRUE; } /* end if */ else { get_time( time_buf ); snmp_log( LOG_ERR, "%s header_osa_table(): " "(GETNEXT-2) index list corrupted\n" "OSA Subagent MIB information may be incomplete!\n", time_buf ); } /* end if */ } /* end if */ } /* end */ } else { get_time( time_buf ); snmp_log( LOG_ERR, "%s header_osa_table(): " "(GETNEXT-2) Toplevel OID not found\n" "OSA Subagent MIB information may be incomplete!\n", time_buf ); } /* end if */ } /* end if - handle GETNEXT requests */ } /* return MATCH_FAILED, if no appropriate OID was found */ if ( !found_OID ) { if (var_len) *var_len = 0; return MATCH_FAILED; } else { /* finalize OID to be returned */ memmove(name, newname, (*length) * sizeof(oid)); if (write_method) *write_method = 0; if (var_len) *var_len = sizeof(long); /* default data type long */ return interface; } /* end if */ } /* end header_osa_table */ /************************************************************************ * update_mib_info() * Whenever a network interface appears or disappears the MIB and/or * interface information has to be updated, this fuction is called. * * parameters: * none * returns: * none * ************************************************************************/ void update_mib_info () { TABLE_OID *mib_info = oid_list_head; char time_buf[TIME_BUF_SIZE]; /* date/time buffer */ int i, retc, sd, /* socket descriptors */ error_code, /* used to save errno */ if_num, /* number of network interfaces */ osaexp_num; /* number of OSA-E devices */ struct ifreq ifr; /* request structure for ioctl */ IPA_CMD_REG* ipa_reg_mib; /* structure for IPA REGISTER MIB command header */ IF_LIST* tmp_list; /* temporary interfaces list */ char* buffer; /* a data buffer */ /* Retrieve ifNumber/ifIndex/ifDescr newly from IF-MIB for all interfaces */ /* retrieve data in temporary list first */ if_num = query_IF_MIB( &tmp_list ); if ( if_num < 0 ) { get_time( time_buf ); snmp_log( LOG_ERR, "%s update_mib_info(): " "could not get interface info from IF-MIB\n" "update_mib_info(): check previous messages for more details\n" "update_mib_info(): keeping original interface lists\n", time_buf ); return; } /* query OSA-E device driver for OSA-E devices * and mark them in IF-MIB interface list * */ osaexp_num = query_OSA_EXP( &tmp_list, if_num ); if ( osaexp_num < 0 ) { get_time( time_buf ); fprintf( stderr, "%s update_mib_info(): " "OSA-E device driver query interface ioctl() " "failed\nupdate_mib_info(): going to stop osasnmpd daemon\n" "update_mib_info(): check subagent logfile for more details\n" "update_mib_info(): sending termination signal to osasnmpd...\n", time_buf ); exit(-1); } else if ( osaexp_num == 0 ) { get_time( time_buf ); snmp_log( LOG_ERR, "%s update_mib_info(): " "no or bad OSA Express devices reported" " - continue processing\n" "update_mib_info(): " "if available, see previous message for reason\n" "update_mib_info(): freeing interface lists\n", time_buf ); free( tmp_list ); /* remove all index entries from Toplevel OID * linked list and store ifNumber * */ clear_oid_list( mib_info ); ifNumber = if_num; return; } /* end if */ /* allocate area, that should contain retrieved MIB * data for a single interface * */ buffer = (char*) malloc( MIB_AREA_LEN ); if ( buffer == NULL ) { get_time( time_buf ); snmp_log( LOG_ERR, "%s update_mib_info(): " "malloc() for REGISTER MIB data buffer " "failed\nupdate_mib_info(): requested %d bytes\n" "update_mib_info(): keeping original interface lists\n", time_buf, MIB_AREA_LEN ); free( tmp_list ); return; } /* end if */ /* socket for query MIB information ioctl */ sd = socket( AF_INET, SOCK_STREAM, 0 ); if ( sd < 0 ) { error_code = errno; get_time( time_buf ); snmp_log(LOG_ERR, "%s update_mib_info(): " "error opening socket() - reason %s\n" "update_mib_info(): cannot update OSA-E MIB information\n" "update_mib_info(): keeping original interface lists\n", time_buf, strerror( error_code ) ); free( tmp_list ); free( buffer ); return; } /* end if */ /* free original interface list and assign the new one (global data) */ if ( if_list != NULL ) free( if_list ); ifNumber = if_num; if_list = tmp_list; /* free entire MIB lists that we maintain so far */ clear_oid_list( mib_info ); /* walk through interface list and query MIB data * for all OSA-E devices register MIB data with * subagent driving code afterwards * */ for ( i=0; i < ifNumber; i++ ) { if ( if_list[i].is_OSAEXP == TRUE ) { /* clear buffer */ memset( buffer, 0, MIB_AREA_LEN ); /* setup ioctl buffer with request and input parameters */ /* map command structure */ ipa_reg_mib = (IPA_CMD_REG*) buffer; /* size of IPA data area */ ipa_reg_mib->ioctl_cmd.data_len = MIB_AREA_LEN - offsetof( IOCTL_CMD_HDR, ipa_cmd_hdr ); /* length of IPA subcommand */ ipa_reg_mib->ioctl_cmd.req_len = sizeof( ipa_reg_mib->ioctl_cmd.ipa_cmd_hdr ); /* set input parameters for IPA Register MIB command */ ipa_reg_mib->ioctl_cmd.ipa_cmd_hdr.request = IPA_REG_MIB; ipa_reg_mib->ioctl_cmd.ipa_cmd_hdr.ifIndex = if_list[i].ifIndex; ipa_reg_mib->ioctl_cmd.ipa_cmd_hdr.ret_code = 0; ipa_reg_mib->ioctl_cmd.ipa_cmd_hdr.seq_num = 0; /* do ioctl() */ strcpy( ifr.ifr_name, if_list[i].if_Name ); /* add interface name */ ifr.ifr_ifru.ifru_data = (char*) buffer; /* add data buffer */ if ( ioctl( sd,SIOC_QETH_ADP_SET_SNMP_CONTROL, &ifr ) < 0 ) { error_code = errno; get_time( time_buf ); /* see if we got a common I/O error */ if ( error_code == -EIO ) { snmp_log( LOG_ERR, "%s update_mib_info(): " "ioctl() failed - reason %s for interface %s\n" "update_mib_info(): " "MIB information may be incomplete\n", time_buf, strerror( error_code ), if_list[i].if_Name ); continue; break; } /* end if */ /* let's see, if we got a return code from IPAssists */ /* or if MIB buffer is exhausted */ switch ( ipa_reg_mib->ioctl_cmd.ipa_cmd_hdr.ret_code ) { case IPA_FAILED: snmp_log( LOG_ERR, "%s update_mib_info(): " "ioctl() failed - IPA command failed\n" "update_mib_info(): for interface %s\n" "update_mib_info(): MIB information may be incomplete\n", time_buf, if_list[i].if_Name ); continue; break; case IPA_NOT_SUPP: snmp_log( LOG_ERR, "%s update_mib_info(): " "ioctl() failed - IPA command not supported\n" "update_mib_info(): for interface %s\n" "update_mib_info(): MIB information may be incomplete\n", time_buf, if_list[i].if_Name ); continue; break; case IPA_NO_DATA: snmp_log( LOG_ERR, "%s update_mib_info(): " "ioctl() failed - valid IPA command, but no" "SNMP data is available for interface %s\n" "update_mib_info(): MIB information may be incomplete\n", time_buf, if_list[i].if_Name ); continue; break; case -ENOMEM: /* should not happen in the near future ;-) */ snmp_log( LOG_ERR, "%s update_mib_info(): " "ioctl() failed - MIB data size > " "constant MIB_AREA_LEN for interface %s\n" "update_mib_info(): " "Enlarge constant for MIB_AREA_LEN " "within ibmOSAMibDefs.h and " "recompile the subagent\n" "update_mib_info(): MIB information may be incomplete\n", time_buf, if_list[i].if_Name ); continue; break; default: snmp_log( LOG_ERR, "%s update_mib_info(): " "ioctl() failed - reason %s " "for interface %s\n" "update_mib_info(): MIB information may be incomplete\n", time_buf, strerror( error_code ), if_list[i].if_Name ); continue; break; } /* end switch */ } else if ( ipa_reg_mib->ioctl_cmd.ipa_cmd_hdr.ret_code != 0 ) { get_time( time_buf ); /* now check IPA SNMP subcommand return code */ switch ( ipa_reg_mib->ioctl_cmd.ipa_cmd_hdr.ret_code ) { case IPA_SNMP_INV_TOPOID: case IPA_SNMP_INV_GROUP: case IPA_SNMP_INV_SUFFIX: case IPA_SNMP_INV_INST: case IPA_SNMP_OID_NREAD: case IPA_SNMP_OID_NWRIT: snmp_log( LOG_ERR, "%s update_mib_info(): " "IPA SNMP subcommand failed - " "return code 0x%x for interface %s\n" "update_mib_info(): MIB information may be incomplete\n", time_buf, ipa_reg_mib->ioctl_cmd.ipa_cmd_hdr.ret_code, if_list[i].if_Name ); continue; break; case IPA_SNMP_NOT_SUPP: snmp_log( LOG_ERR, "%s update_mib_info(): " "IPA SNMP subcommand failed - " "subcommand 0x%x for interface %s\n" "update_mib_info(): MIB information may be incomplete\n", time_buf, ipa_reg_mib->ioctl_cmd.ipa_cmd_hdr.request, if_list[i].if_Name ); continue; break; case IPA_SNMP_NO_DATA: snmp_log( LOG_ERR, "%s update_mib_info(): " "IPA SNMP subcommand failed - " "no data available for interface %s\n" "update_mib_info(): MIB information may be incomplete\n", time_buf, if_list[i].if_Name ); continue; break; default: snmp_log( LOG_ERR, "%s update_mib_info(): " "IPA SNMP subcommand failed - " "undefined return code 0x%x for interface %s\n" "update_mib_info(): MIB information may be incomplete\n", time_buf, ipa_reg_mib->ioctl_cmd.ipa_cmd_hdr.ret_code, if_list[i].if_Name ); continue; break; } /* end switch */ } /* end if */ /* register MIB tables information, that we got from IPAssists */ retc = register_tables ( buffer, mib_info ); if ( retc != 0 ) { get_time( time_buf ); snmp_log( LOG_ERR, "%s update_mib_info(): " "register MIB data with subagent " "driving code failed\nupdate_mib_info(): for interface %s\n" "update_mib_info(): check previous messages for " "more details\nupdate_mib_info(): MIB information may be " "incomplete\n", time_buf, if_list[i].if_Name ); continue; } /* end if */ } /* end if */ } /* end for */ get_time( time_buf ); snmp_log( LOG_INFO, "%s *** OSA-E interface change indicated ***\n" "%s *** Reinitialized MIB information ***\n", time_buf, time_buf ); close( sd ); free( buffer ); } /* end update_mib_info */ /********************************************************************** * query_IF_MIB(): * Retrieve ifIndex values for all OSA devices residing on this * system. Use SNMP to query the master agent for ifNumber and * ifIndex/ifDescr values from the standard IF-MIB. * Then figure out which of these devices are OSA Express devices. * parameters: * INOUT IF_LIST** ifList: List of network interfaces on this system * returns: int if_Number - number of network interfaces on this system * system (>=0) * -1 -an error occurred , no valid info avail *********************************************************************/ int query_IF_MIB ( IF_LIST** ifList ) { char time_buf[TIME_BUF_SIZE]; /* date/time buffer */ /* data structures for SNMP API */ struct snmp_session session, *ss; struct snmp_pdu *pdu; struct snmp_pdu *response; struct variable_list *vars; void *sessp; int status, have_info, valid; /* define static OIDs for if_Number, ifIndex and ifDescr from IF-MIB */ size_t ifNumber_OIDlen = 9, ifIndex_OIDlen = 11, ifDescr_OIDlen = 11; oid ifNumber_OID[9] = { 1,3,6,1,2,1,2,1,0 }, ifIndex_OID[11] = { 1,3,6,1,2,1,2,2,1,1,0 }, ifDescr_OID[11] = { 1,3,6,1,2,1,2,2,1,2,0 }; int i, if_Number, count; /* Initialize the SNMP library */ DEBUGMSGTL(("query_IF_MIB"," about to query IF-MIB information using SNMP\n")); /* Initialize a "session" for localhost */ have_info = TRUE; valid = TRUE; if_Number = 0; snmp_sess_init( &session ); session.peername = NET_SNMP_PEERNAME; session.version = SNMP_VERSION_2c; /* use SNMPv2c */ /* SNMPv2c community name */ session.community = (unsigned char*)NET_SNMP_COMMUNITY; session.community_len = strlen((const char*)session.community); session.timeout = 300 * 1000000L; /* Open the session */ sessp = snmp_sess_open( &session ); if ( sessp == NULL ) { get_time( time_buf ); snmp_log(LOG_ERR, "%s query_IF_MIB(): snmp_sess_open() failed\n", time_buf ); return -1; } /* end if */ ss = snmp_sess_session( sessp ); /* Create and send synchronous GET PDU for if_Number */ pdu = snmp_pdu_create(SNMP_MSG_GET); snmp_add_null_var( pdu, ifNumber_OID, ifNumber_OIDlen ); status = snmp_sess_synch_response( sessp, pdu, &response); /* Process the response and get ifIndex/ifDescr table entries */ if (status == STAT_SUCCESS && response->errstat == SNMP_ERR_NOERROR) { /* get value for if_Number */ if ( response->variables->val.integer == NULL ) { get_time( time_buf ); snmp_log(LOG_ERR, "%s query_IF_MIB(): Get(if_Number) Error in packet\n" "Reason: response->variables->val.integer == NULL\n", time_buf); snmp_free_pdu(response); return -1; } /* end if */ count = (int) *(response->variables->val.integer); snmp_free_pdu(response); if ( count == 0 ) { snmp_sess_close( sessp ); return 0; } /* end if */ /* allocate memory for interface list */ *ifList = (IF_LIST*) malloc((sizeof(IF_LIST) * count)); if (*ifList == NULL) { get_time( time_buf ); snmp_log(LOG_ERR, "%s query_IF_MIB(): malloc() for ifList structure failed!\n", time_buf); snmp_sess_close( sessp ); return -1; } /* end if */ /* if have interfaces, issue GETNEXT PDUs for ifIndex/ifDescr table entries */ for ( i=0; i < count; i++ ) { pdu = snmp_pdu_create( SNMP_MSG_GETNEXT ); snmp_add_null_var( pdu, ifIndex_OID, ifIndex_OIDlen ); snmp_add_null_var( pdu, ifDescr_OID, ifDescr_OIDlen ); status = snmp_sess_synch_response( sessp, pdu, &response ); if (status == STAT_SUCCESS && response->errstat == SNMP_ERR_NOERROR) { /* set device to default value false; * device type is determined later * */ (*ifList)[i].is_OSAEXP = FALSE; (*ifList)[i].kerIndex = 0; (*ifList)[i].ifIndex = 0; /* get values for ifIndex/ifDescr from response PDU * FIXME: (workaround) compare ifDescr values * and omit double entries * */ for(vars = response->variables; vars; vars = vars->next_variable) { if( vars->type == ASN_INTEGER ) (*ifList)[if_Number].ifIndex = (int) *(vars->val.integer); else if( vars->type == ASN_OCTET_STR && vars->val_len <= IFNAME_MAXLEN ) { if ( if_Number == 0 ) { strncpy((char*)((*ifList)[0]).if_Name, (const char*)vars->val.string, vars->val_len); (*ifList)[0].if_Name[vars->val_len] = '\0'; } else { if (strncmp((const char*)(*ifList)[if_Number-1].if_Name, (const char*)vars->val.string, vars->val_len) !=0) { strncpy((char*)(*ifList)[if_Number].if_Name, (const char*)vars->val.string, vars->val_len); (*ifList)[if_Number].if_Name[vars->val_len] = '\0'; valid = TRUE; } else valid = FALSE; } /* end if */ } else { get_time( time_buf ); snmp_log(LOG_ERR, "%s query_IF_MIB(): GetNext(ifIndex;ifDescr)" " response PDU has invalid data\n", time_buf ); free( *ifList ); snmp_free_pdu(response); snmp_sess_close( sessp ); return -1; } /* end if */ } /* end for */ if ( valid == TRUE ) if_Number++; } else { /* FAILURE GETNEXT */ if (status == STAT_SUCCESS) { get_time( time_buf ); snmp_log(LOG_ERR, "%s query_IF_MIB(): GetNext(ifIndex,ifDescr) " "Error in packet\n" "Reason: %s\n",time_buf, snmp_errstring(response->errstat)); } else snmp_sess_perror("query_IF_MIB(): GetNext(ifIndex;ifDescr)", ss); if (response) snmp_free_pdu(response); free( *ifList ); have_info = FALSE; break; } /* end if */ /* store current ifIndex in OIDs for next GetNext request */ ifIndex_OID[ifIndex_OIDlen-1] = (*ifList)[i].ifIndex; ifDescr_OID[ifDescr_OIDlen-1] = (*ifList)[i].ifIndex; snmp_free_pdu( response ); } /* end for */ } else { /* FAILURE GET if_Number */ if (status == STAT_SUCCESS) { get_time( time_buf ); snmp_log(LOG_ERR, "%s query_IF_MIB(): Get(if_Number) Error in packet\n" "Reason: %s\n", time_buf, snmp_errstring(response->errstat)); } else snmp_sess_perror("query_IF_MIB(): Get(if_Number)", ss); if (response) snmp_free_pdu(response); have_info = FALSE; } /* end if */ /* Cleanup */ snmp_sess_close( sessp ); if ( have_info ) return ( if_Number ); else return -1; } /* end query_IF_MIB() */ /********************************************************************** * query_OSA_EXP(): * Retrieve device characteristics for OSA Express devices and mark * OSA Express devices in the interface list from IF-MIB. * parameters: * INOUT IF_LIST** ifList: List of network interfaces on this system * IN int if_Number: number of network interfaces * returns: int num - number of OSA Express devices found on this * system (>=0) * -1 -an error occurred , no valid info avail *********************************************************************/ int query_OSA_EXP ( IF_LIST** ifList, int if_Number ) { int j, num = 0; char time_buf[TIME_BUF_SIZE]; /* date/time buffer */ int sd; struct ifreq ifr; /*open socket to get information if device is an OSA device*/ if ( (sd = socket(AF_INET,SOCK_STREAM,0)) < 0 ) { snmp_log( LOG_ERR,"%s query_OSA_EXP(): " "socket failed\n" "query_OSA_EXP(): cancel init or update " "of MIB information\n",time_buf ); return 0; } /* walk through IF-MIB interface list and mark OSA Express interfaces */ for ( j=0; j < if_Number; j++ ) { int ret=0; memset(&ifr,0,sizeof(ifr)); strncpy(ifr.ifr_name, (*ifList)[j].if_Name,IFNAME_MAXLEN); ret = ioctl(sd, SIOC_QETH_GET_CARD_TYPE, &ifr); if (ret>0) { (*ifList)[j].is_OSAEXP = TRUE; (*ifList)[j].kerIndex = (*ifList)[j].ifIndex; num++; } } /* end for */ close(sd); return num; } /* end query_OSA_EXP() */ /********************************************************************** * get_time(): * gets current time of day string and returns it as * MONTH DAY HH:MM:SS * returns: string with date and time *********************************************************************/ int get_time( char* buffer ) { time_t curtime; struct tm *loctime; curtime = time(NULL); loctime = localtime( &curtime ); strftime( buffer, TIME_BUF_SIZE, "%b %d %T", loctime ); return 0; } /* end get_time () */ static const char* usage_text[] = { "Usage: osasnmpd [-h] [-v] [-l LOGFILE] [-A] [-f] [-L] [-P PIDFILE]", " [-x SOCKADDR]", "", "-h, --help This usage message", "-v, --version Version information", "-l, --logfile LOGFILE Print warnings/messages to LOGFILE", "-A, --append Append to the logfile rather than truncating it", "-L, --stderrlog Print warnings/messages to stdout/err", "-f, --nofork Do not fork() from the calling shell", "-P, --pidfile PIDFILE Save the process ID of the subagent in PIDFILE", "-x, --sockaddr SOCKADDR Bind AgentX port to this address", "" }; /********************************************************************** * usage(): * prints help message with descriptions of available parameters * IN name of subagent * returns: none *********************************************************************/ void usage() { unsigned int i; for (i=0; i < sizeof(usage_text) / sizeof(usage_text[0]); i++) printf("%s\n", usage_text[i]); exit(0); } /* end usage() */ s390-tools-2.3.0/osasnmpd/ibmOSAMibUtil.h000066400000000000000000000057401323410431100177430ustar00rootroot00000000000000/* * osasnmpd - IBM OSA-Express network card SNMP subagent * * Prototypes for the utility functions within ibmOSAMibUtil.c * It should be included in every module that belonging to the * subagent. * * Copyright IBM Corp. 2002, 2017 * * 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include /* included because of "parse error before `get_myaddr'" */ /* in version 4.2.1 this headerfile was included from */ /* ucd-snmp-includes.h */ /* include local defintions of data structures for OSA Express subagent */ #include "ibmOSAMibDefs.h" /************************ * function prototypes * ************************/ /* convert OID string returned by IPAssists to net-snmp oid type */ int str_to_oid_conv ( char*, oid* ); /* convert net-snmp oid type to OID string used by IPAssists */ int oid_to_str_conv ( oid*, size_t, char* ); /* convert IPA access type to net-snmp access type */ int get_acc_type ( int ); /* create pseudo node for toplevel OID linked list */ TABLE_OID* init_oid_list(); /* create pseudo node for index linked list */ REG_INDICES* init_ind_list(); /* inserts an OID after a given entry into the linked list */ TABLE_OID* oid_insert_after ( oid*, size_t, TABLE_OID* ); /* delete a whole Toplevel OID from the linked list including indices */ int delete_oid( oid*, size_t, TABLE_OID* ); /* remove index entries from Toplevel OID list */ int clear_oid_list( TABLE_OID* ); /* searches an OID in the OID linked list */ int search_oid ( oid*, size_t, TABLE_OID*, TABLE_OID** ); /* searches a Toplevel OID in a fully qualified OID */ int search_top_oid ( oid*, size_t, TABLE_OID*, TABLE_OID** ); /* inserts an index into the index linked list */ REG_INDICES* index_insert_after ( char*, int, REG_INDICES* ); /* delete a single entry from the index list or the whole list */ int delete_index( REG_INDICES*, int, int ); /* searches an index in the OID linked list */ int search_index ( char*, REG_INDICES*, REG_INDICES** ); /* main MIB information registry function */ int register_tables ( void*, TABLE_OID* ); /* validates request and sets up fully qualified query/set OID */ int header_osa_table( struct variable*, oid*, size_t*,int, size_t*, WriteMethod**, TABLE_OID* ); /* functions checking for interface changes and updating MIB information */ void update_mib_info ( ); /* retrieves interface information from IF-MIB */ int query_IF_MIB( IF_LIST** ); /* retrieves OSA Express interface information from kernel */ int query_OSA_EXP ( IF_LIST** ,int ); /* get time of day */ int get_time( char* ); /* display help message */ void usage(); s390-tools-2.3.0/osasnmpd/osasnmpd.8000066400000000000000000000036231323410431100171050ustar00rootroot00000000000000.\" Copyright 2017 IBM Corp. .\" s390-tools is free software; you can redistribute it and/or modify .\" it under the terms of the MIT license. See LICENSE for details. .\" .TH OSASNMPD 8 "Apr 2006" "s390-tools" .SH NAME osasnmpd \- IBM OSA-Express network card SNMP subagent. .SH SYNOPSIS \fBosasnmpd\fR [-h] [-v] [-f] [-l \fIlogfile\fR | -L] [-A] [-P \fIpidfile\fR] [-x \fIagentx-socket\fR] .SH DESCRIPTION \fBosasnmpd\fR is an SNMP subagent for the net-snmp 5.1.x package. It supports the MIBs provided by an IBM OSA-Express network card. The subagent communicates via the AgentX protocol with the snmpd daemon. Hence the snmpd daemon must be already started on the same system before osasnmpd can start successfully. .SH OPTIONS .TP \fB-h\fR Print usage message and exit. .TP \fB-v\fR Print Version information and exit. .TP \fB-f\fR Don't fork() from the calling shell. .TP \fB-l\fR \fIlogfile\fR Logs all output from the subagent (including stdout/stderr) to \fIlogfile\fR. .br (By default logfile=/var/log/osasnmpd.log) .TP \fB-A\fR Append to the logfile rather than truncating it. .TP \fB-L\fR Print warnings/messages to stdout/stderr. .TP \fB-P\fR \fIpidfile\fR Save the process ID of the subagent in \fIpidfile\fR. .TP \fB-x\fR \fIagentx-socket\fR Uses the specified socket as AgentX connection rather than the default '/var/agentx/master'. The socket can either be a Unix domain socket path, or the address of a network interface. If a network address of the form inet-addr:port is given, then the subagent will use the port specified. If a network address of the form inet-addr is given, then the subagent will use the default AgentX port, 705. The agentx sockets of the snmpd daemon and osasnmpd must match. .SH AUTHOR .nf This man-page was written by Thomas Weber and is maintained by Ursula Braun .fi .SH SEE ALSO .PP snmpd(1), snmp.conf(5), snmpd.conf(5) s390-tools-2.3.0/osasnmpd/osasnmpd.c000066400000000000000000000175421323410431100171650ustar00rootroot00000000000000/* * osasnmpd - IBM OSA-Express network card SNMP subagent * * OSA-E subagent main module * * This subagent extends the net-snmp master agent to support * the Direct SNMP feature of zSeries OSA-E network cards. * net-snmp AgentX support must be enabled to allow the * subagent connecting to the master agent (snmpd). * * Copyright 2017 IBM Corp. * * s390-tools is free software; you can redistribute it and/or modify * it under the terms of the MIT license. See LICENSE for details. */ #include #include #include #include #include "lib/zt_common.h" #include "ibmOSAMibUtil.h" #include "ibmOSAMib.h" /* osasnmpd version */ #define COPYRIGHT "Copyright IBM Corp. 2003, 2017" /* ptr to OSA Express MIB information stored in linked lists */ extern TABLE_OID* oid_list_head; sig_atomic_t keep_running = 1; sig_atomic_t interfaces_changed = 0; /* signal handler for SIGTERM or SIGINT */ RETSIGTYPE stop_server(int UNUSED(a)) { keep_running = 0; } /* signal handler for SIGUSR1 * which is send by the kernel if the interface information changes */ RETSIGTYPE on_interfaces_changed( int UNUSED(a) ) { interfaces_changed++; } static void sysfs_register() { int sys_fd; char buf[10] = {0, }; sys_fd = open( QETH_SYSFILE, O_WRONLY ); if (sys_fd < 0) { fprintf(stderr, "open(%s) failed - reason %s\n", QETH_SYSFILE, strerror( errno ) ); exit(1); } sprintf(buf, "%d", QETH_UPDATE_MIB_SIGNAL); /* register our process to receive SIGUSR1 on interface changes */ if(write(sys_fd, buf, 10) < 0){ fprintf(stderr, "registration with qeth driver failed - " "reason %s\n", strerror( errno ) ); close(sys_fd); exit(1); } close(sys_fd); } static void sysfs_unregister() { int sys_fd; sys_fd = open( QETH_SYSFILE, O_WRONLY ); if (sys_fd < 0) { fprintf(stderr, "open(%s) failed - reason %s\n", QETH_SYSFILE, strerror( errno ) ); exit(1); } /* unregister our process to receive SIGUSR1 on interface changes */ if(write(sys_fd, "unregister", 11) < 0){ fprintf(stderr, "deregistration with qeth driver failed - " "reason %s\n", strerror( errno ) ); close(sys_fd); exit(1); } close(sys_fd); } static struct option longopts[] = { {"help",no_argument,0,'h'}, {"version",no_argument,0,'v'}, {"append",no_argument,0,'A'}, {"stderrlog",no_argument,0,'L'}, {"nofork",no_argument,0,'f'}, {"logfile",required_argument,0,'l'}, {"pidfile",required_argument,0,'P'}, {"sockaddr",required_argument,0,'x'}, {0,0,0,0} }; #define OPTSTRING "hvALfl:A:P:x:" /* * main routine */ int main( int argc, char *argv[] ) { TABLE_OID* li_ptr; char oidstr[MAX_OID_STR_LEN]; char logfile[PATH_MAX + 1]; char pid_file[PATH_MAX + 1]; FILE *PID; struct sigaction act; int res,c,longIndex,rc; unsigned char rel_a, rel_b, rel_c; struct utsname buf; char suffix[sizeof(buf.release)]; /* default settings, may be overridden by parameters */ int std_err_log = 0; /* 0=turn off stderr logging; 1=turn on */ /* if turned off; use file logging */ int dont_zero_log = 0; /* 0=clear logfile; 1=append to logfile */ int dont_fork = 0; /* 0=dont detach from shell; 1=detach */ int pid_file_set = 0; strcpy( logfile, OSAE_LOGFILE ); /* default log file */ /* check for parameters */ while((c = getopt_long(argc, argv, OPTSTRING, longopts, &longIndex)) != -1) { switch (c) { case 'h': usage(); exit(0); case 'v': printf( "osasnmpd: version %s\n", RELEASE_STRING); printf( "%s\n" , COPYRIGHT ); exit(0); case 'l': if (strlen(optarg) > PATH_MAX) { fprintf( stderr, "osasnmpd: logfile "\ "path too long (limit %d "\ "chars)\n", PATH_MAX); exit(1); } strncpy(logfile, optarg, PATH_MAX); break; case 'A': dont_zero_log = 1; break; case 'L': std_err_log=1; break; case 'f': dont_fork = 1; break; case 'P': if (strlen(optarg) > PATH_MAX) { fprintf( stderr, "osasnmpd: logfile "\ "path too long (limit %d "\ "chars)\n", PATH_MAX); exit(1); } strncpy(pid_file,optarg,PATH_MAX); pid_file_set = 1; break; case 'x': netsnmp_ds_set_string(NETSNMP_DS_APPLICATION_ID, NETSNMP_DS_AGENT_X_SOCKET, optarg); break; default: fprintf(stderr, "Try 'osasnmpd --help' for more" " information.\n"); exit(1); } /* end switch */ } /* end-while */ while (optind < argc) { fprintf(stderr, "osasnmpd: unrecognized argument '%s'\n", argv[optind++]); fprintf(stderr, "Try 'osasnmpd --help' for more" " information.\n"); exit(1); } /* detach from shell (default) */ if (!dont_fork && fork() != 0) exit(0); /* create a pidfile if requested */ if (pid_file_set) { if ((PID = fopen(pid_file, "w")) == NULL) { snmp_log_perror("fopen"); fprintf(stderr, "osasnmpd: cannot create PIDFILE\n"); exit(1); } else { fprintf(PID, "%d\n", (int) getpid() ); fclose(PID); } } /* enable logging to stderr or logfile */ if ( !std_err_log ) { snmp_disable_stderrlog(); snmp_enable_filelog( logfile, dont_zero_log ); } else { snmp_enable_stderrlog(); } snmp_log(LOG_INFO, "IBM OSA-E NET-SNMP 5.1.x subagent version %s\n", RELEASE_STRING ); /* make us a agentx client. */ netsnmp_ds_set_boolean(NETSNMP_DS_APPLICATION_ID, NETSNMP_DS_AGENT_ROLE, 1); /* initialize the agent library */ if ( init_agent("osasnmpd") != 0 ) { fprintf(stderr, "osasnmpd: init_agent() failed\n" "osasnmpd: check subagent logfile for detailed " "information\n" ); exit(1); } /* initialize OSA Express MIB module here */ init_ibmOSAMib(); /* osasnmpd will be used to read osasnmpd.conf files. */ init_snmp("osasnmpd"); act.sa_flags = 0; sigemptyset( &act.sa_mask ); /* handle termination requests (kill -TERM or kill -INT) */ act.sa_handler = stop_server; if( sigaction( SIGTERM, &act, NULL ) < 0 ){ fprintf(stderr, "sigaction( SIGTERM, ... ) failed - " "reason %s\n", strerror( errno ) ); exit( 1 ); } act.sa_handler = stop_server; if( sigaction( SIGINT, &act, NULL ) < 0 ){ fprintf(stderr, "sigaction( SIGINT, ... ) failed - reason %s\n", strerror( errno ) ); exit( 1 ); } /* handle iterface count change requests ( kill -SIGUSR1 ) */ act.sa_handler = on_interfaces_changed; if( sigaction( SIGUSR1, &act, NULL ) ){ fprintf(stderr, "sigaction( SIGUSR1, ... ) failed - " "reason %s\n", strerror( errno ) ); exit( 1 ); } rc = uname(&buf); if (!rc) sscanf(buf.release, "%c.%c.%c-%s", &rel_a, &rel_b, &rel_c, suffix); if(KERNEL_VERSION(2,6,22) >= KERNEL_VERSION(rel_a, rel_b, rel_c)) sysfs_register(); signal(SIGALRM, update_mib_info); snmp_log(LOG_INFO, "Initialization of OSA-E subagent successful...\n"); /* subagent main loop, that calls * agent_check_and_process() in blocking mode * */ while(keep_running) { if( interfaces_changed > 0 ){ interfaces_changed = 0; alarm(0); /* cancel a potentially scheduled alarm */ update_mib_info(); /* reschedule another update_mib_info() since netsnmp does not update the interface counter immediately, but within the next 60 seconds */ alarm(70); } else agent_check_and_process(1); } snmp_log(LOG_INFO, "Received TERM or STOP signal...shutting down " "agent...\n"); /* unregister all Toplevel OIDs we support */ for(li_ptr = oid_list_head; li_ptr->next != NULL; li_ptr =li_ptr->next){ oid_to_str_conv((oid*)li_ptr->next->pObjid, li_ptr->next->length, oidstr); snmp_log(LOG_INFO, "unregister Toplevel OID .%s.....", oidstr ); res = unregister_mib(li_ptr->next->pObjid, li_ptr->next->length); if (res == MIB_UNREGISTERED_OK) snmp_log(LOG_INFO, "successful\n"); else snmp_log(LOG_INFO, "failed %d\n",res); } if(KERNEL_VERSION(2,6,22) >= KERNEL_VERSION(rel_a, rel_b, rel_c)) sysfs_unregister(); snmp_shutdown("osasnmpd"); return 0; } s390-tools-2.3.0/qetharp/000077500000000000000000000000001323410431100150045ustar00rootroot00000000000000s390-tools-2.3.0/qetharp/Makefile000066400000000000000000000004541323410431100164470ustar00rootroot00000000000000include ../common.mak all: qetharp qetharp: qetharp.o install: all $(INSTALL) -d -m 755 $(DESTDIR)$(MANDIR)/man8 $(DESTDIR)$(BINDIR) $(INSTALL) -m 755 qetharp $(DESTDIR)$(BINDIR) $(INSTALL) -m 644 qetharp.8 $(DESTDIR)$(MANDIR)/man8 clean: rm -f qetharp *.o *~ core .PHONY: all install clean s390-tools-2.3.0/qetharp/qeth26.h000066400000000000000000000041411323410431100162660ustar00rootroot00000000000000/* * qetharp - Read and flush the ARP cache on OSA Express network cards * * ioctl definitions for qeth driver * * Copyright IBM Corp. 2001, 2017 * * s390-tools is free software; you can redistribute it and/or modify * it under the terms of the MIT license. See LICENSE for details. */ #ifndef __ASM_S390_QETH_IOCTL_H__ #define __ASM_S390_QETH_IOCTL_H__ #include #define SIOC_QETH_ARP_SET_NO_ENTRIES (SIOCDEVPRIVATE) #define SIOC_QETH_ARP_QUERY_INFO (SIOCDEVPRIVATE + 1) #define SIOC_QETH_ARP_ADD_ENTRY (SIOCDEVPRIVATE + 2) #define SIOC_QETH_ARP_REMOVE_ENTRY (SIOCDEVPRIVATE + 3) #define SIOC_QETH_ARP_FLUSH_CACHE (SIOCDEVPRIVATE + 4) #define SIOC_QETH_ADP_SET_SNMP_CONTROL (SIOCDEVPRIVATE + 5) #define SIOC_QETH_GET_CARD_TYPE (SIOCDEVPRIVATE + 6) struct qeth_arp_cache_entry { __u8 macaddr[6]; __u8 reserved1[2]; __u8 ipaddr[16]; /* for both IPv4 and IPv6 */ __u8 reserved2[32]; } __attribute__ ((packed)); struct qeth_arp_qi_entry7 { __u8 media_specific[32]; __u8 macaddr_type; __u8 ipaddr_type; __u8 macaddr[6]; __u8 ipaddr[4]; } __attribute__((packed)); struct qeth_arp_qi_entry7_short { __u8 macaddr_type; __u8 ipaddr_type; __u8 macaddr[6]; __u8 ipaddr[4]; } __attribute__((packed)); struct qeth_arp_qi_entry5 { __u8 media_specific[32]; __u8 macaddr_type; __u8 ipaddr_type; __u8 ipaddr[4]; } __attribute__((packed)); struct qeth_arp_qi_entry5_short { __u8 macaddr_type; __u8 ipaddr_type; __u8 ipaddr[4]; } __attribute__((packed)); /* * can be set by user if no "media specific information" is wanted * -> saves a lot of space in user space buffer */ #define QETH_QARP_STRIP_ENTRIES 0x8000 #define QETH_QARP_REQUEST_MASK 0x00ff /* data sent to user space as result of query arp ioctl */ #define QETH_QARP_USER_DATA_SIZE 20000 #define QETH_QARP_MASK_OFFSET 4 #define QETH_QARP_ENTRIES_OFFSET 6 struct qeth_arp_query_user_data { union { __u32 data_len; /* set by user space program */ __u32 no_entries; /* set by kernel */ } u; __u16 mask_bits; char *entries; } __attribute__((packed)); #endif /* __ASM_S390_QETH_IOCTL_H__ */ s390-tools-2.3.0/qetharp/qetharp.8000066400000000000000000000075731323410431100165550ustar00rootroot00000000000000.\" Copyright 2017 IBM Corp. .\" s390-tools is free software; you can redistribute it and/or modify .\" it under the terms of the MIT license. See LICENSE for details. .\" .TH QETHARP 8 "Nov 2011" "s390-tools" .SH NAME qetharp \- querying and modifying ARP data. .SH SYNOPSIS .TP 8 .B qetharp .RB [ -hv] .br .RB [ -[c|n][6]q .IR interface ] .br .RB [ -p .IR interface ] .br .RB [ -a .IR interface .RB -i .IR IP_address .RB -m .IR MAC_address ] .br .RB [ -d .IR interface .RB -i .IR IP_address ] .SH DESCRIPTION \fBqetharp\fR queries ARP data, such as MAC and IP addresses, from an OSA hardware ARP cache or a HiperSockets ARP cache. For OSA hardware, qetharp can also modify the cache. The command applies only to devices in layer3 mode. It supports IPv6 for HiperSockets only. .SH OPTIONS .TP \fB-q\fR or \fB--query \fIinterface\fR shows the ARP information about the specified network interface. Depending on the device that the interface has been assigned to, this information is obtained from an OSA feature's ARP cache or a HiperSockets ARP cache. The default command output shows symbolic host names and only includes numerical address for host names that cannot be resolved. Use the \fB-n\fR option to show numerical addresses instead of host names. By default, qetharp omits IPv6 related information. Use the \fB-6\fR option to include IPv6 information for HiperSockets. .TP \fB-n\fR or \fB--numeric\fR shows numerical addresses instead of trying to resolve the addresses to the symbolic host names. This option can only be used with the \fB-q\fR option. .TP \fB-c\fR or \fB--compact\fR limits the output to numerical addresses only. This option can only be used with the \fB-q\fR option. .TP \fB-6\fR or \fB--ipv6\fR includes IPv6 information for HiperSockets. For real HiperSockets, shows the IPv6 addresses. For guest LAN HiperSockets, shows the IPv6 to MAC address mappings. This option can only be used with the \fB-q\fR option. .TP \fB-p\fR or \fB--purge \fIinterface\fR flushes the ARP cache of the OSA. The cache contains dynamic ARP entries, which the OSA adapter creates through ARP queries. After flushing the cache, the OSA adapter creates new dynamic entries. This option only works with OSA devices. .TP \fB-a\fR or \fB--add \fIinterface\fR adds a static ARP entry to the OSA adapter card. This option requires an IP address and a MAC address (\fB-i\fR and \fB-m\fR options). .TP \fB-d\fR or \fB--delete \fIinterface\fR deletes a static ARP entry from the OSA adapter card. This command requires an IP address (\fB-i\fR option). .TP \fB-i\fR or \fB--ip \fIIP_address\fR specifies an IP address to be added to or removed from the OSA adapter card. \fB-m\fR or \fB--mac \fIMAC_address\fR specifies a MAC address to be added to the OSA adapter card. .TP \fB-v\fR or \fB--version\fR shows version information. .TP \fB-h\fR or \fB--help\fR shows usage information for qetharp. .SH RETURN CODES qetharp writes the response to stdout and any error message to stderr. The command completes with one of the following return codes: .TP .BR "0" The qetharp command ran successfully. .TP .BR "1" An error occurred. .SH EXAMPLE .TP \fBqetharp -q eth0\fR shows all ARP entries of OSA. .TP \fBqetharp -nq eth0\fR shows all ARP entries of OSA without resolving host names. .TP \fBqetharp -6q hsi0\fR shows all ARP entries of the HiperSockets interface including IPv6 entries. .TP \fBqetharp -n6q hsi0\fR shows all ARP entries of the HiperSockets interface including IPv6 entries without resolving host names. .TP \fBqetharp -p eth0\fR flushes the OSA ARP cache for eth0. .TP \fBqetharp -a eth0 -i 1.2.3.4 -m aa:bb:cc:dd:ee:ff\fR adds a static ARP entry for the IP address 1.2.3.4 to the OSA ARP cache, using a MAC address of aa:bb:cc:dd:ee:ff .TP \fBqetharp -d eth0 -i 1.2.3.4\fR deletes the static ARP entry for the IP address 1.2.3.4 from the OSA ARP cache. .SH AUTHOR .nf This man-page was written by Frank Pavlic s390-tools-2.3.0/qetharp/qetharp.c000066400000000000000000000343121323410431100166170ustar00rootroot00000000000000/* * qetharp - Read and flush the ARP cache on OSA Express network cards * * Copyright IBM Corp. 2001, 2017 * * 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include "lib/zt_common.h" #include "qetharp.h" #ifndef QETH_QARP_WITH_IPV6 #define IP_TYPE() ipaddr_type #define QETH_QARP_WITH_IPV6 0x4000 #define QETH_QARP_MEDIASPECIFIC_BYTES 32 enum qeth_arp_ipaddrtype { QETHARP_IP_ADDR_V4 = 1, QETHARP_IP_ADDR_V6 = 2, }; struct qeth_arp_entrytype { __u8 mac; __u8 ip; }; struct qeth_arp_qi_entry5_ipv6 { __u8 media_specific[QETH_QARP_MEDIASPECIFIC_BYTES]; struct qeth_arp_entrytype type; __u8 ipaddr[16]; } __attribute__((packed)); struct qeth_arp_qi_entry5_short_ipv6 { struct qeth_arp_entrytype type; __u8 ipaddr[16]; } __attribute__((packed)); struct qeth_arp_qi_entry7_ipv6 { __u8 media_specific[QETH_QARP_MEDIASPECIFIC_BYTES]; struct qeth_arp_entrytype type; __u8 macaddr[6]; __u8 ipaddr[16]; } __attribute__((packed)); struct qeth_arp_qi_entry7_short_ipv6 { struct qeth_arp_entrytype type; __u8 macaddr[6]; __u8 ipaddr[16]; } __attribute__((packed)); #else #define IP_TYPE() type.ip #endif /***************************************************** * Function implementation * *****************************************************/ static inline void qeth_hex_dump(unsigned char *buf, int len) { int i; for (i = 0; i < len; i++) { if (i && !(i % 16)) printf("\n"); printf("%02x ", *(buf + i)); } printf("\n"); } static void show_header() { printf("%-40.40s%-20.20s%-10.10s%-16.16s\n", "Address","HWaddress","HWType","Iface"); } static int ip_to_str(char *tmpbuff, __u8 ipaddr_type, __u8 *ip) { int rc; if (ipaddr_type == IP_VERSION_4) { sprintf(tmpbuff,"%d.%d.%d.%d",ip[0],ip[1],ip[2],ip[3]); } else if (ipaddr_type == IP_VERSION_6) { sprintf(tmpbuff, "%02x%02x:%02x%02x:%02x%02x:%02x%02x:%02x%02x" ":%02x%02x:%02x%02x:%02x%02x", ip[0],ip[1],ip[2],ip[3],ip[4], ip[5],ip[6],ip[7],ip[8],ip[9], ip[10],ip[11],ip[12],ip[13],ip[14], ip[15]); } else { rc = 1; goto out; } rc = 0; out: return rc; } static int lookup_hostname(const char * addr, char *hostname, size_t buflen) { struct addrinfo *addrinfo; int rc; if (getaddrinfo(addr, NULL, NULL, &addrinfo)) { rc = 1; } else { if (getnameinfo(addrinfo->ai_addr, addrinfo->ai_addrlen, hostname, buflen, NULL, 0, 0)) { rc = 1; } else { rc = 0; } freeaddrinfo(addrinfo); } return rc; } static void show_entry5(__u8 ipaddr_type, __u8 *ip, struct option_info *opin) { char tmpbuff[50]; if (ip_to_str(tmpbuff, ipaddr_type, ip)) { printf("unknown entry format\n"); goto out; } if (opin->compact_output == OPTION_INFO_COMPACT_OUTPUT) { printf("%s\n", tmpbuff); } else { char *name; char fqhn[NI_MAXHOST]; if (opin->host_resolution) { name = tmpbuff; } else { if (lookup_hostname(tmpbuff, fqhn, sizeof(fqhn))) { name = tmpbuff; } else { name = fqhn; } } printf("%-40.40s%-20.20s%-10.10s%-16.16s\n", name, "", "hiper", opin->dev_name); } out: return; } static int get_arp_from_hipersockets(struct qeth_arp_query_user_data *udata, struct option_info *opin) { struct qeth_arp_qi_entry5 *entry; struct qeth_arp_qi_entry5_short *entry_s; __u32 bytes_done; int i; bytes_done = 6; if (udata->mask_bits & QETH_QARP_STRIP_ENTRIES) { for (i = 0; i < (int)udata->u.no_entries; i++) { entry_s = (struct qeth_arp_qi_entry5_short *) (((char *)udata) + bytes_done); show_entry5(entry_s->IP_TYPE(), entry_s->ipaddr,opin); bytes_done += entry_s->IP_TYPE() == IP_VERSION_4 ? sizeof(struct qeth_arp_qi_entry5_short) : sizeof(struct qeth_arp_qi_entry5_short_ipv6); } } else { for (i = 0; i < (int)udata->u.no_entries; i++) { entry = (struct qeth_arp_qi_entry5 *) (((char *)udata) + 6 + i * sizeof(*entry)); show_entry5(entry->IP_TYPE(), entry->ipaddr, opin); } } return 0; } static void show_entry7(__u8 ipaddr_type, __u8 *ip, __u8 *mac, unsigned short flags, struct option_info *opin) { char tmpbuff[50]; if (ip_to_str(tmpbuff, ipaddr_type, ip)) { printf("unknown entry format\n"); goto out; } if (opin->compact_output == OPTION_INFO_COMPACT_OUTPUT) { printf("%s\n", tmpbuff); } else { char *name; char macstrbuf[20]; char fqhn[NI_MAXHOST]; if (opin->host_resolution) { name = tmpbuff; } else { if (lookup_hostname(tmpbuff, fqhn, sizeof(fqhn))) { name = tmpbuff; } else { name = fqhn; } } sprintf(macstrbuf,"%02x:%02x:%02x:%02x:%02x:%02x", mac[0],mac[1],mac[2],mac[3],mac[4],mac[5]); printf("%-40.40s%-20.20s%-10.10s%-16.16s\n", name, macstrbuf, (flags==OSACARD_FLAGS)? "ether": (flags==OSA_TR_FLAGS)? "tr":"n/a", opin->dev_name); } out: return; } static int get_arp_from_osacard(struct qeth_arp_query_user_data *udata, unsigned short flags, struct option_info *opin) { struct qeth_arp_qi_entry7 *entry; struct qeth_arp_qi_entry7_short *entry_s; size_t bytes_done = 0; int i; if (udata->mask_bits & QETH_QARP_STRIP_ENTRIES) { for (i = 0; i < (int)udata->u.no_entries; i++){ entry_s = (struct qeth_arp_qi_entry7_short *) (((char *)udata) + 6 + bytes_done); show_entry7(entry_s->IP_TYPE(), entry_s->ipaddr, entry_s->macaddr, flags, opin); bytes_done += entry_s->IP_TYPE() == IP_VERSION_4 ? sizeof(struct qeth_arp_qi_entry7_short) : sizeof(struct qeth_arp_qi_entry7_short_ipv6); } } else { for (i = 0; i < (int)udata->u.no_entries; i++){ entry = (struct qeth_arp_qi_entry7 *) (((char *)udata) + 6 + bytes_done); show_entry7(entry->IP_TYPE(), entry->ipaddr, entry->macaddr, flags, opin); bytes_done += entry->IP_TYPE() == IP_VERSION_4 ? sizeof(struct qeth_arp_qi_entry7_short) : sizeof(struct qeth_arp_qi_entry7_short_ipv6); } } return 0; } static int qetharp_purge(struct option_info *opin) { int sd; struct ifreq ifr; if (!opin->dev_name) { printf("\nError: no interface specified!\n"); return 1; } if ((sd = socket(AF_INET, SOCK_STREAM, 0)) < 0) { perror("Socket failed: %m\n"); return 1; } strcpy(ifr.ifr_name, opin->dev_name); if (ioctl(sd, SIOC_QETH_ARP_FLUSH_CACHE, &ifr) < 0) { close(sd); perror("\nUnsuccessful"); return 1; } return 0; } static int qetharp_add(struct option_info *opin) { int sd; struct ifreq ifr; struct qeth_arp_cache_entry arp_entry; unsigned int i1,i2,i3,i4,i5,i6,r; memset(&arp_entry, 0, sizeof(struct qeth_arp_cache_entry)); if (!opin->dev_name) { printf("\nError: no interface specified!\n"); return 1; } if (!opin->ip_addr) { printf("\nError: no ip address specified!\n"); return 1; } r=sscanf(opin->ip_addr,"%u.%u.%u.%u",&i1,&i2,&i3,&i4); if ( (r!=4) || (i1>255) || (i2>255) || (i3>255) || (i4>255) ) { printf("\nError: invalid ip address specified!\n"); return 1; } arp_entry.ipaddr[0]=i1; arp_entry.ipaddr[1]=i2; arp_entry.ipaddr[2]=i3; arp_entry.ipaddr[3]=i4; if (!opin->mac_addr) { printf("\nError: no MAC address specified!\n"); return 1; } r=sscanf(opin->mac_addr,"%x:%x:%x:%x:%x:%x",&i1,&i2,&i3,&i4,&i5,&i6); if ( (r!=6) || (i1>255) || (i2>255) || (i3>255) || (i4>255) || (i5>255) || (i6>255) ) { printf("\nError: invalid MAC address specified!\n"); return 1; } arp_entry.macaddr[0]=i1; arp_entry.macaddr[1]=i2; arp_entry.macaddr[2]=i3; arp_entry.macaddr[3]=i4; arp_entry.macaddr[4]=i5; arp_entry.macaddr[5]=i6; if ((sd = socket(AF_INET, SOCK_STREAM, 0)) < 0) { perror("Socket failed: %m\n"); return 1; } strcpy(ifr.ifr_name, opin->dev_name); ifr.ifr_ifru.ifru_data = (void*)&arp_entry; if (ioctl(sd, SIOC_QETH_ARP_ADD_ENTRY, &ifr) < 0) { close(sd); perror("\nUnsuccessful"); return 1; } return 0; } static int qetharp_delete(struct option_info *opin) { int sd; struct ifreq ifr; struct qeth_arp_cache_entry arp_entry; unsigned int i1,i2,i3,i4,r; memset(&arp_entry,0,sizeof(struct qeth_arp_cache_entry)); if (!opin->dev_name) { printf("\nError: no interface specified!\n"); return 1; } if (!opin->ip_addr) { printf("\nError: no ip address specified!\n"); return 1; } r=sscanf(opin->ip_addr,"%u.%u.%u.%u",&i1,&i2,&i3,&i4); if ( (r!=4) || (i1>255) || (i2>255) || (i3>255) || (i4>255) ) { printf("\nError: invalid ip address specified!\n"); return 1; } arp_entry.ipaddr[0]=i1; arp_entry.ipaddr[1]=i2; arp_entry.ipaddr[2]=i3; arp_entry.ipaddr[3]=i4; if ((sd = socket(AF_INET, SOCK_STREAM, 0)) < 0) { perror("Socket failed: %m\n"); return 1; } strcpy(ifr.ifr_name, opin->dev_name); ifr.ifr_ifru.ifru_data = (void*)&arp_entry; if (ioctl(sd, SIOC_QETH_ARP_REMOVE_ENTRY, &ifr) < 0) { close(sd); perror("\nUnsuccessful"); return 1; } return 0; } static int qetharp_query(struct option_info *opin) { int sd; struct ifreq ifr; struct qeth_arp_query_user_data *udata; int memsize,result; unsigned short mask_bits; if (!opin->dev_name) { printf("\nError: no interface specified!\n"); return 1; } if ((sd = socket(AF_INET, SOCK_STREAM, 0)) < 0) { perror("Socket failed: %m\n"); return 1; } strcpy(ifr.ifr_name, opin->dev_name); memsize = QETH_QARP_USER_DATA_SIZE; udata = malloc(QETH_QARP_USER_DATA_SIZE); memcpy(&udata->u.data_len, &memsize, sizeof(int)); udata->mask_bits = QETH_QARP_STRIP_ENTRIES; if (opin->ipv6) { udata->mask_bits |= QETH_QARP_WITH_IPV6; } ifr.ifr_ifru.ifru_data = (char *) udata; if (ioctl(sd, SIOC_QETH_ARP_QUERY_INFO, &ifr) < 0) { close(sd); perror("\nUnsuccessful"); return 1; } if (opin->compact_output!=OPTION_INFO_COMPACT_OUTPUT) { show_header(); } if (!udata->u.no_entries) { /* rational: mask_bits are not defined in that case */ return 0; } mask_bits = udata->mask_bits & QETH_QARP_REQUEST_MASK; if (mask_bits == HIPERSOCKET_FLAGS) result = get_arp_from_hipersockets(udata, opin); else if (mask_bits == OSACARD_FLAGS) result = get_arp_from_osacard(udata, mask_bits, opin); else if (mask_bits == OSA_TR_FLAGS) result = get_arp_from_osacard(udata, mask_bits, opin); else { perror("\nReceived entries with invalid format"); return 1; } free(udata); return result; } static void qetharp_usage(void) { printf("qetharp [-[nc6]q interface]|[-p interface]|\n" \ "\t\t[-a interface -i ip-addr -m MAC-addr]|\n" \ "\t\t[-d interface -i ip-addr] [-h] [-v ]\n\n"); printf("where:\n" \ "\tq: prints ARP entries found on the card\n" \ "\tn: in conjunction with the -q option it shows\n" \ "\t\tnumerical addresses instead of trying to\n" \ "\t\tresolve IP addresses to host names.\n" \ "\tc: in conjunction with the -q option it shows\n" \ "\t\tonly numerical addresses without any\n" \ "\t\tother information.\n" \ "\t6: in conjunction with the -q option it shows\n" \ "\t\tIPv6 related entries, if applicable\n" \ "\tp: flushes the ARP table of the card\n" \ "\ta: add static ARP entry\n" \ "\td: delete static ARP entry\n" \ "\tv: prints version information\n" "\th: prints this usage information\n"); } static int qetharp_parse_info(struct option_info *opin) { if (opin->dev_name && (strlen(opin->dev_name) >= IFNAMSIZ)) { printf("\nError: interface name too long\n"); return 1; } if ((opin->purge_flag+opin->query_flag+ opin->add_flag+opin->delete_flag)==0) { qetharp_usage(); return 1; } if ((opin->purge_flag+opin->query_flag+ opin->add_flag+opin->delete_flag)!=1) { printf("\nUse only one of the options '-a', " \ "'-d', '-p' and 'q' per call.\n"); return 1; } if (opin->purge_flag && (opin->query_flag || opin->host_resolution)) { printf("\nError in using '-p' option:\n" \ "\tYou can not use '-p' option in conjunction with " \ "'-q' or '-n'.\n"); return 1; } if (opin->purge_flag) { return qetharp_purge(opin); } if ((opin->host_resolution) && !(opin->query_flag)) { printf("\nError in using '-n' option:\n" \ "\t'-q' option missing!\n"); return 1; } if ((opin->ipv6) && !(opin->query_flag)) { printf("\nError in using '-6' option:\n" \ "\t'-q' option missing!\n"); return 1; } if (opin->query_flag) { return qetharp_query(opin); } if (opin->add_flag) { if ((!opin->ip_flag)||(!opin->mac_flag)) { printf("\nError in using '-a' option:\n" \ "\t'-i' or '-m' option missing!\n"); return 1; } return qetharp_add(opin); } if (opin->delete_flag) { if (!opin->ip_flag) { printf("\nError in using '-d' option:\n" \ "\t'-i' option missing!\n"); return 1; } return qetharp_delete(opin); } return 0; } int main(int argc, char **argv) { int index,c,result; struct option_info info; result=0; memset(&info, 0, sizeof(info)); c = getopt_long(argc, argv, QETHARP_GETOPT_STRING, qetharp_options, &index); if (c == -1 ) { qetharp_usage(); exit(0); } while (c != -1) { switch (c) { case 'h': qetharp_usage(); exit(0); case 'v': printf("qetharp: version %s\n", RELEASE_STRING); printf( "%s\n",COPYRIGHT ); exit(0); case 'q': info.dev_name = optarg; info.query_flag = OPTION_INFO_QUERY; break; case 'n': info.host_resolution = OPTION_INFO_NO_RESOLUTION; break; case '6': info.ipv6 = OPTION_INFO_IPV6; break; case 'p': info.dev_name = optarg; info.purge_flag = OPTION_INFO_PURGE; break; case 'c': info.compact_output = OPTION_INFO_COMPACT_OUTPUT; break; case 'a': info.dev_name = optarg; info.add_flag = OPTION_INFO_ADD; break; case 'd': info.dev_name = optarg; info.delete_flag = OPTION_INFO_DELETE; break; case 'i': info.ip_addr = optarg; info.ip_flag = OPTION_INFO_IP; break; case 'm': info.mac_addr = optarg; info.mac_flag = OPTION_INFO_MAC; break; default: fprintf(stderr, "Try 'qetharp --help' for more" " information.\n"); exit(1); } c = getopt_long(argc, argv,QETHARP_GETOPT_STRING, qetharp_options,&index); } result = qetharp_parse_info(&info); return result; } s390-tools-2.3.0/qetharp/qetharp.h000066400000000000000000000062331323410431100166250ustar00rootroot00000000000000/* * qetharp - Read and flush the ARP cache on OSA Express network cards * * Copyright IBM Corp. 2001, 2017 * * s390-tools is free software; you can redistribute it and/or modify * it under the terms of the MIT license. See LICENSE for details. */ #ifndef __QETHARP_H__ #define __QETHARP_H__ #include #include #include "qeth26.h" #define ifr_name ifr_ifrn.ifrn_name /* interface name */ #define ifr_hwaddr ifr_ifru.ifru_hwaddr /* MAC address */ #define ifr_addr ifr_ifru.ifru_addr /* address */ #define ifr_dstaddr ifr_ifru.ifru_dstaddr /* other end of p-p lnk */ #define ifr_broadaddr ifr_ifru.ifru_broadaddr /* broadcast address */ #define ifr_netmask ifr_ifru.ifru_netmask /* interface net mask */ #define ifr_flags ifr_ifru.ifru_flags /* flags */ #define ifr_metric ifr_ifru.ifru_ivalue /* metric */ #define ifr_mtu ifr_ifru.ifru_mtu /* mtu */ #define ifr_map ifr_ifru.ifru_map /* device map */ #define ifr_slave ifr_ifru.ifru_slave /* slave device */ #define ifr_data ifr_ifru.ifru_data /* for use by interface */ #define ifr_ifindex ifr_ifru.ifru_ivalue /* interface index */ #define ifr_bandwidth ifr_ifru.ifru_ivalue /* link bandwidth */ #define ifr_qlen ifr_ifru.ifru_ivalue /* Queue length */ #define ifr_newname ifr_ifru.ifru_newname /* New name */ /***************************************************** * Declarations for OSA Relevant Things * *****************************************************/ #define DATA_SIZE 20000 #define HIPERSOCKET_FLAGS 5 #define OSACARD_FLAGS 7 #define OSA_TR_FLAGS 0xf #define MAC_LENGTH 6 #define IPV4_LENGTH 4 #define IPV6_LENGTH 16 #define IP_VERSION_4 1 #define IP_VERSION_6 2 /***************************************************** * Declarations for parsing options * *****************************************************/ #define QETHARP_GETOPT_STRING "p:q:a:d:i:m:n6chv" #define OPTION_INFO_QUERY 1 #define OPTION_INFO_PURGE 1 #define OPTION_INFO_NO_RESOLUTION 1 #define OPTION_INFO_COMPACT_OUTPUT 1 #define OPTION_INFO_ADD 1 #define OPTION_INFO_DELETE 1 #define OPTION_INFO_IP 1 #define OPTION_INFO_MAC 1 #define OPTION_INFO_IPV6 1 /***************************************************** * Declarations for version string * *****************************************************/ #define COPYRIGHT "Copyright IBM Corp. 2003, 2017" static struct option qetharp_options[]= { { "query", 1, 0, 'q'}, { "purge", 1, 0, 'p'}, { "ipv6", 0, 0, '6'}, { "numeric", 0, 0, 'n'}, { "compact", 0, 0, 'c'}, { "add", 1, 0, 'a'}, { "delete", 1, 0, 'd'}, { "ip", 1, 0, 'i'}, { "mac", 1, 0, 'm'}, { "help", 0, 0, 'h'}, { "version", 0, 0, 'v'}, {0,0,0,0} }; struct option_info { int purge_flag; int query_flag; int host_resolution; int compact_output; int ipv6; int add_flag; int delete_flag; int ip_flag; int mac_flag; char *dev_name; char *ip_addr; char *mac_addr; }; #endif /* __QETHARP_H__ */ s390-tools-2.3.0/qethconf/000077500000000000000000000000001323410431100151475ustar00rootroot00000000000000s390-tools-2.3.0/qethconf/Makefile000066400000000000000000000006661323410431100166170ustar00rootroot00000000000000include ../common.mak all: install: qethconf $(SED) -e 's/%S390_TOOLS_VERSION%/$(S390_TOOLS_RELEASE)/' \ < qethconf >$(DESTDIR)$(BINDIR)/qethconf; \ chown $(OWNER).$(GROUP) $(DESTDIR)$(BINDIR)/qethconf; \ chmod 755 $(DESTDIR)$(BINDIR)/qethconf; \ $(INSTALL) -d -m 755 $(DESTDIR)$(BINDIR) $(DESTDIR)$(MANDIR)/man8 $(INSTALL) -g $(GROUP) -o $(OWNER) -m 644 qethconf.8 \ $(DESTDIR)$(MANDIR)/man8 clean: .PHONY: all install clean s390-tools-2.3.0/qethconf/qethconf000077500000000000000000000513651323410431100167160ustar00rootroot00000000000000#!/bin/bash # # qethconf - Tool to configure IPA, VIPA, and Proxy ARP for OSA-Express cards # # This shell script simplifies the usage of the OSA-Express functions # # - IP address takeover (IPA) # - Proxy ARP # - Virtual IP address (VIPA) # # IP addresses in IPv4 or IPv6 format must be no longer specified in # hexadecimal format. The script compounds the commands for the functions # according to the given parameters and sends it to the appropriate # OSA-Express device driver /proc file. # # Copyright 2017 IBM Corp. # # s390-tools is free software; you can redistribute it and/or modify # it under the terms of the MIT license. See LICENSE for details. # script_name=${0##*/} # name of this script vipa_ext="_vipa" # command extension for VIPA parp_ext="_rxip" # command extension for Proxy ARP proc_file="/proc/qeth_ipa_takeover" # kernel 2.4 qeth proc file for these features sys_file="/sys/devices/qeth" # kernel 2.6 sys fs file for these features sys_ifnames="/sys/class/net" # include all available if_names echo_cmd="" # echo command to be build # # exit script on error and print usage hint # function __exit_on_error { echo "Try '$script_name --help' for more information." exit 1 } # # error message if layer2 is enabled # function __layer2_enabled { # # check interface name if specified # if [ -z "$1" ]; then echo $script_name": interface name required for function $cmd_type" __exit_on_error elif [ ! -e $sys_ifnames/$1 ]; then echo $script_name": interface does not exist" __exit_on_error fi # # check if layer 2 is enables # if [ -e /sys/class/net/$1/device/layer2 ]; then if [ "`cat /sys/class/net/$1/device/layer2`" != 0 ]; then if [ "$2" != 'list' ]; then echo "$script_name: OSA layer 2 enabled for device $1 !" echo " IPA, VIPA, PARP not supported." exit 1 else return 1 fi fi fi } # # function for printing the usage message # function __usage { printf '\n%s %s %s\n\n' "Usage:" $script_name "TYPE CMD [IPADDR] [INTERFACE]" printf ' %s\n' "Description:" printf ' %s\n' "Shell script to configure IPA, VIPA and Proxy ARP for OSA-Express" printf ' %s\n\n' "cards in layer 3 mode." printf ' %s\n\n' "Parameter:" printf ' %s\t\t%s\n' "TYPE" "" printf '\t\t%s\n' "choose one of these keywords for feature" printf '\t\t%s\n' "ipa - IP address takeover" printf '\t\t%s\n' "vipa - Virtual IP address" printf '\t\t%s\n' "parp|rxip - Proxy ARP" printf '\t\t%s\n' "list_all - list all available entries" printf '\t\t%s\n' "list_msg - list messages and explanation" printf '\n %s\t\t%s\n' "CMD" "" printf '\t\t%s\n' "where" printf '\t\t%s\n' "add - add an IP address or address range" printf '\t\t%s\n' "del - delete an IP address or address range" printf '\t\t%s\n' "inv4 - inverts the selection of address ranges for IPv4" printf '\t\t%s\n' " (only IPA)" printf '\t\t%s\n' "inv6 - inverts the selection of address ranges for IPv6" printf '\t\t%s\n' " (only IPA)" printf '\t\t%s\n' "list - list defined entries per feature" printf '\n %s\t%s\n' "IPADDR" "[-x][/]" printf '\t\t%s\n' "required for commands add and del" printf '\t\t%s\n' "addr - IP address in IPv4 or IPv6 format" printf '\t\t%s\n' " e.g. 192.168.10.38 or FE80::800:5A12:3459" printf '\t\t%s\n' " use option -x for hexadecimal format" printf '\t\t%s\n' " e.g. c0a80a26" printf '\t\t%s\n' "mask_bits - number of bits which are set in the" printf '\t\t%s\n' " network mask (required for IPA)" printf '\n %s\t%s\n' "INTERFACE" "interface name to which the address or address range" printf '\t\t%s\n' "is bound, e.g eth0" exit 0 } function PrintVersion { echo "$script_name: version %S390_TOOLS_VERSION%" echo "Copyright IBM Corp. 2003, 2017" } # # prints a row from /proc/qeth_ipa_takeover # function __print_line { local ip_num= if [ -n "$(echo $raw_cmd | grep '4')" ]; then # IPv4 # convert hex IPv4 address to human format for i in 0 2 4 6; do # convert hex digits to uppercase (bc needs this) # use bc for hex to dec conversion ip_num=`echo ${ipinfo:i:2} | tr '[a-f]' '[A-F]'` ip_num=`echo "ibase=16; $ip_num" | bc` fmt_line="$fmt_line$ip_num" if [ "$i" -ne 6 ]; then fmt_line="$fmt_line." fi done else # IPv6 # convert IPv6 address to human readable format for i in 0 4 8 12 16 20 24 28; do ip_num=${ipinfo:i:4} fmt_line="$fmt_line$(echo $ip_num | sed -e 's/0000/0/')" if [ "$i" -ne 28 ]; then fmt_line="$fmt_line:" fi done fi # add mask bits and interface, if existent if [ -n "$(echo $ipinfo| grep '/')" ]; then fmt_line="$fmt_line/$(echo ${ipinfo##*/} | sed -e 's/:/ /')" elif [ -n "$(echo $ipinfo| grep ':')" ]; then fmt_line="$fmt_line ${ipinfo##*:}" fi # finally echo line to stdout echo $fmt_line } # # list defined entries per IPA/VIPA/PARP function # function __list_entries { local cmd_type=$1 local fmt_line= local device_list= local number_of_devices= declare -i count=0 # # list all available entries for all devices # if [ "$1" = list_all ];then cmd_type="ipa vipa rxip" fi for j in ${cmd_type} do device_list="`cat $sys_file/*.*.*/if_name`" for i in ${device_list} do __layer2_enabled "$i" "list" if [ "$?" -ne 0 ]; then continue fi case "$j" in ipa ) cmd_type_temp=ipa_takeover;; parp ) cmd_type_temp=rxip j=rxip;; * ) cmd_type_temp=$j;; esac fmt_line=/sys/class/net/$i/device/$cmd_type_temp/ for k in 4 6 do # # here we get the data from the sysfs # if [ -f /sys/class/net/$i/device/$cmd_type_temp/add$k ]; then cat_output="`cat /sys/class/net/$i/device/$cmd_type_temp/add$k`" for l in ${cat_output} do if [ -n "$cat_output" ]; then if [ "$j" = rxip ]; then j=parp fi echo "$j add $l $i" count=count+1 fi done fi done done done if [ "$count" -eq 0 ]; then echo $script_name": currently no $cmd_type entries defined" fi exit 0 } # # list messages and explanation # function __list_msgs { local cmd_type=$1 printf '\n\t\t%s\n' "Operation not permitted." printf '\t\t%s\n' "OSA layer2 mode is enabled for the device. IPA, VIPA," printf '\t\t%s\n' "and PARP are not supported with layer 2." printf '\n\t\t%s\n' "Out of memory." printf '\t\t%s\n' "There is not enough free memory to allocate an entry in sysfs." printf '\n\t\t%s\n' "File exists." printf '\t\t%s\n' "The entry to be added (e.g. IP address) does already exist" printf '\n\t\t%s\n' "Invalid argument." printf '\t\t%s\n' "At least one of the following specifications was not valid:" printf '\t\t%s\n' "- The IP address format" printf '\t\t%s\n' "- The mask bits for the IP address" printf '\t\t%s\n' "- The value in the field for IP address inversion" exit 0 } # # IP address conversion - converts IP address to hexadecimal format # function __ip_conv { declare -i count=0 declare -i zeropad=8 ip_temp= ip_shortened="false" strt_end_col="false" ipv4_rule='^[[:digit:]]\{1,3\}\.[[:digit:]]\{1,3\}\.[[:digit:]]\{1,3\}\.[[:digit:]]\{1,3\}$' ipv6_rule='^[[:xdigit:]:.]\+$' # if IP address is given as hex input; option -x # convert it to decimal if [ -n "$(echo $ip_addr | grep '\-x')" ]; then # # hex input given # ip_addr=${ip_addr##-x} if [ -n "$(echo $ip_addr | grep '^[[:xdigit:]]\{8\}$')" ]; then ip_ver=4 elif [ -n "$(echo $ip_addr | grep '^[[:xdigit:]]\{32\}$')" ]; then ip_ver=6 else echo $script_name": bad IP address" __exit_on_error fi ip_hex=$ip_addr # # convert hex given IPv4 address to decimal for kernel 2.6 # ip_addr_length=${#ip_addr} if [ $ip_addr_length = 8 ]; then ip_hex="" while [ $ip_addr_length -gt 0 ]; do ip_addr_length=$(($ip_addr_length-2)) ip_temp='0x'`expr substr $ip_addr 1 2` ip_addr=`expr substr $ip_addr 3 $ip_addr_length` # # conversion from hex to decimal # ip_hex_temp="$(printf '%02d' $ip_temp)" if [ `expr substr $ip_hex_temp 1 1` = 0 ]; then ip_hex_temp=`expr substr $ip_hex_temp 2 1` fi if [ $ip_addr_length -ge 2 ]; then ip_hex_temp="$ip_hex_temp." fi ip_hex=$ip_hex$ip_hex_temp done else # # add ':' signs to hex given IPv6 address # ip_hex="" ip_addr_length=${#ip_addr} while [ $ip_addr_length -gt 0 ]; do ip_temp=`expr substr $ip_addr 1 4` if [ $ip_addr_length -gt 4 ]; then ip_temp="$ip_temp:" fi ip_addr_length=$(($ip_addr_length-4)) ip_addr=`expr substr $ip_addr 5 $ip_addr_length` ip_hex=$ip_hex$ip_temp done fi else # # IPv4 format # ip_addr_orig=$ip_addr if [ -n "$(echo $ip_addr | grep "$ipv4_rule")" ]; then ip_ver=4 until [ -z "$(echo $ip_addr | grep '\.')" ]; do ip_temp="${ip_addr%%.*}" ip_temp=$(echo $ip_temp | sed -e 's/0*//') if [ -z "$ip_temp" ]; then ip_hex=$ip_hex"00" else ip_hex="$ip_hex$(printf '%02x' $ip_temp)" fi ip_addr=${ip_addr#*.} done ip_temp=$(echo $ip_addr | sed -e 's/0*//') if [ -z "$ip_temp" ]; then ip_hex=$ip_hex"00" else ip_hex="$ip_hex$(printf '%02x' $ip_temp)" fi if [ "${#ip_hex}" -gt 8 ]; then echo $script_name": bad IPv4 address" __exit_on_error fi ip_hex=$ip_addr_orig # # IPv6 format # elif [ -n "$(echo $ip_addr | grep "$ipv6_rule")" ]; then # check for IPv4-compatible address or IPv4-mapped address if [ -n "$(echo $ip_addr | grep '\.')" ]; then ipv4_part=${ip_addr##*:} ipv6_part=`echo ${ip_addr%:*} | tr '[a-f]' '[A-F]'`":" if [ -z "$(echo $ipv4_part | grep "$ipv4_rule")" ]; then echo $script_name": bad IP address" __exit_on_error fi ip_temp="$(printf '%02x' ${ipv4_part%%.*})" ipv4_part=${ipv4_part#*.} ip_temp=$ip_temp"$(printf '%02x' ${ipv4_part%%.*})" ipv4_part=${ipv4_part#*.} ip_temp=$ip_temp":""$(printf '%02x' ${ipv4_part%%.*})" ipv4_part=${ipv4_part#*.} ip_temp=$ip_temp"$(printf '%02x' $ipv4_part)" if [ "${#ip_temp}" -gt 9 ]; then echo $script_name": bad IPv6 address" __exit_on_error fi ip_addr=$ipv6_part$ip_temp fi # count number of colons within IP address ip_temp=$ip_addr until [ -z "$(echo $ip_temp | grep ':')" ]; do ip_temp=${ip_temp#*:} count=count+1 done # test number of allowed colons if [ "$count" -gt 7 ] || [ "$count" -lt 2 ]; then echo $script_name": bad IP address" __exit_on_error fi # # have to increase count for zero padding for starting/ending colon # if [ -z "${ip_addr%::*}" ]; then zeropad=zeropad+1 strt_end_col="true" fi if [ -z "${ip_addr#*::}" ]; then zeropad=zeropad+1 strt_end_col="true" fi # # loop through IPv6 address and convert it to 16 byte hex input # ip_ver=6 ip_addr=$ip_addr":" # use colon as end marker here while [ -n "$(echo $ip_addr | grep ':')" ]; do ip_temp=${ip_addr%%:*} if [ -z "$ip_temp" ]; then # found IPv6 double colon shortcut - add missing 0s if [ "$ip_shortened" = false ]; then zeropad=zeropad-$count while [ $zeropad -ne 0 ]; do ip_hex=$ip_hex"0000": zeropad=zeropad-1 done ip_shortened="true" if [ $strt_end_col = "true" ]; then ip_addr=${ip_addr#:} fi elif [ ! $ip_addr = ":" ]; then echo $script_name": IPv6 double colon shortcut can be used only once" __exit_on_error fi fi sign=":" case "${#ip_temp}" in 1 ) ip_hex=$ip_hex"000"$ip_temp$sign;; 2 ) ip_hex=$ip_hex"00"$ip_temp$sign ;; 3 ) ip_hex=$ip_hex"0"$ip_temp$sign ;; 4 ) ip_hex="$ip_hex$ip_temp$sign" ;; 0 ) ;; * ) echo $script_name": bad IPv6 address" __exit_on_error ;; esac ip_addr=${ip_addr#*:} done ip_hex=${ip_hex%*:} # # test if given IPv6 address was too short # if [ "$count" -lt 7 -a "$ip_shortened" = false ]; then echo $script_name": given IPv6 is too short" __exit_on_error fi else echo $script_name": IP address wrong or missing" __exit_on_error fi fi } # # builds echo command according to the given parameters # function __build_cmd { # input parameters local cmd_type=$1 local cmd_parm=$2 local ip_addr=$3 local interface=$4 local mask_bits= ip_ver= local ip_hex= # # allow also shortcut rxip for Proxy ARP # if [ $cmd_type = rxip ]; then cmd_type=parp fi # # check if mask bits are given for parameter IPADDR # if [ -z "$ip_addr" ]; then echo $script_name": IP address parameter missing" __exit_on_error elif [ -n "$(echo $ip_addr | grep '/')" ]; then if [ $cmd_type = ipa ]; then mask_bits=${ip_addr##*/} ip_addr=${ip_addr%%/*} if [ -z "$(echo $mask_bits | grep '^[[:digit:]]\{1,3\}$')" ]; then echo $script_name": invalid mask bits specified" __exit_on_error fi else echo $script_name": mask bits not allowed for $cmd_type" __exit_on_error fi elif [ $cmd_type = ipa ]; then echo $script_name": mask bits required for function $cmd_type" __exit_on_error fi __layer2_enabled $interface __ip_conv # # assemble command # echo_cmd="$echo_cmd$ip_ver $ip_hex" if [ -n "$mask_bits" ]; then if [ "$ip_ver" = 4 ]; then if [ "$mask_bits" -gt 32 ]; then echo $script_name": invalid mask bits specified" __exit_on_error fi else if [ "$mask_bits" -gt 128 ]; then echo $script_name": invalid mask bits specified" __exit_on_error fi fi echo_cmd="${echo_cmd}/$mask_bits" fi } #--------------------------------------- # -- main -- #--------------------------------------- # # parse options (currently none avail) # while [ -n "$(echo $1 | grep '-')" ]; do case "$1" in -v | --version ) PrintVersion exit 0 ;; * ) __usage ;; esac shift done # # check if target proc or sysfs file exists - # otherwise the OSA-E device driver is not loaded # if [ ! -e "$sys_file" ]; then echo $script_name": No QDIO device found" echo "Try 'man $script_name' for more information." exit 1 fi # # parse arguments TYPE and CMD # if [ "$#" -lt 1 -o "$#" -gt 4 ]; then echo $script_name": invalid number of parameters specified" __exit_on_error else case "$1" in ipa ) case "$2" in add | del ) echo_cmd="$2" __build_cmd "$@";; inv4 | inv6 ) if [ "$#" -gt 3 ]; then echo $script_name": too many parameters for $2 command" __exit_on_error else if [ -z "$3" ]; then echo $script_name": interface name required for function $cmd_type" __exit_on_error else __layer2_enabled $3 if [ $2 = inv4 ]; then echo_cmd="invert4 toggle" else echo_cmd="invert6 toggle" fi fi fi;; list ) if [ "$#" -gt 2 ]; then echo $script_name": too many parameters for $2 command" __exit_on_error else __list_entries "$@" fi;; * ) echo $script_name": invalid CMD parameter specified" __exit_on_error ;; esac ;; vipa ) case "$2" in add | del ) echo_cmd="$2" __build_cmd "$@";; list ) if [ "$#" -gt 2 ]; then echo $script_name": too many parameters for $2 command" __exit_on_error else __list_entries "$@" fi;; * ) echo $script_name": invalid CMD parameter specified" __exit_on_error ;; esac;; parp | rxip ) case "$2" in add | del ) echo_cmd="$2" __build_cmd "$@";; list ) if [ "$#" -gt 2 ]; then echo $script_name": too many parameters for $2 command" __exit_on_error else __list_entries "$@" fi;; * ) echo $script_name": invalid CMD parameter specified" __exit_on_error ;; esac;; list_all ) if [ "$#" -gt 1 ]; then echo $script_name": too many parameters for $2 command" __exit_on_error else __list_entries "$@" fi;; list_msg ) if [ "$#" -gt 1 ]; then echo $script_name": too many parameters for $2 command" __exit_on_error else __list_msgs "$@" fi;; * ) echo $script_name": invalid TYPE parameter specified" __exit_on_error ;; esac fi # # finally echo cmd to appropriate OSA-Express proc or sysfs file # and check if command was successful # if [ "$2" = inv4 ] || [ "$2" = inv6 ]; then sys_fs_dir="/sys/class/net/$3/device" else sys_fs_dir="/sys/class/net/$4/device" fi echo_cmd_temp=($echo_cmd) # # build sysfs path # case "$1" in ipa ) proc_file=$sys_fs_dir/ipa_takeover/${echo_cmd_temp[0]} echo_cmd=${echo_cmd_temp[1]} ;; vipa ) proc_file=$sys_fs_dir/vipa/${echo_cmd_temp[0]} echo_cmd=${echo_cmd_temp[1]} ;; parp | rxip ) proc_file=$sys_fs_dir/rxip/${echo_cmd_temp[0]} echo_cmd=${echo_cmd_temp[1]} ;; esac # # check if entry to delete exists # if [ "$2" = del ]; then case "$1" in ipa ) if ! grep -sqi "${echo_cmd_temp[1]}" $sys_fs_dir/ipa_takeover/add$ip_ver; then echo $script_name": no such $1 entry found." exit 0 fi;; vipa ) if ! grep -sqi "${echo_cmd_temp[1]}" $sys_fs_dir/$1/add$ip_ver; then echo $script_name": no such $1 entry found." exit 0 fi;; parp | rxip ) if ! grep -sqi "${echo_cmd_temp[1]}" $sys_fs_dir/rxip/add$ip_ver; then echo $script_name": no such $1 entry found." exit 0 fi;; esac fi # # does proc or sysfs entry exsist ? # if [ -f $proc_file ]; then echo $echo_cmd > $proc_file if [ "$?" -ne 0 ]; then echo $script_name": The qethconf command failed." echo "Enter 'qethconf list_msg' for details." exit 1 fi else echo $script_name": device does not exist or is not capable of $1." __exit_on_error fi if [ "$1" = rxip ]; then message_cmd=parp else message_cmd=$1 fi case "$2" in add ) echo "$script_name: Added $echo_cmd to sysfs entry $proc_file." echo "$script_name: For verification please use \"qethconf $message_cmd list\" " ;; del ) echo "$script_name: Deleted $echo_cmd from sysfs entry $proc_file." echo "$script_name: For verification please use \"qethconf $message_cmd list\" " ;; inv4 | inv6 ) if [ "`cat $proc_file`" != 0 ]; then echo "$script_name: Negating the following IP address takeover settings:" if [ "$2" = inv4 ]; then echo "`cat ${proc_file/invert4/add4}`" else echo "`cat ${proc_file/invert6/add6}`" fi else echo "$script_name: The following IP address takeover settings are valid again:" if [ "$2" = inv4 ]; then echo "`cat ${proc_file/invert4/add4}`" else echo "`cat ${proc_file/invert6/add6}`" fi fi;; esac s390-tools-2.3.0/qethconf/qethconf.8000066400000000000000000000046211323410431100170520ustar00rootroot00000000000000.\" Copyright 2017 IBM Corp. .\" s390-tools is free software; you can redistribute it and/or modify .\" it under the terms of the MIT license. See LICENSE for details. .\" .TH QETHCONF 8 "Apr 2006" "s390-tools" .SH "NAME" qethconf \- Configure QETH functions IPA, VIPA and Proxy ARP. .SH "SYNOPSIS" \fBqethconf\fR \fITYPE\fR \fICMD\fR [[\-x]\fIIPADDR\fR[/\fIMASKBITS\fR]] [\fIINTERFACE\fR] .SH "DESCRIPTION" \fBqethconf\fR simplifies usage of QETH IPA, VIPA and Proxy ARP functions for OSA-Express cards in layer 3 mode. .TP \fITYPE\fR .br Choose one of these keywords for function: .br ipa \- IP address takeover (IPA) .br vipa \- Virtual IP address (VIPA) .br parp|rxip \- Proxy ARP .br list_all \- List all available IPA, VIPA and Proxy ARP entries from the sysfs .br list_msg \- List messages and explanation of errors during command execution .TP \fICMD\fR .br where .br add \- Add an IP address or address range. .br del \- Delete an IP address or address range. .br inv4 \- Invert the selection of address ranges for IPv4 (only IPA). .br inv6 \- Invert the selection of address ranges for IPv6 (only IPA). .br list \- List TYPE specific entries from the sysfs .TP \fIIPADDR\fR Required for commands add and del. .br IP address in IPv4 or IPv6 format, .br e.g. 192.168.10.38 or FE80::1:800:23e7:f5db .br Use preceding option \-x for hexadecimal input format, e.g. \-xc0a80a26 .TP \fIMASKBITS\fR Number of bits set in the network mask. This allows to specify an address range, e.g. 192.168.10.0/24. This subparameter is only valid for function IPA. .TP \fIINTERFACE\fR Interface name to which the address or address range is bound, e.g. eth0. This parameter is required for add, del and the inv4 and inv6 command. .SH "RESTRICTIONS" .nf Needs at least bash version 2.0. .SH "EXAMPLES" .nf qethconf ipa add 192.168.10.0/24 eth0 qethconf ipa add \-xc0a80a00/24 eth1 qethconf ipa inv4 eth0 qethconf parp add 10.0.0.2 eth0 (or qethconf rxip add 10.0.0.2 eth0) qethconf vipa del 192.168.10.2 tr0 qethconf vipa add FE80::1:800:23e7:f5db eth1 qethconf list_all .SH "AUTHOR" .nf This man\-page was written by Thomas Weber. .SH "SEE ALSO" .PP See the appropriate chapters for QETH IPA, VIPA and Proxy ARP in the "Device Drivers, Features, and Commands" manual for Linux on System z. s390-tools-2.3.0/qethqoat/000077500000000000000000000000001323410431100151665ustar00rootroot00000000000000s390-tools-2.3.0/qethqoat/Makefile000066400000000000000000000005421323410431100166270ustar00rootroot00000000000000include ../common.mak all: qethqoat qethqoat: qethqoat.o install: all $(INSTALL) -d -m 755 $(DESTDIR)$(BINDIR) $(DESTDIR)$(MANDIR)/man8 $(INSTALL) -g $(GROUP) -o $(OWNER) -m 755 qethqoat $(DESTDIR)$(BINDIR) $(INSTALL) -g $(GROUP) -o $(OWNER) -m 644 qethqoat.8 $(DESTDIR)$(MANDIR)/man8 clean: rm -f *.o *~ qethqoat core .PHONY: all install clean s390-tools-2.3.0/qethqoat/qethqoat.8000066400000000000000000000025361323410431100171130ustar00rootroot00000000000000.\" Copyright 2017 IBM Corp. .\" s390-tools is free software; you can redistribute it and/or modify .\" it under the terms of the MIT license. See LICENSE for details. .\" .TH QETHQOAT 8 "April 2012" "s390-tools" .SH NAME qethqoat \- query the OSA address table .SH SYNOPSIS .B qethqoat .RB [ \-hv ] .br .B qethqoat .RB [ \-r "] [" \-s .IR scope ] .I interface .br .B qethqoat .RB \-f .I file .SH DESCRIPTION Use \fBqethqoat\fP to query the OSA address table and display physical and logical device information. .SH OPTIONS .TP .BR \-v ", " \-\-version Prints the version number of qethqoat and exits. .TP .BR \-h ", " \-\-help Displays the help information for the command. .TP .BR \-r ", " \-\-raw Writes raw data to stdout. .TP .BR \-f ", " \-\-file Reads input from file. .TP .BR \-s ", " \-\-scope Defines the scope of the query. Following scopes are supported. \fB0\fP Query the level of the OSA address table \fB1\fP Interface (default) .SH RETURN CODES qethqoat writes the response to stdout and any error message to stderr. The command completes with one of the following return codes: .TP .BR "0" The qethqoat command ran successfully. .TP .BR "1" An error occurred. .SH EXAMPLE .TP To display physical and logical device information for interface eth0 issue: \fBqethqoat eth0\fP .SH AUTHOR .nf Written by Frank Blaschka .fi s390-tools-2.3.0/qethqoat/qethqoat.c000066400000000000000000000374601323410431100171720ustar00rootroot00000000000000/* * qethqoat - Query the OSA address table and display physical and logical * device information * * Copyright IBM Corp. 2012, 2017 * * 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "lib/zt_common.h" #include "qethqoat.h" static iconv_t l_iconv_ebcdic_ascii; static void hex_dump(char *buf, int len) { int i; for (i = 0; i < len; i++) { if (i && !(i % 16)) printf("\n"); printf("%02x ", *(buf + i)); } printf("\n"); } static int mac_is_zero(__u8 *mac) { return !(mac[0] | mac[1] | mac[2] | mac[3] | mac[4] | mac[5]); } static void ebctoasc(char *inout, size_t len) { iconv(l_iconv_ebcdic_ascii, &inout, &len, &inout, &len); } static void print_version() { printf("qethqoat: Tool to query the OSA address table version %s\n", RELEASE_STRING); printf("Copyright IBM Corp. 2012, 2017\n"); } static void print_ip4(struct qeth_qoat_des_ip4 *ip4, int hdr) { struct in_addr ia; ia.s_addr = ip4->ip4_address; if (hdr) printf("\n%-39s %s\n%-39s %s\n", "IPv4 Address:", "IPA Flags:", "-------------", "----------"); printf("%-39s 0x%08x\n", inet_ntoa(ia), ip4->flags); } static void print_ip4mc(struct qeth_qoat_des_ip4mc *ip4mc, int hdr) { struct in_addr ia; ia.s_addr = ip4mc->ip4_mc_address; if (hdr) printf("\n%-39s %s\n%-39s %s\n", "IPv4 Multicast Address:", "MAC Address:", "-----------------------", "------------"); printf("%-39s %02x:%02x:%02x:%02x:%02x:%02x\n", inet_ntoa(ia), ip4mc->ip4_mc_mac[0], ip4mc->ip4_mc_mac[1], ip4mc->ip4_mc_mac[2], ip4mc->ip4_mc_mac[3], ip4mc->ip4_mc_mac[4], ip4mc->ip4_mc_mac[5]); } static void print_ip6(struct qeth_qoat_des_ip6 *ip6, int hdr) { char tmp[128]; struct in6_addr ia; memcpy(&ia.s6_addr, &ip6->ip6_address, 16); inet_ntop(AF_INET6, &ia, tmp, 128); if (hdr) printf("\n%-39s %s\n%-39s %s\n", "IPv6 Address:", "IPA Flags:", "-------------", "----------"); printf("%-39s 0x%08x\n", tmp, ip6->flags); } static void print_ip6mc(struct qeth_qoat_des_ip6mc *ip6mc, int hdr) { char tmp[128]; struct in6_addr ia; memcpy(&ia.s6_addr, &ip6mc->ip6_mc_address, 16); inet_ntop(AF_INET6, &ia, tmp, 128); if (hdr) printf("\n%-39s %s\n%-39s %s\n", "IPv6 Multicast Address:", "MAC Address:", "-----------------------", "------------"); printf("%-39s %02x:%02x:%02x:%02x:%02x:%02x\n", tmp, ip6mc->ip6_mc_mac[0], ip6mc->ip6_mc_mac[1], ip6mc->ip6_mc_mac[2], ip6mc->ip6_mc_mac[3], ip6mc->ip6_mc_mac[4], ip6mc->ip6_mc_mac[5]); } static void print_vmac(struct qeth_qoat_des_vmac *vmac, int hdr) { if (hdr) printf("\nvmac\n----\n"); printf("%02x:%02x:%02x:%02x:%02x:%02x\n", vmac->vmac[0], vmac->vmac[1], vmac->vmac[2], vmac->vmac[3], vmac->vmac[4], vmac->vmac[5]); } static void print_vlan(struct qeth_qoat_des_vlan *vlan, int hdr) { if (hdr) printf("\nvlan\n----\n"); printf("%d\n", vlan->vlanid); } static void print_gmac(struct qeth_qoat_des_gmac *gmac, int hdr) { if (hdr) printf("\ngmac\n----\n"); printf("%02x:%02x:%02x:%02x:%02x:%02x\n", gmac->gmac[0], gmac->gmac[1], gmac->gmac[2], gmac->gmac[3], gmac->gmac[4], gmac->gmac[5]); } static void print_aiq(struct qeth_qoat_des_aiq *aiq, int hdr) { if (hdr) printf("\naiq routing variables\n---------------------\n"); printf("0x%x %d %d\n", aiq->protocol, aiq->src_port, aiq->des_port); } static void print_physical(struct qeth_qoat_physical *phdr) { char *speed, *media, *jumbo, *osagen, *chpid_type; char tmp[128]; printf("PCHID: 0x%04x\n", phdr->pchid); printf("CHPID: 0x%02x\n", phdr->chpid); printf("Manufacturer MAC address: %02x:%02x:%02x:%02x:%02x:%02x\n", phdr->physical_mac[0], phdr->physical_mac[1], phdr->physical_mac[2], phdr->physical_mac[3], phdr->physical_mac[4], phdr->physical_mac[5]); printf("Configured MAC address: %02x:%02x:%02x:%02x:%02x:%02x\n", phdr->logical_mac[0], phdr->logical_mac[1], phdr->logical_mac[2], phdr->logical_mac[3], phdr->logical_mac[4], phdr->logical_mac[5]); printf("Data device sub-channel address: 0x%04x\n", phdr->data_sub_channel); printf("CULA: 0x%02x\n", phdr->cula); printf("Unit address: 0x%02x\n", phdr->unit_address); printf("Physical port number: %d\n", phdr->physical_port); printf("Number of output queues: %d\n", phdr->nr_out_queues); printf("Number of input queues: %d\n", phdr->nr_in_queues); printf("Number of active input queues: %d\n", phdr->nr_active_in_queues); switch (phdr->interface_flags_chpid_type) { case OAT_IFF_CHPID_TYPE_OSD: chpid_type = "OSD"; break; case OAT_IFF_CHPID_TYPE_OSX: chpid_type = "OSX"; break; case OAT_IFF_CHPID_TYPE_OSM: chpid_type = "OSM"; break; default: sprintf(tmp, "unknown (0x%x)", phdr->interface_flags_chpid_type); chpid_type = tmp; } printf("CHPID Type: %s\n", chpid_type); printf("Interface flags: 0x%08x\n", phdr->interface_flags); switch (phdr->osa_gen) { case OAT_OSA_GEN_OSAE3: osagen = "OSA-Express3"; break; case OAT_OSA_GEN_OSAE4S: osagen = "OSA-Express4S"; break; case OAT_OSA_GEN_OSAE5S: osagen = "OSA-Express5S"; break; case OAT_OSA_GEN_OSAE6S: osagen = "OSA-Express6S"; break; default: sprintf(tmp, "unknown (0x%x)", phdr->osa_gen); osagen = tmp; } printf("OSA Generation: %s\n", osagen); switch (phdr->port_speed) { case OAT_PORT_SPEED_10mbs_half: speed = "10 Mb/s / half duplex"; break; case OAT_PORT_SPEED_10mbs_full: speed = "10 Mb/s / full duplex"; break; case OAT_PORT_SPEED_100mbs_half: speed = "100 Mb/s / half duplex"; break; case OAT_PORT_SPEED_100mbs_full: speed = "100 Mb/s / full duplex"; break; case OAT_PORT_SPEED_1000mbs_half: speed = "1000 Mb/s / half duplex"; break; case OAT_PORT_SPEED_1000mbs_full: speed = "1000 Mb/s / full duplex"; break; case OAT_PORT_SPEED_NA: speed = "NA / NA"; break; case OAT_PORT_SPEED_10gbs_full: speed = "10 Gb/s / full duplex"; break; case OAT_PORT_SPEED_UNKNOWN: speed = "unknown / unknown"; break; default: sprintf(tmp, "(0x%x) / (0x%x)", phdr->port_speed, phdr->port_speed); speed = tmp; } printf("Port speed/mode: %s\n", speed); switch (phdr->port_media) { case OAT_PORT_MEDIA_COPPER: media = "copper"; break; case OAT_PORT_MEDIA_MULTI_MODE: media = "multi mode (SR/SX)"; break; case OAT_PORT_MEDIA_SINGLE_MODE: media = "single mode (LR/LX)"; break; default: sprintf(tmp, "unknown (0x%x)", phdr->port_media); media = tmp; } printf("Port media type: %s\n", media); if (phdr->port_media_att & OAT_PORT_MEDIA_ATT_JUMBO) jumbo = "yes"; else jumbo = "no"; printf("Jumbo frames: %s\n", jumbo); printf("Firmware: 0x%08x\n", phdr->firmware); printf("\n"); } static void print_logical(struct qeth_qoat_logical *lhdr) { char prouter[] = "primary"; char srouter[] = "secondary"; char nrouter[] = "no"; char *router; if (lhdr->ip4_primary_router) router = prouter; else if (lhdr->ip4_secondary_router) router = srouter; else router = nrouter; printf("IPv4 router: %s %s\n", router, lhdr->ip4_active_router ? "active" : ""); if (lhdr->ip6_primary_router) router = prouter; else if (lhdr->ip6_secondary_router) router = srouter; else router = nrouter; printf("IPv6 router: %s %s\n", router, lhdr->ip6_active_router ? "active" : ""); printf("IPv4 vmac router: %s\n", lhdr->ip4_vmac_router ? "yes" : "no"); printf("IPv6 vmac router: %s\n", lhdr->ip6_vmac_router ? "yes" : "no"); printf("Connection isolation: %s\n", lhdr->isolation_f ? "active" : "not active"); printf("Connection isolation VEPA: %s\n", lhdr->isolation_vepa ? "yes" : "no"); if (lhdr->ip4_global_vlan_active) printf("IPv4 global vlan id: %d\n", lhdr->ip4_global_vlanid); if (lhdr->ip4_vmac_active) printf("IPv4 l3 vmac: %02x:%02x:%02x:%02x:%02x:%02x" " %s generated\n", lhdr->ip4_vmac[0], lhdr->ip4_vmac[1], lhdr->ip4_vmac[2], lhdr->ip4_vmac[3], lhdr->ip4_vmac[4], lhdr->ip4_vmac[4], lhdr->ip4_vmac_source ? "OSA" : "Host"); if (lhdr->ip6_global_vlan_active) printf("IPv6 global vlan id: %d\n", lhdr->ip6_global_vlanid); if (lhdr->ip6_vmac_active) printf("IPv6 l3 vmac: %02x:%02x:%02x:%02x:%02x:%02x" " %s generated\n", lhdr->ip6_vmac[0], lhdr->ip6_vmac[1], lhdr->ip6_vmac[2], lhdr->ip6_vmac[3], lhdr->ip6_vmac[4], lhdr->ip6_vmac[4], lhdr->ip6_vmac_source ? "OSA" : "Host"); if (lhdr->port_name_f) { ebctoasc((char *)lhdr->port_name, 8); printf("Port name: %.8s\n", lhdr->port_name); } printf("IPv4 assists enabled: 0x%08x\n", lhdr->ip4_ass_enabled); printf("IPv6 assists enabled: 0x%08x\n", lhdr->ip6_ass_enabled); printf("IPv4 outbound checksum enabled: 0x%08x\n", lhdr->out_csum_enabled); printf("IPv6 outbound checksum enabled: 0x%08x\n", lhdr->out_csum_enabled6); printf("IPv4 inbound checksum enabled: 0x%08x\n", lhdr->in_csum_enabled); printf("IPv6 inbound checksum enabled: 0x%08x\n", lhdr->in_csum_enabled6); if (lhdr->l2_vlanid) printf("L2 vlan id: %d\n", lhdr->l2_vlanid); if (!mac_is_zero(lhdr->l2_vmac)) printf("L2 vmac: %02x:%02x:%02x:%02x:%02x:%02x\n", lhdr->l2_vmac[0], lhdr->l2_vmac[1], lhdr->l2_vmac[2], lhdr->l2_vmac[3], lhdr->l2_vmac[4], lhdr->l2_vmac[5]); } static void parse_descriptor(struct qeth_qoat_hdr *oat_hdr, struct qeth_print_hdr *phdr, char *buf, int *processed) { int i; char *ptr; *processed += oat_hdr->len; for (i = 0; i < oat_hdr->type.descriptor.reply_entry_count; i++) { ptr = buf + *processed; switch (oat_hdr->type.descriptor.des_type) { case OAT_DES_TYPE_IP4: print_ip4((struct qeth_qoat_des_ip4 *)ptr, phdr->ip4_h); phdr->ip4_h = 0; break; case OAT_DES_TYPE_IP4MC: print_ip4mc((struct qeth_qoat_des_ip4mc *)ptr, phdr->ip4mc_h); phdr->ip4mc_h = 0; break; case OAT_DES_TYPE_IP6: print_ip6((struct qeth_qoat_des_ip6 *)ptr, phdr->ip6_h); phdr->ip6_h = 0; break; case OAT_DES_TYPE_IP6MC: print_ip6mc((struct qeth_qoat_des_ip6mc *)ptr, phdr->ip6mc_h); phdr->ip6mc_h = 0; break; case OAT_DES_TYPE_VMAC: print_vmac((struct qeth_qoat_des_vmac *)ptr, phdr->vmac_h); phdr->vmac_h = 0; break; case OAT_DES_TYPE_VLAN: print_vlan((struct qeth_qoat_des_vlan *)ptr, phdr->vlan_h); phdr->vlan_h = 0; break; case OAT_DES_TYPE_GMAC: print_gmac((struct qeth_qoat_des_gmac *)ptr, phdr->gmac_h); phdr->gmac_h = 0; break; case OAT_DES_TYPE_AIQ: print_aiq((struct qeth_qoat_des_aiq *)ptr, phdr->aiq_h); phdr->aiq_h = 0; break; default: printf("Unknown descriptor (0x%x)\n", oat_hdr->type.descriptor.des_type); hex_dump(ptr, oat_hdr->type.descriptor.reply_entry_len); } *processed += oat_hdr->type.descriptor.reply_entry_len; } } static int print_IPA_error(int rc) { switch (rc) { case 0x0: break; case 0x4: fprintf(stderr, "Error: Command not supported\n"); break; case 0x8: fprintf(stderr, "Error: Invalid/unsupported sub_command/scope\n"); break; case 0x10: fprintf(stderr, "Error: No active data connection\n"); break; case 0x14: fprintf(stderr, "Error: OSA temporary resource shortage\n"); break; default: return 1; } return 0; } static void parse_data(char *buf, int len) { int buffer_processed; int frame_processed; struct qeth_qoat_ipa_reply *ipa_hdr; struct qeth_qoat_hdr *oat_hdr; struct qeth_print_hdr phdr = {1, 1, 1, 1, 1, 1, 1, 1}; buffer_processed = 0; do { frame_processed = 0; ipa_hdr = (struct qeth_qoat_ipa_reply *) (buf + buffer_processed); if (print_IPA_error(ipa_hdr->rc)) fprintf(stderr, "OSA reported error code 0x%x\n", ipa_hdr->rc); if (ipa_hdr->subcommand == 0) { printf("Supported Scope mask: 0x%08x\n", ipa_hdr->supported_scope); printf("Supported Descriptor hdr types: 0x%08x\n", ipa_hdr->supported_descriptor); } frame_processed += sizeof(struct qeth_qoat_ipa_reply); if (frame_processed >= ipa_hdr->len) break; do { oat_hdr = (struct qeth_qoat_hdr *) (buf + buffer_processed + frame_processed); switch (oat_hdr->hdr_type) { case OAT_HDR_TYPE_PHYSICAL: print_physical(&oat_hdr->type.physical); frame_processed += oat_hdr->len; break; case OAT_HDR_TYPE_LOGICAL: print_logical(&oat_hdr->type.logical); frame_processed += oat_hdr->len; break; case OAT_HDR_TYPE_DESCRIPTOR: parse_descriptor(oat_hdr, &phdr, buf + buffer_processed, &frame_processed); break; default: printf("Unknown oat hdr (0x%x)\n", oat_hdr->hdr_type); return; } } while (frame_processed < ipa_hdr->len); buffer_processed += ipa_hdr->len; } while (buffer_processed < len); } static void printusage() { fprintf(stdout, "Usage: qethqoat [-h] [-v]\n" " qethqoat [-r] [-s scope] interface\n" " qethqoat -f file\n\n" "Use qethqoat to query the OSA address table and display " "physical and logical\ndevice information\n\n" "-h, --help Displays the help information.\n" "-r, --raw Writes raw data to stdout.\n" "-f, --file Reads input from file.\n" "-v, --version Prints the version number.\n" "-s, --scope Defines the scope of the query.\n" "\t 0 Query the level of the OSA address table\n" "\t 1 Interface (default)\n" ); } static const struct option qethqoat_opts[] = { { "help", 0, 0, 'h'}, { "raw", 0, 0, 'r'}, { "file", 1, 0, 'f'}, { "version", 0, 0, 'v'}, { "scope", 1, 0, 's'}, { 0, 0, 0, 0} }; static const char qethqoat_opts_str[] = "vhrf:s:"; int main(int argc, char **argv) { struct qoat_opts opts; int sd, c, rc, index; struct ifreq ifr; struct qeth_query_oat_data oat_data; size_t datalen = 131072; opts.raw = 0; opts.scope = 1; opts.file = NULL; while ((c = getopt_long(argc, argv, qethqoat_opts_str, qethqoat_opts, &index)) != -1) { switch (c) { case 'h': printusage(); return 0; case 'r': opts.raw = 1; break; case 'f': opts.file = optarg; break; case 's': opts.scope = atoi(optarg); break; case 'v': print_version(); return 0; default: printusage(); return 1; } } if (optind == argc) { if (!opts.file) { /* No -f file, interface name needed */ printusage(); return 1; } } else { if (opts.file) { /* Have -f file, no interface name allowed */ printusage(); return 1; } opts.ifname = argv[optind]; if (strlen(opts.ifname) >= IFNAMSIZ) { fprintf(stderr, "qethqoat: Interface name too long\n"); return 1; } } oat_data.command = 0; oat_data.ptr = (__u64)(unsigned long)malloc(datalen); if (!oat_data.ptr) { perror("qethqoat"); return 1; } oat_data.buffer_len = datalen; oat_data.response_len = 0; if (opts.file) { FILE *rf = fopen(opts.file, "r"); if (!rf) { perror("qethqoat"); free((void *)(unsigned long)oat_data.ptr); return 1; } oat_data.response_len = fread( (char *)(unsigned long)oat_data.ptr, sizeof(char), oat_data.buffer_len, rf); fclose(rf); goto parse; } sd = socket(AF_INET, SOCK_STREAM, 0); if (sd < 0) { perror("qethqoat"); free((void *)(unsigned long)oat_data.ptr); return 1; } strncpy(ifr.ifr_name, opts.ifname, IFNAMSIZ); oat_data.command = opts.scope; ifr.ifr_ifru.ifru_data = (void *)&oat_data; rc = ioctl(sd, SIOC_QETH_QUERY_OAT, &ifr); if (rc) { if (print_IPA_error(rc)) perror("qethqoat"); close(sd); free((void *)(unsigned long)oat_data.ptr); return 1; } close(sd); parse: if (opts.raw) { fwrite((char *)(unsigned long)oat_data.ptr, sizeof(char), oat_data.response_len, stdout); } else { l_iconv_ebcdic_ascii = iconv_open("ISO-8859-1", "EBCDIC-US"); if (l_iconv_ebcdic_ascii == (iconv_t) -1) { perror("qethqoat"); free((void *)(unsigned long)oat_data.ptr); return 1; } parse_data((char *)(unsigned long)oat_data.ptr, oat_data.response_len); } free((void *)(unsigned long)oat_data.ptr); return 0; } s390-tools-2.3.0/qethqoat/qethqoat.h000066400000000000000000000116241323410431100171710ustar00rootroot00000000000000/* * qethqoat - Query the OSA address table and display physical and logical * device information * * Copyright IBM Corp. 2012, 2017 * * s390-tools is free software; you can redistribute it and/or modify * it under the terms of the MIT license. See LICENSE for details. */ #ifndef _QETHQOAT_H #define _QETHQOAT_H #include #define SIOC_QETH_QUERY_OAT (SIOCDEVPRIVATE + 7) struct qeth_query_oat_data { __u32 command; /* scope of the query */ __u32 buffer_len; /* length of the buffer */ __u32 response_len; /* length of the response in the buffer */ __u64 ptr; /* pointer to buffer */ }; struct qeth_qoat_ipa_reply { __u16 len; __u8 reserved1[2]; __u32 command; __u16 rc; __u8 frames_total; __u8 frames_seq; __u8 reserved2[4]; __u32 subcommand; __u8 reserved3[4]; __u32 supported_scope; __u32 supported_descriptor; } __attribute__((packed)); struct qeth_qoat_physical { __u16 pchid; __u16 chpid; __u8 physical_mac[6]; __u8 logical_mac[6]; __u16 data_sub_channel; __u8 cula; __u8 unit_address; __u16 physical_port; __u16 nr_out_queues; __u16 nr_in_queues; __u16 nr_active_in_queues; #define OAT_IFF_CHPID_TYPE_OSD 0x0 #define OAT_IFF_CHPID_TYPE_OSX 0x1 #define OAT_IFF_CHPID_TYPE_OSM 0x2 __u32 interface_flags_chpid_type:4; __u32 interface_flags:28; #define OAT_OSA_GEN_OSAE3 0x01 #define OAT_OSA_GEN_OSAE4S 0x02 #define OAT_OSA_GEN_OSAE5S 0x03 #define OAT_OSA_GEN_OSAE6S 0x04 __u8 osa_gen; #define OAT_PORT_SPEED_UNKNOWN 0x00 #define OAT_PORT_SPEED_10mbs_half 0x01 #define OAT_PORT_SPEED_10mbs_full 0x02 #define OAT_PORT_SPEED_100mbs_half 0x03 #define OAT_PORT_SPEED_100mbs_full 0x04 #define OAT_PORT_SPEED_1000mbs_half 0x05 #define OAT_PORT_SPEED_1000mbs_full 0x06 #define OAT_PORT_SPEED_NA 0x07 #define OAT_PORT_SPEED_10gbs_full 0x08 __u8 port_speed; #define OAT_PORT_MEDIA_COPPER 0x01 #define OAT_PORT_MEDIA_MULTI_MODE 0x02 #define OAT_PORT_MEDIA_SINGLE_MODE 0x04 __u8 port_media; #define OAT_PORT_MEDIA_ATT_JUMBO 0x80 __u8 port_media_att; __u32 firmware; __u8 reserved1[24]; } __attribute__((packed)); struct qeth_qoat_logical { __u8 ip4_primary_router:1; __u8 ip4_secondary_router:1; __u8 ip4_active_router:1; __u8 ip6_primary_router:1; __u8 ip6_secondary_router:1; __u8 ip6_active_router:1; __u8 ip4_vmac_router:1; __u8 ip6_vmac_router:1; __u8 ip4_vmac_active:1; __u8 ip4_vmac_source:1; __u8 ip4_global_vlan_active:1; __u8 ip6_vmac_active:1; __u8 ip6_vmac_source:1; __u8 ip6_global_vlan_active:1; __u8 reserved1:2; __u8 port_name_f:1; __u8 isolation_f:1; __u8 isolation_vepa:1; __u8 reserved2:5; __u8 reserved3; __u16 ip4_global_vlanid; __u8 ip4_vmac[6]; __u16 ip6_global_vlanid; __u8 ip6_vmac[6]; __u8 port_name[8]; __u32 ip4_ass_enabled; __u32 ip6_ass_enabled; __u32 out_csum_enabled; __u32 out_csum_enabled6; __u32 in_csum_enabled; __u32 in_csum_enabled6; __u32 reserved4; __u16 l2_vlanid; __u8 l2_vmac[6]; __u16 nr_des; __u8 reserved5[14]; } __attribute__((packed)); struct qeth_qoat_des_ip4 { __u32 ip4_address; __u32 flags; } __attribute__((packed)); struct qeth_qoat_des_ip4mc { __u32 ip4_mc_address; __u8 ip4_mc_mac[6]; __u8 reserved[6]; } __attribute__((packed)); struct qeth_qoat_des_ip6 { __u8 ip6_address[16]; __u32 flags; __u8 reserved[4]; } __attribute__((packed)); struct qeth_qoat_des_ip6mc { __u8 ip6_mc_address[16]; __u8 ip6_mc_mac[6]; __u8 reserved[2]; } __attribute__((packed)); struct qeth_qoat_des_vmac { __u8 vmac[6]; __u8 reserved[2]; } __attribute__((packed)); struct qeth_qoat_des_vlan { __u16 vlanid; __u8 reserved[2]; } __attribute__((packed)); struct qeth_qoat_des_gmac { __u8 gmac[6]; __u8 reserved[2]; } __attribute__((packed)); struct qeth_qoat_des_aiq { __u32 protocol; __u8 src_address[16]; __u8 des_address[16]; __u16 src_port; __u16 des_port; } __attribute__((packed)); struct qeth_qoat_descriptor { #define OAT_DES_TYPE_IP4 0x00000001 #define OAT_DES_TYPE_IP4MC 0x00000002 #define OAT_DES_TYPE_IP6 0x00000004 #define OAT_DES_TYPE_IP6MC 0x00000008 #define OAT_DES_TYPE_VMAC 0x00000100 #define OAT_DES_TYPE_VLAN 0x00000200 #define OAT_DES_TYPE_GMAC 0x00000400 #define OAT_DES_TYPE_AIQ 0x00010000 __u32 des_type; __u32 rv_type; __u16 rv_version; __u16 qid; __u32 reply_entry_len; __u16 reply_entry_version; __u16 reply_entry_count; __u16 dh; __u8 reserved[10]; } __attribute__((packed)); struct qeth_qoat_hdr { #define OAT_HDR_TYPE_PHYSICAL 0x0004 #define OAT_HDR_TYPE_LOGICAL 0x0008 #define OAT_HDR_TYPE_DESCRIPTOR 0x0010 __u16 hdr_type; __u16 len; __u16 version; __u8 reserved1[6]; __u32 ec; union { struct qeth_qoat_physical physical; struct qeth_qoat_logical logical; struct qeth_qoat_descriptor descriptor; } type; } __attribute__((packed)); struct qeth_print_hdr { int ip4_h; int ip4mc_h; int ip6_h; int ip6mc_h; int vmac_h; int vlan_h; int gmac_h; int aiq_h; }; struct qoat_opts { int raw; char *ifname; int scope; char *file; }; #endif s390-tools-2.3.0/scripts/000077500000000000000000000000001323410431100150275ustar00rootroot00000000000000s390-tools-2.3.0/scripts/Makefile000066400000000000000000000014151323410431100164700ustar00rootroot00000000000000include ../common.mak SCRIPTS = dbginfo.sh zfcpdbf scsi_logging_level MAN_PAGES = dbginfo.sh.1 zfcpdbf.1 all: install: $(INSTALL) -d -m 755 $(DESTDIR)$(BINDIR) $(DESTDIR)$(MANDIR)/man1 @for i in $(SCRIPTS); \ do \ cat $$i | \ sed -e 's/%S390_TOOLS_VERSION%/$(S390_TOOLS_RELEASE)/' \ >$(DESTDIR)$(BINDIR)/$$i; \ chown $(OWNER):$(GROUP) $(DESTDIR)$(BINDIR)/$$i; \ chmod 755 $(DESTDIR)$(BINDIR)/$$i; \ done sed -e 's/%S390_TOOLS_VERSION%/$(S390_TOOLS_RELEASE)/' cpictl > \ $(DESTDIR)$(TOOLS_LIBDIR)/cpictl chown $(OWNER):$(GROUP) $(DESTDIR)$(TOOLS_LIBDIR)/cpictl chmod 775 $(DESTDIR)$(TOOLS_LIBDIR)/cpictl @for i in $(MAN_PAGES); \ do \ $(INSTALL) -g $(GROUP) -o $(OWNER) -m 644 $$i \ $(DESTDIR)$(MANDIR)/man1; \ done clean: .PHONY: all install clean s390-tools-2.3.0/scripts/cpictl000077500000000000000000000143301323410431100162340ustar00rootroot00000000000000#!/bin/bash # # cpictl - Configure the Control-Program-Information (CPI) settings # # This is an internal helper script that is used by the "cpi.service" # systemd unit and the "90-cpi.rules" udev rule. # # The bash shell is really needed. Other shells have different ideas of how # bitwise operators work. # # Copyright 2017 IBM Corp. # # s390-tools is free software; you can redistribute it and/or modify # it under the terms of the MIT license. See LICENSE for details. # readonly CPI_LOCK="/var/lock/cpictl.lock" readonly PRG="${0##*/}" readonly SYSTEM_LEVEL_PATH="/sys/firmware/cpi/system_level" readonly SYSTEM_TYPE_PATH="/sys/firmware/cpi/system_type" readonly SYSTEM_NAME_PATH="/sys/firmware/cpi/system_name" readonly SYSPLEX_NAME_PATH="/sys/firmware/cpi/sysplex_name" readonly CPI_SET="/sys/firmware/cpi/set" declare LEVEL declare TYPE declare NAME declare SYSPLEX declare -i DRYRUN=0 # Exit codes readonly EXIT_SUCCESS=0 readonly EXIT_FAILURE=1 readonly EXIT_ARG_TOO_LONG=3 readonly EXIT_INVALID_CHARS=4 print_help_and_exit() { cat <&2 exit $EXIT_FAILURE } fail_with() { echo "$1" >&2 echo "Try '$PRG --help' for more information." >&2 exit ${2:-$EXIT_FAILURE} } cpi_commit() { echo 1 > "$CPI_SET" } do_length_check() { [ ${#1} -gt 8 ] && fail_with "$PRG: Specified $2 too long. The maximum length is 8 characters." $EXIT_ARG_TOO_LONG } do_character_check() { echo "$1" | grep -q -E '^[a-zA-Z0-9@#$ ]*$' || fail_with "$PRG: Invalid characters in $2. Valid characters are: A-Z0-9 @#$" $EXIT_INVALID_CHARS } cpi_set_bit() { LEVEL=$(printf '0x%x' $((LEVEL | (1 << (63 - $1)) )) ) } cpi_set_oslevel() { local kver=$(echo "${1:-$(uname -r)}" | grep -E -o '^[0-9]+[.][0-9]+[.][0-9]+') local maj=$((${kver%%.*} % 256)) local min=${kver#*.} min=$((${min%.*} % 256)) local rev=$((${kver##*.} % 256)) local hexlevel=$(printf '0x%02x%02x%02x' $maj $min $rev) LEVEL=$(printf '0x%016x' $(((LEVEL & 0xFFFFFFFFFF000000) | hexlevel))) } cpi_set_type() { TYPE="$1" do_length_check "$TYPE" "system type" do_character_check "$TYPE" "system type" } cpi_set_sysplex() { SYSPLEX="$1" do_length_check "$SYSPLEX" "sysplex name" do_character_check "$SYSPLEX" "sysplex name" } cpi_set_name() { NAME="$1" do_length_check "$NAME" "system name" do_character_check "$NAME" "system name" } # cpictl starts here if [ $# -le 0 ]; then echo "$PRG: No parameters specified" print_parse_error_and_exit fi opts=$(getopt -o b:ehL:N:S:T:v -l set-bit:,environment,help,level:,name:,sysplex:,type:,commit,dry-run,show,version -n $PRG -- "$@") if [ $? -ne 0 ]; then print_parse_error_and_exit fi # This guarantees that only one instance will be running, and will serialize # the execution of multiple instances [ -e "$CPI_LOCK" -a ! -w "$CPI_LOCK" ] && fail_with "$PRG: Cannot access lock file: $CPI_LOCK" [ ! -w "${CPI_LOCK%/*}" ] && fail_with "$PRG: Cannot access lock file: $CPI_LOCK" exec 9<> "$CPI_LOCK" flock -x 9 # Get current values from sys/firmware read LEVEL < "$SYSTEM_LEVEL_PATH" read TYPE < "$SYSTEM_TYPE_PATH" read NAME < "$SYSTEM_NAME_PATH" read SYSPLEX < "$SYSPLEX_NAME_PATH" # Parse command line options: Use eval to remove getopt quotes eval set -- $opts while [ -n $1 ]; do case "$1" in --help|-h) print_help_and_exit ;; --version|-v) print_version_and_exit ;; -b|--set-bit) case "$2" in kvm) cpi_set_bit 0 ;; *) fail_with "$PRG: Unknown bit \"$2\" for the $1 option" ;; esac shift 2 ;; -L|--level) cpi_set_oslevel "$2" shift 2 ;; -e|--environment) cpi_set_type "$CPI_SYSTEM_TYPE" cpi_set_name "$CPI_SYSTEM_NAME" cpi_set_oslevel "$CPI_SYSTEM_LEVEL" cpi_set_sysplex "$CPI_SYSPLEX_NAME" shift ;; -T|--type) cpi_set_type "$2" shift 2 ;; -S|--sysplex) cpi_set_sysplex "$2" shift 2 ;; -N|--name) cpi_set_name "$2" shift 2 ;; --show) cpi_show exit $EXIT_SUCCESS ;; --commit) cpi_commit exit $EXIT_SUCCESS ;; --dry-run) DRYRUN=1 shift ;; --) shift break ;; *) break; ;; esac done # Print settings for --dry-run or commit them to sysfs otherwise if [ $DRYRUN -eq 1 ]; then cat <<-EndDryrun System type: $TYPE System level: $LEVEL System name: $NAME Sysplex name: $SYSPLEX EndDryrun else echo "$LEVEL" > "$SYSTEM_LEVEL_PATH" echo "$TYPE" > "$SYSTEM_TYPE_PATH" echo "$NAME" > "$SYSTEM_NAME_PATH" echo "$SYSPLEX" > "$SYSPLEX_NAME_PATH" cpi_commit fi exit $EXIT_SUCCESS s390-tools-2.3.0/scripts/dbginfo.sh000077500000000000000000000716751323410431100170160ustar00rootroot00000000000000#!/bin/sh # # dbginfo.sh - Tool to collect runtime, configuration, and trace information # # Copyright IBM Corp. 2002, 2017 # # s390-tools is free software; you can redistribute it and/or modify # it under the terms of the MIT license. See LICENSE for details. # # Switching to neutral locale LC_ALL=C export LC_ALL # The general name of this script readonly SCRIPTNAME="${0##*/}" ######################################## # print version info print_version() { cat </dev/null)" -ne 0; then echo "${SCRIPTNAME}: Error: You must be user root to run \"${SCRIPTNAME}\"!" exit 1 fi ####################################### # Parsing the command line # paramWORKDIR_BASE="/tmp/" while [ ${#} -gt 0 ]; do case ${1} in --help|-h) print_usage exit 0 ;; --version|-v) print_version exit 0 ;; --directory|-d) paramWORKDIR_BASE=${2} shift ;; -*|--*|*) echo echo "${SCRIPTNAME}: invalid option \"${1}\"" echo "Try '${SCRIPTNAME} --help' for more information" echo exit 1 ;; esac shift done if test -z "${paramWORKDIR_BASE}"; then echo "${SCRIPTNAME}: Error: No directory specified for data collection!" echo exit 1 fi if test ! -d "${paramWORKDIR_BASE}"; then echo "${SCRIPTNAME}: Error: The specified directory \"${paramWORKDIR_BASE}\" does not exist!" echo exit 1 fi ######################################## # Global used variables # # The base working directory readonly WORKDIR_BASE="$(echo "${paramWORKDIR_BASE}" | sed -e 's#/$##')/" # The terminal readonly TERMINAL="$(tty 2>/dev/null)" # The hostname of the system readonly SYSTEMHOSTNAME="$(hostname -s 2>/dev/null)" # The kernel release version as delivered from uname -r readonly KERNEL_RELEASE_VERSION="$(uname -r 2>/dev/null)" # The processor ID for the first processor readonly PROCESSORID="$(grep -E ".*processor 0:.*" /proc/cpuinfo | \ sed 's/.*identification[[:space:]]*\=[[:space:]]*\([[:alnum:]]*\).*/\1/g')" # The processor version for the first processor readonly PROCESSORVERSION="$(grep -E ".*processor 0:.*" /proc/cpuinfo | \ sed 's/.*version[[:space:]]*\=[[:space:]]*\([[:alnum:]]*\).*/\1/g')" # The current date readonly DATETIME="$(date +%Y-%m-%d-%H-%M-%S 2>/dev/null)" # The current working directory for the actual script execution if test -z "${PROCESSORID}"; then readonly WORKDIR_CURRENT="DBGINFO-${DATETIME}-${SYSTEMHOSTNAME:-localhost}" else readonly WORKDIR_CURRENT="DBGINFO-${DATETIME}-${SYSTEMHOSTNAME:-localhost}-${PROCESSORID}" fi # The current path where the collected information is put together readonly WORKPATH="${WORKDIR_BASE}${WORKDIR_CURRENT}/" # The current TAR archive that finally includes all collected information readonly WORKARCHIVE="${WORKDIR_BASE}${WORKDIR_CURRENT}.tgz" # The log file of activities from this script execution readonly LOGFILE="${WORKPATH}dbginfo.log" # The file to indicate that another instance of the script is already running readonly LOCKFILE="/tmp/${SCRIPTNAME}.lock" # File that includes output of Linux commands readonly OUTPUT_FILE_CMD="${WORKPATH}runtime.out" # File that includes output of z/VM commands (if running in z/VM) readonly OUTPUT_FILE_VMCMD="${WORKPATH}zvm_runtime.out" # File that includes content of files from sysfs readonly OUTPUT_FILE_SYSFS="${WORKPATH}sysfsfiles.out" # File that includes content of OSA OAT readonly OUTPUT_FILE_OSAOAT="${WORKPATH}osa_oat" # File that includes the output of journalctl readonly OUTPUT_FILE_JOURNALCTL="${WORKPATH}journalctl.out" # File that includes the output of OpenVSwitch readonly OUTPUT_FILE_OVS="${WORKPATH}openvswitch" # File that includes the KVM domain xml file readonly OUTPUT_FILE_XML="${WORKPATH}domain_xml" # File that includes the docker inspect output readonly OUTPUT_FILE_DOCKER="${WORKPATH}docker_inspect.out" # Mount point of the debug file system readonly MOUNT_POINT_DEBUGFS="/sys/kernel/debug" # The amount of steps running the whole collections readonly COLLECTION_COUNT=11 # The kernel version (e.g. '2' from 2.6.32 or '3' from 3.2.1) readonly KERNEL_VERSION=$(uname -r 2>/dev/null | cut -d'.' -f1) # The kernel major revision number (e.g. '6' from 2.6.32 or '2' from 3.2.1) readonly KERNEL_MAJOR_REVISION=$(uname -r 2>/dev/null | cut -d'.' -f2) # The kernel mainor revision number (e.g. '32' from 2.6.32 or '1' from 3.2.1) readonly KERNEL_MINOR_REVISION=$(uname -r 2>/dev/null | cut -d'.' -f3 | sed 's/[^0-9].*//g') # Is this kernel supporting sysfs - since 2.4 (0=yes, 1=no) if test "${KERNEL_VERSION}" -lt 2 || ( test "${KERNEL_VERSION}" -eq 2 && test "${KERNEL_MAJOR_REVISION}" -le 4 ); then readonly LINUX_SUPPORT_SYSFS=1 else readonly LINUX_SUPPORT_SYSFS=0 fi # Is this kernel potentially using the /sys/kernel/debug feature - since 2.6.13 (0=yes, 1=no) if test "${KERNEL_VERSION}" -lt 2 || ( test "${KERNEL_VERSION}" -eq 2 && ( test "${KERNEL_MAJOR_REVISION}" -lt 6 || ( test "${KERNEL_MAJOR_REVISION}" -eq 6 && test "${KERNEL_MINOR_REVISION}" -lt 13 ))); then readonly LINUX_SUPPORT_SYSFSDBF=1 else readonly LINUX_SUPPORT_SYSFSDBF=0 fi if test "x${PROCESSORVERSION}" = "xFF" || test "x${PROCESSORVERSION}" = "xff"; then readonly RUNTIME_ENVIRONMENT=$(grep -E "VM00.*Control Program.*" /proc/sysinfo| sed 's/.*:[[:space:]]*\([[:graph:]]*\).*/\1/g') else readonly RUNTIME_ENVIRONMENT="LPAR" fi ######################################## # Collection of proc fs entries PROCFILES="\ /proc/buddyinfo\ /proc/cio_ignore\ /proc/cmdline\ /proc/cpuinfo\ /proc/crypto\ /proc/dasd/devices\ /proc/dasd/statistics\ /proc/devices\ /proc/diskstats\ /proc/driver/z90crypt\ /proc/interrupts\ /proc/iomem\ /proc/mdstat\ /proc/meminfo\ /proc/misc\ /proc/modules\ /proc/mounts\ /proc/net/vlan\ /proc/net/bonding\ /proc/partitions\ /proc/qeth\ /proc/qeth_perf\ /proc/qeth_ipa_takeover\ /proc/sched_debug\ /proc/schedstat\ /proc/service_levels\ /proc/slabinfo\ /proc/stat\ /proc/swaps\ /proc/sys/kernel\ /proc/sys/vm\ /proc/sysinfo\ /proc/version\ /proc/zoneinfo\ " # Adding files to PROCFILES in case scsi devices are available if test -e /proc/scsi; then PROCFILES="${PROCFILES}\ $(find /proc/scsi -type f -perm /444 2>/dev/null)\ " fi # Adding files to PROCFILES in case we run on Kernel 2.4 or older if test "${LINUX_SUPPORT_SYSFS}" -eq 1; then PROCFILES="${PROCFILES}\ /proc/chpids\ /proc/chandev\ /proc/ksyms\ /proc/lvm/global\ /proc/subchannels\ " fi # Adding s390dbf files to PROCFILE in case we run on Kernel lower than 2.6.13 if test "${LINUX_SUPPORT_SYSFSDBF}" -eq 1; then if test -e /proc/s390dbf; then PROCFILES="${PROCFILES}\ $(find /proc/s390dbf -type f -not -path "*/raw" -not -path "*/flush" 2>/dev/null)\ " fi fi ######################################## LOGFILES="\ /var/log/anaconda.*\ /var/log/audit\ /var/log/boot*\ /var/log/cron*\ /var/log/dmesg*\ /var/log/dracut.log*\ /var/log/IBMtape.trace\ /var/log/IBMtape.errorlog\ /var/log/libvirt\ /var/log/lin_tape.trace\ /var/log/lin_tape.errorlog\ /var/log/messages*\ /var/log/syslog*\ /var/log/sa\ /var/log/yum.log\ /var/log/openvswitch/ovs-vswitchd.log\ /var/log/openvswitch/ovsdb-server.log\ /var/run/docker/libcontainerd/containerd/events.log\ /run/containerd/events.log\ " ######################################## CONFIGFILES="\ /boot/grub2/grub.cfg\ /boot/zipl/active_devices.txt\ /boot/zipl/config\ /etc/*.conf\ /etc/anacrontab\ /etc/auto.*\ /etc/cron.*\ /etc/crontab\ /etc/crypttab\ /etc/default\ /etc/depmod.d\ /etc/dracut.conf.d\ /etc/exports\ /etc/fstab\ /etc/groups\ /etc/grub.d\ /etc/hosts*\ /etc/iscsi\ /etc/inittab\ /etc/libvirt\ /etc/logrotate.d\ /etc/lvm\ /etc/modprobe.conf*\ /etc/modprobe.d\ /etc/mtab\ /etc/multipath\ /etc/network\ /etc/networks\ /etc/pam.d\ /etc/profile\ /etc/profile.d\ /etc/pki/tls/openssl.cnf\ /etc/rc.d\ /etc/rc.local\ /etc/resolv.*\ /etc/rsyslog.d\ /etc/ssl/openssl.conf\ /etc/ssl/openssl.cnf\ /etc/sysconfig\ /etc/sysctl.d\ /etc/syslog*\ /etc/systemd\ /etc/udev*\ /etc/xinet.d\ /etc/*release\ $(find /lib/modules -name modules.dep 2>/dev/null)\ /etc/docker\ /lib/systemd/system/docker.service\ /usr/lib/systemd/system/docker.service\ /etc/apparmor.d\ " ######################################## CMDS="uname -a\ :uptime\ :runlevel\ :iptables -L\ :ulimit -a\ :ps -emo pid,tid,nlwp,policy,user,tname,ni,pri,psr,sgi_p,stat,wchan,start_time,time,pcpu,pmem,vsize,size,rss,share,command\ :ps -eHo pid,tid,nlwp,policy,user,tname,ni,pri,psr,sgi_p,stat,wchan,start_time,time,pcpu,pmem,vsize,size,rss,share,command\ :ps axX\ :dmesg -s 1048576\ :last\ :lsshut\ :ifconfig -a\ :nm-tool\ :route -n\ :ip route list\ :ip route list table all\ :ip rule list\ :ip neigh list\ :ip link show\ :ip ntable\ :ip a sh\ :firewall-cmd --list-all\ :ipcs -a\ :netstat -pantu\ :netstat -s\ :dmsetup ls\ :dmsetup ls --tree\ :dmsetup table\ :dmsetup table --target multipath\ :dmsetup status\ :multipathd -k'show config'\ :multipathd -k'show maps'\ :multipathd -k'show topo'\ :multipathd -k'show paths'\ :multipathd -k'show maps stats'\ :multipathd -k'show maps status'\ :multipathd -k'show status'\ :multipathd -k'show daemon'\ :multipathd -k'show blacklist'\ :multipathd -k'show devices'\ :multipath -v6 -ll\ :multipath -d\ :multipath -t\ :lsqeth\ :lschp\ :lscss\ :lscpu -ae\ :lsmem\ :lsdasd\ :lsdasd -u\ :ziorep_config -ADM\ :lsmod\ :lsdev\ :lsscsi\ :lstape\ :lszfcp\ :lszfcp -D\ :lszfcp -V\ :icainfo\ :icastats\ :lszcrypt -VV\ :ivp.e\ :pkcsconf -mlist\ :cat /var/lib/opencryptoki/pk_config_data\ :ls -al /usr/lib64/opencryptoki/stdll\ :SPident\ :rpm -qa | sort\ :sysctl -a\ :lsof\ :mount\ :df -h\ :df -i\ :pvpath -qa\ :find /boot -print0 | sort -z | xargs -0 -n 10 ls -ld\ :find /dev -print0 | sort -z | xargs -0 -n 10 ls -ld\ :java -version\ :cat /root/.bash_history\ :env\ :journalctl --all --no-pager --since=$(date -d '5 day ago' +%Y-%m-%d) --until=now --lines=50000 \ > '${OUTPUT_FILE_JOURNALCTL}'\ :openssl engine\ :systemd-delta\ :systemctl --all --no-pager show\ :systemctl --all --no-pager list-units\ :systemctl --all --no-pager list-unit-files\ :docker info\ :docker images\ :docker network ls\ :docker ps -a\ :docker version\ :docker stats --no-stream\ :systemctl status docker.service\ " ######################################## VM_CMDS="q userid\ :q users\ :q privclass\ :q cplevel\ :q cpservice\ :q ssi\ :q cpus\ :q srm\ :q vtod\ :q timezone\ :q loaddev\ :q v osa\ :q v dasd\ :q v crypto\ :q v fcp\ :q v pav\ :q v sw\ :q v st\ :q v nic\ :q st\ :q xstore\ :q xstore user system\ :q sxspages\ :q vmlan\ :q vswitch\ :q vswitch details\ :q vswitch access\ :q vswitch active\ :q vswitch accesslist\ :q vswitch promiscuous\ :q vswitch controller\ :q port group all active details\ :q set\ :q comm\ :q controller all\ :q fcp\ :q frames\ :q lan\ :q lan all details\ :q lan all access\ :q cache\ :q nic\ :q pav\ :q proc\ :q proc topology\ :q mt\ :q qioass\ :q spaces\ :q swch all\ :q trace\ :q mdcache\ :q alloc page\ :q alloc spool\ :q dump\ :q dumpdev\ :q reorder VMUSERID\ :q quickdsp VMUSERID\ :ind load\ :ind sp\ :ind user\ " ############################################################################### ######################################## collect_cmdsout() { local cmd local ifs_orig ifs_orig="${IFS}" pr_syslog_stdout "1 of ${COLLECTION_COUNT}: Collecting command output" IFS=: for cmd in ${CMDS}; do IFS=${ifs_orig} call_run_command "${cmd}" "${OUTPUT_FILE_CMD}" done IFS="${ifs_orig}" pr_log_stdout " " } ######################################## collect_vmcmdsout() { local vm_command local cp_command local vm_cmds local vm_userid local module_loaded local ifs_orig local cp_buffer_size local rc_buffer_size module_loaded=1 ifs_orig="${IFS}" if echo "${RUNTIME_ENVIRONMENT}" | grep -qi "z/VM" >/dev/null 2>&1; then pr_syslog_stdout "2 of ${COLLECTION_COUNT}: Collecting z/VM command output" if which vmcp >/dev/null 2>&1; then cp_command="vmcp" if ! lsmod 2>/dev/null | grep -q vmcp && modinfo vmcp >/dev/null 2>&1; then modprobe vmcp && module_loaded=0 && sleep 2 fi elif which hcp >/dev/null 2>&1; then cp_command="hcp" if ! lsmod 2>/dev/null | grep -q cpint; then modprobe cpint && module_loaded=0 && sleep 2 fi else pr_log_stdout " " pr_log_stdout "${SCRIPTNAME}: Warning: No program found to communicate to z/VM CP" pr_log_stdout " Skipping collection of z/VM command output" pr_log_stdout " " return 1 fi vm_userid=$(${cp_command} q userid 2>/dev/null | sed -ne 's/^\([^[:space:]]*\).*$/\1/p') vm_cmds=$(echo "${VM_CMDS}" | sed "s/VMUSERID/${vm_userid}/g") IFS=: for vm_command in ${vm_cmds}; do IFS="${ifs_orig}" cp_buffer_size=2 rc_buffer_size=2 while test ${rc_buffer_size} -eq 2 && test ${cp_buffer_size} -lt 1024; do cp_buffer_size=$(( cp_buffer_size * 2 )) eval ${cp_command} -b ${cp_buffer_size}k "${vm_command}" >/dev/null 2>&1 rc_buffer_size=$? done call_run_command "${cp_command} -b ${cp_buffer_size}k ${vm_command}" "${OUTPUT_FILE_VMCMD}" IFS=: done IFS=${ifs_orig} if test ${module_loaded} -eq 0 && test "x${cp_command}" = "xhcp"; then rmmod cpint elif test ${module_loaded} -eq 0 && test "x${cp_command}" = "xvmcp"; then rmmod vmcp fi else pr_syslog_stdout "2 of ${COLLECTION_COUNT}: Collecting z/VM command output skipped - no z/VM environment" fi pr_log_stdout " " } ######################################## collect_procfs() { local file_name pr_syslog_stdout "3 of ${COLLECTION_COUNT}: Collecting procfs" for file_name in ${PROCFILES}; do call_collect_file "${file_name}" done pr_log_stdout " " } ######################################## collect_sysfs() { local debugfs_mounted local dir_name local file_name debugfs_mounted=0 # Requires kernel version newer then 2.4 if test "${LINUX_SUPPORT_SYSFS}" -eq 0; then pr_syslog_stdout "4 of ${COLLECTION_COUNT}: Collecting sysfs" # Requires kernel version of 2.6.13 or newer if test "${LINUX_SUPPORT_SYSFSDBF}" -eq 0; then if ! grep -qE "${MOUNT_POINT_DEBUGFS}.*debugfs" /proc/mounts 2>/dev/null; then if mount -t debugfs debugfs "${MOUNT_POINT_DEBUGFS}" >/dev/null 2>&1; then sleep 2 debugfs_mounted=1 else pr_log_stdout "${SCRIPTNAME}: Warning: Unable to mount debugfs at \"${MOUNT_POINT_DEBUGFS}\"" fi fi fi # Collect sysfs files using multiple threads (-J 1) while excluding # files known to block on read (-x). Stop reading a file that takes # more than 5 seconds (-T 5) such as an active ftrace buffer. dump2tar /sys -z -o "${WORKPATH}/sysfs.tgz" -x '*/tracing/trace_pipe*' \ -x '*/tracing/per_cpu/*' --ignore-failed-read -J 1 -T 5 if [ $? -ne 0 ] ; then echo "${SCRIPTNAME}: Warning: dump2tar failed or is unavailable - falling back to slow path" call_run_command "find /sys -print0 | sort -z | xargs -0 -n 10 ls -ld" "${OUTPUT_FILE_SYSFS}" find /sys -noleaf -type d 2>/dev/null | while IFS= read -r dir_name; do mkdir -p "${WORKPATH}${dir_name}" done find /sys -noleaf -type f -perm /444\ -a -not -name "*trace_pipe*"\ 2>/dev/null | while IFS= read -r file_name; do echo " ${file_name}" if ! dd if="${file_name}" status=noxfer iflag=nonblock of="${WORKPATH}${file_name}" >/dev/null 2>&1; then echo "${SCRIPTNAME}: Warning: failed to copy \"${file_name}\"" fi done fi if test ${debugfs_mounted} -eq 1; then umount "${MOUNT_POINT_DEBUGFS}" fi else pr_syslog_stdout "4 of ${COLLECTION_COUNT}: Collecting sysfs skipped. Kernel $(uname -r) must be newer than 2.4" fi pr_log_stdout " " } ######################################## collect_logfiles() { local file_name pr_syslog_stdout "5 of ${COLLECTION_COUNT}: Collecting log files" for file_name in ${LOGFILES}; do call_collect_file "${file_name}" done pr_log_stdout " " } ######################################## collect_configfiles() { local file_name pr_syslog_stdout "6 of ${COLLECTION_COUNT}: Collecting config files" for file_name in ${CONFIGFILES}; do call_collect_file "${file_name}" done pr_log_stdout " " } ######################################## collect_osaoat() { local network_devices local network_device network_devices=$(lsqeth 2>/dev/null | grep "Device name" \ | sed 's/.*:[[:space:]]\+\([^[:space:]]*\)[[:space:]]\+/\1/g') if which qethqoat >/dev/null 2>&1; then if test -n "${network_devices}"; then pr_syslog_stdout "7 of ${COLLECTION_COUNT}: Collecting osa oat output" for network_device in ${network_devices}; do call_run_command "qethqoat ${network_device}" "${OUTPUT_FILE_OSAOAT}.out" && call_run_command "qethqoat -r ${network_device}" "${OUTPUT_FILE_OSAOAT}_${network_device}.raw" done else pr_syslog_stdout "7 of ${COLLECTION_COUNT}: Collecting osa oat output skipped - no devices" fi else pr_syslog_stdout "7 of ${COLLECTION_COUNT}: Collecting osa oat output skipped - not available" fi pr_log_stdout " " } ######################################## # OpenVSwitch collect_ovs() { local br_list local ovscmd local bridge local ovsbrcmd local ovscmds local ovsbrcmds br_list=$(ovs-vsctl list-br) ovscmds="ovs-dpctl -s show\ :ovs-vsctl -t 5 show\ :ovsdb-client dump\ " if test -n "${br_list}"; then pr_syslog_stdout "8 of ${COLLECTION_COUNT}: Collecting OpenVSwitch output" IFS=: for ovscmd in ${ovscmds}; do IFS=${ifs_orig} call_run_command "${ovscmd}" "${OUTPUT_FILE_OVS}.out" done IFS="${ifs_orig}" for bridge in ${br_list}; do ovsbrcmds="ovs-ofctl show ${bridge}\ :ovs-ofctl dump-flows ${bridge}\ :ovs-appctl fdb/show ${bridge}\ " IFS=: for ovsbrcmd in ${ovsbrcmds}; do IFS=${ifs_orig} call_run_command "${ovsbrcmd}" "${OUTPUT_FILE_OVS}.out" done IFS="${ifs_orig}" done else pr_syslog_stdout "8 of ${COLLECTION_COUNT}: Collecting OpenVSwitch output skipped" fi pr_log_stdout " " } ######################################## collect_domain_xml() { local domain_list local domain domain_list=$(virsh list --all --name) if test -n "${domain_list}"; then pr_syslog_stdout "9 of ${COLLECTION_COUNT}: Collecting domain xml files" for domain in ${domain_list}; do call_run_command "virsh dumpxml ${domain}" "${OUTPUT_FILE_XML}_${domain}.xml" done else pr_syslog_stdout "9 of ${COLLECTION_COUNT}: Collecting domain xml files skipped" fi pr_log_stdout " " } ######################################## collect_docker() { local item_list local item # call docker inspect for all containers item_list=$(docker ps -qa) if test -n "${item_list}"; then pr_syslog_stdout "10a of ${COLLECTION_COUNT}: Collecting docker container output" for item in ${item_list}; do call_run_command "docker inspect ${item}" "${OUTPUT_FILE_DOCKER}" done else pr_syslog_stdout "10a of ${COLLECTION_COUNT}: Collecting docker container output skipped" fi # call docker inspect for all networks item_list=$(docker network ls -q) if test -n "${item_list}"; then pr_syslog_stdout "10b of ${COLLECTION_COUNT}: Collecting docker network output" for item in ${item_list}; do call_run_command "docker network inspect ${item}" "${OUTPUT_FILE_DOCKER}" done else pr_syslog_stdout "10b of ${COLLECTION_COUNT}: Collecting docker network output skipped" fi pr_log_stdout " " } ######################################## post_processing() { local file_mtime local file_mtime_epoche local tmp_file local file_name pr_syslog_stdout "11 of ${COLLECTION_COUNT}: Postprocessing" find "${WORKPATH}etc/libvirt/qemu/" -maxdepth 1 -name "*.xml" 2>/dev/null | while IFS= read -r file_name; do file_mtime_epoche=$(stat --format=%Y "${file_name}") file_mtime=$(date +%Y%m%d%H%M.%S --date="@${file_mtime_epoche}") tmp_file=${file_name}.$$ echo " ${file_name}" if ! sed "s/\( \+passwd='\).*\('\)/\1********\2/g" "${file_name}" > "${tmp_file}"; then echo "${SCRIPTNAME}: Warning: Postprocessing failed on ${file_name}" echo fi mv "${tmp_file}" "${file_name}" touch --time=mtime -t "${file_mtime}" "${file_name}" done find "${WORKPATH}etc/libvirt/" -name "auth.conf" 2>/dev/null | while IFS= read -r file_name; do file_mtime_epoche=$(stat --format=%Y "${file_name}") file_mtime=$(date +%Y%m%d%H%M.%S --date="@${file_mtime_epoche}") tmp_file=${file_name}.$$ echo " ${file_name}" if ! sed "s/\(password=\).*/\1********/g" "${file_name}" > "${tmp_file}"; then echo "${SCRIPTNAME}: Warning: Postprocessing failed on ${file_name}" echo fi mv "${tmp_file}" "${file_name}" touch --time=mtime -t "${file_mtime}" "${file_name}" done find "${WORKPATH}" -maxdepth 1 -name "*.xml" 2>/dev/null | while IFS= read -r file_name; do file_mtime_epoche=$(stat --format=%Y "${file_name}") file_mtime=$(date +%Y%m%d%H%M.%S --date="@${file_mtime_epoche}") tmp_file=${file_name}.$$ echo " ${file_name}" if ! sed "s/\( \+passwd='\).*\('\)/\1********\2/g" "${file_name}" > "${tmp_file}"; then echo "${SCRIPTNAME}: Warning: Postprocessing failed on ${file_name}" echo fi mv "${tmp_file}" "${file_name}" touch --time=mtime -t "${file_mtime}" "${file_name}" done pr_log_stdout " " } ######################################## # Be aware that this output must be # redirected into a separate logfile call_run_command() { local cmd local logfile local raw_cmd cmd="${1}" logfile="${2}" raw_cmd=$(echo "${cmd}" | sed -ne 's/^\([^[:space:]]*\).*$/\1/p') echo "#######################################################" >> "${logfile}" echo "${USER}@${SYSTEMHOSTNAME:-localhost}> ${cmd}" >> "${logfile}" # check if command exists if ! which "${raw_cmd}" >/dev/null 2>&1; then # check if command is a builtin if ! command -v "${raw_cmd}" >/dev/null 2>&1; then echo "${SCRIPTNAME}: Warning: Command \"${raw_cmd}\" not available" >> "${logfile}" echo >> "${logfile}" return 1 fi fi if ! eval "${cmd}" >> "${logfile}" 2>&1; then echo "${SCRIPTNAME}: Warning: Command \"${cmd}\" failed" >> "${logfile}" echo >> "${logfile}" return 1 else echo >> "${logfile}" return 0 fi } ######################################## call_collect_file() { local directory_name local file_name file_name="${1}" echo " ${file_name}" directory_name=$(dirname "${file_name}" 2>/dev/null) if test ! -e "${WORKPATH}${directory_name}"; then mkdir -p "${WORKPATH}${directory_name}" 2>&1 fi if ! cp -r --preserve=mode,timestamps -d -L --parents "${file_name}" "${WORKPATH}" 2>&1; then return 1 else return 0 fi } ############################################################################### ######################################## # print that an instance is already running print_alreadyrunning() { print_version cat < "${LOCKFILE}" fi if ! mkdir "${WORKPATH}" 2>/dev/null; then echo "${SCRIPTNAME}: Error: Target directory \"${WORKPATH}\" already exists or" echo " \"${WORKDIR_BASE}\" does not exist!" exit 1 fi chmod 0700 "${WORKPATH}" } ######################################## # create gzip-ped tar file create_package() { pr_stdout "Finalizing: Creating archive with collected data" cd "${WORKDIR_BASE}" if ! tar -czf "${WORKARCHIVE}" "${WORKDIR_CURRENT}"; then pr_stdout " " pr_stdout "${SCRIPTNAME}: Error: Collection of data failed!" pr_stdout " The creation of \"${WORKARCHIVE}\" was not successful." pr_stdout " Please check the directory \"${WORKDIR_BASE}\"" pr_stdout " to provide enough free available space." else chmod 0600 "${WORKARCHIVE}" pr_stdout " " pr_stdout "Collected data was saved to:" pr_stdout " >> ${WORKARCHIVE} <<" fi pr_stdout " " pr_stdout "Review the collected data before sending to your service organization. " pr_stdout " " } ######################################## # Cleaning up the prepared/collected information environment_cleanup() { if ! rm -rf "${WORKPATH}" 2>/dev/null; then pr_stdout " " pr_stdout "${SCRIPTNAME}: Warning: Deletion of \"${WORKPATH}\" failed" pr_stdout " Please remove the directory manually" pr_stdout " " fi if ! rm -f "${LOCKFILE}" 2>/dev/null; then pr_stdout " " pr_stdout "${SCRIPTNAME}: Warning: Deletion of \"${WORKDIR_BASE}${SCRIPTNAME}\" failed" pr_stdout " Please remove the file manually" pr_stdout " " fi } ######################################## # Function to perform a cleanup in case of a received signal emergency_exit() { pr_stdout " " pr_stdout "${SCRIPTNAME}: Info: Data collection has been interrupted" pr_stdout " Cleanup of temporary collected data" environment_cleanup pr_stdout "${SCRIPTNAME}: Info: Emergency exit processed" pr_stdout " " logger -t "${SCRIPTNAME}" "Data collection interrupted" exit } ######################################## # Function to print to stdout when rediretion is active pr_stdout() { echo "${@}" >&8 } ######################################## # Function to print to stdout and into log file when rediretion is active pr_log_stdout() { echo "$@" echo "$@" >&8 } ######################################## # Function to print to stdout and into log file when rediretion is active pr_syslog_stdout() { echo "$@" echo "$@" >&8 logger -t "${SCRIPTNAME}" "$@" } ############################################################################### # Running the script environment_setup print_version # saving stdout/stderr and redirecting stdout/stderr into log file exec 8>&1 9>&2 >"${LOGFILE}" 2>&1 # trap on SIGHUP=1 SIGINT=2 SIGTERM=15 trap emergency_exit SIGHUP SIGINT SIGTERM pr_log_stdout "" pr_log_stdout "Hardware platform = $(uname -i)" pr_log_stdout "Kernel version = ${KERNEL_VERSION}.${KERNEL_MAJOR_REVISION}.${KERNEL_MINOR_REVISION} ($(uname -r 2>/dev/null))" pr_log_stdout "Runtime environment = ${RUNTIME_ENVIRONMENT}" pr_log_stdout "" logger -t "${SCRIPTNAME}" "Starting data collection" collect_cmdsout collect_vmcmdsout # Collecting the proc file system (content is specific based on kernel version) collect_procfs # Collecting sysfs in case we run on Kernel 2.4 or newer collect_sysfs collect_logfiles collect_configfiles collect_osaoat collect_ovs collect_domain_xml collect_docker post_processing create_package environment_cleanup logger -t "${SCRIPTNAME}" "Data collection completed" exec 1>&8 2>&9 8>&- 9>&- #EOF s390-tools-2.3.0/scripts/dbginfo.sh.1000066400000000000000000000051311323410431100171320ustar00rootroot00000000000000.TH DBGINFO.SH 1 "February 2017" "s390-tools" .SH NAME dbginfo.sh \- collect runtime, configuration and trace information for debugging Linux on System z .SH SYNOPSIS .br \fBdbginfo.sh\fP [OPTIONS] .br \fBdbginfo.sh\fP {\-h|\-v} .SH DESCRIPTION This script collects runtime, configuration and trace information that can be used to debug a Linux on System z instance. For Linux on z/VM, the script also traces information about the z/VM system. The debug information is written to a file /tmp/DBGINFO\-\-