[s390utils/f15/master] updated to recent RHEL-6 package
Dan Horák
sharkcz at fedoraproject.org
Fri Mar 25 14:00:19 UTC 2011
commit f7e20b2418639158258c86af41190c58130c0b4b
Author: Dan Horák <dan at danny.cz>
Date: Fri Mar 25 14:59:32 2011 +0100
updated to recent RHEL-6 package
.gitignore | 1 +
0044-xcec-bridge-fix-multicast-forwarding.patch | 41 +
0045-ziomon-wrong-return-codes.patch | 53 +
...-process-devices-with-non-zero-subchannel.patch | 30 +
...mpletion-of-any-pending-actions-affecting.patch | 102 +
...-add-infrastructure-code-for-new-features.patch | 169 +
...w-hypervisor-performance-data-on-System-z.patch | 8264 +++++++++++++++++++
...-support-for-CMS-EDF-filesystems-via-fuse.patch | 6056 ++++++++++++++
...smem-chmem-Tools-to-manage-memory-hotplug.patch | 729 ++
...onf-Prevent-re-IPL-loop-for-dump-on-panic.patch | 562 ++
...a-program-if-a-terminal-device-is-availab.patch | 410 +
...pl-Add-ELF-dump-support-needed-for-makedu.patch | 8702 ++++++++++++++++++++
...f-support-for-OSA-CHPID-types-OSX-and-OSM.patch | 91 +
...not-specify-z-VM-user-ID-as-argument-to-l.patch | 68 +
0057-tunedasd-add-new-option-Q-query_reserve.patch | 340 +
0058-fdasd-dasdfmt-fix-format-7-label.patch | 96 +
...-cmm_pages-not-set-and-restored-correctly.patch | 69 +
...LUN-reporting-for-SAN-volume-controller-S.patch | 69 +
...Accept-uppercase-and-lowercase-hex-digits.patch | 39 +
...Add-DELAY_MINUTES-description-to-man-page.patch | 225 +
...se-fix-read-and-write-errors-in-text-mode.patch | 164 +
0064-switch-to-using-udevadm-settle.patch | 25 +
...ptop-Fix-man-page-typo-for-current-weight.patch | 30 +
...r-overflow-when-writing-to-read-only-devi.patch | 61 +
...cted-filesystem-block-size-on-FBA-devices.patch | 31 +
lib-zfcp-hbaapi-2.0-module.patch | 25 -
lib-zfcp-hbaapi-2.0-sgutils.patch | 36 -
lib-zfcp-hbaapi-2.1-module.patch | 26 +
lib-zfcp-hbaapi-2.1-u8.patch | 13 +
lib-zfcp-hbaapi-2.1-vendorlib.patch | 37 +
s390utils.spec | 196 +-
sources | 2 +-
32 files changed, 26692 insertions(+), 70 deletions(-)
---
diff --git a/.gitignore b/.gitignore
index cf43303..421d790 100644
--- a/.gitignore
+++ b/.gitignore
@@ -2,3 +2,4 @@ s390-tools-1.8.2.tar.bz2
cmsfs-1.1.8c.tar.gz
lib-zfcp-hbaapi-2.0.tar.gz
src_vipa-2.0.4.tar.gz
+/lib-zfcp-hbaapi-2.1.tar.gz
diff --git a/0044-xcec-bridge-fix-multicast-forwarding.patch b/0044-xcec-bridge-fix-multicast-forwarding.patch
new file mode 100644
index 0000000..1a5a904
--- /dev/null
+++ b/0044-xcec-bridge-fix-multicast-forwarding.patch
@@ -0,0 +1,41 @@
+From 30321208dbccbd634c5e91b594372d150df07a03 Mon Sep 17 00:00:00 2001
+From: =?UTF-8?q?Dan=20Hor=C3=A1k?= <dan at danny.cz>
+Date: Tue, 25 Jan 2011 10:38:42 +0100
+Subject: [PATCH 44/46] xcec-bridge: fix multicast forwarding
+
+Description: xcec-bridge: fix multicast forwarding
+Symptom: Forwarding of multicast traffic does not work.
+Problem: xcec-bridge was developed for the broken packet socket
+ support of the qeth layer 3 driver. The code assumes
+ there are no link level heades for incoming packets.
+Solution: New qeth layer 3 driver has full packet socket support
+ so xcec-bridge has to account link level header.
+---
+ ip_watcher/xcec-bridge.c | 9 +++++----
+ 1 files changed, 5 insertions(+), 4 deletions(-)
+
+diff --git a/ip_watcher/xcec-bridge.c b/ip_watcher/xcec-bridge.c
+index d6dd112..7f551d2 100644
+--- a/ip_watcher/xcec-bridge.c
++++ b/ip_watcher/xcec-bridge.c
+@@ -549,12 +549,13 @@ void process_packet(struct int_sock *i_s)
+ if (s_ll.sll_pkttype==PACKET_BROADCAST) {
+ s_in.sin_addr.s_addr=INADDR_BROADCAST;
+ } else {
+- memcpy(&s_in.sin_addr,&buffer[16],4);
++ memcpy(&s_in.sin_addr, &buffer[16 + ETH_HLEN], 4);
+ }
+
+- retval=sendto(i_s_item->o_fd,buffer,buffer_len,0,
+- (struct sockaddr *)&s_in,
+- sizeof(struct sockaddr_in));
++ 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 " \
+--
+1.7.3.4
+
diff --git a/0045-ziomon-wrong-return-codes.patch b/0045-ziomon-wrong-return-codes.patch
new file mode 100644
index 0000000..566480f
--- /dev/null
+++ b/0045-ziomon-wrong-return-codes.patch
@@ -0,0 +1,53 @@
+From 7ff057ee33e5e452c09308249b36fa5da051b238 Mon Sep 17 00:00:00 2001
+From: =?UTF-8?q?Dan=20Hor=C3=A1k?= <dan at danny.cz>
+Date: Tue, 25 Jan 2011 10:57:40 +0100
+Subject: [PATCH 45/46] ziomon: wrong return codes
+
+Description: ziomon: ziomon tools return 1 when using option -h, --help and -v,
+ when return code must be 0. ziomon return 0 when using no command line
+ parameter, return code must be 1 here.
+Symptom: Confusing error code. Some automated test cases maybe fail.
+Problem: 1 as been introduced as rc for parse_parms besides error codes,
+ but is not distinguished from them when parse_params is called.
+Solution: In function parse_parms substitute "exit 1" by "exit 0"
+ in case the option -h, --help or -v was handled by the program.
+ Substitute "exit 0" by "exit 1" in case the case with no
+ commandline parametes was handled by the program.
+---
+ ziomon/ziomon | 6 +++---
+ 1 files changed, 3 insertions(+), 3 deletions(-)
+
+diff --git a/ziomon/ziomon b/ziomon/ziomon
+index b4c6e36..9af2f4e 100755
+--- a/ziomon/ziomon
++++ b/ziomon/ziomon
+@@ -124,7 +124,7 @@ function parse_params() {
+
+ if [ $# -eq 0 ]; then
+ print_usage;
+- exit 0;
++ exit 1;
+ fi
+
+ let i=0;
+@@ -132,7 +132,7 @@ function parse_params() {
+ case $1 in
+ --help|-h)
+ print_usage;
+- exit 1;;
++ exit 0;;
+ --verbose|-V)
+ (( WRP_DEBUG++ ));;
+ --duration|-d)
+@@ -152,7 +152,7 @@ function parse_params() {
+ [ $? -ne 0 ] && ((error++));;
+ --version|-v)
+ print_version;
+- exit 1;;
++ exit 0;;
+ -*)
+ echo "$WRP_TOOLNAME: Invalid option -- $1";
+ echo "Try '$WRP_TOOLNAME --help' for more information.";
+--
+1.7.3.4
+
diff --git a/0046-qethconf-process-devices-with-non-zero-subchannel.patch b/0046-qethconf-process-devices-with-non-zero-subchannel.patch
new file mode 100644
index 0000000..7445e64
--- /dev/null
+++ b/0046-qethconf-process-devices-with-non-zero-subchannel.patch
@@ -0,0 +1,30 @@
+From 037964c1dd79a637e66b51544b69243a2f4216ea Mon Sep 17 00:00:00 2001
+From: =?UTF-8?q?Dan=20Hor=C3=A1k?= <dan at danny.cz>
+Date: Tue, 25 Jan 2011 11:00:09 +0100
+Subject: [PATCH 46/46] qethconf: process devices with non-zero subchannel
+
+Description: qethconf: process devices with subchannel set != 0
+Symptom: qethconf ipa list does not show devices with subchannel
+ set != 0.
+Problem: The code matches only for subchannel set 0.
+Solution: Extend code to match for other subchannel IDs.
+---
+ qethconf/qethconf | 2 +-
+ 1 files changed, 1 insertions(+), 1 deletions(-)
+
+diff --git a/qethconf/qethconf b/qethconf/qethconf
+index 10dc902..9d9de1e 100644
+--- a/qethconf/qethconf
++++ b/qethconf/qethconf
+@@ -205,7 +205,7 @@ function __list_entries
+ fi
+ for j in ${cmd_type}
+ do
+- device_list="`cat $sys_file/0.0.*/if_name`"
++ device_list="`cat $sys_file/*.*.*/if_name`"
+ for i in ${device_list}
+ do
+ __layer2_enabled "$i" "list"
+--
+1.7.3.4
+
diff --git a/0047-wait-for-completion-of-any-pending-actions-affecting.patch b/0047-wait-for-completion-of-any-pending-actions-affecting.patch
new file mode 100644
index 0000000..3ff225c
--- /dev/null
+++ b/0047-wait-for-completion-of-any-pending-actions-affecting.patch
@@ -0,0 +1,102 @@
+From 68c07ef0b9d9731c040880e0db3570f48a85f9b8 Mon Sep 17 00:00:00 2001
+From: =?UTF-8?q?Dan=20Hor=C3=A1k?= <dan at danny.cz>
+Date: Fri, 28 Jan 2011 13:06:00 +0100
+Subject: [PATCH 47/61] wait for completion of any pending actions affecting device
+
+Delay I/O operations until all pending requests against the common I/O layer
+have been completed. The kernel now provides /proc/cio_settle file and a write
+there will be blocked until all requests are completed.
+---
+ zconf/chccwdev | 5 +++++
+ zconf/chchp | 5 +++++
+ zconf/cio_ignore | 9 +++++++++
+ 3 files changed, 19 insertions(+), 0 deletions(-)
+
+diff --git a/zconf/chccwdev b/zconf/chccwdev
+index d2a697b..03985a1 100755
+--- a/zconf/chccwdev
++++ b/zconf/chccwdev
+@@ -28,6 +28,7 @@
+ #==============================================================================
+ CMD=$(basename $0)
+ MAX_RETRIES=5
++CIO_SETTLE="/proc/cio_settle"
+
+ if [ "$(cat /proc/filesystems|grep sysfs)" = "" ]; then
+ echo "ERROR: $CMD requires sysfs support!" >&2
+@@ -160,6 +161,10 @@ while [ $# -gt 0 ]; do
+ shift
+ done
+
++if [ -w $CIO_SETTLE ] ; then
++ echo 1 > $CIO_SETTLE
++fi
++
+ #
+ # Parse the BUSIDLIST and expand the ranges and short IDs.
+ #
+diff --git a/zconf/chchp b/zconf/chchp
+index 520ce3f..4a62579 100755
+--- a/zconf/chchp
++++ b/zconf/chchp
+@@ -30,6 +30,7 @@ VERSION="%S390_TOOLS_VERSION%"
+ TOOLNAME=${0##*/}
+ MAX_CHPID_CSS=255
+ MAX_CHPID_ID=255
++CIO_SETTLE="/proc/cio_settle"
+
+ # Print help text
+ function print_help()
+@@ -408,3 +409,7 @@ for CHPID in $CHPID_LIST ; do
+ get_chpid_id TO_ID $CHPID_TO
+ loop_chpids $FROM_CSS $FROM_ID $TO_CSS $TO_ID perform_command
+ done
++
++if [ -w $CIO_SETTLE ] ; then
++ echo 1 > $CIO_SETTLE
++fi
+diff --git a/zconf/cio_ignore b/zconf/cio_ignore
+index 71dccb1..476c481 100755
+--- a/zconf/cio_ignore
++++ b/zconf/cio_ignore
+@@ -8,6 +8,8 @@
+
+ VERSION="%S390_TOOLS_VERSION%"
+ BLACKLIST="/proc/cio_ignore"
++CIO_SETTLE="/proc/cio_settle"
++WAIT_FOR_CIO=0
+ SYSINFO="/proc/sysinfo"
+ CONSDRV="/sys/bus/ccw/drivers/3215"
+ MAXCSSID=0
+@@ -706,9 +708,11 @@ while [ $# -gt 0 ] ; do
+ -r|--remove)
+ shift
+ remove_device $1
++ WAIT_FOR_CIO=1
+ ;;
+ -R|--remove-all)
+ remove_all_devices
++ WAIT_FOR_CIO=1
+ ;;
+ -l|--list)
+ list_blacklisted 1
+@@ -724,6 +728,7 @@ while [ $# -gt 0 ] ; do
+ ;;
+ -p|--purge)
+ purge
++ WAIT_FOR_CIO=1
+ ;;
+ *)
+ warn "invalid option '$1'"
+@@ -734,4 +739,8 @@ while [ $# -gt 0 ] ; do
+ shift
+ done
+
++if [ \( -w $CIO_SETTLE \) -a $WAIT_FOR_CIO = 1 ] ; then
++ echo 1 > $CIO_SETTLE
++fi
++
+ exit 0
+--
+1.7.3.5
+
diff --git a/0048-add-infrastructure-code-for-new-features.patch b/0048-add-infrastructure-code-for-new-features.patch
new file mode 100644
index 0000000..3ac141d
--- /dev/null
+++ b/0048-add-infrastructure-code-for-new-features.patch
@@ -0,0 +1,169 @@
+From 1ce37d173f564b5070d1819d0f5cec4f015bcbdf Mon Sep 17 00:00:00 2001
+From: =?UTF-8?q?Dan=20Hor=C3=A1k?= <dan at danny.cz>
+Date: Fri, 28 Jan 2011 13:14:48 +0100
+Subject: [PATCH 48/61] add infrastructure code for new features
+
+Summary: s390-tools: Add infrastructure code for new features
+Description: Add some infrastructure code from s390-tools upstream
+ to be used by new features:
+ * Add linked list implementation
+ * Add typedefs for replacing __uxx and __sxx datatypes
+ * Define S390_TOOLS_LIBDIR and S390_TOOLS_SYSCONFDIR macros
+---
+ common.mak | 4 ++
+ include/list.h | 104 +++++++++++++++++++++++++++++++++++++++++++++++++++
+ include/zt_common.h | 11 +++++
+ 3 files changed, 119 insertions(+), 0 deletions(-)
+ create mode 100644 include/list.h
+
+diff --git a/common.mak b/common.mak
+index 0a7916e..625cf6c 100644
+--- a/common.mak
++++ b/common.mak
+@@ -42,8 +42,12 @@ else
+ WARNFLAGS = -W -Wall
+ endif
+ CFLAGS = $(WARNFLAGS) -O3 -DS390_TOOLS_RELEASE=$(S390_TOOLS_RELEASE) \
++ -DS390_TOOLS_LIBDIR=$(TOOLS_LIBDIR) \
++ -DS390_TOOLS_SYSCONFDIR=$(SYSCONFDIR) \
+ $(OPT_FLAGS)
+ CXXFLAGS = $(WARNFLAGS) -O3 -DS390_TOOLS_RELEASE=$(S390_TOOLS_RELEASE) \
++ -DS390_TOOLS_LIBDIR=$(TOOLS_LIBDIR) \
++ -DS390_TOOLS_SYSCONFDIR=$(SYSCONFDIR) \
+ $(OPT_FLAGS)
+ export AS LD CC CPP AR NM STRIP OBJCOPY OBJDUMP INSTALL CFLAGS
+
+diff --git a/include/list.h b/include/list.h
+new file mode 100644
+index 0000000..b7344ed
+--- /dev/null
++++ b/include/list.h
+@@ -0,0 +1,104 @@
++/*
++ * Linked list functions
++ *
++ * Copyright IBM Corp. 2001, 2010
++ * Author(s): Carsten Otte (cotte at de.ibm.com)
++ * Michael Holzheu <holzheu at linux.vnet.ibm.com>
++ */
++
++#ifndef LIST_H
++#define LIST_H
++
++#include <stddef.h>
++
++struct list {
++ struct list *next, *prev;
++};
++
++#define EMPTY_LIST(list) { &(list), &(list) }
++
++/*
++ * Add entry to begining of list
++ */
++static inline void list_add(struct list *entry, struct list *head)
++{
++ entry->next = head->next;
++ entry->next->prev = entry;
++ head->next = entry;
++ entry->prev = head;
++}
++
++/*
++ * Add entry to end of list
++ */
++static inline void list_add_end(struct list *entry, struct list *head)
++{
++ entry->prev = head->prev;
++ entry->prev->next = entry;
++ head->prev = entry;
++ entry->next = head;
++}
++
++/*
++ * Remove entry
++ */
++static inline void list_del(struct list *entry)
++{
++ entry->next->prev = entry->prev;
++ entry->prev->next = entry->next;
++ entry->next = entry;
++ entry->prev = entry;
++}
++
++/*
++ * Check if list is empty
++ */
++static inline int list_is_empty(struct list *head)
++{
++ if ((head->next == head) && (head->prev == head))
++ return 1;
++ else
++ return 0;
++}
++
++/*
++ * Initialize list
++ */
++static inline void list_init(struct list *head)
++{
++ head->next = head;
++ head->prev = head;
++}
++
++#define list_entry(ptr, type, member) ({ \
++ const typeof(((type *) 0)->member) *__member_ptr = (ptr); \
++ (type *)((char *)__member_ptr - offsetof(type, member) ); })
++
++#define list_entry_first(ptr, type, member) \
++ list_entry((ptr)->next, type, member)
++
++#define list_entry_next(ptr, type, member) \
++ list_entry((ptr)->next, type, member)
++
++#define list_entry_prev(ptr, type, member) \
++ list_entry((ptr)->prev, type, member)
++
++/*
++ * List iterators
++ */
++#define list_get(entry, type, member) \
++ ((type *)((char *)(entry)-(unsigned long)(&((type *)0)->member)))
++
++#define list_iterate(i, head, member) \
++ for (i = list_get((head)->next, typeof(*i), member); \
++ &i->member != (head); \
++ i = list_get(i->member.next, typeof(*i), member))
++
++#define list_iterate_safe(i, head, member, n) \
++ for (i = list_get((head)->next, typeof(*i), member), \
++ n = list_get(i->member.next, typeof(*i), member); \
++ &i->member != (head); \
++ i = n, \
++ n = list_get(n->member.next, typeof(*n), member))
++
++#endif /* LIST_H */
+diff --git a/include/zt_common.h b/include/zt_common.h
+index ba9a850..7950651 100644
+--- a/include/zt_common.h
++++ b/include/zt_common.h
+@@ -21,5 +21,16 @@
+ #endif
+
+ #define RELEASE_STRING STRINGIFY (S390_TOOLS_RELEASE)
++#define TOOLS_LIBDIR STRINGIFY (S390_TOOLS_LIBDIR)
++#define TOOLS_SYSCONFDIR STRINGIFY (S390_TOOLS_SYSCONFDIR)
++
++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 /* ZT_COMMON_H */
+--
+1.7.3.5
+
diff --git a/0049-hyptop-Show-hypervisor-performance-data-on-System-z.patch b/0049-hyptop-Show-hypervisor-performance-data-on-System-z.patch
new file mode 100644
index 0000000..1b6c28f
--- /dev/null
+++ b/0049-hyptop-Show-hypervisor-performance-data-on-System-z.patch
@@ -0,0 +1,8264 @@
+From 096c2b87270417456ce9d92046838795ccd32ad1 Mon Sep 17 00:00:00 2001
+From: =?UTF-8?q?Dan=20Hor=C3=A1k?= <dan at danny.cz>
+Date: Fri, 28 Jan 2011 13:16:36 +0100
+Subject: [PATCH 49/61] hyptop: Show hypervisor performance data on System z
+
+Summary: hyptop: Show hypervisor performance data on System z
+Description: hyptop provides a dynamic real-time view of a System z hypervisor
+ environment. It 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 guests. The tool provides a curses based
+ user interface similar to the popular Linux 'top' command.
+---
+ Makefile | 2 +-
+ hyptop/Makefile | 27 +
+ hyptop/dg_debugfs.c | 86 ++++
+ hyptop/dg_debugfs.h | 20 +
+ hyptop/dg_debugfs_lpar.c | 361 ++++++++++++++
+ hyptop/dg_debugfs_vm.c | 267 ++++++++++
+ hyptop/helper.c | 383 ++++++++++++++
+ hyptop/helper.h | 100 ++++
+ hyptop/hyptop.8 | 213 ++++++++
+ hyptop/hyptop.c | 352 +++++++++++++
+ hyptop/hyptop.h | 220 +++++++++
+ hyptop/nav_desc.c | 243 +++++++++
+ hyptop/nav_desc.h | 55 ++
+ hyptop/opts.c | 416 ++++++++++++++++
+ hyptop/opts.h | 20 +
+ hyptop/sd.h | 479 ++++++++++++++++++
+ hyptop/sd_core.c | 435 ++++++++++++++++
+ hyptop/sd_cpu_items.c | 178 +++++++
+ hyptop/sd_sys_items.c | 325 ++++++++++++
+ hyptop/table.c | 1231 ++++++++++++++++++++++++++++++++++++++++++++++
+ hyptop/table.h | 424 ++++++++++++++++
+ hyptop/table_col_unit.c | 369 ++++++++++++++
+ hyptop/tbox.c | 240 +++++++++
+ hyptop/tbox.h | 52 ++
+ hyptop/win_cpu_types.c | 248 ++++++++++
+ hyptop/win_cpu_types.h | 26 +
+ hyptop/win_fields.c | 272 ++++++++++
+ hyptop/win_fields.h | 31 ++
+ hyptop/win_help.c | 122 +++++
+ hyptop/win_help.h | 23 +
+ hyptop/win_sys.c | 387 +++++++++++++++
+ hyptop/win_sys_list.c | 380 ++++++++++++++
+ 32 files changed, 7986 insertions(+), 1 deletions(-)
+ create mode 100644 hyptop/Makefile
+ create mode 100644 hyptop/dg_debugfs.c
+ create mode 100644 hyptop/dg_debugfs.h
+ create mode 100644 hyptop/dg_debugfs_lpar.c
+ create mode 100644 hyptop/dg_debugfs_vm.c
+ create mode 100644 hyptop/helper.c
+ create mode 100644 hyptop/helper.h
+ create mode 100644 hyptop/hyptop.8
+ create mode 100644 hyptop/hyptop.c
+ create mode 100644 hyptop/hyptop.h
+ create mode 100644 hyptop/nav_desc.c
+ create mode 100644 hyptop/nav_desc.h
+ create mode 100644 hyptop/opts.c
+ create mode 100644 hyptop/opts.h
+ create mode 100644 hyptop/sd.h
+ create mode 100644 hyptop/sd_core.c
+ create mode 100644 hyptop/sd_cpu_items.c
+ create mode 100644 hyptop/sd_sys_items.c
+ create mode 100644 hyptop/table.c
+ create mode 100644 hyptop/table.h
+ create mode 100644 hyptop/table_col_unit.c
+ create mode 100644 hyptop/tbox.c
+ create mode 100644 hyptop/tbox.h
+ create mode 100644 hyptop/win_cpu_types.c
+ create mode 100644 hyptop/win_cpu_types.h
+ create mode 100644 hyptop/win_fields.c
+ create mode 100644 hyptop/win_fields.h
+ create mode 100644 hyptop/win_help.c
+ create mode 100644 hyptop/win_help.h
+ create mode 100644 hyptop/win_sys.c
+ create mode 100644 hyptop/win_sys_list.c
+
+diff --git a/Makefile b/Makefile
+index 4b798bc..89c5fc5 100644
+--- a/Makefile
++++ b/Makefile
+@@ -7,7 +7,7 @@ LIB_DIRS = libvtoc libu2s
+ SUB_DIRS = $(LIB_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
++ ziomon iucvterm hyptop
+
+ all: subdirs_make
+
+diff --git a/hyptop/Makefile b/hyptop/Makefile
+new file mode 100644
+index 0000000..43d9e0f
+--- /dev/null
++++ b/hyptop/Makefile
+@@ -0,0 +1,27 @@
++include ../common.mak
++
++CPPFLAGS += -I../include
++
++all: hyptop
++
++LDLIBS += -lncurses
++
++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 \
++ win_sys_list.o win_sys.o win_fields.o \
++ win_cpu_types.o win_help.o nav_desc.o
++
++$(OBJECTS): *.h Makefile
++
++hyptop: $(OBJECTS)
++
++install: all
++ $(INSTALL) -g $(GROUP) -o $(OWNER) -m 755 hyptop $(USRSBINDIR)
++ $(INSTALL) -g $(GROUP) -o $(OWNER) -m 644 hyptop.8 $(MANDIR)/man8
++
++clean:
++ rm -f *.o *~ hyptop core
++
++.PHONY: all install clean
+diff --git a/hyptop/dg_debugfs.c b/hyptop/dg_debugfs.c
+new file mode 100644
+index 0000000..13a6342
+--- /dev/null
++++ b/hyptop/dg_debugfs.c
+@@ -0,0 +1,86 @@
++/*
++ * hyptop - Show hypervisor performance data on System z
++ *
++ * Common functions for debugfs data gatherer
++ *
++ * Copyright IBM Corp. 2010
++ * Author(s): Michael Holzheu <holzheu at linux.vnet.ibm.com>
++ */
++
++#include <errno.h>
++#include <string.h>
++#include <sys/types.h>
++#include <sys/stat.h>
++#include <fcntl.h>
++#include "helper.h"
++#include "hyptop.h"
++#include "dg_debugfs.h"
++
++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[PATH_MAX];
++ int fh;
++
++ path[0] = 0;
++ strcat(path, l_debugfs_dir);
++ strcat(path, "/s390_hypfs/");
++ strcat(path, file);
++ fh = open(path, O_RDONLY);
++ if (fh == -1)
++ return -errno;
++ else
++ return fh;
++}
++
+diff --git a/hyptop/dg_debugfs.h b/hyptop/dg_debugfs.h
+new file mode 100644
+index 0000000..f46956c
+--- /dev/null
++++ b/hyptop/dg_debugfs.h
+@@ -0,0 +1,20 @@
++/*
++ * hyptop - Show hypervisor performance data on System z
++ *
++ * Common functions for debugfs data gatherer
++ *
++ * Copyright IBM Corp. 2010
++ * Author(s): Michael Holzheu <holzheu at linux.vnet.ibm.com>
++ */
++
++#ifndef DG_DEBUGFS_H
++#define DG_DEBUGFS_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);
++
++#endif /* DG_DEBUGFS_H */
+diff --git a/hyptop/dg_debugfs_lpar.c b/hyptop/dg_debugfs_lpar.c
+new file mode 100644
+index 0000000..4ae1b6d
+--- /dev/null
++++ b/hyptop/dg_debugfs_lpar.c
+@@ -0,0 +1,361 @@
++/*
++ * hyptop - Show hypervisor performance data on System z
++ *
++ * Hyptop LPAR data gatherer that operates on debugfs
++ *
++ * Copyright IBM Corp. 2010
++ * Author(s): Michael Holzheu <holzheu at linux.vnet.ibm.com>
++ */
++
++#include <unistd.h>
++#include <string.h>
++#include <iconv.h>
++#include <errno.h>
++#include "hyptop.h"
++#include "sd.h"
++#include "helper.h"
++#include "dg_debugfs.h"
++
++#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[80];
++} __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[56];
++} __attribute__ ((packed));
++
++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)
++{
++ sd_cpu_cpu_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);
++}
++
++/*
++ * 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);
++
++ 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_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;
++ static int first = 1;
++ 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);
++ sys_hdr = l_sd_sys_fill(lpar, sys_hdr);
++ sd_sys_commit(lpar);
++ }
++
++ if (first && (time_hdr->flags & LPAR_PHYS_FLG)) {
++ l_sd_sys_root_cpu_phys_fill(sys, (void *) sys_hdr);
++ first = 0;
++ }
++ 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_diff,
++ &sd_sys_item_mgm_diff,
++ &sd_sys_item_cpu,
++ &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_cpu_cnt,
++ &sd_sys_item_cpu_diff,
++ &sd_sys_item_mgm_diff,
++ &sd_sys_item_cpu,
++ &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_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_type,
++ &sd_cpu_item_cpu_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);
++ return 0;
++}
+diff --git a/hyptop/dg_debugfs_vm.c b/hyptop/dg_debugfs_vm.c
+new file mode 100644
+index 0000000..6aec28f
+--- /dev/null
++++ b/hyptop/dg_debugfs_vm.c
+@@ -0,0 +1,267 @@
++/*
++ * hyptop - Show hypervisor performance data on System z
++ *
++ * Hyptop z/VM data gatherer that operates on debugfs
++ *
++ * Copyright IBM Corp. 2010
++ * Author(s): Michael Holzheu <holzheu at linux.vnet.ibm.com>
++ */
++
++#include <stdlib.h>
++#include <unistd.h>
++#include <errno.h>
++#include "hyptop.h"
++#include "sd.h"
++#include "helper.h"
++#include "dg_debugfs.h"
++
++#define VM_CPU_TYPE "UN"
++#define VM_CPU_ID "ALL"
++#define NAME_LEN 8
++#define DEBUGFS_FILE "diag_2fc"
++
++static u64 l_update_time_us;
++static long l_2fc_buf_size;
++
++/*
++ * 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 cpu_min;
++ 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));
++
++/*
++ * Fill "guest" with data
++ */
++static void l_sd_sys_fill(struct sd_sys *guest, struct l_diag2fc_data *data)
++{
++ struct sd_cpu *cpu;
++
++ cpu = sd_cpu_get(guest, VM_CPU_ID);
++ if (!cpu)
++ cpu = sd_cpu_new(guest, VM_CPU_ID, SD_CPU_TYPE_STR_UN,
++ data->vcpus);
++
++ sd_cpu_cnt(cpu) = data->vcpus;
++ sd_cpu_cpu_time_us_set(cpu, data->used_cpu);
++ sd_cpu_online_time_us_set(cpu, data->el_time);
++
++ sd_sys_weight_cur_set(guest, data->cpu_shares);
++ sd_sys_weight_min_set(guest, data->cpu_min);
++ 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);
++
++ 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_diff,
++ &sd_sys_item_cpu,
++ &sd_sys_item_online,
++ &sd_sys_item_mem_use,
++ &sd_sys_item_mem_max,
++ &sd_sys_item_weight_min,
++ &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_cpu,
++ &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_open(DEBUGFS_FILE);
++ if (fh < 0)
++ return fh;
++ else
++ close(fh);
++ l_2fc_buf_size = sizeof(struct l_debugfs_d2fc_hdr);
++ sd_dg_register(&dg_debugfs_vm_dg);
++ return 0;
++}
+diff --git a/hyptop/helper.c b/hyptop/helper.c
+new file mode 100644
+index 0000000..bcdea9f
+--- /dev/null
++++ b/hyptop/helper.c
+@@ -0,0 +1,383 @@
++/*
++ * hyptop - Show hypervisor performance data on System z
++ *
++ * Helper functions
++ *
++ * Copyright IBM Corp. 2010
++ * Author(s): Michael Holzheu <holzheu at linux.vnet.ibm.com>
++ * Christian Borntraeger <borntraeger at de.ibm.com>
++ */
++
++#include <mntent.h>
++#include <stdio.h>
++#include <stdlib.h>
++#include <string.h>
++#include <unistd.h>
++#include <ctype.h>
++#include <iconv.h>
++#include <limits.h>
++#include <time.h>
++#include <sys/time.h>
++#include <mntent.h>
++#include <errno.h>
++#include <assert.h>
++#include <sys/stat.h>
++#include <sys/types.h>
++#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)
++ 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 initilize iconv\n");
++}
+diff --git a/hyptop/helper.h b/hyptop/helper.h
+new file mode 100644
+index 0000000..61717f3
+--- /dev/null
++++ b/hyptop/helper.h
+@@ -0,0 +1,100 @@
++/*
++ * hyptop - Show hypervisor performance data on System z
++ *
++ * Helper functions
++ *
++ * Copyright IBM Corp. 2010
++ * Author(s): Michael Holzheu <holzheu at linux.vnet.ibm.com>
++ * Christian Borntraeger <borntraeger at de.ibm.com>
++ */
++
++#ifndef HELPER_H
++#define HELPER_H
++
++#include <stdlib.h>
++#include <limits.h>
++#include <sys/types.h>
++#include "zt_common.h"
++
++/*
++ * min/max macros
++ */
++#define MAX(x, y) ((x) > (y) ? (x) : (y))
++#define MIN(x, y) ((x) < (y) ? (x) : (y))
++#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 */
+diff --git a/hyptop/hyptop.8 b/hyptop/hyptop.8
+new file mode 100644
+index 0000000..99a729c
+--- /dev/null
++++ b/hyptop/hyptop.8
+@@ -0,0 +1,213 @@
++.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
++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 <WINDOW NAME>" " or " "\-\-window=<WINDOW NAME>"
++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 <SYSTEM>,..." " or " "\-\-sys=<SYSTEM>,..."
++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 <F_LETTER>[:<UNIT>],..." " or " "\-\-fields=<F_LETTER>[:<UNIT>],..."
++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 <F_LETTER>" " or " "\-\-sort=<F_LETTER>"
++Select sort field for current window. To reverse the sort order, specify the
++option twice. See FIELDS below for definitions.
++.TP
++.BR "\-t <TYPE>,..." " or " "\-\-cpu_types=<TYPE>,..."
++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 <SECONDS>" " or " "\-\-delay=<SECONDS>"
++Specifies the delay between screen updates.
++.TP
++.BR "\-n <ITERATIONS>" " or " "\-\-iterations=<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.
++
++.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' - 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
++
++ In "sys" window:
++ 'p' - CPU type
++ 'v' - Visualization of CPU time per second
++
++The following fields are available under z/VM:
++
++ In "sys_list" and "sys" window:
++ 'c' - CPU time per second
++ 'C' - Total CPU time
++ 'o' - Online time
++
++ In "sys_list" window:
++ '#' - Number of CPUs
++ 'u' - Used memory
++ 'a' - Maximum memory
++ 'n' - Minimum weight
++ 't' - Current weight
++ 'x' - Maximum weight
++
++ In "sys" window:
++ 'v' - Visualization of CPU time per second
++
++.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 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
+diff --git a/hyptop/hyptop.c b/hyptop/hyptop.c
+new file mode 100644
+index 0000000..c42e8b0
+--- /dev/null
++++ b/hyptop/hyptop.c
+@@ -0,0 +1,352 @@
++/*
++ * hyptop - Show hypervisor performance data on System z
++ *
++ * Main & init functions
++ *
++ * Copyright IBM Corp. 2010
++ * Author(s): Michael Holzheu <holzheu at linux.vnet.ibm.com>
++ */
++
++#include <errno.h>
++#include <stdlib.h>
++#include <ncurses.h>
++#include <time.h>
++#include <signal.h>
++#include <sys/ioctl.h>
++#include <sys/stat.h>
++#include <sys/types.h>
++#include "helper.h"
++#include "sd.h"
++#include "hyptop.h"
++#include "win_cpu_types.h"
++#include "opts.h"
++#include "dg_debugfs.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;
++}
++
++/*
++ * Init curses
++ */
++static void l_term_init(void)
++{
++ if (g.o.batch_mode_specified)
++ return;
++
++ 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 \"--batchmode\"\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
++ */
++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
++ */
++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 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;
++}
+diff --git a/hyptop/hyptop.h b/hyptop/hyptop.h
+new file mode 100644
+index 0000000..fe39976
+--- /dev/null
++++ b/hyptop/hyptop.h
+@@ -0,0 +1,220 @@
++/*
++ * hyptop - Show hypervisor performance data on System z
++ *
++ * Command line options, window definition, print functions, etc.
++ *
++ * Copyright IBM Corp. 2010
++ * Author(s): Michael Holzheu <holzheu at linux.vnet.ibm.com>
++ */
++
++#ifndef HYPTOP_H
++#define HYPTOP_H
++
++#include <stdio.h>
++#include <stdlib.h>
++#include <ncurses.h>
++#include <termios.h>
++#include "list.h"
++#include "helper.h"
++#include "table.h"
++#include "nav_desc.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 hyptop_exit(int rc);
++extern void hyptop_text_mode(void);
++
++#endif /* HYPTOP_H */
+diff --git a/hyptop/nav_desc.c b/hyptop/nav_desc.c
+new file mode 100644
+index 0000000..703489f
+--- /dev/null
++++ b/hyptop/nav_desc.c
+@@ -0,0 +1,243 @@
++/*
++ * hyptop - Show hypervisor performance data on System z
++ *
++ * Description of navigation keys
++ *
++ * Copyright IBM Corp. 2010
++ * Author(s): Michael Holzheu <holzheu at linux.vnet.ibm.com>
++ */
++
++#include <stdio.h>
++#include <string.h>
++#include <assert.h>
++#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 = {"<key>", 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 = {"<key>", 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, " ");
++}
+diff --git a/hyptop/nav_desc.h b/hyptop/nav_desc.h
+new file mode 100644
+index 0000000..05fcde9
+--- /dev/null
++++ b/hyptop/nav_desc.h
+@@ -0,0 +1,55 @@
++/*
++ * hyptop - Show hypervisor performance data on System z
++ *
++ * Description of navigation keys
++ *
++ * Copyright IBM Corp. 2010
++ * Author(s): Michael Holzheu <holzheu at linux.vnet.ibm.com>
++ */
++
++#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);
++
++struct nav_desc nav_desc_quit;
++struct nav_desc nav_desc_select_mode_enter;
++struct nav_desc nav_desc_select_mode_leave;
++struct nav_desc nav_desc_win_enter_sys;
++struct nav_desc nav_desc_win_leave_sys;
++struct nav_desc nav_desc_win_leave_sys_fast;
++struct nav_desc nav_desc_win_enter_fields;
++struct nav_desc nav_desc_win_leave_fields;
++struct nav_desc nav_desc_win_leave_fields_fast;
++struct nav_desc nav_desc_win_enter_cpu_types;
++struct nav_desc nav_desc_win_leave_cpu_types;
++struct nav_desc nav_desc_win_leave_cpu_types_fast;
++struct nav_desc nav_desc_marks_clear;
++struct nav_desc nav_desc_mark_toggle;
++struct nav_desc nav_desc_mark_toggle_view;
++struct nav_desc nav_desc_col_unit_increase;
++struct nav_desc nav_desc_col_unit_decrease;
++struct nav_desc nav_desc_row_unit_increase;
++struct nav_desc nav_desc_row_unit_decrease;
++struct nav_desc nav_desc_select_col_next;
++struct nav_desc nav_desc_select_col_prev;
++struct nav_desc nav_desc_select_col_hotkey;
++struct nav_desc nav_desc_toggle_mark_hotkey;
++struct nav_desc nav_desc_scroll_up_line;
++struct nav_desc nav_desc_scroll_down_line;
++struct nav_desc nav_desc_scroll_up_page;
++struct nav_desc nav_desc_scroll_down_page;
++struct nav_desc nav_desc_scroll_up_head;
++struct nav_desc nav_desc_scroll_down_tail;
++
++#endif /* NAV_DESC_H */
+diff --git a/hyptop/opts.c b/hyptop/opts.c
+new file mode 100644
+index 0000000..05a4126
+--- /dev/null
++++ b/hyptop/opts.c
+@@ -0,0 +1,416 @@
++/*
++ * hyptop - Show hypervisor performance data on System z
++ *
++ * Command line parsing
++ *
++ * Copyright IBM Corp. 2010
++ * Author(s): Michael Holzheu <holzheu at linux.vnet.ibm.com>
++ */
++
++#include <stdio.h>
++#include <ctype.h>
++#include "zt_common.h"
++#include "helper.h"
++#include "hyptop.h"
++#include "getopt.h"
++#include "sd.h"
++
++static const char l_copyright_str[] = "Copyright IBM Corp. 2010";
++
++/*
++ * 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 occurances 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'},
++ { 0, 0, 0, 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");
++}
++
+diff --git a/hyptop/opts.h b/hyptop/opts.h
+new file mode 100644
+index 0000000..babeda1
+--- /dev/null
++++ b/hyptop/opts.h
+@@ -0,0 +1,20 @@
++/*
++ * hyptop - Show hypervisor performance data on System z
++ *
++ * Command line parsing
++ *
++ * Copyright IBM Corp. 2010
++ * Author(s): Michael Holzheu <holzheu at linux.vnet.ibm.com>
++ */
++
++#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 */
+diff --git a/hyptop/sd.h b/hyptop/sd.h
+new file mode 100644
+index 0000000..7dd4c93
+--- /dev/null
++++ b/hyptop/sd.h
+@@ -0,0 +1,479 @@
++/*
++ * 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
++ * Author(s): Michael Holzheu <holzheu at linux.vnet.ibm.com>
++ */
++
++#ifndef SD_H
++#define SD_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 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 list list;
++ struct sd_info i;
++ u64 update_time_us;
++ u32 child_cnt;
++ u32 child_cnt_active;
++ struct list child_list;
++ u32 cpu_cnt;
++ u32 cpu_cnt_active;
++ struct list cpu_list;
++ 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 list 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_mgm_time_us_set(struct sd_cpu *cpu, u64 value)
++{
++ 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;
++ 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_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_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_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;
++ 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 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_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_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_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 *);
++
++/*
++ * Iterators
++ */
++#define sd_sys_iterate(parent, sys) \
++ list_iterate(sys, &parent->child_list, list)
++
++#define sd_cpu_iterate(parent, cpuptr) \
++ list_iterate(cpu, &parent->cpu_list, list)
++
++#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 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 */
+diff --git a/hyptop/sd_core.c b/hyptop/sd_core.c
+new file mode 100644
+index 0000000..c3aeaa0
+--- /dev/null
++++ b/hyptop/sd_core.c
+@@ -0,0 +1,435 @@
++/*
++ * 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
++ * Author(s): Michael Holzheu <holzheu at linux.vnet.ibm.com>
++ */
++
++#include <string.h>
++#include <time.h>
++#include "sd.h"
++#include "hyptop.h"
++#include "helper.h"
++#include "opts.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 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)
++{
++ 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;
++
++ 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();
++}
++
++/*
++ * Get CPU from sys by ID
++ */
++struct sd_cpu *sd_cpu_get(struct sd_sys *sys, const char* id)
++{
++ struct sd_cpu *cpu;
++
++ list_iterate(cpu, &sys->cpu_list, list) {
++ 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;
++
++ list_add_end(&cpu->list, &parent->cpu_list);
++
++ return cpu;
++}
++
++/*
++ * Get system by ID
++ */
++struct sd_sys *sd_sys_get(struct sd_sys *parent, const char* id)
++{
++ struct sd_sys *sys;
++
++ list_iterate(sys, &parent->child_list, list) {
++ 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));
++ list_init(&sys_new->child_list);
++ list_init(&sys_new->cpu_list);
++ list_init(&sys_new->list);
++
++ if (parent) {
++ sys_new->i.parent = parent;
++ parent->child_cnt++;
++ list_add_end(&sys_new->list, &parent->child_list);
++ }
++ 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;
++
++ list_iterate(cpu, &sys->cpu_list, list)
++ l_cpu_update_start(cpu);
++ list_iterate(child, &sys->child_list, list)
++ 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;
++
++ list_iterate_safe(cpu, &sys->cpu_list, list, tmp) {
++ if (!cpu->i.active) {
++ /* CPU has not been updated, remove it */
++ list_del(&cpu->list);
++ sd_cpu_free(cpu);
++ continue;
++ }
++ }
++}
++
++/*
++ * End update cycle for system
++ */
++static void l_sys_update_end(struct sd_sys *sys)
++{
++ struct sd_sys *child, *tmp;
++
++ if (sys->child_cnt_active == sys->child_cnt)
++ return;
++
++ l_cpu_update_end(sys);
++
++ list_iterate_safe(child, &sys->child_list, list, tmp) {
++ if (!child->i.active) {
++ /* child has not been updated, remove it */
++ list_del(&child->list);
++ 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',
++};
+diff --git a/hyptop/sd_cpu_items.c b/hyptop/sd_cpu_items.c
+new file mode 100644
+index 0000000..803a9b9
+--- /dev/null
++++ b/hyptop/sd_cpu_items.c
+@@ -0,0 +1,178 @@
++/*
++ * hyptop - Show hypervisor performance data on System z
++ *
++ * Provide CPU Items
++ *
++ * Copyright IBM Corp. 2010
++ * Author(s): Michael Holzheu <holzheu at linux.vnet.ibm.com>
++ */
++
++#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;
++ 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 cpu item value
++ */
++static u64 l_cpu_item_64(struct sd_cpu_item *item, struct sd_cpu *cpu)
++{
++ if (sd_cpu_state(cpu) == SD_CPU_STATE_STOPPED)
++ return 0;
++ if (!cpu->d_cur)
++ return 0;
++ if (!sd_cpu_type_selected(cpu->type))
++ return 0;
++ return l_cpu_info_u64(cpu->d_cur, item->offset);
++}
++
++/*
++ * 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_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_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_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_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_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_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, 'S', "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,
++};
+diff --git a/hyptop/sd_sys_items.c b/hyptop/sd_sys_items.c
+new file mode 100644
+index 0000000..046faf4
+--- /dev/null
++++ b/hyptop/sd_sys_items.c
+@@ -0,0 +1,325 @@
++/*
++ * hyptop - Show hypervisor performance data on System z
++ *
++ * Provide System Items
++ *
++ * Copyright IBM Corp. 2010
++ * Author(s): Michael Holzheu <holzheu at linux.vnet.ibm.com>
++ */
++
++#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 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);
++}
++
++/*
++ * 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_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_cpu_oper_cnt = {
++ .table_col = TABLE_COL_CNT_SUM('e', "#cpuope"),
++ .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('p', "#cpusp"),
++ .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_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_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_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_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_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_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, 'S', "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,
++};
+diff --git a/hyptop/table.c b/hyptop/table.c
+new file mode 100644
+index 0000000..352960f
+--- /dev/null
++++ b/hyptop/table.c
+@@ -0,0 +1,1231 @@
++/*
++ * hyptop - Show hypervisor performance data on System z
++ *
++ * Table module: Provide line mode and curses base table
++ *
++ * Copyright IBM Corp. 2010
++ * Author(s): Michael Holzheu <holzheu at linux.vnet.ibm.com>
++ */
++
++#include <stdlib.h>
++#include <stdio.h>
++#include <ncurses.h>
++#include <string.h>
++#include <errno.h>
++#include <ctype.h>
++#include "table.h"
++#include "hyptop.h"
++#include "helper.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;
++
++ list_iterate(key, &t->mark_key_list, list) {
++ 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));
++ list_add_end(&key->list, &t->mark_key_list);
++ t->mark_keys_cnt++;
++}
++
++/*
++ * Remove mark key from table
++ */
++static void l_mark_key_remove(struct table *t, char *str)
++{
++ struct table_mark_key *key;
++
++ list_iterate(key, &t->mark_key_list, list) {
++ if (strcmp(str, key->str) == 0) {
++ list_del(&key->list);
++ ht_free(key);
++ t->mark_keys_cnt--;
++ 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;
++
++ list_iterate(row, &t->row_list, list)
++ row->marked = 0;
++ list_iterate_safe(key, &t->mark_key_list, list, tmp) {
++ list_del(&key->list);
++ ht_free(key);
++ }
++ t->mark_keys_cnt = 0;
++}
++
++/*
++ * 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;
++
++ list_iterate(row, &t->row_list, list) {
++ 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);
++ list_init(&table_row->list);
++ 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));
++
++ list_init(&t->row_list);
++ list_init(&t->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;
++
++ list_iterate_safe(row, &t->row_list, list, tmp) {
++ list_del(&row->list);
++ 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];
++
++ 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;
++ }
++ }
++}
++
++/*
++ * 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);
++ list_iterate(row, &t->row_list, list) {
++ 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 (list_is_empty(&t->row_list) || !t->attr_sorted_table) {
++ list_add_end(&row->list, &t->row_list);
++ } else {
++ list_iterate(tmp, &t->row_list, list) {
++ if (l_row_less_than(t, tmp, row))
++ break;
++ }
++ list_add_end(&row->list, &tmp->list);
++ }
++ 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);
++ list_iterate(row, &t->row_list, list)
++ l_row_format(t, row);
++ l_row_format(t, t->row_last);
++}
++
++/*
++ * Sort table (TODO: Use better sorting algorithm)
++ */
++static void l_table_sort(struct table *t)
++{
++ struct table_row *row_min;
++ struct table_row *row;
++ struct table_row *tmp;
++ struct list list;
++
++ list_init(&list);
++
++ /*
++ * Sort row list into temp list
++ */
++ while (!list_is_empty(&t->row_list)) {
++ row_min = NULL;
++
++ list_iterate(row, &t->row_list, list) {
++ if (row_min == NULL)
++ row_min = row;
++ else if (l_row_less_than(t, row, row_min))
++ row_min = row;
++ }
++ list_del(&row_min->list);
++ list_add(&row_min->list, &list);
++ }
++ /*
++ * Copy temp list to original list
++ */
++ list_iterate_safe(row, &list, list, tmp) {
++ list_del(&row->list);
++ list_add_end(&row->list, &t->row_list);
++ }
++}
++
++/*
++ * 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;
++
++ list_iterate(row, &t->row_list, list) {
++ 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);
++ list_iterate(row, &t->row_list, list) {
++ 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);
++ list_iterate(row, &t->row_list, list) {
++ 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);
++ }
++}
+diff --git a/hyptop/table.h b/hyptop/table.h
+new file mode 100644
+index 0000000..c441245
+--- /dev/null
++++ b/hyptop/table.h
+@@ -0,0 +1,424 @@
++/*
++ * hyptop - Show hypervisor performance data on System z
++ *
++ * Table module: Provide line mode and curses base table
++ *
++ * Copyright IBM Corp. 2010
++ * Author(s): Michael Holzheu <holzheu at linux.vnet.ibm.com>
++ */
++
++#ifndef TABLE_H
++#define TABLE_H
++
++#include <string.h>
++#include <assert.h>
++#include "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;
++ char str[TABLE_STR_MAX];
++};
++
++/*
++ * Table Row
++ */
++struct table_row {
++ struct list list;
++ int entry_count;
++ struct table_entry *entries;
++ int marked;
++};
++
++/*
++ * Table Mark Key
++ */
++struct table_mark_key {
++ struct list list;
++ char str[TABLE_STR_MAX];
++};
++
++/*
++ * Table
++ */
++struct table {
++ struct 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 list mark_key_list;
++ unsigned int mark_keys_cnt;
++ 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;
++}
++
++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;
++}
++
++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;
++}
++
++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);
++}
++
++/*
++ * Interate over all mark keys
++ */
++#define table_iterate_mark_keys(t, key) \
++ list_iterate(key, &t->mark_key_list, list)
++
++#endif /* TABLE_H */
+diff --git a/hyptop/table_col_unit.c b/hyptop/table_col_unit.c
+new file mode 100644
+index 0000000..a8f5851
+--- /dev/null
++++ b/hyptop/table_col_unit.c
+@@ -0,0 +1,369 @@
++/*
++ * hyptop - Show hypervisor performance data on System z
++ *
++ * Table unit module: Provide different units for data
++ *
++ * Copyright IBM Corp. 2010
++ * Author(s): Michael Holzheu <holzheu at linux.vnet.ibm.com>
++ */
++
++#include <stdio.h>
++#include "table.h"
++
++#define L_VISUAL_ROW_CNT 45
++#define L_COL_FMT_STR_0 "%.0lf"
++#define L_COL_FMT_STR_2 "%.2lf"
++
++/*
++ * 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;
++
++ 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)
++{
++ 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);
++}
++
++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)
++{
++ 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)
++{
++ 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,
++};
+diff --git a/hyptop/tbox.c b/hyptop/tbox.c
+new file mode 100644
+index 0000000..82eda0c
+--- /dev/null
++++ b/hyptop/tbox.c
+@@ -0,0 +1,240 @@
++/*
++ * hyptop - Show hypervisor performance data on System z
++ *
++ * Text box: Provide scrollable text window under curses.
++ *
++ * Copyright IBM Corp. 2010
++ * Author(s): Michael Holzheu <holzheu at linux.vnet.ibm.com>
++ */
++
++#include <assert.h>
++#include <ncurses.h>
++#include "hyptop.h"
++#include "tbox.h"
++#include "helper.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;
++
++ list_iterate_safe(line, &tb->line_list, list, tmp) {
++ list_del(&line->list);
++ 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);
++ if (list_is_empty(&tb->line_list))
++ list_add(&line->list, &tb->line_list);
++ else
++ list_add(&line->list, &tb->last_line->list);
++ 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)
++ */
++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);
++ list_iterate(line, &tb->line_list, list) {
++ 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));
++ list_init(&tb->line_list);
++ return tb;
++}
+diff --git a/hyptop/tbox.h b/hyptop/tbox.h
+new file mode 100644
+index 0000000..60397f3
+--- /dev/null
++++ b/hyptop/tbox.h
+@@ -0,0 +1,52 @@
++/*
++ * hyptop - Show hypervisor performance data on System z
++ *
++ * Text box: Provide scrollable text window under curses.
++ *
++ * Copyright IBM Corp. 2010
++ * Author(s): Michael Holzheu <holzheu at linux.vnet.ibm.com>
++ */
++
++#ifndef TBOX_H
++#define TBOX_H
++
++#include "list.h"
++
++#define TBOX_MAX_STR 120
++
++struct tbox_line {
++ struct list list;
++ char *str;
++};
++
++struct tbox {
++ struct 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 */
+diff --git a/hyptop/win_cpu_types.c b/hyptop/win_cpu_types.c
+new file mode 100644
+index 0000000..7d3dff2
+--- /dev/null
++++ b/hyptop/win_cpu_types.c
+@@ -0,0 +1,248 @@
++/*
++ * hyptop - Show hypervisor performance data on System z
++ *
++ * Window "cpu_types": Select CPU types used for CPU data calculation.
++ *
++ * Copyright IBM Corp. 2010
++ * Author(s): Michael Holzheu <holzheu at linux.vnet.ibm.com>
++ */
++
++#include "helper.h"
++#include "hyptop.h"
++#include "table.h"
++#include "win_cpu_types.h"
++#include "sd.h"
++#include "nav_desc.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;
++}
+diff --git a/hyptop/win_cpu_types.h b/hyptop/win_cpu_types.h
+new file mode 100644
+index 0000000..b0f29a7
+--- /dev/null
++++ b/hyptop/win_cpu_types.h
+@@ -0,0 +1,26 @@
++/*
++ * hyptop - Show hypervisor performance data on System z
++ *
++ * Window "cpu_types": Select CPU types used for CPU data calculation.
++ *
++ * Copyright IBM Corp. 2010
++ * Author(s): Michael Holzheu <holzheu at linux.vnet.ibm.com>
++ */
++
++#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 */
+diff --git a/hyptop/win_fields.c b/hyptop/win_fields.c
+new file mode 100644
+index 0000000..8d5e233
+--- /dev/null
++++ b/hyptop/win_fields.c
+@@ -0,0 +1,272 @@
++/*
++ * hyptop - Show hypervisor performance data on System z
++ *
++ * Window "fields": Select fields dialog.
++ *
++ * Copyright IBM Corp. 2010
++ * Author(s): Michael Holzheu <holzheu at linux.vnet.ibm.com>
++ */
++
++#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;
++}
+diff --git a/hyptop/win_fields.h b/hyptop/win_fields.h
+new file mode 100644
+index 0000000..b399203
+--- /dev/null
++++ b/hyptop/win_fields.h
+@@ -0,0 +1,31 @@
++/*
++ * hyptop - Show hypervisor performance data on System z
++ *
++ * Window "fields": Select fields dialog.
++ *
++ * Copyright IBM Corp. 2010
++ * Author(s): Michael Holzheu <holzheu at linux.vnet.ibm.com>
++ */
++
++#ifndef WIN_FIELDS_H
++#define WIN_FIELDS_H
++
++#include "table.h"
++#include "hyptop.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 */
+diff --git a/hyptop/win_help.c b/hyptop/win_help.c
+new file mode 100644
+index 0000000..f18d0bb
+--- /dev/null
++++ b/hyptop/win_help.c
+@@ -0,0 +1,122 @@
++/*
++ * hyptop - Show hypervisor performance data on System z
++ *
++ * Window "help": Show online help text.
++ *
++ * Copyright IBM Corp. 2010
++ * Author(s): Michael Holzheu <holzheu at linux.vnet.ibm.com>
++ */
++
++#include "helper.h"
++#include "table.h"
++#include "hyptop.h"
++#include "win_help.h"
++#include "sd.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;
++}
+diff --git a/hyptop/win_help.h b/hyptop/win_help.h
+new file mode 100644
+index 0000000..5f4f45d
+--- /dev/null
++++ b/hyptop/win_help.h
+@@ -0,0 +1,23 @@
++/*
++ * hyptop - Show hypervisor performance data on System z
++ *
++ * Window "help": Show online help text.
++ *
++ * Copyright IBM Corp. 2010
++ * Author(s): Michael Holzheu <holzheu at linux.vnet.ibm.com>
++ */
++
++#ifndef WIN_HELP_H
++#define WIN_HELP_H
++
++#include "tbox.h"
++#include "hyptop.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 */
+diff --git a/hyptop/win_sys.c b/hyptop/win_sys.c
+new file mode 100644
+index 0000000..2039c72
+--- /dev/null
++++ b/hyptop/win_sys.c
+@@ -0,0 +1,387 @@
++/*
++ * hyptop - Show hypervisor performance data on System z
++ *
++ * Window "sys": Shows one system in more detail.
++ *
++ * Copyright IBM Corp. 2010
++ * Author(s): Michael Holzheu <holzheu at linux.vnet.ibm.com>
++ */
++
++#include <errno.h>
++#include "helper.h"
++#include "table.h"
++#include "hyptop.h"
++#include "sd.h"
++#include "win_fields.h"
++#include "win_help.h"
++#include "opts.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) / sd_cpu_cnt(cpu);
++ steal_us = sd_cpu_item_s64(&sd_cpu_item_steal_diff, cpu) /
++ sd_cpu_cnt(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)
++ 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;
++
++ 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)
++ 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;
++ unsigned int i, item_cnt;
++
++ /* 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] = "Visualization of CPU time per second";
++ 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,
++};
+diff --git a/hyptop/win_sys_list.c b/hyptop/win_sys_list.c
+new file mode 100644
+index 0000000..79eb092
+--- /dev/null
++++ b/hyptop/win_sys_list.c
+@@ -0,0 +1,380 @@
++/*
++ * 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
++ * Author(s): Michael Holzheu <holzheu at linux.vnet.ibm.com>
++ */
++
++#include "helper.h"
++#include "table.h"
++#include "hyptop.h"
++#include "win_fields.h"
++#include "win_help.h"
++#include "sd.h"
++#include "nav_desc.h"
++#include "opts.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)
++ 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,
++};
+--
+1.7.3.5
+
diff --git a/0050-cmsfs-fuse-support-for-CMS-EDF-filesystems-via-fuse.patch b/0050-cmsfs-fuse-support-for-CMS-EDF-filesystems-via-fuse.patch
new file mode 100644
index 0000000..8c50277
--- /dev/null
+++ b/0050-cmsfs-fuse-support-for-CMS-EDF-filesystems-via-fuse.patch
@@ -0,0 +1,6056 @@
+From d7e1d7b005747e4ff08db77ab0eaade80e63636a Mon Sep 17 00:00:00 2001
+From: =?UTF-8?q?Dan=20Hor=C3=A1k?= <dan at danny.cz>
+Date: Fri, 28 Jan 2011 13:19:48 +0100
+Subject: [PATCH 50/61] cmsfs-fuse: support for CMS EDF filesystems via fuse
+
+Summary: cmsfs-fuse: support for CMS EDF filesystems via fuse
+Description: Use the cmsfs-fuse command to 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.
+---
+ Makefile | 2 +-
+ README | 12 +
+ cmsfs-fuse/Makefile | 31 +
+ cmsfs-fuse/amap.c | 217 ++
+ cmsfs-fuse/cmsfs-fuse.1 | 206 ++
+ cmsfs-fuse/cmsfs-fuse.c | 4536 +++++++++++++++++++++++++++++++++++++++++
+ cmsfs-fuse/cmsfs-fuse.h | 134 ++
+ cmsfs-fuse/config.c | 122 ++
+ cmsfs-fuse/dasd.c | 224 ++
+ cmsfs-fuse/ebcdic.h | 153 ++
+ cmsfs-fuse/edf.h | 123 ++
+ cmsfs-fuse/etc/filetypes.conf | 107 +
+ cmsfs-fuse/helper.h | 54 +
+ 13 files changed, 5920 insertions(+), 1 deletions(-)
+ create mode 100644 cmsfs-fuse/Makefile
+ create mode 100644 cmsfs-fuse/amap.c
+ create mode 100644 cmsfs-fuse/cmsfs-fuse.1
+ create mode 100644 cmsfs-fuse/cmsfs-fuse.c
+ create mode 100644 cmsfs-fuse/cmsfs-fuse.h
+ create mode 100644 cmsfs-fuse/config.c
+ create mode 100644 cmsfs-fuse/dasd.c
+ create mode 100644 cmsfs-fuse/ebcdic.h
+ create mode 100644 cmsfs-fuse/edf.h
+ create mode 100644 cmsfs-fuse/etc/filetypes.conf
+ create mode 100644 cmsfs-fuse/helper.h
+
+diff --git a/Makefile b/Makefile
+index 89c5fc5..e1f6f83 100644
+--- a/Makefile
++++ b/Makefile
+@@ -7,7 +7,7 @@ LIB_DIRS = libvtoc libu2s
+ SUB_DIRS = $(LIB_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
++ ziomon iucvterm hyptop cmsfs-fuse
+
+ all: subdirs_make
+
+diff --git a/README b/README
+index ffd5e54..4335b43 100644
+--- a/README
++++ b/README
+@@ -157,6 +157,11 @@ s390-tools (1.8.2)
+ - ts-shell: Terminal server shell to authorize and control IUCV terminal
+ connections for individual Linux users.
+
++ * cmsfs-fuse:
++ Use the cmsfs-fuse command to 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.
+
+ For more information refer to the following publications:
+ * "Device Drivers, Features, and Commands" chapter "Useful Linux commands"
+@@ -179,6 +184,13 @@ Dependencies:
+ For executing the ziomon tools an installed blktrace package is required.
+ See: git://git.kernel.dk/blktrace.git
+
++ * cmsfs-fuse:
++ cmsfs-fuse depends 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.
++ For further information about FUSE see: http://fuse.sourceforge.net/
++ cmsfs-fuse requires FUSE version 2.8.1 or newer for full functionality.
++
+ Release History:
+ ================
+ 1.8.2
+diff --git a/cmsfs-fuse/Makefile b/cmsfs-fuse/Makefile
+new file mode 100644
+index 0000000..7df81e0
+--- /dev/null
++++ b/cmsfs-fuse/Makefile
+@@ -0,0 +1,31 @@
++#!/usr/bin/make -f
++
++include ../common.mak
++
++CPPFLAGS += -I../include
++
++all: cmsfs-fuse
++
++CFLAGS += -D_FILE_OFFSET_BITS=64 -DHAVE_SETXATTR -I/usr/include/fuse
++LDLIBS += -lfuse -lpthread -lrt -ldl -lm
++
++OBJECTS = cmsfs-fuse.o dasd.o amap.o config.o
++$(OBJECTS): *.h Makefile
++
++CMSFS_FUSE_DIR = $(SYSCONFDIR)/cmsfs-fuse
++CONFIG_FILES = filetypes.conf
++
++cmsfs-fuse: $(OBJECTS)
++
++install: all
++ $(INSTALL) -g $(GROUP) -o $(OWNER) -m 755 cmsfs-fuse $(USRBINDIR)
++ $(INSTALL) -g $(GROUP) -o $(OWNER) -m 644 cmsfs-fuse.1 $(MANDIR)/man1
++ $(INSTALL) -g $(GROUP) -o $(OWNER) -d $(CMSFS_FUSE_DIR)
++ for cnf in $(CONFIG_FILES); do \
++ $(INSTALL) -g $(GROUP) -o $(OWNER) -m 644 etc/$$cnf $(CMSFS_FUSE_DIR) ; \
++ done
++
++clean:
++ rm -f cmsfs-fuse *.o
++
++.PHONY: all install clean
+diff --git a/cmsfs-fuse/amap.c b/cmsfs-fuse/amap.c
+new file mode 100644
+index 0000000..04f83fc
+--- /dev/null
++++ b/cmsfs-fuse/amap.c
+@@ -0,0 +1,217 @@
++/*
++ * cmsfs-fuse - CMS EDF filesystem support for Linux
++ * Allocation map functions.
++ *
++ * Copyright IBM Corp. 2010
++ * Author(s): Jan Glauber <jang at linux.vnet.ibm.com>
++ */
++
++#define _GNU_SOURCE
++#include <stdlib.h>
++#include <unistd.h>
++#include <stdio.h>
++#include <errno.h>
++#include <sys/types.h>
++#include <stdint.h>
++#include "zt_common.h"
++#include "helper.h"
++#include "edf.h"
++#include "cmsfs-fuse.h"
++
++/*
++ * Get 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);
++
++ if (cmsfs.amap_levels == 0)
++ return cmsfs.amap;
++
++ if (level--) {
++ ptr = get_fixed_pointer(ptr + block * PTR_SIZE);
++ if (!ptr)
++ DIE("amap invalid ptr at addr: %lx\n",
++ ptr + block * PTR_SIZE);
++ return get_amap_addr(level, addr, ptr);
++ }
++ return ptr;
++}
++
++/*
++ * Mark disk address as allocated in alloc map. Unaligned addr is tolerated.
++ */
++static void amap_block_set(off_t addr)
++{
++ off_t amap = get_amap_addr(cmsfs.amap_levels, addr, cmsfs.amap);
++ int rc, block = amap_blocknumber(addr);
++ unsigned int byte, bit;
++ u8 entry;
++
++ if (block > 0)
++ addr -= 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 used */
++ BUG(entry & (1 << (7 - bit)));
++
++ entry |= (1 << (7 - bit));
++ rc = _write(&entry, sizeof(entry), amap + byte);
++ 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);
++ unsigned int byte, bit;
++ u8 entry;
++
++ if (block > 0)
++ addr -= 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);
++}
++
++/*
++ * Return the first free bit in one byte.
++ */
++static 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;
++}
++
++/*
++ * Look for the first unallocated block and return addr of allocated block.
++ */
++static off_t __get_free_block(int level, off_t amap, int block_nr)
++{
++ off_t ptr, addr = amap;
++ unsigned int bit;
++ int left, rc, i;
++ u8 entry;
++
++ if (level > 0) {
++ level--;
++ left = PTRS_PER_BLOCK;
++ while (left--) {
++ ptr = get_fixed_pointer(addr);
++ if (!ptr)
++ return 0;
++ ptr = __get_free_block(level, ptr, block_nr);
++ if (ptr)
++ return ptr;
++ addr += PTR_SIZE;
++ block_nr++;
++ }
++ return 0;
++ }
++
++ for (i = 0; i < cmsfs.blksize; i++) {
++ rc = _read(&entry, sizeof(entry), amap + i);
++ BUG(rc < 0);
++
++ if (entry != 0xff) {
++ /* get first empty bit and add to addr */
++ bit = find_first_empty_bit(entry);
++
++ /* bit -> addr */
++ addr = (i * cmsfs.blksize * 8 + cmsfs.blksize * bit)
++ + (block_nr * BYTES_PER_BLOCK);
++ amap_block_set(addr);
++ return addr;
++ }
++ }
++ return 0;
++}
++
++/*
++ * Allocate a free block and increment label block counter.
++ */
++off_t get_free_block(void)
++{
++ off_t addr;
++
++ if (cmsfs.used_blocks + cmsfs.reserved_blocks >= cmsfs.total_blocks)
++ return -ENOSPC;
++ 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--;
++ }
++}
+diff --git a/cmsfs-fuse/cmsfs-fuse.1 b/cmsfs-fuse/cmsfs-fuse.1
+new file mode 100644
+index 0000000..2dc825d
+--- /dev/null
++++ b/cmsfs-fuse/cmsfs-fuse.1
+@@ -0,0 +1,206 @@
++.\" Copyright 2010 Jan Glauber (jan.glauber at de.ibm.com)
++.\"
++.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
+diff --git a/cmsfs-fuse/cmsfs-fuse.c b/cmsfs-fuse/cmsfs-fuse.c
+new file mode 100644
+index 0000000..6c5b0b5
+--- /dev/null
++++ b/cmsfs-fuse/cmsfs-fuse.c
+@@ -0,0 +1,4536 @@
++/*
++ * cmsfs-fuse - CMS EDF filesystem support for Linux
++ * Main functions.
++ *
++ * Copyright IBM Corp. 2010
++ * Author(s): Jan Glauber <jang at linux.vnet.ibm.com>
++ */
++
++#define FUSE_USE_VERSION 26
++#define _GNU_SOURCE
++#include <unistd.h>
++#include <stdio.h>
++#include <stdint.h>
++#include <string.h>
++#include <stddef.h>
++#include <errno.h>
++#include <stdlib.h>
++#include <sys/types.h>
++#include <sys/stat.h>
++#include <sys/mman.h>
++#include <linux/fs.h>
++#include <sys/time.h>
++#include <assert.h>
++#include <fcntl.h>
++#include <sys/ioctl.h>
++#include <search.h>
++#include <iconv.h>
++#include <ctype.h>
++#include <math.h>
++#ifdef HAVE_SETXATTR
++#include <linux/xattr.h>
++#endif
++#include <fuse.h>
++#include <fuse_opt.h>
++
++#include "zt_common.h"
++#include "list.h"
++#include "helper.h"
++#include "edf.h"
++#include "cmsfs-fuse.h"
++#include "ebcdic.h"
++
++struct cmsfs cmsfs;
++struct list open_file_list;
++struct list text_type_list;
++FILE *logfile;
++
++#define PAGE_SIZE 0xfff
++#define FSNAME_MAX_LEN 50
++#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 USED_BLOCK_ADDR (cmsfs.blksize * 2 + 32)
++
++#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_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, size_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;
++
++/*
++ * 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 */
++ ssize_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;
++ /* record write state for variable headers */
++ struct var_record_state *vrstate;
++ /* 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;
++ /* commited 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 list list;
++ /* usage counter for all openers */
++ int use_count;
++ /* usage counter for all writers */
++ int write_count;
++ /* unlink flag */
++ int unlinked;
++};
++
++struct var_record_state {
++ int rlen;
++ int record_state;
++ int block_state;
++};
++
++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
++ */
++struct xattr xattr_format = { .name = "user.record_format", .size = 1 };
++struct xattr xattr_lrecl = { .name = "user.record_lrecl", .size = 5 };
++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 *) fi->fh;
++}
++
++int _read(void *buf, size_t size, off_t addr)
++{
++ if (((addr + (off_t) size - 1) & ~DATA_BLOCK_MASK) >
++ (addr & ~DATA_BLOCK_MASK))
++ DIE("read: crossing blocks addr: %lx size: %ld\n",
++ addr, size);
++ if ((addr < cmsfs.fdir) || ((size_t) addr > cmsfs.size))
++ return -EIO;
++
++ memcpy(buf, cmsfs.map + addr, size);
++ return 0;
++}
++
++int _write(const void *buf, size_t size, off_t addr)
++{
++ if (((addr + (off_t) size - 1) & ~DATA_BLOCK_MASK) >
++ (addr & ~DATA_BLOCK_MASK))
++ DIE("write: crossing blocks addr: %x size: %d\n",
++ (int)addr, (int)size);
++
++ if ((addr < (2 * cmsfs.blksize)) || ((size_t) addr > cmsfs.size))
++ return -EIO;
++
++ if (buf == NULL)
++ memset(cmsfs.map + addr, 0, size);
++ else
++ memcpy(cmsfs.map + addr, buf, size);
++ return 0;
++}
++
++int _zero(off_t addr, size_t size)
++{
++ return _write(NULL, size, addr);
++}
++
++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 + 1;
++ 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);
++
++ list_iterate(f, &open_file_list, list)
++ 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 = daylight;
++
++ /* prepare for mktime */
++ tm.tm_hour--;
++ 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((unsigned long long *) fst->name,
++ (unsigned long long *) 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, ssize_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,
++ size_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;
++}
++
++static int end_of_file(struct file *f, int record)
++{
++ if (record == f->fst->nr_records)
++ return 1;
++ return 0;
++}
++
++static void walk_fixed_data_block(struct file *f, off_t addr, int *record,
++ size_t *total, int block, int disp)
++{
++ off_t offset = block * cmsfs.blksize + disp;
++ int rlen = (f->fst->record_len > cmsfs.blksize) ?
++ cmsfs.blksize : f->fst->record_len;
++ int first = (offset % f->fst->record_len) ?
++ rlen - (offset % rlen) : 0;
++ int left = cmsfs.blksize - disp;
++
++ if (first) {
++ BUG(first > left);
++ set_record_extension(f, record, addr, first, block);
++ left -= first;
++ if (addr != NULL_BLOCK)
++ addr += first;
++ }
++
++ while (left >= rlen) {
++ /*
++ * Increment record number only after adding a possible
++ * extension. *record starts with -1 so the first is 0.
++ */
++ (*record)++;
++ if (end_of_file(f, *record))
++ return;
++
++ set_record_len(f, *record, f->fst->record_len);
++ set_record(f, record, addr, rlen, total, block);
++
++ left -= rlen;
++ if (addr != NULL_BLOCK)
++ addr += rlen;
++ }
++
++ /* partial record left */
++ if (left > 0) {
++ (*record)++;
++ if (end_of_file(f, *record))
++ return;
++
++ set_record_len(f, *record, f->fst->record_len);
++ set_record(f, record, addr, left, total, block);
++ return;
++ }
++}
++
++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, size_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 + 1))
++ 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 void cache_fixed_data_block(struct file *f, off_t addr, int *block,
++ int *record, size_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)
++ return;
++
++ walk_fixed_data_block(f, addr, record, total, *block, disp);
++ f->blist[*block].disk_addr = addr & ~DATA_BLOCK_MASK;
++ f->blist[*block].hi_record_nr = *record + 1;
++ (*block)++;
++}
++
++/*
++ * 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, size_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;
++ cache_file_fixed(f, ptr, level, block, disp, record, total);
++ /* don't increment for null block pointers */
++ if (addr)
++ addr += PTR_SIZE;
++ }
++ return 0;
++ }
++ cache_fixed_data_block(f, addr, block, record, total, 0);
++ return 0;
++}
++
++static int cache_variable_data_block(struct file *f, off_t addr, int *block,
++ int *record, int disp, size_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;
++
++ rc = walk_var_data_block(f, addr, disp, record, total, *block, skip);
++ if (rc < 0)
++ return rc;
++
++ f->blist[*block].disk_addr = addr & ~DATA_BLOCK_MASK;
++ /* 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, size_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);
++
++ list_iterate(ft, &text_type_list, list)
++ 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;
++ size_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 = f->fst->nr_records * f->fst->record_len / cmsfs.blksize;
++ if (f->fst->nr_records * f->fst->record_len % cmsfs.blksize)
++ nr++;
++ f->nr_null_blocks = nr - f->fst->nr_blocks;
++ f->fst->nr_blocks = nr;
++}
++
++static ssize_t get_file_size_fixed(struct fst_entry *fst)
++{
++ return fst->nr_records * fst->record_len;
++}
++
++static ssize_t get_file_size_variable_slow(struct fst_entry *fst)
++{
++ struct record *rec;
++ ssize_t total = 0;
++ int rc = 0;
++ struct file *f = create_file_object(fst, &rc);
++
++ if (f == NULL)
++ return rc;
++
++ rec = get_record(f, f->fst->nr_records - 1);
++ total = rec->file_start + rec->total_len;
++
++ /*
++ * Note: need to add header bytes since the record information does
++ * not contain them but get_file_size_logical will remove them...
++ */
++ total += f->fst->nr_records * VAR_RECORD_HEADER_SIZE;
++ destroy_file_object(f);
++ return total;
++}
++
++static ssize_t get_file_size_variable(struct fst_entry *fst)
++{
++ struct var_ptr vptr;
++ ssize_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. Cannot scan that block,
++ * need to scan the whole file instead...
++ */
++ total = get_file_size_variable_slow(fst);
++ goto skip;
++ }
++ 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 += (fst->nr_blocks - 1) * cmsfs.blksize;
++skip:
++ return total;
++}
++
++/*
++ * Return the file size as it is on the disk. Includes headers for
++ * variable records.
++ */
++static ssize_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 ssize_t get_file_size_logical(struct fst_entry *fst)
++{
++ ssize_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 -= 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 = fst.record_len * 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 = 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 alwasy 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;
++ list_add(&f->list, &open_file_list);
++ } else
++ f->use_count++;
++
++ if (fi->flags & O_RDWR || fi->flags & O_WRONLY)
++ f->write_count++;
++
++ fi->fh = (u64) 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), USED_BLOCK_ADDR);
++ 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;
++
++ (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 */
++ buf->f_files = cmsfs.total_blocks * cmsfs.blksize / inode_size;
++
++ buf->f_ffree = free_blocks * cmsfs.blksize / inode_size;
++ 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;
++}
++
++static int cmsfs_rename(const char *path, const char *new_path)
++{
++ char uc_old_name[MAX_FNAME];
++ char fname[8], ftype[8];
++ struct fst_entry fst;
++ char *uc_new_name;
++ struct file *f;
++ off_t fst_addr;
++ int rc;
++
++ if (cmsfs.readonly)
++ return -EACCES;
++
++ fst_addr = lookup_file(path + 1, &fst, HIDE_UNLINKED);
++ if (!fst_addr)
++ return -ENOENT;
++
++ 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;
++ struct file *f;
++ ssize_t len;
++ 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;
++
++ 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;
++
++ 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->vrstate->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)
++ return f->write_ptr;
++ new_block = 1;
++ len = cmsfs.blksize;
++ } else
++ len = ((f->write_ptr | DATA_BLOCK_MASK) + 1) - f->write_ptr;
++ }
++
++ if (new_block) {
++ if (f->fst->record_format == RECORD_LEN_VARIABLE)
++ f->vrstate->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->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->vrstate->record_state == RWS_HEADER_COMPLETE ||
++ f->vrstate->record_state == RWS_RECORD_INCOMPLETE)
++ return 0;
++
++ if (f->vrstate->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->vrstate->record_state = RWS_HEADER_COMPLETE;
++ return 1;
++ }
++
++ /* block cannot be spanned if a header starts on it */
++ if (f->vrstate->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->vrstate->rlen = vheader;
++ f->vrstate->record_state = RWS_HEADER_COMPLETE;
++ 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->vrstate->rlen = vheader;
++ f->vrstate->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;
++ }
++ 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->vrstate->rlen < rlen)
++ rlen = f->vrstate->rlen;
++ rc = _write(buf, rlen, f->write_ptr);
++ if (rc < 0)
++ return rc;
++
++ f->write_ptr += rlen;
++
++ if (f->vrstate->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->vrstate->rlen -= rlen;
++
++ BUG(f->vrstate->rlen < 0);
++ if (!f->vrstate->rlen)
++ f->vrstate->record_state = RWS_RECORD_COMPLETE;
++
++ DEBUG("%s: wrote %d record bytes\n", __func__, rlen);
++ if (size <= 0)
++ return copied;
++ }
++
++ /* record is not yet finished */
++ f->vrstate->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;
++ size_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 += 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);
++}
++
++/*
++ * 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);
++
++ if (f->fst->record_format == RECORD_LEN_VARIABLE)
++ f->vrstate->record_state = RWS_RECORD_COMPLETE;
++
++ /* 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)
++ 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)
++{
++ ssize_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) {
++ 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, 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 comitted bytes */
++ offset -= f->wcache_used;
++
++ /* 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;
++}
++
++/*
++ * Get the address of the last directory entry.
++ */
++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((unsigned long long *) fst.name,
++ (unsigned long long *) 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_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);
++ list_del(&f->list);
++ 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->vrstate = malloc(sizeof(*f->vrstate));
++ if (f->vrstate == NULL)
++ goto oom_fst;
++ memset(f->vrstate, 0, sizeof(*f->vrstate));
++
++ /*
++ * 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_vrstate;
++
++ 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_vrstate:
++ free(f->vrstate);
++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->vrstate);
++
++ 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\n");
++ fuse_opt_add_arg(outargs, "--version");
++ exit(0);
++
++ default:
++ DIE("Process arguments error\n");
++ }
++}
++
++static void map_device(int fd)
++{
++ int prot;
++
++ /* fstat on block device says st_size = 0... */
++ lseek(fd, 0, SEEK_SET);
++ cmsfs.size = lseek(fd, 0, SEEK_END);
++ DEBUG("mmap size: %lu", cmsfs.size);
++
++ /* 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)
++ DIE_PERROR("mmap failed");
++ DEBUG(" addr: %p read-only: %d\n", cmsfs.map, cmsfs.readonly);
++}
++
++static void cmsfs_init(int fd)
++{
++ map_device(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");
++
++ list_init(&text_type_list);
++ scan_conf_file(&text_type_list);
++ list_init(&open_file_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);
++ cmsfs.fd = 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;
++}
+diff --git a/cmsfs-fuse/cmsfs-fuse.h b/cmsfs-fuse/cmsfs-fuse.h
+new file mode 100644
+index 0000000..6e7fda6
+--- /dev/null
++++ b/cmsfs-fuse/cmsfs-fuse.h
+@@ -0,0 +1,134 @@
++/*
++ * cmsfs-fuse - CMS EDF filesystem support for Linux
++ * Data structures.
++ *
++ * Copyright IBM Corp. 2010
++ * Author(s): Jan Glauber <jang at linux.vnet.ibm.com>
++ */
++
++#ifndef _CMSFS_H
++#define _CMSFS_H
++
++#define _GNU_SOURCE
++#include <search.h>
++#include <iconv.h>
++#include "list.h"
++#include "list.h"
++
++#define COMP "cmsfs-fuse: "
++extern struct cmsfs cmsfs;
++
++/* conversion between absolute and relative addresses */
++#define ABS(x) ((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 */
++ size_t size;
++ /* formatted blocksize */
++ int blksize;
++ /* number of 512 byte blocks per block */
++ int nr_blocks_512;
++ /* disk info */
++ unsigned int format;
++ /* device is read only */
++ int readonly;
++ /* access permission for other users */
++ int allow_other;
++ /* 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;
++ int 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 list list;
++};
++
++
++#define MAX_TYPE_LEN 9
++
++#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)
++extern int scan_conf_file(struct list *list);
++extern int is_edf_char(int c);
++#define BYTES_PER_BLOCK (cmsfs.amap_bytes_per_block)
++
++extern int get_device_info(struct cmsfs *cmsfs);
++extern int scan_conf_file(struct 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
+diff --git a/cmsfs-fuse/config.c b/cmsfs-fuse/config.c
+new file mode 100644
+index 0000000..9f9de45
+--- /dev/null
++++ b/cmsfs-fuse/config.c
+@@ -0,0 +1,122 @@
++/*
++ * cmsfs-fuse - CMS EDF filesystem support for Linux
++ * Config option parsing.
++ *
++ * Copyright IBM Corp. 2010
++ * Author(s): Jan Glauber <jang at linux.vnet.ibm.com>
++ */
++
++#define _GNU_SOURCE
++#include <stdlib.h>
++#include <stdio.h>
++#include <sys/types.h>
++#include <sys/stat.h>
++#include <unistd.h>
++#include <fcntl.h>
++#include <string.h>
++#include <errno.h>
++#include <ctype.h>
++#include "cmsfs-fuse.h"
++#include "helper.h"
++#include "zt_common.h"
++
++#define MAX_LINE_LEN 80
++
++char *conffile;
++
++int open_conf_file(FILE **fh)
++{
++ const char *home_env = getenv("HOME");
++
++ if (home_env == NULL)
++ goto no_home;
++
++ conffile = malloc(4096);
++ if (conffile == NULL)
++ DIE_PERROR("malloc failed");
++ sprintf(conffile, "%s/.cmsfs-fuse/filetypes.conf", home_env);
++ *fh = fopen(conffile, "r");
++ if (*fh == NULL)
++ goto no_home;
++out:
++ DEBUG("using config file: %s\n", conffile);
++ return 0;
++
++no_home:
++ sprintf(conffile, "%s/%s", TOOLS_SYSCONFDIR,
++ "/cmsfs-fuse/filetypes.conf");
++ *fh = fopen(conffile, "r");
++ if (*fh == NULL) {
++ free(conffile);
++ return -ENOENT;
++ }
++ goto out;
++}
++
++void add_filetype(char *name, struct list *head)
++{
++ struct filetype *entry;
++
++ entry = malloc(sizeof(*entry));
++ if (entry == NULL)
++ DIE_PERROR("malloc failed");
++ strncpy(entry->name, name, MAX_TYPE_LEN);
++ list_add(&entry->list, head);
++}
++
++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, conffile);
++ 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, conffile);
++ return 0;
++ }
++
++ return 1;
++}
++
++int scan_conf_file(struct list *head)
++{
++ 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, head);
++ }
++ fclose(fh);
++ free(conffile);
++ return 0;
++}
+diff --git a/cmsfs-fuse/dasd.c b/cmsfs-fuse/dasd.c
+new file mode 100644
+index 0000000..d79d34d
+--- /dev/null
++++ b/cmsfs-fuse/dasd.c
+@@ -0,0 +1,224 @@
++/*
++ * cmsfs-fuse - CMS EDF filesystem support for Linux
++ * DASD specific functions.
++ *
++ * Copyright IBM Corp. 2010
++ * Author(s): Jan Glauber <jang at linux.vnet.ibm.com>
++ */
++
++#define _GNU_SOURCE
++#include <stdlib.h>
++#include <unistd.h>
++#include <fcntl.h>
++#include <stdio.h>
++#include <errno.h>
++#include <string.h>
++#include <sys/types.h>
++#include <sys/stat.h>
++#include <sys/ioctl.h>
++#include "helper.h"
++#include "edf.h"
++#include "cmsfs-fuse.h"
++
++typedef struct dasd_info {
++ 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;
++ char characteristics[64]; /* from read_device_characteristics */
++ char configuration_data[256]; /* from read_configuration_data */
++} dasd_info_t;
++
++typedef struct dasd_info2 {
++ 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;
++ char characteristics[64]; /* from read_device_characteristics */
++ 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 reserved[8];
++} dasd_info2_t;
++
++#define HDIO_GETGEO 0x0301
++#define BLKSSZGET _IO(0x12, 104)
++#define BIODASDINFO _IOR('D', 1, dasd_info_t)
++#define BIODASDINFO2 _IOR('D', 3, dasd_info2_t)
++
++/* CMS disk label starts with ASCII string "CMS1" */
++#define VOL_LABEL_EBCDIC 0xc3d4e2f1
++
++#define DASD_FORMAT_LDL 1
++
++static int disk_supported(int fd, struct cmsfs *cmsfs)
++{
++ unsigned int cms_id = VOL_LABEL_EBCDIC;
++ struct cms_label label;
++ int offset, rc;
++
++ /* check that this is a ldl disk */
++ if (cmsfs->format != DASD_FORMAT_LDL) {
++ fprintf(stderr, COMP "Disk not LDL formatted\n");
++ return 0;
++ }
++
++ /* label is on block number 3 */
++ offset = 2 * cmsfs->blksize;
++
++ rc = lseek(fd, offset, 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) {
++ fprintf(stderr, COMP "Disk is not a CMS disk\n");
++ return 0;
++ }
++
++ 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_bdev(int fd, struct cmsfs *cmsfs)
++{
++ struct dasd_info2 *info = NULL;
++
++ if (ioctl(fd, BLKSSZGET, &cmsfs->blksize) != 0)
++ DIE("ioctl error get blocksize\n");
++
++ info = malloc(sizeof(struct dasd_info2));
++ if (info == NULL)
++ DIE_PERROR("malloc failed");
++
++ /* get disk information */
++ if (ioctl(fd, BIODASDINFO2, info) == 0) {
++ /* INFO2 failed - try INFO using the same (larger) buffer */
++ if (ioctl(fd, BIODASDINFO, info) != 0)
++ DIE("ioctl dasd info failed\n");
++ }
++ cmsfs->format = info->format;
++ free(info);
++}
++
++static int blocksizes[] = { 4096, 512, 2048, 1024 };
++
++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->blksize = 0;
++
++ /*
++ * Read the blocksize from label. Unfortunately the blocksize
++ * position depends on the blocksize... time for some heuristics.
++ */
++ for (i = 0; i < ARRAY_SIZE(blocksizes); i++) {
++ offset = blocksizes[i] * 2;
++
++ 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->blksize = blocksizes[i];
++ break;
++ }
++ }
++
++ if (!cmsfs->blksize)
++ DIE("Error detecting blocksize from file!\n");
++
++ /*
++ * Hardcoded since the label doesn't contain that info.
++ * Checking the disk identifier must be sufficient.
++ */
++ cmsfs->format = DASD_FORMAT_LDL;
++}
++
++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) {
++ 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_bdev(fd, cmsfs);
++ 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");
++}
+diff --git a/cmsfs-fuse/ebcdic.h b/cmsfs-fuse/ebcdic.h
+new file mode 100644
+index 0000000..9183f09
+--- /dev/null
++++ b/cmsfs-fuse/ebcdic.h
+@@ -0,0 +1,153 @@
++/*
++ * 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
++ * Author(s): Jan Glauber <jang at linux.vnet.ibm.com>
++ */
++
++#ifndef _EBCDIC_H
++#define _EBCDIC_H
++
++#include <sys/types.h>
++#include <stdlib.h>
++
++/*
++ * 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
+diff --git a/cmsfs-fuse/edf.h b/cmsfs-fuse/edf.h
+new file mode 100644
+index 0000000..8c74a4e
+--- /dev/null
++++ b/cmsfs-fuse/edf.h
+@@ -0,0 +1,123 @@
++/*
++ * cmsfs-fuse - CMS EDF filesystem support for Linux
++ * EDF and label structures.
++ *
++ * Copyright IBM Corp. 2010
++ * Author(s): Jan Glauber <jang at linux.vnet.ibm.com>
++ */
++
++#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];
++
++ 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(unsigned long long *name, unsigned long long *type)
++{
++ if (*name == 0ULL)
++ return 0;
++
++ /* Assumption: type = 0 is not legal */
++ if (*type == 0ULL)
++ return 0;
++ return 1;
++}
++
++#endif
+diff --git a/cmsfs-fuse/etc/filetypes.conf b/cmsfs-fuse/etc/filetypes.conf
+new file mode 100644
+index 0000000..6de94dc
+--- /dev/null
++++ b/cmsfs-fuse/etc/filetypes.conf
+@@ -0,0 +1,107 @@
++#
++# 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
+diff --git a/cmsfs-fuse/helper.h b/cmsfs-fuse/helper.h
+new file mode 100644
+index 0000000..714b8a0
+--- /dev/null
++++ b/cmsfs-fuse/helper.h
+@@ -0,0 +1,54 @@
++/*
++ * cmsfs-fuse - CMS EDF filesystem support for Linux
++ * Common helper functions.
++ *
++ * Copyright IBM Corp. 2010
++ * Author(s): Jan Glauber <jang at linux.vnet.ibm.com>
++ */
++
++#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 min(x, y) ((x) < (y) ? (x) : (y))
++#define max(x, y) ((x) > (y) ? (x) : (y))
++
++#define ARRAY_SIZE(arr) (sizeof(arr) / sizeof((arr)[0]))
++
++#endif
+--
+1.7.3.5
+
diff --git a/0051-lsmem-chmem-Tools-to-manage-memory-hotplug.patch b/0051-lsmem-chmem-Tools-to-manage-memory-hotplug.patch
new file mode 100644
index 0000000..682c9dc
--- /dev/null
+++ b/0051-lsmem-chmem-Tools-to-manage-memory-hotplug.patch
@@ -0,0 +1,729 @@
+From 411a47d37b69a0763d1d7b1e3e132cfab67815cd Mon Sep 17 00:00:00 2001
+From: =?UTF-8?q?Dan=20Hor=C3=A1k?= <dan at danny.cz>
+Date: Fri, 28 Jan 2011 14:15:39 +0100
+Subject: [PATCH 51/61] lsmem/chmem: Tools to manage memory hotplug
+
+Summary: lsmem/chmem: Tools to manage memory hotplug.
+Description: With lsmem, you can display the online status of all available
+ memory. With chmem, you can set memory online or offline.
+---
+ README | 2 +
+ zconf/Makefile | 17 +++-
+ zconf/chmem | 325 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++
+ zconf/chmem.8 | 71 ++++++++++++
+ zconf/lsmem | 158 +++++++++++++++++++++++++++
+ zconf/lsmem.8 | 69 ++++++++++++
+ 6 files changed, 639 insertions(+), 3 deletions(-)
+ create mode 100644 zconf/chmem
+ create mode 100644 zconf/chmem.8
+ create mode 100644 zconf/lsmem
+ create mode 100644 zconf/lsmem.8
+
+diff --git a/README b/README
+index 4335b43..dbb1475 100644
+--- a/README
++++ b/README
+@@ -112,6 +112,8 @@ s390-tools (1.8.2)
+ adapters.
+ - cio_ignore: Query and modify the contents of the CIO device driver
+ blacklist.
++ - lsmem: Display the online status of the available memory.
++ - chmem: Set hotplug memory online or offline.
+
+ * dumpconf:
+ Allows to configure the dump device used for system dump in case a kernel
+diff --git a/zconf/Makefile b/zconf/Makefile
+index 9fe8b42..10f2b87 100644
+--- a/zconf/Makefile
++++ b/zconf/Makefile
+@@ -5,14 +5,16 @@ include ../common.mak
+
+ SCRIPTS = lsdasd lstape lscss chccwdev lsqeth lszfcp lschp chchp lszcrypt \
+ chzcrypt lsluns cio_ignore znetconf
++USRSBIN_SCRIPTS = lsmem chmem
+ MANPAGES= lsdasd.8 lstape.8 lscss.8 chccwdev.8 lsqeth.8 lszfcp.8 lschp.8 \
+- chchp.8 lszcrypt.8 chzcrypt.8 lsluns.8 cio_ignore.8 znetconf.8
++ chchp.8 lszcrypt.8 chzcrypt.8 lsluns.8 cio_ignore.8 znetconf.8 \
++ chmem.8 lsmem.8
+
+ all:
+
+ clean:
+
+-install: install-scripts install-manpages
++install: install-scripts install-manpages install-usrsbin-scripts
+ $(INSTALL) -g $(GROUP) -o $(OWNER) -m 755 lsznet.raw $(TOOLS_LIBDIR)
+ $(INSTALL) -g $(GROUP) -o $(OWNER) -m 755 znetcontrolunits \
+ $(TOOLS_LIBDIR)
+@@ -26,6 +28,15 @@ install-scripts: $(SCRIPTS)
+ chmod 755 $(BINDIR)/$$i; \
+ done
+
++install-usrsbin-scripts: $(USRSBIN_SCRIPTS)
++ @for i in $^; do \
++ cat $$i | \
++ sed -e 's+%S390_TOOLS_VERSION%+$(S390_TOOLS_RELEASE)+' \
++ >$(USRSBINDIR)/$$i; \
++ chown $(OWNER).$(GROUP) $(USRSBINDIR)/$$i; \
++ chmod 755 $(USRSBINDIR)/$$i; \
++ done
++
+ install-manpages: $(MANPAGES)
+ @if [ ! -d $(MANDIR) ]; then \
+ mkdir -p $(MANDIR)/man8; \
+@@ -38,4 +49,4 @@ install-manpages: $(MANPAGES)
+ install -o $(OWNER) -g $(GROUP) -m 644 $$i $(MANDIR)/man8; \
+ done
+
+-.PHONY: all install clean install-scripts install-manpages
++.PHONY: all install clean install-scripts install-manpages install-usrsbin-scripts
+diff --git a/zconf/chmem b/zconf/chmem
+new file mode 100644
+index 0000000..bdc25a4
+--- /dev/null
++++ b/zconf/chmem
+@@ -0,0 +1,325 @@
++#!/usr/bin/perl
++###############################################################################
++# chmem - script to show memory hotplug status.
++#
++# Copyright IBM Corp. 2010
++# Author(s): Gerald Schaefer <gerald.schaefer at de.ibm.com>
++###############################################################################
++
++use strict;
++use warnings;
++use Getopt::Long qw(:config no_ignore_case no_auto_abbrev);
++use File::Basename;
++
++my $script_name = fileparse($0);
++my $online = 0;
++my $offline = 0;
++my $memdir = "/sys/devices/system/memory";
++my $block_size = 0;
++my $total_blocks = 0;
++my $devices = {};
++my $dev_size;
++my $blocks_per_dev = 0;
++my $devs_per_block = 0;
++my $ret = 0;
++
++sub chmem_usage()
++{
++ print <<HERE;
++Usage: $script_name [OPTIONS] SIZE|RANGE
++
++The $script_name command sets a particular size or range of memory online
++or offline.
++
++Specify SIZE as <size>[m|M|g|G]. With m or M, <size> specifies the memory
++size in MB (1024 x 1024 bytes). With g or G, <size> specifies the memory size
++in GB (1024 x 1024 x 1024 bytes). The default unit is MB.
++
++Specify RANGE in the form 0x<start>-0x<end> as shown in the output of the
++lsmem command. <start> is the hexadecimal address of the first byte and <end>
++is the hexadecimal address of the last byte in the memory range.
++
++SIZE and RANGE must be aligned to the Linux memory block size, as shown in
++the output of the lsmem command.
++
++OPTIONS
++ -e, --enable
++ Set the given RANGE or SIZE of memory online.
++
++ -d, --disable
++ Set the given RANGE or SIZE of memory offline.
++
++ -h, --help
++ Print a short help text, then exit.
++
++ -v, --version
++ Print the version number, then exit.
++HERE
++}
++
++sub chmem_version()
++{
++ print "$script_name: version %S390_TOOLS_VERSION%\n";
++ print "Copyright IBM Corp. 2010\n";
++}
++
++sub chmem_get_dev_size()
++{
++ my $i = 0;
++ my $device = 0;
++ my $old_device = 0;
++
++ while (-d "$memdir/memory$i") {
++ $device = `cat $memdir/memory$i/phys_device`;
++ chomp($device);
++ if ($device > $old_device) {
++ $dev_size = int($dev_size / ($device - $old_device));
++ last;
++ }
++ $dev_size += $block_size;
++ $i++;
++ }
++}
++
++sub chmem_online($)
++{
++ my $block = shift;
++
++ qx(echo online > $memdir/memory$block/state 2>/dev/null);
++ return $? >> 8;
++}
++
++sub chmem_offline($)
++{
++ my $block = shift;
++
++ qx(echo offline > $memdir/memory$block/state 2>/dev/null);
++ return $? >> 8;;
++}
++
++sub chmem_read_attr($$$)
++# parameters: state, device, block
++{
++ my @attributes = qw(state phys_device);
++ foreach (0..1) {
++ $_[$_] = `cat $memdir/memory$_[2]/$attributes[$_]`;
++ chomp($_[$_]);
++ }
++}
++
++sub chmem_read_devices()
++{
++ my $i = 0;
++ my $device = 0;
++ my $old_device = 0;
++ my $blocks = 0;
++ my $state;
++
++ while (-d "$memdir/memory$i") {
++ chmem_read_attr($state, $device, $i);
++ if ($device != $old_device) {
++ $devices->{$old_device}->{'id'} = $old_device;
++ $devices->{$old_device}->{'blocks'} = $blocks;
++ $old_device = $device;
++ $blocks = 0;
++ }
++ if ($state eq "online") {
++ $blocks++;
++ }
++ $i++;
++ }
++ $devices->{$old_device}->{'blocks'} = $blocks;
++ $devices->{$old_device}->{'id'} = $old_device;
++}
++
++sub chmem_dev_action($$)
++{
++ my ($dev_id, $blocks) = @_;
++ my ($start_block, $end_block, $tmp_block, $max_blocks);
++ my $state;
++ my $i = 0;
++ my $count = 0;
++
++ if ($blocks_per_dev > 0) {
++ $start_block = $dev_id * $blocks_per_dev;
++ $end_block = $start_block + $blocks_per_dev - 1;
++ $max_blocks = $blocks_per_dev;
++ } else {
++ $start_block = int($dev_id / $devs_per_block);
++ $end_block = $start_block;
++ $max_blocks = 1;
++ }
++ if ($blocks > $max_blocks) {
++ $blocks = $max_blocks;
++ }
++ while ($count < $blocks && $i < $max_blocks) {
++ $tmp_block = $online ? $start_block + $i : $end_block - $i;
++ $state = `cat $memdir/memory$tmp_block/state`;
++ chomp($state);
++ if ($offline && $state eq "online") {
++ $count++ unless chmem_offline($tmp_block);
++ }
++ if ($online && $state eq "offline") {
++ $count++ unless chmem_online($tmp_block);
++ }
++ $i++;
++ }
++ return $count;
++}
++
++sub chmem_size($)
++{
++ my $size = shift;
++ my ($blocks, $dev_blocks, $dev_id);
++
++ $blocks = int($size / $block_size);
++ if ($online) {
++ foreach my $device (sort {$b->{'blocks'} <=> $a->{'blocks'} ||
++ $a->{'id'} <=> $b->{'id'}}
++ values %{$devices}) {
++ $dev_blocks = $device->{'blocks'};
++ $dev_id = $device->{'id'};
++ if ($dev_blocks < $blocks_per_dev || $dev_blocks == 0) {
++ $blocks -= chmem_dev_action($dev_id, $blocks);
++ if ($blocks == 0) {
++ last;
++ }
++ }
++ }
++ if ($blocks > 0) {
++ printf(STDERR "chmem: Could only set %lu MB of memory ".
++ "online.\n", $size - $blocks * $block_size);
++ $ret = 1;
++ }
++ } else {
++ foreach my $device (sort {$a->{'blocks'} <=> $b->{'blocks'} ||
++ $b->{'id'} <=> $a->{'id'}}
++ values %{$devices}) {
++ $dev_blocks = $device->{'blocks'};
++ $dev_id = $device->{'id'};
++ if ($dev_blocks > 0) {
++ $blocks -= chmem_dev_action($dev_id, $blocks);
++ if ($blocks == 0) {
++ last;
++ }
++ }
++ }
++ if ($blocks > 0) {
++ printf(STDERR "chmem: Could only set %lu MB of memory ".
++ "offline.\n", $size - $blocks * $block_size);
++ $ret = 1;
++ }
++ }
++}
++
++sub chmem_range($$)
++{
++ my ($start, $end) = @_;
++ my $block = 0;
++ my $state;
++
++ while ($start < $end && $block < $total_blocks - 1) {
++ $block = int($start / ($block_size << 20));
++ $state = `cat $memdir/memory$block/state`;
++ chomp($state);
++ if ($online && $state eq "offline") {
++ if (chmem_online($block)) {
++ printf(STDERR "chmem: Could not set ".
++ "0x%016x-0x%016x online\n", $start,
++ $start + ($block_size << 20) - 1);
++ $ret = 1;
++ }
++ }
++ if ($offline && $state eq "online") {
++ if (chmem_offline($block)) {
++ printf(STDERR "chmem: Could not set ".
++ "0x%016x-0x%016x offline\n", $start,
++ $start + ($block_size << 20) - 1);
++ $ret = 1;
++ }
++ }
++ $start += $block_size << 20;
++ }
++}
++
++sub chmem_check()
++{
++ unless (-d $memdir) {
++ die "chmem: No memory hotplug interface in sysfs ($memdir).\n";
++ }
++ $block_size = `cat $memdir/block_size_bytes`;
++ chomp($block_size);
++ if ($block_size =~ /(?:0x)?([[:xdigit:]]+)/) {
++ $block_size = unpack("Q", pack("H16",
++ substr("0" x 16 . $1, -16)));
++ $block_size = $block_size >> 20;
++ } else {
++ die "chmem: Unknown block size format in sysfs.\n";
++ }
++ if ($online == 0 && $offline == 0) {
++ die "chmem: Please specify one of the options -e or -d.\n";
++ }
++ if ($online == 1 && $offline == 1) {
++ die "chmem: You cannot specify both options -e and -d.\n";
++ }
++
++ while (-d "$memdir/memory$total_blocks") {
++ $total_blocks++;
++ }
++ chmem_get_dev_size();
++ if ($dev_size >= $block_size) {
++ $blocks_per_dev = int($dev_size / $block_size);
++ } else {
++ $devs_per_block = int($block_size / $dev_size);
++ }
++}
++
++sub chmem_action()
++{
++ my ($start, $end, $size, $unit);
++
++ if (!defined($ARGV[0])) {
++ die "chmem: Missing size or range.\n";
++ }
++ if ($ARGV[0] =~ /^0x([[:xdigit:]]+)-0x([[:xdigit:]]+)$/) {
++ $start = unpack("Q", pack("H16", substr("0" x 16 . $1, -16)));
++ $end = unpack("Q", pack("H16", substr("0" x 16 . $2, -16)));
++ if ($start % ($block_size << 20) ||
++ ($end + 1) % ($block_size << 20)) {
++ die "chmem: Start address and (end address + 1) must ".
++ "be aligned to memory block size ($block_size MB).\n";
++ }
++ chmem_range($start, $end);
++ } else {
++ if ($ARGV[0] =~ m/^(\d+)([mg]?)$/i) {
++ $size = $1;
++ $unit = $2 || "";
++ if ($unit =~ /g/i) {
++ $size = $size << 10;
++ }
++ if ($size % $block_size) {
++ die "chmem: Size must be aligned to memory ".
++ "block size ($block_size MB).\n";
++ }
++ chmem_size($size);
++ } else {
++ printf(STDERR "chmem: Invalid size or range: %s\n",
++ $ARGV[0]);
++ exit 1;
++ }
++ }
++}
++
++
++# Main
++unless (GetOptions('v|version' => sub {chmem_version(); exit 0;},
++ 'h|help' => sub {chmem_usage(); exit 0;},
++ 'e|enable' => \$online,
++ 'd|disable' => \$offline)) {
++ die "Try '$script_name --help' for more information.\n";
++};
++
++chmem_read_devices();
++chmem_check();
++chmem_action();
++exit $ret;
+diff --git a/zconf/chmem.8 b/zconf/chmem.8
+new file mode 100644
+index 0000000..34bea3c
+--- /dev/null
++++ b/zconf/chmem.8
+@@ -0,0 +1,71 @@
++.TH CHMEM 8 "Apr 2010" "s390-tools"
++.
++.
++.SH NAME
++chmem \- set memory online or offline.
++.
++.SH SYNOPSIS
++.B chmem
++.RB OPTIONS
++.RB [SIZE|RANGE]
++.
++.
++.SH DESCRIPTION
++The chmem command sets a particular size or range of memory online or offline.
++.
++.IP "\(hy" 2
++Specify SIZE as <size>[m|M|g|G]. With m or M, <size> specifies the memory
++size in MB (1024 x 1024 bytes). With g or G, <size> specifies the memory size
++in GB (1024 x 1024 x 1024 bytes). The default unit is MB.
++.
++.IP "\(hy" 2
++Specify RANGE in the form 0x<start>-0x<end> as shown in the output of the
++lsmem command. <start> is the hexadecimal address of the first byte and <end>
++is the hexadecimal address of the last byte in the memory range.
++.
++.PP
++SIZE and RANGE must be aligned to the Linux memory block size, as shown in
++the output of the lsmem command.
++
++Setting memory online can fail if the hypervisor does not have enough memory
++left, for example because memory was overcommitted. Setting memory offline can
++fail if Linux cannot free the memory. If only part of the requested memory can
++be set online or offline, a message tells you how much memory was set online
++or offline instead of the requested amount.
++.
++.
++.SH OPTIONS
++.TP
++.BR \-h ", " \-\-help
++Print a short help text, then exit.
++.
++.TP
++.BR \-v ", " \-\-version
++Print the version number, then exit.
++.
++.TP
++.BR \-e ", " \-\-enable
++Set the given RANGE or SIZE of memory online.
++.
++.TP
++.BR \-d ", " \-\-disable
++Set the given RANGE or SIZE of memory offline.
++.
++.
++.SH EXAMPLES
++.TP
++.B chmem --enable 1024
++This command requests 1024 MB of memory to be set online.
++.
++.TP
++.B chmem -e 2g
++This command requests 2 GB of memory to be set online.
++.
++.TP
++.B chmem --disable 0x00000000e4000000-0x00000000f3ffffff
++This command requests the memory range starting with 0x00000000e4000000
++and ending with 0x00000000f3ffffff to be set offline.
++.
++.
++.SH SEE ALSO
++.BR lsmem (8)
+diff --git a/zconf/lsmem b/zconf/lsmem
+new file mode 100644
+index 0000000..e6ed1fa
+--- /dev/null
++++ b/zconf/lsmem
+@@ -0,0 +1,158 @@
++#!/usr/bin/perl
++###############################################################################
++# lsmem - script to show memory hotplug status.
++#
++# Copyright IBM Corp. 2010
++# Author(s): Gerald Schaefer <gerald.schaefer at de.ibm.com>
++###############################################################################
++
++use strict;
++use warnings;
++use Getopt::Long qw(:config no_ignore_case no_auto_abbrev);
++use File::Basename;
++
++my $script_name = fileparse($0);
++my $memdir = "/sys/devices/system/memory";
++my $block_size = 0;
++my $list_all = 0;
++my $dev_size = 0;
++
++
++sub lsmem_read_attr($$$$)
++# parameters: state, rem, device, block_nr
++{
++ my @attributes = qw(state removable phys_device);
++ foreach (0..2) {
++ $_[$_] = `cat $memdir/memory$_[3]/$attributes[$_]`;
++ chomp($_[$_]);
++ }
++}
++
++sub lsmem_get_dev_size()
++{
++ my $i = 0;
++ my ($device, $old_device) = (0, 0);
++
++ while (-d "$memdir/memory$i") {
++ $device = `cat $memdir/memory$i/phys_device`;
++ chomp($device);
++ if ($device > $old_device) {
++ $dev_size = int($dev_size / ($device - $old_device));
++ last;
++ }
++ $dev_size += $block_size;
++ $i++;
++ }
++}
++
++sub lsmem_list()
++{
++ my $i = 0;
++ my ($start, $end, $size) = (0, 0, 0);
++ my ($state, $old_state) = (0, 0);
++ my ($rem, $old_rem) = (0, 0);
++ my ($device, $old_device) = (0, 0);
++ my ($mem_online, $mem_offline) = (0, 0);
++ my ($last_block, $end_dev) = (0, 0);
++
++ if (-d "$memdir/memory0") {
++ lsmem_read_attr($old_state, $old_rem, $old_device, 0);
++ } else {
++ die "lsmem: No memory hotplug interface in sysfs ($memdir).\n";
++ }
++
++ $block_size = `cat $memdir/block_size_bytes`;
++ chomp($block_size);
++ if ($block_size =~ /(?:0x)?([[:xdigit:]]+)/) {
++ $block_size = unpack("Q", pack("H16",
++ substr("0" x 16 . $1, -16)));
++ $block_size = $block_size >> 20;
++ } else {
++ die "lsmem: Unknown block size format in sysfs.\n";
++ }
++ lsmem_get_dev_size();
++
++ print <<HERE;
++Address Range Size (MB) State Removable Device
++===============================================================================
++HERE
++ while (-d "$memdir/memory$i") {
++ $i++;
++ if (-d "$memdir/memory$i") {
++ lsmem_read_attr($state, $rem, $device, $i);
++ } else {
++ $last_block = 1;
++ }
++ if ($state ne $old_state || $rem != $old_rem || $list_all ||
++ $last_block) {
++ $end = $i * ($block_size << 20) - 1;
++ $size = ($end - $start + 1) >> 20;
++ if ($old_state eq "going-offline") {
++ $old_state = "on->off";
++ }
++ printf("0x%016x-0x%016x %10lu %-7s ", $start, $end,
++ $size, $old_state);
++ if ($old_state eq "online") {
++ printf(" %-9s ", $old_rem ? "yes" : "no");
++ $mem_online += $size;
++ } else {
++ printf(" %-9s ", "-");
++ $mem_offline += $size;
++ }
++ $end_dev = ($end / $dev_size) >> 20;
++ if ($old_device == $end_dev) {
++ printf("%d\n", $old_device);
++ } else {
++ printf("%d-%d\n", $old_device, $end_dev);
++ }
++ $old_state = $state;
++ $old_rem = $rem;
++ $old_device = $device;
++ $start = $end + 1;
++ }
++ }
++ printf("\n");
++ printf("Memory device size : %lu MB\n", $dev_size);
++ printf("Memory block size : %lu MB\n", $block_size);
++ printf("Total online memory : %lu MB\n", $mem_online);
++ printf("Total offline memory: %lu MB\n", $mem_offline);
++}
++
++sub lsmem_usage()
++{
++ print <<HERE;
++Usage: $script_name [OPTIONS]
++
++The $script_name command lists the ranges of available memory with their online
++status. The listed memory blocks correspond to the memory block representation
++in sysfs. The command also shows the memory block size, the device size, and
++the amount of memory in online and offline state.
++
++OPTIONS
++ -a, --all
++ List each individual memory block, instead of combining memory blocks
++ with similar attributes.
++
++ -h, --help
++ Print a short help text, then exit.
++
++ -v, --version
++ Print the version number, then exit.
++HERE
++}
++
++sub lsmem_version()
++{
++ print "$script_name: version %S390_TOOLS_VERSION%\n";
++ print "Copyright IBM Corp. 2010\n";
++}
++
++
++# Main
++unless (GetOptions('v|version' => sub {lsmem_version(); exit 0;},
++ 'h|help' => sub {lsmem_usage(); exit 0;},
++ 'a|all' => \$list_all)) {
++ die "Try '$script_name --help' for more information.\n";
++};
++
++lsmem_list();
+diff --git a/zconf/lsmem.8 b/zconf/lsmem.8
+new file mode 100644
+index 0000000..ed052ea
+--- /dev/null
++++ b/zconf/lsmem.8
+@@ -0,0 +1,69 @@
++.TH LSMEM 8 "Apr 2010" s390\-tools
++.
++.
++.SH NAME
++lsmem \- list the ranges of available memory with their online status.
++.
++.
++.SH SYNOPSIS
++.B lsmem
++.RB [OPTIONS]
++.
++.
++.SH DESCRIPTION
++The lsmem command lists the ranges of available memory with their online
++status. The listed memory blocks correspond to the memory block representation
++in sysfs. The command also shows the memory block size, the device size, and
++the amount of memory in online and offline state.
++.
++.SS "Column description"
++.
++.TP 4
++Address Range
++Start and end address of the memory range.
++.
++.TP 4
++Size
++Size of the memory range in MB (1024 x 1024 bytes).
++.
++.TP 4
++State
++Indication of the online status of the memory range. State on->off means
++that the address range is in transition from online to offline.
++.
++.TP 4
++Removable
++"yes" if the memory range can be set offline, "no" if it cannot be set offline.
++A dash ("\-") means that the range is already offline.
++.
++.TP 4
++Device
++Device number or numbers that correspond to the memory range.
++
++Each device represents a memory unit for the hypervisor in control of the
++memory. The hypervisor cannot reuse a memory unit unless the corresponding
++memory range is completely offline. For best memory utilization, each device
++should either be completely online or completely offline.
++
++The chmem command with the size parameter automatically chooses the best suited
++device or devices when setting memory online or offline. The device size depends
++on the hypervisor and on the amount of total online and offline memory.
++.
++.
++.SH OPTIONS
++.TP
++.BR \-a ", " \-\-all
++List each individual memory block, instead of combining memory blocks with
++similar attributes.
++.
++.TP
++.BR \-h ", " \-\-help
++Print a short help text, then exit.
++.
++.TP
++.BR \-v ", " \-\-version
++Print the version number, then exit.
++.
++.
++.SH SEE ALSO
++.BR chmem (8)
+--
+1.7.3.5
+
diff --git a/0052-dumpconf-Prevent-re-IPL-loop-for-dump-on-panic.patch b/0052-dumpconf-Prevent-re-IPL-loop-for-dump-on-panic.patch
new file mode 100644
index 0000000..71d7f16
--- /dev/null
+++ b/0052-dumpconf-Prevent-re-IPL-loop-for-dump-on-panic.patch
@@ -0,0 +1,562 @@
+From 9d93b66b6eda5f3dbaf6804663af21927c3aab8f Mon Sep 17 00:00:00 2001
+From: =?UTF-8?q?Dan=20Hor=C3=A1k?= <dan at danny.cz>
+Date: Fri, 28 Jan 2011 14:17:36 +0100
+Subject: [PATCH 52/61] dumpconf: Prevent re-IPL loop for dump on panic
+
+Summary: dumpconf: Prevent re-IPL loop for dump on panic.
+Description: A new keyword DELAY_MINUTES is introduced in the dumpconf.
+ configuration file. Using this keyword the activation of dumpconf
+ can be delayed in order to prevent potential re-IPL loops.
+---
+ etc/init.d/dumpconf | 271 ++++++++++++++++++++++++++++++++++--------------
+ etc/sysconfig/dumpconf | 10 ++-
+ 2 files changed, 202 insertions(+), 79 deletions(-)
+
+diff --git a/etc/init.d/dumpconf b/etc/init.d/dumpconf
+index 1dd898d..27f52e4 100755
+--- a/etc/init.d/dumpconf
++++ b/etc/init.d/dumpconf
+@@ -15,25 +15,48 @@
+ # chkconfig: 0123456 01 99
+
+ DUMP_CONFIG_FILE=/etc/sysconfig/dumpconf
++CMDFULL=$0
++CMD="dumpconf"
++LOCKFILE=/var/lock/subsys/$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
+- echo "no config file found: $DUMP_CONFIG_FILE"
++ pr_error "no config file found: $DUMP_CONFIG_FILE"
+ exit 1
+ fi
+
+ if [ "$(cat /proc/filesystems|grep sysfs)" = "" ]; then
+- echo "no sysfs found" >&2
++ pr_error "no sysfs found"
+ exit 1
+ fi
+
+ SYSFSDIR=$(cat /proc/mounts|awk '$3=="sysfs"{print $2; exit}')
+ if [ "$SYSFSDIR" = "" ]; then
+- echo "sysfs not mounted" >&2
++ pr_error "sysfs not mounted"
+ exit 1
+ fi
+
+@@ -41,12 +64,12 @@ check_environment()
+ ON_PANIC_CONFIG_FILE=/$SYSFSDIR/firmware/shutdown_act\
+ ions/on_panic
+ if [ ! -d $DUMP_CONFIG_DIR ]; then
+- echo "kernel has no dump on panic support"
++ pr_info "kernel has no dump on panic support"
+ exit 0
+ fi
+ REIPL_CONFIG_DIR=/$SYSFSDIR/firmware/reipl
+ if [ ! -d $REIPL_CONFIG_DIR ]; then
+- echo "kernel has no dump on panic support"
++ pr_info "kernel has no dump on panic support"
+ exit 0
+ fi
+ VMCMD_CONFIG_DIR=/$SYSFSDIR/firmware/vmcmd
+@@ -90,6 +113,43 @@ Try 'dumpconf --help' for more information.
+ EOF
+ }
+
++cleanup_pidfile()
++{
++ if [ $(ps $1 | grep $CMD | wc -l) -eq 0 ]; then
++ rm -f $PIDFILE
++ fi
++}
++
++handle_stop_request()
++{
++ rm -f $PIDFILE 2>/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()
+ {
+@@ -98,7 +158,7 @@ verify_ccw_dump_device()
+ line=$(lsdasd $1)
+ fi
+ if [ "$line" == "" ]; then
+- echo "WARNING: device $1 not found!"
++ pr_info "WARNING: device $1 not found!"
+ return 1
+ fi
+ found=false
+@@ -115,7 +175,7 @@ verify_ccw_dump_device()
+ if [ $? == 0 ]; then
+ return 0
+ else
+- echo "WARNING: $1 is no valid dump device!"
++ pr_info "WARNING: $1 is no valid dump device!"
+ return 1
+ fi
+ }
+@@ -166,28 +226,28 @@ setup_device()
+ echo $DEV > $1/$2/device
+ else
+ RETVAL=1
+- echo "ERROR: Invalid DEVICE '$DEVICE'." $ERRMSG >&2
++ 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
+- echo "ERROR: Invalid WWPN '$WWPN'." $ERRMSG >&2
++ pr_error "ERROR: Invalid WWPN '$WWPN'." $ERRMSG
+ return
+ fi
+ echo $LUN > $1/fcp/lun 2>/dev/null || RETVAL=1
+ if [ $RETVAL -eq 1 ]; then
+- echo "ERROR: Invalid LUN '$LUN'." $ERRMSG >&2
++ pr_error "ERROR: Invalid LUN '$LUN'." $ERRMSG
+ return
+ fi
+ echo $BOOTPROG > $1/fcp/bootprog 2>/dev/null || RETVAL=1
+ if [ $RETVAL -eq 1 ]; then
+- echo "ERROR: Invalid BOOTPROG '$BOOTPROG'." $ERRMSG >&2
++ 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
+- echo "ERROR: Invalid BR_LBA '$BR_LBA'." $ERRMSG >&2
++ pr_error "ERROR: Invalid BR_LBA '$BR_LBA'." $ERRMSG
+ return
+ fi
+ fi
+@@ -201,7 +261,7 @@ setup_nss_device()
+ setup_reipl()
+ {
+ if [ "$REIPL_TYPE" == "" ]; then
+- echo "reipl on panic configured: Using default reipl values."
++ pr_info "reipl on panic configured: Using default reipl values."
+ return
+ fi
+
+@@ -210,7 +270,7 @@ setup_reipl()
+ elif [ "$REIPL_TYPE" == "nss" ]; then
+ setup_nss_device $REIPL_CONFIG_DIR
+ else
+- echo "ERROR: Unknown reipl type '$REIPL_TYPE'." $ERRMSG >&2
++ pr_error "ERROR: Unknown reipl type '$REIPL_TYPE'." $ERRMSG
+ RETVAL=1
+ return
+ fi
+@@ -221,7 +281,7 @@ setup_reipl()
+ return
+ fi
+
+- echo "$REIPL_TYPE reipl device configured."
++ pr_info "$REIPL_TYPE reipl device configured."
+ }
+
+ setup_dump()
+@@ -229,7 +289,7 @@ setup_dump()
+ if [ "$DUMP_TYPE" == "ccw" ] || [ "$DUMP_TYPE" == "fcp" ]; then
+ setup_device $DUMP_CONFIG_DIR $DUMP_TYPE
+ elif [ "$DUMP_TYPE" != "none" ]; then
+- echo "ERROR: Unknown dump type '$DUMP_TYPE'." $ERRMSG >&2
++ pr_error "ERROR: Unknown dump type '$DUMP_TYPE'." $ERRMSG
+ RETVAL=1
+ return
+ fi
+@@ -241,7 +301,7 @@ setup_dump()
+ return
+ fi
+
+- echo "$ON_PANIC on panic configured: Using $DUMP_TYPE dump device."
++ pr_info "$ON_PANIC on panic configured: Using $DUMP_TYPE dump device."
+ }
+
+ setup_on_panic_vmcmd()
+@@ -257,69 +317,69 @@ setup_on_panic_vmcmd()
+ fi
+ done
+ if [ ! -d $VMCMD_CONFIG_DIR ]; then
+- echo "ERROR: No vmcmd support. Are you running on LPAR?" >&2
++ pr_error "ERROR: No vmcmd support. Are you running on LPAR?"
+ RETVAL=1
+ elif [ "$VMCMD" == "" ]; then
+- echo "ERROR: No VMCMD_x keyword specified." $ERRMSG >&2
++ 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
+- echo "vmcmd on panic configured:"
+- echo -e "$VMCMD"
++ pr_info "vmcmd on panic configured:"
++ pr_info -e "$VMCMD"
+ fi
+ }
+
+ print_fcp_device()
+ {
+ DEVICE=$(cat $1/fcp/device) || RETVAL=1
+- echo "device..: $DEVICE"
++ pr_info "device..: $DEVICE"
+ WWPN=$(cat $1/fcp/wwpn) || RETVAL=1
+- echo "wwpn....: $WWPN"
++ pr_info "wwpn....: $WWPN"
+ LUN=$(cat $1/fcp/lun) || RETVAL=1
+- echo "lun.....: $LUN"
++ pr_info "lun.....: $LUN"
+ BOOTPROG=$(cat $1/fcp/bootprog) || RETVAL=1
+- echo "bootprog: $BOOTPROG"
++ pr_info "bootprog: $BOOTPROG"
+ BR_LBA=$(cat $1/fcp/br_lba) || RETVAL=1
+- echo "br_lba..: $BR_LBA"
++ pr_info "br_lba..: $BR_LBA"
+ }
+
+ print_ccw_device()
+ {
+ DEVICE=$(cat $1/ccw/device) || RETVAL=1
+- echo "device..: $DEVICE"
++ pr_info "device..: $DEVICE"
+ }
+
+ print_nss_name()
+ {
+ NAME=$(cat $1/nss/device) || RETVAL=1
+- echo "device..: $NAME"
++ pr_info "device..: $NAME"
+ }
+
+ status_dump()
+ {
+ CONF_DUMP_TYPE=$(cat $DUMP_CONFIG_DIR/dump_type) || RETVAL=1
+ if [ "$CONF_DUMP_TYPE" == "none" ]; then
+- echo "type....: no dump device configured"
++ pr_info "type....: no dump device configured"
+ elif [ "$CONF_DUMP_TYPE" == "ccw" ]; then
+- echo "type....: ccw"
++ 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
+- echo "type....: fcp"
++ pr_info "type....: fcp"
+ print_fcp_device $DUMP_CONFIG_DIR
+ else
+- echo "ERROR: Unknown dump device type '$CONF_DUMP_TYPE'!" >&2
+- echo " Please check if you have the latest dumpconf package!" >&2
++ 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
+- echo "type....: $REIPL_TYPE"
++ pr_info "type....: $REIPL_TYPE"
+ if [ "$REIPL_TYPE" == "ccw" ]; then
+ print_ccw_device $REIPL_CONFIG_DIR
+ elif [ "$REIPL_TYPE" == "fcp" ]; then
+@@ -327,16 +387,16 @@ status_reipl()
+ elif [ "$REIPL_TYPE" == "nss" ]; then
+ print_nss_name $REIPL_CONFIG_DIR
+ else
+- echo "ERROR: Unknown reipl device type '$REIPL_TYPE'!" >&2
+- echo " Please check if you have the latest dumpconf package!" >&2
++ pr_error "ERROR: Unknown reipl device type '$REIPL_TYPE'!"
++ pr_error " Please check if you have the latest dumpconf package!"
+ fi
+ }
+
+ status_dump_reipl()
+ {
+- echo -e "\ndump:"
++ pr_info -e "\ndump:"
+ status_dump
+- echo -e "\nreipl:"
++ pr_info -e "\nreipl:"
+ status_reipl
+ }
+
+@@ -345,33 +405,65 @@ status_vmcmd()
+ {
+ VMCMD=$(cat $VMCMD_CONFIG_DIR/on_panic) || RETVAL=1
+ if [ "$VMCMD" == "" ]; then
+- echo "WARNING: No VM command specified!"
++ pr_info "WARNING: No VM command specified!"
+ else
+- echo "---------------"
+- echo "$VMCMD"
++ 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 [ $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="stop"
+ fi
+
+- if [ "$ON_PANIC" == "reipl" ]; then
+- setup_reipl
+- elif [ "$ON_PANIC" == "dump" ] || [ "$ON_PANIC" == "dump_reipl" ]; then
+- setup_dump
+- elif [ "$ON_PANIC" == "vmcmd" ]; then
+- setup_on_panic_vmcmd
+- elif [ "$ON_PANIC" == "stop" ]; then
+- echo "stop on panic configured."
+- else
+- echo "ERROR: Unknown 'on panic' type '$ON_PANIC'." $ERRMSG >&2
+- RETVAL=1
+- 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 $RETVAL
++ return
+ fi
+
+ echo $ON_PANIC > $ON_PANIC_CONFIG_FILE 2> /dev/null || RETVAL=1
+@@ -380,20 +472,21 @@ start()
+
+ if [ $RETVAL -eq 1 ]; then
+ echo stop > $ON_PANIC_CONFIG_FILE
+- echo "ERROR: $ON_PANIC not supported by hardware!" >&2
++ pr_error "ERROR: $ON_PANIC not supported by hardware!"
+ fi
+-
+- return $RETVAL
+ }
+
+ stop()
+ {
++ if [ -f $PIDFILE ]; then
++ kill -TERM $(cat $PIDFILE)
++ fi
+ echo none > $DUMP_CONFIG_DIR/dump_type || RETVAL=1
+ echo stop > $ON_PANIC_CONFIG_FILE || RETVAL=1
+ if [ $RETVAL -eq 0 ]; then
+- echo "Dump on panic is disabled now"
++ pr_info "Dump on panic is disabled now"
+ else
+- echo "Disabling dump on panic failed" >&2
++ pr_error "Disabling dump on panic failed"
+ fi
+ return $RETVAL
+ }
+@@ -401,34 +494,55 @@ stop()
+ status()
+ {
+ ON_PANIC=$(cat $ON_PANIC_CONFIG_FILE) || RETVAL=1
+- echo "on_panic: $ON_PANIC"
+- if [ "$ON_PANIC" == "vmcmd" ]; then
+- status_vmcmd
+- elif [ "$ON_PANIC" == "reipl" ]; then
+- status_reipl
+- elif [ "$ON_PANIC" == "dump" ]; then
+- status_dump
+- elif [ "$ON_PANIC" == "dump_reipl" ]; then
+- status_dump_reipl
+- elif [ "$ON_PANIC" != "stop" ]; then
+- echo "ERROR: Unknown on_panic type '$ON_PANIC'" >&2
++ 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
+ }
+
+-if [ "$1" = "-h" ] || [ "$1" = "--help" ]; then
+- printhelp
+- exit 0
+-elif [ "$1" = "-v" ] || [ "$1" = "--version" ]; then
+- printversion
+- exit 0
+-fi
++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
+ start|restart|reload|force-reload|try-restart)
+- start
++ start $2
+ ;;
+ stop)
+ stop
+@@ -439,6 +553,7 @@ case "$1" in
+ *)
+ print_invalid_option $1
+ RETVAL=1
++ ;;
+ esac
+
+ exit $RETVAL
+diff --git a/etc/sysconfig/dumpconf b/etc/sysconfig/dumpconf
+index cef621b..155a2cc 100644
+--- a/etc/sysconfig/dumpconf
++++ b/etc/sysconfig/dumpconf
+@@ -13,13 +13,19 @@
+ # /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)
+@@ -48,5 +54,7 @@
+ #
+ # Re-IPL on panic
+ # 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
+--
+1.7.3.5
+
diff --git a/0053-ttyrun-run-a-program-if-a-terminal-device-is-availab.patch b/0053-ttyrun-run-a-program-if-a-terminal-device-is-availab.patch
new file mode 100644
index 0000000..3af7a01
--- /dev/null
+++ b/0053-ttyrun-run-a-program-if-a-terminal-device-is-availab.patch
@@ -0,0 +1,410 @@
+From bc6e654149018090b7954e6667d3c7e7654625f6 Mon Sep 17 00:00:00 2001
+From: =?UTF-8?q?Dan=20Hor=C3=A1k?= <dan at danny.cz>
+Date: Fri, 28 Jan 2011 14:18:39 +0100
+Subject: [PATCH 53/61] ttyrun: run a program if a terminal device is available
+
+Summary: ttyrun: run a program if a terminal device is available
+Description: Depending on your setup, Linux on System z might or might not
+ provide a particular terminal or console. ttyrun safely starts
+ getty programs and prevents respawns through the init program
+ if a terminal is not available.
+---
+ iucvterm/doc/Makefile | 2 +-
+ iucvterm/doc/ttyrun.8 | 146 +++++++++++++++++++++++++++++++++++++++
+ iucvterm/src/Makefile | 11 +++-
+ iucvterm/src/ttyrun.c | 183 +++++++++++++++++++++++++++++++++++++++++++++++++
+ 4 files changed, 339 insertions(+), 3 deletions(-)
+ create mode 100644 iucvterm/doc/ttyrun.8
+ create mode 100644 iucvterm/src/ttyrun.c
+
+diff --git a/iucvterm/doc/Makefile b/iucvterm/doc/Makefile
+index 5815f21..a646765 100644
+--- a/iucvterm/doc/Makefile
++++ b/iucvterm/doc/Makefile
+@@ -2,7 +2,7 @@
+
+ include ../../common.mak
+
+-MANS = iucvconn.1 iucvtty.1 ts-shell.1 hvc_iucv.9 chiucvallow.8
++MANS = iucvconn.1 iucvtty.1 ts-shell.1 hvc_iucv.9 chiucvallow.8 ttyrun.8
+
+ all:
+
+diff --git a/iucvterm/doc/ttyrun.8 b/iucvterm/doc/ttyrun.8
+new file mode 100644
+index 0000000..fc7a16f
+--- /dev/null
++++ b/iucvterm/doc/ttyrun.8
+@@ -0,0 +1,146 @@
++.\" ttyrun.8
++.\"
++.\"
++.\" Copyright IBM Corp. 2010
++.\" Author(s): Hendrik Brueckner <brueckner at linux.vnet.ibm.com>
++.\" -------------------------------------------------------------------------
++.TH "ttyrun" "8" "April 2010" "s390-tools" "System Management Commands"
++.LO 8
++.
++.ds s ttyrun
++.
++.
++.SH NAME
++ttyrun \- Start a program if a specified terminal device is available
++.
++.
++.
++.SH SYNOPSIS
++.B \*s
++.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 \-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)
+diff --git a/iucvterm/src/Makefile b/iucvterm/src/Makefile
+index f1f8f7c..369c887 100644
+--- a/iucvterm/src/Makefile
++++ b/iucvterm/src/Makefile
+@@ -11,20 +11,27 @@ CPPFLAGS += -DUSE_NLS -DGETTEXT_TEXTDOMAIN=\"$(GETTEXT_TEXTDOMAIN)\"
+ #CPPFLAGS += -D__DEBUG__
+
+ PROGRAMS = iucvconn iucvtty
++SYSTOOLS = ttyrun
+
+-all: $(PROGRAMS)
++all: $(PROGRAMS) $(SYSTOOLS)
+ check:
+ install:
+ for prg in $(PROGRAMS); do \
+ $(INSTALL) -g $(GROUP) -o $(OWNER) -m 755 $$prg $(USRBINDIR) ; \
+ done
++ for prg in $(SYSTOOLS); do \
++ $(INSTALL) -g $(GROUP) -o $(OWNER) -m 755 $$prg $(BINDIR) ; \
++ done
+
+ clean:
+- -rm -f *.o $(PROGRAMS)
++ -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
+diff --git a/iucvterm/src/ttyrun.c b/iucvterm/src/ttyrun.c
+new file mode 100644
+index 0000000..55c2bc2
+--- /dev/null
++++ b/iucvterm/src/ttyrun.c
+@@ -0,0 +1,183 @@
++/*
++ * 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 IBM Corp. 2010
++ * Author(s): Hendrik Brueckner <brueckner at linux.vnet.ibm.com>
++ */
++#include <errno.h>
++#include <getopt.h>
++#include <limits.h>
++#include <signal.h>
++#include <stdlib.h>
++#include <stdio.h>
++#include <string.h>
++#include <sys/types.h>
++#include <syslog.h>
++#include <fcntl.h>
++#include <unistd.h>
++
++#include "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] <term> <program> [<program_options>]\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"
++"-h, --help Displays this help, then exits.\n"
++"-v, --version Displays version information, then exits.\n";
++
++static void help_exit(const char *prg)
++{
++ printf(usage, prg, prg);
++ exit(EXIT_SUCCESS);
++}
++
++static void 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\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'},
++ { NULL, no_argument, NULL, 0 },
++};
++
++int main(int argc, char *argv[])
++{
++ int rc, tty, i, c, index, done, term_index;
++ 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 = 0;
++ while (!done) {
++ c = getopt_long(argc, argv, "-hve:", 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 '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) {
++ 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);
++}
+--
+1.7.3.5
+
diff --git a/0054-zgetdump-zipl-Add-ELF-dump-support-needed-for-makedu.patch b/0054-zgetdump-zipl-Add-ELF-dump-support-needed-for-makedu.patch
new file mode 100644
index 0000000..57312af
--- /dev/null
+++ b/0054-zgetdump-zipl-Add-ELF-dump-support-needed-for-makedu.patch
@@ -0,0 +1,8702 @@
+From d401e50fb13e62e1d97f17e3a53e6d73bff6f587 Mon Sep 17 00:00:00 2001
+From: =?UTF-8?q?Dan=20Hor=C3=A1k?= <dan at danny.cz>
+Date: Fri, 28 Jan 2011 14:20:46 +0100
+Subject: [PATCH 54/61] zgetdump/zipl: Add ELF dump support (needed for makedumpfile)
+
+Summary: zgetdump/zipl: Add ELF dump support (needed for makedumpfile)
+Description: The zgetdump tool can be used now for dump format conversion.
+ It can read ELF, s390, and LKCD and write ELF and s390 format
+ dumps. A mount option based on "fuse" is added to zgetdump that
+ allows dumps to be converted in memory on the fly without the
+ need of copying them. The following two options are added to
+ zgetdump:
+ * fmt: Specify output dump format (elf or s390)
+ * mount: Mount dump instead of copying it to standard output
+
+ The zipl dump tools now store the prefix registers in the dump
+ header.
+
+ With this patch also multi-volume support for tape dump is
+ removed, because today's tape drives have enough capacity to
+ store a dump on a single volume.
+---
+ zdump/Makefile | 26 +-
+ zdump/df_elf.h | 100 ++++
+ zdump/df_lkcd.h | 77 +++
+ zdump/df_s390.c | 128 +++++
+ zdump/df_s390.h | 148 +++++
+ zdump/dfi.c | 613 ++++++++++++++++++++
+ zdump/dfi.h | 212 +++++++
+ zdump/dfi_elf.c | 291 ++++++++++
+ zdump/dfi_kdump.c | 122 ++++
+ zdump/dfi_lkcd.c | 333 +++++++++++
+ zdump/dfi_s390.c | 95 ++++
+ zdump/dfi_s390mv.c | 547 ++++++++++++++++++
+ zdump/dfi_s390tape.c | 198 +++++++
+ zdump/dfo.c | 204 +++++++
+ zdump/dfo.h | 54 ++
+ zdump/dfo_elf.c | 299 ++++++++++
+ zdump/dfo_s390.c | 200 +++++++
+ zdump/dt.c | 131 +++++
+ zdump/dt.h | 29 +
+ zdump/dt_s390mv.c | 19 +
+ zdump/dt_s390sv.c | 129 +++++
+ zdump/opts.c | 242 ++++++++
+ zdump/stdout.c | 38 ++
+ zdump/zfuse.c | 238 ++++++++
+ zdump/zg.c | 411 ++++++++++++++
+ zdump/zg.h | 185 ++++++
+ zdump/zgetdump.8 | 334 ++++++++++--
+ zdump/zgetdump.c | 1431 ++++-------------------------------------------
+ zdump/zgetdump.h | 250 +++------
+ zipl/boot/dumpcommon.S | 136 ++++-
+ zipl/boot/eckd2dump.S | 32 +-
+ zipl/boot/eckd2mvdump.S | 19 +-
+ zipl/boot/fba0.S | 30 +-
+ zipl/boot/fba2dump.S | 30 +-
+ zipl/boot/tapedump.S | 392 ++------------
+ zipl/include/boot.h | 4 +-
+ zipl/src/boot.c | 2 +-
+ zipl/src/install.c | 21 +-
+ 38 files changed, 5766 insertions(+), 1984 deletions(-)
+ create mode 100644 zdump/df_elf.h
+ create mode 100644 zdump/df_lkcd.h
+ create mode 100644 zdump/df_s390.c
+ create mode 100644 zdump/df_s390.h
+ create mode 100644 zdump/dfi.c
+ create mode 100644 zdump/dfi.h
+ create mode 100644 zdump/dfi_elf.c
+ create mode 100644 zdump/dfi_kdump.c
+ create mode 100644 zdump/dfi_lkcd.c
+ create mode 100644 zdump/dfi_s390.c
+ create mode 100644 zdump/dfi_s390mv.c
+ create mode 100644 zdump/dfi_s390tape.c
+ create mode 100644 zdump/dfo.c
+ create mode 100644 zdump/dfo.h
+ create mode 100644 zdump/dfo_elf.c
+ create mode 100644 zdump/dfo_s390.c
+ create mode 100644 zdump/dt.c
+ create mode 100644 zdump/dt.h
+ create mode 100644 zdump/dt_s390mv.c
+ create mode 100644 zdump/dt_s390sv.c
+ create mode 100644 zdump/opts.c
+ create mode 100644 zdump/stdout.c
+ create mode 100644 zdump/zfuse.c
+ create mode 100644 zdump/zg.c
+ create mode 100644 zdump/zg.h
+
+diff --git a/zdump/Makefile b/zdump/Makefile
+index cb546de..83c54ef 100644
+--- a/zdump/Makefile
++++ b/zdump/Makefile
+@@ -1,18 +1,34 @@
+ include ../common.mak
+
+-CPPFLAGS += -D_FILE_OFFSET_BITS=64 -I../include
++CPPFLAGS += -D_FILE_OFFSET_BITS=64 -I../include -I/usr/include/fuse
++LDLIBS += -lz
+
+ all: zgetdump
+
+-zgetdump.o: zgetdump.h
+-zgetdump: zgetdump.o
++OBJECTS = zgetdump.o opts.o zg.o \
++ dfi.o dfi_lkcd.o dfi_elf.o dfi_s390.o dfi_s390mv.o dfi_s390tape.o \
++ dfo.o dfo_elf.o dfo_s390.o \
++ df_s390.o \
++ dt.o dt_s390sv.o dt_s390mv.o \
++ stdout.o
++
++ifneq ("$(WITHOUT_FUSE)","1")
++LDLIBS += -lfuse
++OBJECTS += zfuse.o
++else
++CPPFLAGS += -DWITHOUT_FUSE
++endif
++
++$(OBJECTS): *.h Makefile
++
++zgetdump: $(OBJECTS)
+
+ install: all
+ $(INSTALL) -d -m 755 $(MANDIR)/man8 $(BINDIR)
+ $(INSTALL) -m 755 zgetdump $(BINDIR)
+- $(INSTALL) -m 644 zgetdump.8 $(MANDIR)/man8
++ $(INSTALL) -m 644 zgetdump.8 $(MANDIR)/man8
+
+ clean:
+- rm -f *.o *~ zgetdump core
++ rm -f *.o *~ zgetdump core.*
+
+ .PHONY: all install clean
+diff --git a/zdump/df_elf.h b/zdump/df_elf.h
+new file mode 100644
+index 0000000..13121c3
+--- /dev/null
++++ b/zdump/df_elf.h
+@@ -0,0 +1,100 @@
++/*
++ * zgetdump - Tool for copying and converting System z dumps
++ *
++ * ELF core dump format definitions
++ *
++ * Copyright IBM Corp. 2001, 2010
++ * Author(s): Michael Holzheu <holzheu at linux.vnet.ibm.com>
++ */
++
++#ifndef DF_ELF_H
++#define DF_ELF_H
++
++#include <linux/types.h>
++#include <elf.h>
++#include "zg.h"
++#include "dfo.h"
++
++/*
++ * S390 CPU timer note (u64)
++ */
++#ifndef NT_S390_TIMER
++#define NT_S390_TIMER 0x301
++#endif
++
++/*
++ * S390 TOD clock comparator note (u64)
++ */
++#ifndef NT_S390_TODCMP
++#define NT_S390_TODCMP 0x302
++#endif
++
++/*
++ * S390 TOD programmable register note (u32)
++ */
++#ifndef NT_S390_TODPREG
++#define NT_S390_TODPREG 0x303
++#endif
++
++/*
++ * S390 control registers note (16 * u32)
++ */
++#ifndef NT_S390_CTRS
++#define NT_S390_CTRS 0x304
++#endif
++
++/*
++ * S390 prefix note (u32)
++ */
++#ifndef NT_S390_PREFIX
++#define NT_S390_PREFIX 0x305
++#endif
++
++/*
++ * prstatus ELF Note
++ */
++struct nt_prstatus_64 {
++ u8 pad1[32];
++ u32 pr_pid;
++ u8 pad2[76];
++ u64 psw[2];
++ u64 gprs[16];
++ u32 acrs[16];
++ u64 orig_gpr2;
++ u32 pr_fpvalid;
++ u8 pad3[4];
++} __attribute__ ((packed));
++
++/*
++ * fpregset ELF Note
++ */
++struct nt_fpregset_64 {
++ u32 fpc;
++ u32 pad;
++ u64 fprs[16];
++} __attribute__ ((packed));
++
++/*
++ * prpsinfo ELF Note
++ */
++struct nt_prpsinfo_64 {
++ char pr_state;
++ char pr_sname;
++ char pr_zomb;
++ char pr_nice;
++ u64 pr_flag;
++ u32 pr_uid;
++ u32 pr_gid;
++ u32 pr_pid, pr_ppid, pr_pgrp, pr_sid;
++ char pr_fname[16];
++ char pr_psargs[80];
++};
++
++static inline void df_elf_ensure_s390x(void)
++{
++#ifndef __s390x__
++ ERR_EXIT("The ELF dump format is only supported on s390x (64 bit)");
++#endif
++}
++
++#endif /* DF_ELF_H */
+diff --git a/zdump/df_lkcd.h b/zdump/df_lkcd.h
+new file mode 100644
+index 0000000..ebb9eef
+--- /dev/null
++++ b/zdump/df_lkcd.h
+@@ -0,0 +1,77 @@
++/*
++ * zgetdump - Tool for copying and converting System z dumps
++ *
++ * LKCD dump format definitions
++ *
++ * Copyright IBM Corp. 2001, 2010
++ * Author(s): Michael Holzheu <holzheu at linux.vnet.ibm.com>
++ */
++
++#ifndef DF_LKCD_H
++#define DF_LKCD_H
++
++#define DF_LKCD_MAGIC 0xa8190173618f23edULL
++#define DF_LKCD_MAGIC_ASM 0x733339302d64756dULL
++#define DF_LKCD_VERSION 0x8 /* dump version number */
++#define DF_LKCD_PANIC_LEN 0x100 /* dump panic string length */
++#define DF_LKCD_HDR_SIZE 0x10000 /* Max space for the dump header */
++
++#define DF_LKCD_COMPRESS_NONE 0x0 /* don't compress this dump */
++#define DF_LKCD_COMPRESS_GZIP 0x2 /* use GZIP compression */
++
++#define DF_LKCD_DH_RAW 0x1 /* raw pg (no compression) */
++#define DF_LKCD_DH_COMPRESSED 0x2 /* pg is compressed */
++#define DF_LKCD_DH_END 0x4 /* end marker on a full dump */
++
++#define DF_LKCD_UCP_SIZE (PAGE_SIZE + sizeof(struct df_lkcd_pg_hdr))
++
++/*
++ * LKCD standard header
++ */
++struct df_lkcd_hdr {
++ u64 magic;
++ u32 version;
++ u32 hdr_size;
++ u32 dump_level;
++ u32 page_size;
++ u64 mem_size;
++ u64 mem_start;
++ u64 mem_end;
++ u32 num_dump_pgs;
++ char panic_string[0x100];
++ u64 time_tv_sec;
++ u64 time_tv_usec;
++ char utsname_sysname[65];
++ char utsname_nodename[65];
++ char utsname_release[65];
++ char utsname_version[65];
++ char utsname_machine[65];
++ char utsname_domainname[65];
++ u64 current_task;
++ u32 dump_compress;
++ u32 dump_flags;
++ u32 dump_device;
++} __attribute__((packed));
++
++/*
++ * s390 LKCD asm header
++ */
++struct df_lkcd_hdr_asm {
++ u64 magic;
++ u32 version;
++ u32 hdr_size;
++ u16 cpu_cnt;
++ u16 real_cpu_cnt;
++ u32 lc_vec[512];
++} __attribute__((packed));
++
++/*
++ * Page header
++ */
++struct df_lkcd_pg_hdr {
++ u64 addr; /* Address of dump page */
++ u32 size; /* Size of dump page */
++ u32 flags; /* flags (DF_LKCD_COMPRESSED, DF_LKCD_RAW,...) */
++} __attribute__((packed));
++
++#endif /* DF_LKCD_H */
+diff --git a/zdump/df_s390.c b/zdump/df_s390.c
+new file mode 100644
+index 0000000..b1807bb
+--- /dev/null
++++ b/zdump/df_s390.c
+@@ -0,0 +1,128 @@
++/*
++ * zgetdump - Tool for copying and converting System z dumps
++ *
++ * S390 dump format common functions
++ *
++ * Copyright IBM Corp. 2001, 2010
++ * Author(s): Michael Holzheu <holzheu at linux.vnet.ibm.com>
++ */
++
++#include <string.h>
++#include <time.h>
++#include <fcntl.h>
++#include <stdio.h>
++#include <unistd.h>
++#include <sys/types.h>
++#include <sys/stat.h>
++#include "zgetdump.h"
++
++/*
++ * Check, if we can access the lowcore information in the dump
++ */
++static int check_addr_max(struct df_s390_hdr *hdr, u64 addr_max)
++{
++ unsigned int i, lc_size;
++
++ lc_size = dfi_lc_size(df_s390_to_dfi_arch(hdr->arch));
++ for (i = 0; i < hdr->cpu_cnt; i++) {
++ if (hdr->lc_vec[i] + lc_size > addr_max)
++ return -1;
++ }
++ return 0;
++}
++
++/*
++ * Convert lowcore information into internal CPU representation
++ */
++void df_s390_cpu_info_add(struct df_s390_hdr *hdr, u64 addr_max)
++{
++ unsigned int i;
++
++ if (hdr->version < 5) {
++ /* No Prefix registers in header */
++ hdr->cpu_cnt = 0;
++ dfi_cpu_info_init(DFI_CPU_CONTENT_NONE);
++ } else if (check_addr_max(hdr, addr_max) != 0) {
++ /* Only lowcore pointers available */
++ dfi_cpu_info_init(DFI_CPU_CONTENT_LC);
++ } else {
++ /* All register info available */
++ dfi_cpu_info_init(DFI_CPU_CONTENT_ALL);
++ }
++
++ for (i = 0; i < hdr->cpu_cnt; i++)
++ dfi_cpu_add_from_lc(hdr->lc_vec[i]);
++}
++
++/*
++ * Convert s390 TOD clock into timeval structure
++ */
++static void tod2timeval(struct timeval *xtime, u64 todval)
++{
++ /* adjust todclock to 1970 */
++ todval -= 0x8126d60e46000000LL - (0x3c26700LL * 1000000 * 4096);
++
++ todval >>= 12;
++ xtime->tv_sec = todval / 1000000;
++ xtime->tv_usec = todval % 1000000;
++}
++
++/*
++ * Convert s390 header information into internal representation
++ */
++void df_s390_hdr_add(struct df_s390_hdr *hdr)
++{
++ struct timeval timeval;
++
++ if (hdr->tod) {
++ tod2timeval(&timeval, hdr->tod);
++ dfi_attr_time_set(&timeval);
++ }
++ dfi_attr_version_set(hdr->version);
++ dfi_arch_set(df_s390_to_dfi_arch(hdr->arch));
++ if (hdr->cpu_id)
++ dfi_attr_cpu_id_set(hdr->cpu_id);
++ if (hdr->version >= 3 && hdr->mem_size_real)
++ dfi_attr_mem_size_real_set(hdr->mem_size_real);
++ if (hdr->version >= 2 && hdr->build_arch)
++ dfi_attr_build_arch_set(df_s390_to_dfi_arch(hdr->build_arch));
++ if (hdr->version >= 5 && hdr->real_cpu_cnt)
++ dfi_attr_real_cpu_cnt_set(hdr->real_cpu_cnt);
++}
++
++/*
++ * Add end marker information to internal representation
++ */
++void df_s390_em_add(struct df_s390_em *em)
++{
++ struct timeval timeval;
++
++ if (em->tod) {
++ tod2timeval(&timeval, em->tod);
++ dfi_attr_time_end_set(&timeval);
++ }
++}
++
++/*
++ * Verify end marker
++ */
++int df_s390_em_verify(struct df_s390_em *em, struct df_s390_hdr *hdr)
++{
++ if (strncmp(em->str, DF_S390_EM_STR, strlen(DF_S390_EM_STR)) != 0)
++ return -EINVAL;
++ if (hdr->tod > em->tod)
++ return -EINVAL;
++ return 0;
++}
++
++/*
++ * Read s390 dump tool from DASD with given block size
++ */
++void df_s390_dumper_read(struct zg_fh *fh, int blk_size,
++ struct df_s390_dumper *dumper)
++{
++ int offset = DF_S390_MAGIC_BLK_ECKD * blk_size;
++
++ zg_seek(fh, offset, ZG_CHECK);
++ zg_read(fh, dumper, sizeof(*dumper), ZG_CHECK);
++}
+diff --git a/zdump/df_s390.h b/zdump/df_s390.h
+new file mode 100644
+index 0000000..81b519b
+--- /dev/null
++++ b/zdump/df_s390.h
+@@ -0,0 +1,148 @@
++/*
++ * zgetdump - Tool for copying and converting System z dumps
++ *
++ * S390 dump format common functions
++ *
++ * Copyright IBM Corp. 2001, 2010
++ * Author(s): Michael Holzheu <holzheu at linux.vnet.ibm.com>
++ */
++
++#ifndef DF_S390_H
++#define DF_S390_H
++
++#include "dt.h"
++#include "zg.h"
++
++#define DF_S390_MAGIC 0xa8190173618f23fdULL
++#define DF_S390_HDR_SIZE 0x1000
++#define DF_S390_EM_SIZE 16
++#define DF_S390_EM_STR "DUMP_END"
++#define DF_S390_CPU_MAX 512
++#define DF_S390_MAGIC_BLK_ECKD 3
++
++/*
++ * Architecture of dumped system
++ */
++enum df_s390_arch {
++ DF_S390_ARCH_32 = 1,
++ DF_S390_ARCH_64 = 2,
++};
++
++/*
++ * s390 dump header format
++ */
++struct df_s390_hdr {
++ u64 magic; /* 0x000 */
++ u32 version; /* 0x008 */
++ u32 hdr_size; /* 0x00c */
++ u32 dump_level; /* 0x010 */
++ u32 page_size; /* 0x014 */
++ u64 mem_size; /* 0x018 */
++ u64 mem_start; /* 0x020 */
++ u64 mem_end; /* 0x028 */
++ u32 num_pages; /* 0x030 */
++ u32 pad; /* 0x034 */
++ u64 tod; /* 0x038 */
++ u64 cpu_id; /* 0x040 */
++ u32 arch; /* 0x048 */
++ u32 volnr; /* 0x04c */
++ u32 build_arch; /* 0x050 */
++ u64 mem_size_real; /* 0x054 */
++ u8 mvdump; /* 0x05c */
++ u16 cpu_cnt; /* 0x05d */
++ u16 real_cpu_cnt; /* 0x05f */
++ u8 end_pad1[0x200-0x061]; /* 0x061 */
++ u64 mvdump_sign; /* 0x200 */
++ u64 mvdump_zipl_time; /* 0x208 */
++ u8 end_pad2[0x800-0x210]; /* 0x210 */
++ u32 lc_vec[DF_S390_CPU_MAX]; /* 0x800 */
++} __attribute__((packed));
++
++/*
++ * End marker: Should be at the end of every valid s390 crash dump.
++ */
++struct df_s390_em {
++ char str[8];
++ u64 tod;
++} __attribute__((packed));
++
++/*
++ * Convert DFI arch to s390 arch
++ */
++static inline enum df_s390_arch df_s390_from_dfi_arch(enum dfi_arch dfi_arch)
++{
++ return dfi_arch == DFI_ARCH_64 ? DF_S390_ARCH_64 : DF_S390_ARCH_32;
++}
++
++/*
++ * Convert s390 arch to DFI arch
++ */
++static inline enum dfi_arch df_s390_to_dfi_arch(enum df_s390_arch df_s390_arch)
++{
++ return df_s390_arch == DF_S390_ARCH_64 ? DFI_ARCH_64 : DFI_ARCH_32;
++}
++
++/*
++ * Dump tool structure (version 1)
++ */
++struct df_s390_dumper_v1 {
++ char code[0xff7 - 0x8];
++ u8 force;
++ u64 mem;
++} __attribute__ ((packed));
++
++#define DF_S390_DUMPER_SIZE_V1 0x1000
++
++/*
++ * Dump tool structure (version 2)
++ */
++struct df_s390_dumper_v2 {
++ char code[0x1ff7 - 0x8];
++ u8 force;
++ u64 mem;
++} __attribute__ ((packed));
++
++#define DF_S390_DUMPER_SIZE_V2 0x2000
++
++/*
++ * Dump tool structure
++ */
++struct df_s390_dumper {
++ char magic[7];
++ u8 version;
++ union {
++ struct df_s390_dumper_v1 v1;
++ struct df_s390_dumper_v2 v2;
++ } d;
++} __attribute__ ((packed));
++
++/*
++ * Dumper member access helpers
++ */
++#define df_s390_dumper_magic(dumper) ((dumper).magic)
++#define df_s390_dumper_version(dumper) ((dumper).version)
++#define df_s390_dumper_mem(dumper) \
++ ((dumper).version == 1 ? dumper.d.v1.mem : dumper.d.v2.mem)
++#define df_s390_dumper_force(dumper) \
++ ((dumper).version == 1 ? dumper.d.v1.force : dumper.d.v2.force)
++#define df_s390_dumper_size(dumper) \
++ ((dumper).version == 1 ? 0x1000 : 0x2000)
++
++/*
++ * s390 dump helpers
++ */
++extern void df_s390_hdr_add(struct df_s390_hdr *hdr);
++extern void df_s390_em_add(struct df_s390_em *em);
++extern void df_s390_cpu_info_add(struct df_s390_hdr *hdr, u64 addr_max);
++extern int df_s390_em_verify(struct df_s390_em *em, struct df_s390_hdr *hdr);
++extern void df_s390_dumper_read(struct zg_fh *fh, int32_t blk_size,
++ struct df_s390_dumper *dumper);
++
++/*
++ * DASD multi-volume dumper functions
++ */
++extern int dt_s390mv_init(void);
++extern void dt_s390mv_exit(void);
++extern void dt_s390mv_info(void);
++
++#endif /* DF_S390_H */
+diff --git a/zdump/dfi.c b/zdump/dfi.c
+new file mode 100644
+index 0000000..fc7bf12
+--- /dev/null
++++ b/zdump/dfi.c
+@@ -0,0 +1,613 @@
++/*
++ * zgetdump - Tool for copying and converting System z dumps
++ *
++ * Generic input dump format functions (DFI - Dump Format Input)
++ *
++ * Copyright IBM Corp. 2001, 2010
++ * Author(s): Michael Holzheu <holzheu at linux.vnet.ibm.com>
++ */
++
++#include <time.h>
++#include "zgetdump.h"
++
++#define TIME_FMT_STR "%a, %d %b %Y %H:%M:%S %z"
++#define PROGRESS_HASH_CNT 50
++
++/*
++ * DFI vector - ensure that tape is the first in the list!
++ */
++static struct dfi *dfi_vec[] = {
++ &dfi_s390tape,
++ &dfi_s390mv,
++ &dfi_s390,
++ &dfi_lkcd,
++ &dfi_elf,
++ NULL,
++};
++
++/*
++ * CPU information
++ */
++struct cpus {
++ struct list list;
++ enum dfi_cpu_content content;
++ unsigned int cnt;
++};
++
++/*
++ * Memory information
++ */
++struct mem {
++ struct dfi_mem_chunk *chunk_cache;
++ u64 start_addr;
++ u64 end_addr;
++ unsigned int chunk_cnt;
++ struct list chunk_list;
++};
++
++/*
++ * Dump header attribute information
++ */
++struct attr {
++ unsigned int *dfi_version;
++ struct timeval *time;
++ struct timeval *time_end;
++ u64 *cpu_id;
++ u64 *mem_size_real;
++ enum dfi_arch *build_arch;
++ unsigned int *vol_nr;
++ u32 *real_cpu_cnt;
++};
++
++/*
++ * File local static data
++ */
++static struct {
++ enum dfi_arch arch;
++ struct attr attr;
++ struct mem mem;
++ struct cpus cpus;
++ struct dfi *dfi;
++} l;
++
++/*
++ * Print Dump date
++ */
++static void date_print(void)
++{
++ char time_str[80];
++ struct tm *tmp;
++
++ if (l.attr.time) {
++ tmp = localtime(&l.attr.time->tv_sec);
++ strftime(time_str, sizeof(time_str), TIME_FMT_STR, tmp);
++ STDERR(" Dump created.......: %s\n", time_str);
++ }
++ if (l.attr.time_end) {
++ tmp = localtime(&l.attr.time_end->tv_sec);
++ strftime(time_str, sizeof(time_str), TIME_FMT_STR, tmp);
++ STDERR(" Dump ended.........: %s\n", time_str);
++ }
++}
++
++/*
++ * Print memory map
++ */
++static void mem_map_print(void)
++{
++ struct dfi_mem_chunk *mem_chunk;
++
++ STDERR("\nMemory map:\n");
++ dfi_mem_chunk_iterate(mem_chunk) {
++ STDERR(" %016llx - %016llx (%llu MB)\n", mem_chunk->start,
++ mem_chunk->end, TO_MIB(mem_chunk->size));
++ }
++}
++
++/*
++ * Print dump information (--info option)
++ */
++void dfi_info_print(void)
++{
++ STDERR("General dump info:\n");
++ STDERR(" Dump format........: %s\n", l.dfi->name);
++ if (l.attr.dfi_version)
++ STDERR(" Version............: %d\n", *l.attr.dfi_version);
++ date_print();
++ if (l.attr.cpu_id)
++ STDERR(" Dump CPU ID........: %llx\n", *l.attr.cpu_id);
++ if (l.attr.vol_nr)
++ STDERR(" Volume number......: %d\n", *l.attr.vol_nr);
++ if (l.attr.build_arch)
++ STDERR(" Build arch.........: %s\n",
++ dfi_arch_str(*l.attr.build_arch));
++ STDERR(" System arch........: %s\n", dfi_arch_str(l.arch));
++ if (l.cpus.cnt)
++ STDERR(" CPU count (online).: %d\n", l.cpus.cnt);
++ if (l.attr.real_cpu_cnt)
++ STDERR(" CPU count (real)...: %d\n", *l.attr.real_cpu_cnt);
++ STDERR(" Dump memory range..: %lld MB\n", TO_MIB(dfi_mem_range()));
++ if (l.attr.mem_size_real)
++ STDERR(" Real memory range..: %lld MB\n",
++ TO_MIB(*l.attr.mem_size_real));
++ mem_map_print();
++ if (l.dfi->info_dump) {
++ STDERR("\nDump device info:\n");
++ l.dfi->info_dump();
++ }
++}
++
++/*
++ * Add memory chunk
++ */
++void dfi_mem_chunk_add(u64 start, u64 size, void *data,
++ dfi_mem_chunk_read_fn read_fn)
++{
++ struct dfi_mem_chunk *mem_chunk;
++
++ mem_chunk = zg_alloc(sizeof(*mem_chunk));
++ mem_chunk->start = start;
++ mem_chunk->end = start + size - 1;
++ mem_chunk->size = size;
++ mem_chunk->read_fn = read_fn;
++ mem_chunk->data = data;
++
++ list_add_end(&mem_chunk->list, &l.mem.chunk_list);
++ l.mem.start_addr = MIN(l.mem.start_addr, mem_chunk->start);
++ l.mem.end_addr = MAX(l.mem.end_addr, mem_chunk->end);
++ l.mem.chunk_cache = mem_chunk;
++ l.mem.chunk_cnt++;
++}
++
++/*
++ * Return mem_chunk list head
++ */
++struct list *dfi_mem_chunk_list(void)
++{
++ return &l.mem.chunk_list;
++}
++
++/*
++ * Return number of memory chunks in input dump
++ */
++unsigned int dfi_mem_chunk_cnt(void)
++{
++ return l.mem.chunk_cnt;
++}
++
++/*
++ * Return maximum memory range
++ */
++u64 dfi_mem_range(void)
++{
++ return l.mem.end_addr - l.mem.start_addr + 1;
++}
++
++/*
++ * Return first memory chunk
++ */
++struct dfi_mem_chunk *dfi_mem_chunk_first(void)
++{
++ if (list_is_empty(&l.mem.chunk_list))
++ return NULL;
++ return list_entry_first(&l.mem.chunk_list, struct dfi_mem_chunk, list);
++}
++
++/*
++ * Return next memory chunk
++ */
++struct dfi_mem_chunk *dfi_mem_chunk_next(struct dfi_mem_chunk *mem_chunk)
++{
++ if (mem_chunk->list.next == &l.mem.chunk_list)
++ return NULL;
++ return list_entry_next(&mem_chunk->list, struct dfi_mem_chunk, list);
++}
++
++/*
++ * Return previous memory chunk
++ */
++struct dfi_mem_chunk *dfi_mem_chunk_prev(struct dfi_mem_chunk *mem_chunk)
++{
++ if (mem_chunk->list.prev == &l.mem.chunk_list)
++ return NULL;
++ return list_entry_prev(&mem_chunk->list, struct dfi_mem_chunk, list);
++}
++
++/*
++ * Check if memory chunk contains address
++ */
++static int mem_chunk_has_addr(struct dfi_mem_chunk *mem_chunk, u64 addr)
++{
++ return (addr >= mem_chunk->start && addr <= mem_chunk->end);
++}
++
++/*
++ * Find memory chunk for given address
++ */
++struct dfi_mem_chunk *dfi_mem_chunk_find(u64 addr)
++{
++ struct dfi_mem_chunk *mem_chunk;
++
++ if (mem_chunk_has_addr(l.mem.chunk_cache, addr))
++ return l.mem.chunk_cache;
++ dfi_mem_chunk_iterate(mem_chunk) {
++ if (mem_chunk_has_addr(mem_chunk, addr)) {
++ l.mem.chunk_cache = mem_chunk;
++ return mem_chunk;
++ }
++ }
++ return NULL;
++}
++
++/*
++ * Initialize CPU info
++ */
++void dfi_cpu_info_init(enum dfi_cpu_content cpu_content)
++{
++ l.cpus.content = cpu_content;
++}
++
++/*
++ * Allocate new DFI CPU
++ */
++struct dfi_cpu *dfi_cpu_alloc(void)
++{
++ return zg_alloc(sizeof(struct dfi_cpu));
++}
++
++/*
++ * Add DFI CPU
++ */
++void dfi_cpu_add(struct dfi_cpu *cpu)
++{
++ list_add_end(&cpu->list, &l.cpus.list);
++ l.cpus.cnt++;
++}
++
++/*
++ * Return CPU with number cpu_nr
++ */
++struct dfi_cpu *dfi_cpu(unsigned int cpu_nr)
++{
++ struct dfi_cpu *cpu;
++ unsigned int i = 0;
++
++ dfi_cpu_iterate(cpu) {
++ if (i == cpu_nr)
++ return cpu;
++ i++;
++ }
++ return NULL;
++}
++
++/*
++ * Return CPU count
++ */
++unsigned int dfi_cpu_cnt(void)
++{
++ return l.cpus.cnt;
++}
++
++/*
++ * Return CPU content
++ */
++enum dfi_cpu_content dfi_cpu_content(void)
++{
++ return l.cpus.content;
++}
++
++/*
++ * Set DFI architecture
++ */
++void dfi_arch_set(enum dfi_arch arch)
++{
++ l.arch = arch;
++}
++
++/*
++ * Return DFI architecture
++ */
++enum dfi_arch dfi_arch(void)
++{
++ return l.arch;
++}
++
++/*
++ * Return DFI CPU list
++ */
++struct list *dfi_cpu_list(void)
++{
++ return &l.cpus.list;
++}
++
++/*
++ * Read memory at given address
++ */
++void dfi_mem_read(u64 addr, void *buf, size_t cnt)
++{
++ struct dfi_mem_chunk *mem_chunk;
++ u64 size, copied = 0;
++
++ while (copied != cnt) {
++ mem_chunk = dfi_mem_chunk_find(addr);
++ size = MIN(cnt - copied, mem_chunk->end - addr + 1);
++ mem_chunk->read_fn(mem_chunk, addr - mem_chunk->start,
++ buf + copied, size);
++ copied += size;
++ addr += size;
++ }
++}
++
++/*
++ * Get input dump format name
++ */
++const char *dfi_name(void)
++{
++ return l.dfi->name;
++}
++
++/*
++ * Can input dump format seek?
++ */
++int dfi_feat_seek(void)
++{
++ return l.dfi->feat_bits & DFI_FEAT_SEEK;
++};
++
++/*
++ * Can input dump format be used for copying?
++ */
++int dfi_feat_copy(void)
++{
++ return l.dfi->feat_bits & DFI_FEAT_COPY;
++};
++
++/*
++ * Return DFI arch string
++ */
++const char *dfi_arch_str(enum dfi_arch arch)
++{
++ switch (arch) {
++ case DFI_ARCH_32:
++ return "s390 (32 bit)";
++ case DFI_ARCH_64:
++ return "s390x (64 bit)";
++ case DFI_ARCH_UNKNOWN:
++ return "unknown";
++ }
++ ABORT("dfi_arch_str: Invalid dfi arch: %d", arch);
++}
++
++/*
++ * Initialize input dump format.
++ */
++int dfi_init(void)
++{
++ struct dfi *dfi;
++ int i = 0, rc;
++
++ l.mem.start_addr = U64_MAX;
++ l.mem.end_addr = 0;
++ list_init(&l.mem.chunk_list);
++ list_init(&l.cpus.list);
++ while ((dfi = dfi_vec[i])) {
++ l.dfi = dfi;
++ g.fh = zg_open(g.opts.device, O_RDONLY, ZG_CHECK);
++ rc = dfi->init();
++ if (rc == 0 || rc == -EINVAL)
++ return rc;
++ zg_close(g.fh);
++ i++;
++ }
++ ERR_EXIT("No valid dump found on \"%s\"", g.opts.device);
++}
++
++/*
++ * Attribute: Dump time
++ */
++void dfi_attr_time_set(struct timeval *time)
++{
++ l.attr.time = zg_alloc(sizeof(*l.attr.time));
++ *l.attr.time = *time;
++}
++
++struct timeval *dfi_attr_time(void)
++{
++ return l.attr.time;
++}
++
++/*
++ * Attribute: Dump end time
++ */
++void dfi_attr_time_end_set(struct timeval *time_end)
++{
++ l.attr.time_end = zg_alloc(sizeof(*l.attr.time_end));
++ *l.attr.time_end = *time_end;
++}
++
++struct timeval *dfi_attr_time_end(void)
++{
++ return l.attr.time_end;
++}
++
++/*
++ * Attribute: Volume number
++ */
++void dfi_attr_vol_nr_set(unsigned int vol_nr)
++{
++ l.attr.vol_nr = zg_alloc(sizeof(*l.attr.vol_nr));
++ *l.attr.vol_nr = vol_nr;
++}
++
++/*
++ * Attribute: DFI version
++ */
++void dfi_attr_version_set(unsigned int dfi_version)
++{
++ l.attr.dfi_version = zg_alloc(sizeof(*l.attr.dfi_version));
++ *l.attr.dfi_version = dfi_version;
++}
++
++/*
++ * Attribute: CPU ID
++ */
++void dfi_attr_cpu_id_set(u64 cpu_id)
++{
++ l.attr.cpu_id = zg_alloc(sizeof(*l.attr.cpu_id));
++ *l.attr.cpu_id = cpu_id;
++}
++
++u64 *dfi_attr_cpu_id(void)
++{
++ return l.attr.cpu_id;
++}
++
++/*
++ * Attribute: Real memory size
++ */
++void dfi_attr_mem_size_real_set(u64 mem_size_real)
++{
++ l.attr.mem_size_real = zg_alloc(sizeof(*l.attr.mem_size_real));
++ *l.attr.mem_size_real = mem_size_real;
++}
++
++u64 *dfi_attr_mem_size_real(void)
++{
++ return l.attr.mem_size_real;
++}
++
++/*
++ * Attribute: Build architecture
++ */
++void dfi_attr_build_arch_set(enum dfi_arch build_arch)
++{
++ l.attr.build_arch = zg_alloc(sizeof(*l.attr.build_arch));
++ *l.attr.build_arch = build_arch;
++}
++
++enum dfi_arch *dfi_attr_build_arch(void)
++{
++ return l.attr.build_arch;
++}
++
++/*
++ * Attribute: Real CPU count
++ */
++void dfi_attr_real_cpu_cnt_set(unsigned int real_cnt_cnt)
++{
++ l.attr.real_cpu_cnt = zg_alloc(sizeof(*l.attr.real_cpu_cnt));
++ *l.attr.real_cpu_cnt = real_cnt_cnt;
++}
++
++unsigned int *dfi_attr_real_cpu_cnt(void)
++{
++ return l.attr.real_cpu_cnt;
++}
++
++/*
++ * Convert 32 bit CPU register set to 64 bit
++ */
++static void cpu_32_to_64(struct dfi_cpu *cpu_64, struct dfi_cpu_32 *cpu_32)
++{
++ int i;
++
++ for (i = 0; i < 16; i++) {
++ cpu_64->gprs[i] = cpu_32->gprs[i];
++ cpu_64->ctrs[i] = cpu_32->ctrs[i];
++ cpu_64->acrs[i] = cpu_32->acrs[i];
++ if (i < 4)
++ cpu_64->fprs[i] = cpu_32->fprs[i];
++ }
++ cpu_64->psw[0] = cpu_32->psw[0];
++ cpu_64->psw[1] = cpu_32->psw[1];
++ cpu_64->prefix = cpu_32->prefix;
++ cpu_64->timer = cpu_32->timer;
++ cpu_64->todcmp = cpu_32->todcmp;
++}
++
++/*
++ * Convert 64 bit CPU register set to 32 bit
++ */
++void dfi_cpu_64_to_32(struct dfi_cpu_32 *cpu_32, struct dfi_cpu *cpu_64)
++{
++ int i;
++
++ for (i = 0; i < 16; i++) {
++ cpu_32->gprs[i] = (u32) cpu_64->gprs[i];
++ cpu_32->ctrs[i] = (u32) cpu_64->ctrs[i];
++ cpu_32->acrs[i] = (u32) cpu_64->acrs[i];
++ if (i < 4)
++ cpu_32->fprs[i] = (u32) cpu_64->fprs[i];
++ }
++ cpu_32->psw[0] = (u32) cpu_64->psw[0];
++ cpu_32->psw[1] = (u32) cpu_64->psw[1];
++ cpu_32->prefix = cpu_64->prefix;
++ cpu_32->timer = cpu_64->timer;
++ cpu_32->todcmp = cpu_64->todcmp;
++}
++
++/*
++ * Copy 64 bit lowcore to internal register set
++ */
++static void lc2cpu_64(struct dfi_cpu *cpu, struct dfi_lowcore_64 *lc)
++{
++ memcpy(&cpu->gprs, lc->gpregs_save_area, sizeof(cpu->gprs));
++ memcpy(&cpu->ctrs, lc->cregs_save_area, sizeof(cpu->ctrs));
++ memcpy(&cpu->acrs, lc->access_regs_save_area, sizeof(cpu->acrs));
++ memcpy(&cpu->fprs, lc->floating_pt_save_area, sizeof(cpu->fprs));
++ memcpy(&cpu->fpc, &lc->fpt_creg_save_area, sizeof(cpu->fpc));
++ memcpy(&cpu->psw, lc->st_status_fixed_logout, sizeof(cpu->psw));
++ memcpy(&cpu->prefix, &lc->prefixreg_save_area, sizeof(cpu->prefix));
++ memcpy(&cpu->timer, lc->timer_save_area, sizeof(cpu->timer));
++ memcpy(&cpu->todpreg, &lc->tod_progreg_save_area, sizeof(cpu->todpreg));
++ memcpy(&cpu->todcmp, lc->clock_comp_save_area, sizeof(cpu->todcmp));
++}
++
++/*
++ * Copy 32 bit lowcore to internal 32 bit cpu
++ */
++static void lc2cpu_32(struct dfi_cpu_32 *cpu, struct dfi_lowcore_32 *lc)
++{
++ memcpy(&cpu->gprs, lc->gpregs_save_area, sizeof(cpu->gprs));
++ memcpy(&cpu->ctrs, lc->cregs_save_area, sizeof(cpu->ctrs));
++ memcpy(&cpu->acrs, lc->access_regs_save_area, sizeof(cpu->acrs));
++ memcpy(&cpu->fprs, lc->floating_pt_save_area, sizeof(cpu->fprs));
++ memcpy(&cpu->psw, lc->st_status_fixed_logout, sizeof(cpu->psw));
++ memcpy(&cpu->prefix, &lc->prefixreg_save_area, sizeof(cpu->prefix));
++ memcpy(&cpu->timer, lc->timer_save_area, sizeof(cpu->timer));
++ memcpy(&cpu->todcmp, lc->clock_comp_save_area, sizeof(cpu->todcmp));
++}
++
++/*
++ * Initialize and add a new CPU with given lowcore pointer
++ *
++ * Note: When this function is called, the memory chunks have to be already
++ * defined by the DFI dump specific code.
++ */
++void dfi_cpu_add_from_lc(u32 lc_addr)
++{
++ struct dfi_cpu *cpu = dfi_cpu_alloc();
++
++ switch (l.cpus.content) {
++ case DFI_CPU_CONTENT_LC:
++ cpu->prefix = lc_addr;
++ break;
++ case DFI_CPU_CONTENT_ALL:
++ if (l.arch == DFI_ARCH_32) {
++ struct dfi_cpu_32 cpu_32;
++ struct dfi_lowcore_32 lc;
++ dfi_mem_read(lc_addr, &lc, sizeof(lc));
++ lc2cpu_32(&cpu_32, &lc);
++ cpu_32_to_64(cpu, &cpu_32);
++ } else {
++ struct dfi_lowcore_64 lc;
++ dfi_mem_read(lc_addr, &lc, sizeof(lc));
++ lc2cpu_64(cpu, &lc);
++ }
++ break;
++ case DFI_CPU_CONTENT_NONE:
++ ABORT("dfi_cpu_add_from_lc() called for CONTENT_NONE");
++ }
++ dfi_cpu_add(cpu);
++}
++
+diff --git a/zdump/dfi.h b/zdump/dfi.h
+new file mode 100644
+index 0000000..0b9d849
+--- /dev/null
++++ b/zdump/dfi.h
+@@ -0,0 +1,212 @@
++/*
++ * zgetdump - Tool for copying and converting System z dumps
++ *
++ * Generic input dump format functions (DFI - Dump Format Input)
++ *
++ * Copyright IBM Corp. 2001, 2010
++ * Author(s): Michael Holzheu <holzheu at linux.vnet.ibm.com>
++ */
++
++#ifndef DFI_H
++#define DFI_H
++
++#include "zg.h"
++#include "list.h"
++
++/*
++ * CPU info functions and definitions
++ */
++
++enum dfi_arch {
++ DFI_ARCH_32 = 0,
++ DFI_ARCH_64 = 1,
++ DFI_ARCH_UNKNOWN = 2,
++};
++
++struct dfi_lowcore_32 {
++ u8 pad_0x0000[0x0084 - 0x0000]; /* 0x0000 */
++ u16 cpu_addr; /* 0x0084 */
++ u8 pad_0x0086[0x00d4 - 0x0086]; /* 0x0086 */
++ u32 extended_save_area_addr; /* 0x00d4 */
++ u32 timer_save_area[2]; /* 0x00d8 */
++ u32 clock_comp_save_area[2]; /* 0x00e0 */
++ u32 mcck_interruption_code[2]; /* 0x00e8 */
++ u8 pad_0x00f0[0x00f4-0x00f0]; /* 0x00f0 */
++ u32 external_damage_code; /* 0x00f4 */
++ u32 failing_storage_address; /* 0x00f8 */
++ u8 pad_0x00fc[0x0100-0x00fc]; /* 0x00fc */
++ u32 st_status_fixed_logout[2]; /* 0x0100 */
++ u32 prefixreg_save_area; /* 0x0108 */
++ u8 pad_0x0110[0x0120-0x010c]; /* 0x010c */
++ u32 access_regs_save_area[16]; /* 0x0120 */
++ u32 floating_pt_save_area[8]; /* 0x0160 */
++ u32 gpregs_save_area[16]; /* 0x0180 */
++ u32 cregs_save_area[16]; /* 0x01c0 */
++ u8 pad_0x0200[0x1000 - 0x0200]; /* 0x0200 */
++};
++
++struct dfi_lowcore_64 {
++ u8 pad_0x0000[0x0084 - 0x0000]; /* 0x0000 */
++ u16 cpu_addr; /* 0x0084 */
++ u8 pad_0x0086[0x1200 - 0x0086]; /* 0x0086 */
++ u64 floating_pt_save_area[16]; /* 0x1200 */
++ u64 gpregs_save_area[16]; /* 0x1280 */
++ u32 st_status_fixed_logout[4]; /* 0x1300 */
++ u8 pad_0x1310[0x1318-0x1310]; /* 0x1310 */
++ u32 prefixreg_save_area; /* 0x1318 */
++ u32 fpt_creg_save_area; /* 0x131c */
++ u8 pad_0x1320[0x1324-0x1320]; /* 0x1320 */
++ u32 tod_progreg_save_area; /* 0x1324 */
++ u32 timer_save_area[2]; /* 0x1328 */
++ u32 clock_comp_save_area[2]; /* 0x1330 */
++ u8 pad_0x1338[0x1340-0x1338]; /* 0x1338 */
++ u32 access_regs_save_area[16]; /* 0x1340 */
++ u64 cregs_save_area[16]; /* 0x1380 */
++ u8 pad_0x1400[0x2000-0x1400]; /* 0x1400 */
++} __attribute__((packed));
++
++static inline u64 dfi_lc_size(enum dfi_arch arch)
++{
++ if (arch == DFI_ARCH_64)
++ return 0x2000;
++ else
++ return 0x1000;
++}
++
++struct dfi_cpu {
++ struct list list;
++ u64 gprs[16];
++ u64 ctrs[16];
++ u32 acrs[16];
++ u64 fprs[16];
++ u32 fpc;
++ u64 psw[2];
++ u32 prefix;
++ u64 timer;
++ u64 todcmp;
++ u32 todpreg;
++};
++
++struct dfi_cpu_32 {
++ u32 gprs[16];
++ u32 ctrs[16];
++ u32 acrs[16];
++ u64 fprs[4];
++ u32 psw[2];
++ u32 prefix;
++ u64 timer;
++ u64 todcmp;
++};
++
++extern void dfi_cpu_64_to_32(struct dfi_cpu_32 *cpu_32, struct dfi_cpu *cpu_64);
++
++extern enum dfi_arch dfi_arch(void);
++extern void dfi_arch_set(enum dfi_arch arch);
++extern const char *dfi_arch_str(enum dfi_arch arch);
++
++enum dfi_cpu_content {
++ DFI_CPU_CONTENT_NONE, /* No register information available */
++ DFI_CPU_CONTENT_LC, /* Only lowcore information available */
++ DFI_CPU_CONTENT_ALL, /* Complete register information available */
++};
++
++#define dfi_cpu_iterate(cpu) \
++ list_iterate(cpu, dfi_cpu_list(), list)
++
++extern struct list *dfi_cpu_list(void);
++extern void dfi_cpu_info_init(enum dfi_cpu_content content);
++extern struct dfi_cpu *dfi_cpu_alloc(void);
++extern struct dfi_cpu *dfi_cpu(unsigned int cpu_nr);
++extern void dfi_cpu_add(struct dfi_cpu *cpu);
++extern unsigned int dfi_cpu_cnt(void);
++extern enum dfi_cpu_content dfi_cpu_content(void);
++extern void dfi_cpu_add_from_lc(u32 lc_addr);
++
++/*
++ * Mem chunk functions and definitions
++ */
++struct dfi_mem_chunk;
++
++typedef void (*dfi_mem_chunk_read_fn)(struct dfi_mem_chunk *mem_chunk,
++ u64 off, void *buf, u64 cnt);
++
++struct dfi_mem_chunk {
++ struct list list; /* List */
++ u64 start; /* Start address in memory */
++ u64 end; /* End address in memory */
++ u64 size; /* Size of chunk in dump file */
++ u64 out_start; /* Start offset in dump file */
++ u64 out_end; /* End offset in dump file */
++ dfi_mem_chunk_read_fn read_fn; /* Chunk read callback */
++ void *data; /* Data for callback */
++};
++
++extern void dfi_mem_chunk_add(u64 start, u64 size, void *data,
++ dfi_mem_chunk_read_fn read_fn);
++extern u64 dfi_mem_range(void);
++extern unsigned int dfi_mem_chunk_cnt(void);
++extern struct dfi_mem_chunk *dfi_mem_chunk_first(void);
++extern struct dfi_mem_chunk *dfi_mem_chunk_next(struct dfi_mem_chunk *chunk);
++extern struct dfi_mem_chunk *dfi_mem_chunk_prev(struct dfi_mem_chunk *chunk);
++extern struct dfi_mem_chunk *dfi_mem_chunk_find(u64 addr);
++
++extern struct list *dfi_mem_chunk_list(void);
++#define dfi_mem_chunk_iterate(mem_chunk) \
++ list_iterate(mem_chunk, dfi_mem_chunk_list(), list)
++
++/*
++ * Dump header attribute set/get functions
++ */
++extern void dfi_attr_time_set(struct timeval *time);
++extern struct timeval *dfi_attr_time(void);
++
++extern void dfi_attr_time_end_set(struct timeval *time_end);
++extern struct timeval *dfi_attr_time_end(void);
++
++extern void dfi_attr_cpu_id_set(u64 cpu_id);
++extern u64 *dfi_attr_cpu_id(void);
++
++extern void dfi_attr_mem_size_real_set(u64 mem_size_real);
++extern u64 *dfi_attr_mem_size_real();
++
++extern void dfi_attr_vol_nr_set(unsigned int vol_nr);
++extern unsigned int *dfi_attr_vol_nr(void);
++
++extern void dfi_attr_version_set(unsigned int dfi_version);
++extern unsigned int *dfi_attr_dfi_version(void);
++
++extern void dfi_attr_build_arch_set(enum dfi_arch build_arch);
++extern enum dfi_arch *dfi_attr_build_arch(void);
++
++extern void dfi_attr_real_cpu_cnt_set(u32 real_cpu_cnt);
++extern u32 *dfi_attr_real_cpu_cnt(void);
++
++/*
++ * DFI external functions
++ */
++extern void dfi_mem_read(u64 addr, void *buf, size_t cnt);
++extern void dfi_info_print(void);
++
++/*
++ * DFI feature bits
++ */
++#define DFI_FEAT_SEEK 0x1 /* Necessary for fuse mount */
++#define DFI_FEAT_COPY 0x2 /* Necessary for stdout */
++
++extern int dfi_feat_seek(void);
++extern int dfi_feat_copy(void);
++
++/*
++ * DFI operations
++ */
++struct dfi {
++ const char *name;
++ int (*init)(void);
++ void (*info_dump)(void);
++ int feat_bits;
++};
++
++extern const char *dfi_name(void);
++extern int dfi_init(void);
++
++#endif /* DFI_H */
+diff --git a/zdump/dfi_elf.c b/zdump/dfi_elf.c
+new file mode 100644
+index 0000000..866411b
+--- /dev/null
++++ b/zdump/dfi_elf.c
+@@ -0,0 +1,291 @@
++/*
++ * zgetdump - Tool for copying and converting System z dumps
++ *
++ * ELF core dump input format
++ *
++ * Copyright IBM Corp. 2001, 2010
++ * Author(s): Michael Holzheu <holzheu at linux.vnet.ibm.com>
++ */
++
++#include <elf.h>
++#include <stdio.h>
++#include <string.h>
++#include <stdlib.h>
++#include <unistd.h>
++#include <assert.h>
++#include "zgetdump.h"
++
++/*
++ * Read memory for given memory chunk
++ */
++static void dfi_elf_mem_chunk_read_fn(struct dfi_mem_chunk *mem_chunk, u64 off,
++ void *buf, u64 cnt)
++{
++ u64 elf_load_off = *((u64 *) mem_chunk->data);
++
++ zg_seek(g.fh, elf_load_off + off, ZG_CHECK);
++ zg_read(g.fh, buf, cnt, ZG_CHECK);
++}
++
++/*
++ * Add load (memory chunk) to DFI dump
++ */
++static int pt_load_add(Elf64_Phdr *phdr)
++{
++ u64 *off_ptr;
++
++ if (phdr->p_paddr != phdr->p_vaddr) {
++ phdr->p_paddr = phdr->p_vaddr;
++ STDERR("Dump file \"%s\" is a user space core dump\n",
++ g.opts.device);
++ }
++ if (phdr->p_filesz == 0) /* Skip null pt loads */
++ return 0;
++ off_ptr = zg_alloc(sizeof(*off_ptr));
++ *off_ptr = phdr->p_offset;
++ dfi_mem_chunk_add(phdr->p_paddr, phdr->p_memsz, off_ptr,
++ dfi_elf_mem_chunk_read_fn);
++ if (phdr->p_offset + phdr->p_memsz > zg_size(g.fh))
++ return -EINVAL;
++ return 0;
++}
++
++/*
++ * Skip name of note
++ */
++static void nt_name_skip(Elf64_Nhdr *note)
++{
++ zg_seek_cur(g.fh, ROUNDUP(note->n_namesz, 4), ZG_CHECK);
++}
++
++/*
++ * Read note
++ */
++static int nt_read(Elf64_Nhdr *note, void *buf)
++{
++ off_t buf_len = ROUNDUP(note->n_descsz, 4);
++ char tmp_buf[buf_len];
++
++ nt_name_skip(note);
++ if (zg_read(g.fh, tmp_buf, buf_len, ZG_CHECK_ERR) != buf_len)
++ return -EINVAL;
++ if (buf)
++ memcpy(buf, tmp_buf, note->n_descsz);
++ return 0;
++}
++
++/*
++ * Skip note
++ */
++static int nt_skip(Elf64_Nhdr *note)
++{
++ return nt_read(note, NULL);
++}
++
++/*
++ * Ensure that CPU is already defined by prstatus note
++ */
++static void check_cpu(struct dfi_cpu *cpu, const char *note_str)
++{
++ if (cpu)
++ return;
++ ERR_EXIT("Invalid ELF dump (%s before prstatus found)", note_str);
++}
++
++/*
++ * Read prstatus note and return new DFI CPU
++ */
++static struct dfi_cpu *nt_prstatus_read(Elf64_Nhdr *note)
++{
++ struct dfi_cpu *cpu = dfi_cpu_alloc();
++ struct nt_prstatus_64 nt_prstatus;
++
++ if (nt_read(note, &nt_prstatus))
++ return NULL;
++
++ memcpy(cpu->gprs, &nt_prstatus.gprs, sizeof(cpu->gprs));
++ memcpy(cpu->psw, &nt_prstatus.psw, sizeof(cpu->psw));
++ memcpy(cpu->acrs, &nt_prstatus.acrs, sizeof(cpu->acrs));
++
++ dfi_cpu_add(cpu);
++ return cpu;
++}
++
++/*
++ * Read fpregset note
++ */
++static int nt_fpregset_read(struct dfi_cpu *cpu, Elf64_Nhdr *note)
++{
++ struct nt_fpregset_64 nt_fpregset;
++
++ check_cpu(cpu, "FPREGSET");
++ if (nt_read(note, &nt_fpregset))
++ return -EINVAL;
++
++ memcpy(&cpu->fpc, &nt_fpregset.fpc, sizeof(cpu->fpc));
++ memcpy(cpu->fprs, &nt_fpregset.fprs, sizeof(cpu->fprs));
++ return 0;
++}
++
++/*
++ * Read s390 timer note
++ */
++static int nt_s390_timer_read(struct dfi_cpu *cpu, Elf64_Nhdr *note)
++{
++ check_cpu(cpu, "S390_TIMER");
++ return nt_read(note, &cpu->timer);
++}
++
++/*
++ * Read s390 todcmp note
++ */
++static int nt_s390_todcmp_read(struct dfi_cpu *cpu, Elf64_Nhdr *note)
++{
++ check_cpu(cpu, "S390_TODCMP");
++ return nt_read(note, &cpu->todcmp);
++}
++
++/*
++ * Read s390 todpreg note
++ */
++static int nt_s390_todpreg_read(struct dfi_cpu *cpu, Elf64_Nhdr *note)
++{
++ check_cpu(cpu, "S390_TODPREG");
++ return nt_read(note, &cpu->todpreg);
++}
++
++/*
++ * Read s390 ctrs note
++ */
++static int nt_s390_ctrs_read(struct dfi_cpu *cpu, Elf64_Nhdr *note)
++{
++ check_cpu(cpu, "S390_CTRS");
++ return nt_read(note, &cpu->ctrs);
++}
++
++/*
++ * Read s390 prefix note
++ */
++static int nt_s390_prefix_read(struct dfi_cpu *cpu, Elf64_Nhdr *note)
++{
++ check_cpu(cpu, "S390_PREFIX");
++ return nt_read(note, &cpu->prefix);
++}
++
++/*
++ * Add all notes for notes phdr
++ */
++static int pt_notes_add(Elf64_Phdr *phdr)
++{
++ u64 start_off = zg_tell(g.fh, ZG_CHECK);
++ struct dfi_cpu *cpu_current = NULL;
++ u64 notes_start_off;
++ Elf64_Nhdr note;
++ int rc;
++
++ zg_seek(g.fh, phdr->p_offset, ZG_CHECK);
++ notes_start_off = zg_tell(g.fh, ZG_CHECK);
++ while (zg_tell(g.fh, ZG_CHECK) - notes_start_off < phdr->p_filesz) {
++ rc = zg_read(g.fh, ¬e, sizeof(note), ZG_CHECK_ERR);
++ if (rc != sizeof(note))
++ return -EINVAL;
++ switch (note.n_type) {
++ case NT_PRSTATUS:
++ cpu_current = nt_prstatus_read(¬e);
++ if (!cpu_current)
++ return -EINVAL;
++ break;
++ case NT_FPREGSET:
++ if (nt_fpregset_read(cpu_current, ¬e))
++ return -EINVAL;
++ break;
++ case NT_S390_TIMER:
++ if (nt_s390_timer_read(cpu_current, ¬e))
++ return -EINVAL;
++ break;
++ case NT_S390_TODCMP:
++ if (nt_s390_todcmp_read(cpu_current, ¬e))
++ return -EINVAL;
++ break;
++ case NT_S390_TODPREG:
++ if (nt_s390_todpreg_read(cpu_current, ¬e))
++ return -EINVAL;
++ break;
++ case NT_S390_CTRS:
++ if (nt_s390_ctrs_read(cpu_current, ¬e))
++ return -EINVAL;
++ break;
++ case NT_S390_PREFIX:
++ if (nt_s390_prefix_read(cpu_current, ¬e))
++ return -EINVAL;
++ break;
++ default:
++ if (nt_skip(¬e))
++ return -EINVAL;
++ break;
++ }
++ }
++ zg_seek(g.fh, start_off, ZG_CHECK);
++ return 0;
++}
++
++/*
++ * Read ELF header
++ */
++static int read_elf_hdr(Elf64_Ehdr *ehdr)
++{
++ if (zg_size(g.fh) < sizeof(*ehdr))
++ return -ENODEV;
++ zg_read(g.fh, ehdr, sizeof(*ehdr), ZG_CHECK);
++ if (memcmp(ehdr->e_ident, ELFMAG, SELFMAG) != 0)
++ return -ENODEV;
++ if (ehdr->e_type != ET_CORE)
++ return -ENODEV;
++ if (ehdr->e_machine != EM_S390 || ehdr->e_ident[EI_CLASS] != ELFCLASS64)
++ ERR_EXIT("Only s390x (64 bit) core dump files are supported");
++ return 0;
++}
++
++/*
++ * Initialize ELF input dump format
++ */
++static int dfi_elf_init(void)
++{
++ Elf64_Ehdr ehdr;
++ Elf64_Phdr phdr;
++ int i;
++
++ if (read_elf_hdr(&ehdr) != 0)
++ return -ENODEV;
++
++ df_elf_ensure_s390x();
++ dfi_arch_set(DFI_ARCH_64);
++ dfi_cpu_info_init(DFI_CPU_CONTENT_ALL);
++
++ for (i = 0; i < ehdr.e_phnum; i++) {
++ zg_read(g.fh, &phdr, sizeof(phdr), ZG_CHECK);
++ switch (phdr.p_type) {
++ case PT_LOAD:
++ if (pt_load_add(&phdr))
++ return -EINVAL;
++ break;
++ case PT_NOTE:
++ if (pt_notes_add(&phdr))
++ return -EINVAL;
++ break;
++ default:
++ break;
++ }
++ }
++ dfi_attr_version_set(ehdr.e_ident[EI_VERSION]);
++ return 0;
++}
++
++/*
++ * ELF DFI operations
++ */
++struct dfi dfi_elf = {
++ .name = "elf",
++ .init = dfi_elf_init,
++ .feat_bits = DFI_FEAT_COPY | DFI_FEAT_SEEK,
++};
+diff --git a/zdump/dfi_kdump.c b/zdump/dfi_kdump.c
+new file mode 100644
+index 0000000..537ea55
+--- /dev/null
++++ b/zdump/dfi_kdump.c
+@@ -0,0 +1,122 @@
++/*
++ * zgetdump - Tool for copying and converting System z dumps
++ *
++ * kdump input format
++ *
++ * Copyright IBM Corp. 2001, 2010
++ * Author(s): Michael Holzheu <holzheu at linux.vnet.ibm.com>
++ */
++
++#include "zgetdump.h"
++
++struct l_new_utsname {
++ char sysname[65];
++ char nodename[65];
++ char release[65];
++ char version[65];
++ char machine[65];
++ char domainname[65];
++};
++
++struct df_kdump_hdr {
++ char signature[8];
++ int header_version;
++ struct l_new_utsname utsname;
++ struct timeval timestamp;
++ unsigned int status;
++ int block_size;
++ int sub_hdr_size;
++ unsigned int bitmap_blocks;
++ unsigned int max_mapnr;
++ unsigned int total_ram_blocks;
++ unsigned int device_blocks;
++ unsigned int written_blocks;
++ unsigned int current_cpu;
++ int nr_cpus;
++ void *tasks[0];
++};
++
++struct df_kdump_sub_hdr {
++ unsigned long phys_base;
++ int dump_level;
++ int split;
++ unsigned long start_pfn;
++ unsigned long end_pfn;
++ off_t offset_vmcoreinfo;
++ unsigned long size_vmcoreinfo;
++};
++
++/*
++ * File local static data
++ */
++static struct {
++ struct df_kdump_hdr hdr; /* kdump (diskdump) dump header */
++ struct df_kdump_sub_hdr shdr; /* kdump subheader */
++} l;
++
++#ifdef DEBUG
++static void print_header(void)
++{
++ STDERR("diskdump main header\n");
++ STDERR(" signature : %s\n", l.hdr.signature);
++ STDERR(" header_version : %d\n", l.hdr.header_version);
++ STDERR(" status : %d\n", l.hdr.status);
++ STDERR(" block_size : %d\n", l.hdr.block_size);
++ STDERR(" sub_hdr_size : %d\n", l.hdr.sub_hdr_size);
++ STDERR(" bitmap_blocks : %d\n", l.hdr.bitmap_blocks);
++ STDERR(" max_mapnr : 0x%x\n", l.hdr.max_mapnr);
++ STDERR(" total_ram_blocks : %d\n", l.hdr.total_ram_blocks);
++ STDERR(" device_blocks : %d\n", l.hdr.device_blocks);
++ STDERR(" written_blocks : %d\n", l.hdr.written_blocks);
++ STDERR(" current_cpu : %d\n", l.hdr.current_cpu);
++ STDERR(" nr_cpus : %d\n", l.hdr.nr_cpus);
++ STDERR("kdump sub header\n");
++ STDERR(" phys_base : 0x%lx\n", l.shdr.phys_base);
++ STDERR(" dump_level : %d\n", l.shdr.dump_level);
++ STDERR(" split : %d\n", l.shdr.split);
++ STDERR(" start_pfn : 0x%lx\n", l.shdr.start_pfn);
++ STDERR(" end_pfn : 0x%lx\n", l.shdr.end_pfn);
++}
++#endif
++
++/*
++ * Read kdump dump header
++ */
++static int read_kdump_hdr(void)
++{
++ if ((zg_type(g.fh) == ZG_TYPE_FILE) && (zg_size(g.fh) < sizeof(l.hdr)))
++ return -ENODEV;
++ zg_read(g.fh, &l.hdr, sizeof(l.hdr), ZG_CHECK);
++ if (memcmp(l.hdr.signature, "KDUMP", 5) != 0)
++ return -ENODEV;
++ zg_seek(g.fh, l.hdr.block_size, ZG_CHECK);
++ zg_read(g.fh, &l.shdr, sizeof(l.shdr), ZG_CHECK);
++ dfi_attr_version_set(l.hdr.header_version);
++ dfi_attr_real_cpu_cnt_set(l.hdr.nr_cpus);
++ dfi_arch_set(DFI_ARCH_64);
++#ifdef DEBUG
++ print_header();
++#endif
++ return 0;
++}
++
++/*
++ * Initialize kdump DFI
++ */
++static int dfi_kdump_init(void)
++{
++ if (read_kdump_hdr() != 0)
++ return -ENODEV;
++ dfi_mem_chunk_add(0, l.hdr.max_mapnr * PAGE_SIZE, NULL, NULL);
++ return 0;
++
++}
++
++/*
++ * S390 DFI operations
++ */
++struct dfi dfi_kdump = {
++ .name = "kdump",
++ .init = dfi_kdump_init,
++ .feat_bits = 0,
++};
+diff --git a/zdump/dfi_lkcd.c b/zdump/dfi_lkcd.c
+new file mode 100644
+index 0000000..0c5e8a9
+--- /dev/null
++++ b/zdump/dfi_lkcd.c
+@@ -0,0 +1,333 @@
++/*
++ * zgetdump - Tool for copying and converting System z dumps
++ *
++ * LKCD dump input format
++ *
++ * Copyright IBM Corp. 2001, 2010
++ * Author(s): Michael Holzheu <holzheu at linux.vnet.ibm.com>
++ */
++
++#include <zlib.h>
++#include "zgetdump.h"
++
++#define MEM_HOLE_SIZE_MIN (1024 * 1024)
++#define IDX_KIB 64 /* One index entry per IDX_KIB */
++#define IDX_TO_ADDR(idx) (idx * IDX_KIB * 1024)
++#define ADDR_TO_IDX(addr) (addr / (1024 * IDX_KIB))
++
++/*
++ * File local static data
++ */
++static struct {
++ u64 *pg_hdr_idx;
++ u64 page_last;
++ struct df_lkcd_hdr hdr;
++ struct df_lkcd_hdr_asm hdr_asm;
++ int dump_full;
++} l;
++
++/*
++ * Read LKCD page buffer, either compressed or uncompressed
++ */
++static void read_page_buf(struct df_lkcd_pg_hdr *pg_hdr, void *buf)
++{
++ unsigned long size = PAGE_SIZE;
++ unsigned char cbuf[PAGE_SIZE];
++
++ switch (pg_hdr->flags) {
++ case DF_LKCD_DH_RAW:
++ zg_read(g.fh, buf, pg_hdr->size, ZG_CHECK);
++ break;
++ case DF_LKCD_DH_COMPRESSED:
++ zg_read(g.fh, cbuf, pg_hdr->size, ZG_CHECK);
++ uncompress(buf, &size, cbuf, pg_hdr->size);
++ if (size != PAGE_SIZE)
++ ABORT("Invalid page size: %ld", size);
++ break;
++ default:
++ ERR_EXIT("Unsupported page flags: %x at addr %Lx",
++ pg_hdr->flags, pg_hdr->addr);
++ }
++}
++
++/*
++ * Read next LKCD page from current file position
++ *
++ * If we find the page, we copy the page content. If the page is not present
++ * we copy zeroes and skip it. If the page address is not yet reached, we just
++ * skip it.
++ */
++static int read_next_page(u64 addr, void *buf)
++{
++ struct df_lkcd_pg_hdr pg_hdr;
++
++ zg_read(g.fh, &pg_hdr, sizeof(pg_hdr), ZG_CHECK);
++ l.page_last = pg_hdr.addr / PAGE_SIZE;
++ if (pg_hdr.addr == addr) {
++ read_page_buf(&pg_hdr, buf);
++ return 0;
++ }
++ if (pg_hdr.addr > addr) {
++ memset(buf, 0, PAGE_SIZE);
++ zg_seek_cur(g.fh, pg_hdr.size, ZG_CHECK);
++ return 0;
++ }
++ zg_seek_cur(g.fh, pg_hdr.size, ZG_CHECK);
++ return -ENODEV;
++}
++
++/*
++ * Read LKCD dump page for flex dump
++ *
++ * If the page after the last read page should be read, we just read
++ * the next one. Otherwise we seek to the beginning of the page cluster
++ * of the page index and search the page there.
++ */
++static void read_page_flex(u64 pg_num, void *buf)
++{
++ u64 addr = pg_num * PAGE_SIZE;
++
++ if (l.pg_hdr_idx[ADDR_TO_IDX(addr)] == 0)
++ ABORT("Dump page index broken");
++
++ if (l.page_last == pg_num - 1) {
++ read_next_page(addr, buf);
++ return;
++ }
++
++ zg_seek(g.fh, l.pg_hdr_idx[ADDR_TO_IDX(addr)], ZG_CHECK);
++ do {
++ if (read_next_page(addr, buf) == 0)
++ break;
++ } while (1);
++}
++
++/*
++ * Read lkcd page for full dump
++ */
++static void read_page_full(u64 pg_num, void *buf)
++{
++ zg_seek(g.fh, DF_LKCD_HDR_SIZE + pg_num * DF_LKCD_UCP_SIZE +
++ sizeof(struct df_lkcd_pg_hdr), ZG_CHECK);
++ zg_read(g.fh, buf, PAGE_SIZE, ZG_CHECK);
++}
++
++/*
++ * Read lkcd page
++ */
++static void read_page(u64 pg_num, void *buf)
++{
++ if (l.dump_full)
++ read_page_full(pg_num, buf);
++ else
++ read_page_flex(pg_num, buf);
++}
++
++/*
++ * LKCD mem chunk read callback
++ */
++static void dfi_lkcd_mem_chunk_read_fn(struct dfi_mem_chunk *mem_chunk, u64 off,
++ void *buf, u64 cnt)
++{
++ u64 copied = 0, size, pg_nr, addr = off + mem_chunk->start;
++ char pg_buf[PAGE_SIZE];
++ unsigned int pg_off;
++
++ while (copied != cnt) {
++ pg_nr = (addr + copied) / PAGE_SIZE;
++ pg_off = (addr + copied) % PAGE_SIZE;
++ size = MIN(cnt - copied, PAGE_SIZE - pg_off);
++ read_page(pg_nr, pg_buf);
++ memcpy(buf + copied, &pg_buf[pg_off], size);
++ copied += size;
++ }
++}
++
++/*
++ * Did we find the end of the LCKD dump?
++ */
++static int dump_end(u64 addr, struct df_lkcd_pg_hdr *pg_hdr)
++{
++ if (addr == pg_hdr->addr) {
++ /*
++ * This is a workaroud for a bug in vmconvert,
++ * where instaed of the end marker the last
++ * page was written twice. Sorry for that...
++ */
++ return 1;
++ }
++ if (pg_hdr->addr == 0 && pg_hdr->size == 4 && pg_hdr->flags == 0) {
++ /*
++ * zfcpdump bug (wrong end marker)
++ */
++ return 1;
++ }
++ if (pg_hdr->flags == DF_LKCD_DH_END)
++ return 1;
++ return 0;
++}
++
++/*
++ * Init memory chunks for full dump
++ *
++ * Full dump: It is not compressed and it does not have any memory holes.
++ */
++static int mem_init_full(void)
++{
++ dfi_mem_chunk_add(0, l.hdr.mem_end, NULL, dfi_lkcd_mem_chunk_read_fn);
++ l.dump_full = 1;
++ return 0;
++}
++
++/*
++ * Init memory chunks for flex dump
++ *
++ * Flex dump: It is compressed and/or it has memory holes.
++ */
++static int mem_init_flex(void)
++{
++ u64 addr = U64_MAX, idx = 0, mem_chunk_start = 0, rc;
++ struct df_lkcd_pg_hdr pg_hdr;
++ int dump_incomplete = 0;
++
++ l.pg_hdr_idx = zg_alloc(sizeof(u64) * (ADDR_TO_IDX(l.hdr.mem_end) + 1));
++ zg_seek(g.fh, DF_LKCD_HDR_SIZE, ZG_CHECK_NONE);
++ zg_progress_init("Analyzing dump", l.hdr.mem_end);
++ do {
++ rc = zg_read(g.fh, &pg_hdr, sizeof(pg_hdr), ZG_CHECK_ERR);
++ if (rc != sizeof(pg_hdr)) {
++ dump_incomplete = 1;
++ break;
++ }
++ if (dump_end(addr, &pg_hdr))
++ break;
++ if (pg_hdr.addr - addr > MEM_HOLE_SIZE_MIN) {
++ dfi_mem_chunk_add(mem_chunk_start,
++ addr + PAGE_SIZE - mem_chunk_start,
++ NULL,
++ dfi_lkcd_mem_chunk_read_fn);
++ mem_chunk_start = pg_hdr.addr;
++ }
++ addr = pg_hdr.addr;
++ zg_progress(addr);
++ if (addr >= IDX_TO_ADDR(idx)) {
++ idx = ADDR_TO_IDX(addr);
++ l.pg_hdr_idx[idx] = zg_tell(g.fh, ZG_CHECK) -
++ sizeof(pg_hdr);
++ idx++;
++ }
++ zg_seek_cur(g.fh, pg_hdr.size, ZG_CHECK);
++ } while (1);
++
++ if (addr != mem_chunk_start) {
++ dfi_mem_chunk_add(mem_chunk_start,
++ l.hdr.mem_end - mem_chunk_start,
++ NULL,
++ dfi_lkcd_mem_chunk_read_fn);
++ }
++ zg_progress(l.hdr.mem_end);
++ if (g.opts.action != ZG_ACTION_MOUNT)
++ fprintf(stderr, "\n");
++ if (dump_incomplete)
++ return -EINVAL;
++ return 0;
++}
++
++/*
++ * Do we have a full dump?
++ */
++static int is_full_dump()
++{
++ u64 full_size;
++ int pages;
++
++ if (l.hdr.dump_compress != DF_LKCD_COMPRESS_NONE)
++ return 0;
++ pages = l.hdr.mem_end / PAGE_SIZE;
++ full_size = DF_LKCD_HDR_SIZE + pages * DF_LKCD_UCP_SIZE +
++ sizeof(struct df_lkcd_pg_hdr);
++ if (zg_size(g.fh) != full_size)
++ return 0;
++ return 1;
++}
++
++/*
++ * Init memory chunks
++ */
++static int mem_init(void)
++{
++ if (is_full_dump())
++ return mem_init_full();
++ else
++ return mem_init_flex();
++}
++
++/*
++ * Initialize CPU information
++ */
++static void cpu_init(void)
++{
++ unsigned int i;
++
++ if (l.hdr_asm.magic != DF_LKCD_MAGIC_ASM) {
++ /* Old LKCD dump without asm header */
++ dfi_cpu_info_init(DFI_CPU_CONTENT_NONE);
++ return;
++ }
++
++ dfi_cpu_info_init(DFI_CPU_CONTENT_ALL);
++ for (i = 0; i < l.hdr_asm.cpu_cnt; i++)
++ dfi_cpu_add_from_lc(l.hdr_asm.lc_vec[i]);
++}
++
++/*
++ * Read LKCD dump header and dump asm header
++ */
++static int read_lkcd_hdr(void)
++{
++ if (zg_size(g.fh) < DF_LKCD_HDR_SIZE)
++ return -ENODEV;
++
++ /* Read dump header */
++ zg_read(g.fh, &l.hdr, sizeof(l.hdr), ZG_CHECK);
++
++ if (l.hdr.magic != DF_LKCD_MAGIC)
++ return -ENODEV;
++
++ /* Read asm header */
++ zg_seek(g.fh, l.hdr.hdr_size, ZG_CHECK);
++ zg_read(g.fh, &l.hdr_asm, sizeof(l.hdr_asm), ZG_CHECK);
++ if (strncmp(l.hdr.utsname_machine, "s390x", sizeof("s390x")) == 0)
++ dfi_arch_set(DFI_ARCH_64);
++ else if (strncmp(l.hdr.utsname_machine, "s390", sizeof("s390")) == 0)
++ dfi_arch_set(DFI_ARCH_32);
++ else
++ ERR_EXIT("Dump architecture \"%s\" is not supported",
++ l.hdr.utsname_machine);
++ if (l.hdr_asm.magic == DF_LKCD_MAGIC_ASM)
++ dfi_attr_real_cpu_cnt_set(l.hdr_asm.real_cpu_cnt);
++ dfi_attr_version_set(l.hdr.version);
++ return 0;
++}
++
++/*
++ * Initialize LKCD DFI
++ */
++static int dfi_lkcd_init(void)
++{
++ if (read_lkcd_hdr() != 0)
++ return -ENODEV;
++ if (mem_init() != 0)
++ return -EINVAL;
++ cpu_init();
++ return 0;
++}
++
++/*
++ * LKCD DFI operations
++ */
++struct dfi dfi_lkcd = {
++ .name = "lkcd",
++ .init = dfi_lkcd_init,
++ .feat_bits = DFI_FEAT_COPY | DFI_FEAT_SEEK,
++};
+diff --git a/zdump/dfi_s390.c b/zdump/dfi_s390.c
+new file mode 100644
+index 0000000..8a69849
+--- /dev/null
++++ b/zdump/dfi_s390.c
+@@ -0,0 +1,95 @@
++/*
++ * zgetdump - Tool for copying and converting System z dumps
++ *
++ * S390 dump input format
++ *
++ * Copyright IBM Corp. 2001, 2010
++ * Author(s): Michael Holzheu <holzheu at linux.vnet.ibm.com>
++ */
++
++#include <string.h>
++#include <time.h>
++#include <fcntl.h>
++#include <stdio.h>
++#include <unistd.h>
++#include <sys/types.h>
++#include <sys/stat.h>
++#include <linux/fs.h>
++#include "zgetdump.h"
++
++/*
++ * File local static data
++ */
++static struct {
++ struct df_s390_hdr hdr; /* s390 dump header */
++ struct df_s390_em em; /* s390 end marker */
++} l;
++
++/*
++ * S390 mem chunk read callback
++ */
++static void dfi_s390_mem_chunk_read(struct dfi_mem_chunk *mem_chunk, u64 off,
++ void *buf, u64 cnt)
++{
++ (void) mem_chunk;
++
++ zg_seek(g.fh, off + DF_S390_HDR_SIZE, ZG_CHECK);
++ zg_read(g.fh, buf, cnt, ZG_CHECK);
++}
++
++/*
++ * Read s390 dump header
++ */
++static int read_s390_hdr(void)
++{
++ if ((zg_type(g.fh) == ZG_TYPE_FILE) && (zg_size(g.fh) < sizeof(l.hdr)))
++ return -ENODEV;
++ zg_read(g.fh, &l.hdr, sizeof(l.hdr), ZG_CHECK);
++ if (l.hdr.magic != DF_S390_MAGIC)
++ return -ENODEV;
++ df_s390_hdr_add(&l.hdr);
++ return 0;
++}
++
++/*
++ * Init end marker
++ */
++static int read_s390_em(void)
++{
++ u64 rc;
++
++ rc = zg_seek(g.fh, l.hdr.mem_size + DF_S390_HDR_SIZE, ZG_CHECK_NONE);
++ if (rc != l.hdr.mem_size + DF_S390_HDR_SIZE)
++ return -EINVAL;
++ rc = zg_read(g.fh, &l.em, sizeof(l.em), ZG_CHECK_ERR);
++ if (rc != sizeof(l.em))
++ return -EINVAL;
++ if (df_s390_em_verify(&l.em, &l.hdr) != 0)
++ return -EINVAL;
++ df_s390_em_add(&l.em);
++ return 0;
++}
++
++/*
++ * Initialize s390 DFI
++ */
++static int dfi_s390_init(void)
++{
++ if (read_s390_hdr() != 0)
++ return -ENODEV;
++ dfi_mem_chunk_add(0, l.hdr.mem_size, NULL, dfi_s390_mem_chunk_read);
++ if (read_s390_em() != 0)
++ return -EINVAL;
++ df_s390_cpu_info_add(&l.hdr, l.hdr.mem_size);
++ zg_seek(g.fh, sizeof(l.hdr), ZG_CHECK);
++ return 0;
++}
++
++/*
++ * S390 DFI operations
++ */
++struct dfi dfi_s390 = {
++ .name = "s390",
++ .init = dfi_s390_init,
++ .feat_bits = DFI_FEAT_COPY | DFI_FEAT_SEEK,
++};
+diff --git a/zdump/dfi_s390mv.c b/zdump/dfi_s390mv.c
+new file mode 100644
+index 0000000..f79188d
+--- /dev/null
++++ b/zdump/dfi_s390mv.c
+@@ -0,0 +1,547 @@
++/*
++ * zgetdump - Tool for copying and converting System z dumps
++ *
++ * S390 multi-volume dump input format
++ *
++ * Copyright IBM Corp. 2001, 2010
++ * Author(s): Michael Holzheu <holzheu at linux.vnet.ibm.com>
++ */
++
++#include <string.h>
++#include <time.h>
++#include <fcntl.h>
++#include <stdio.h>
++#include <unistd.h>
++#include <dirent.h>
++#include <sys/ioctl.h>
++#include <sys/types.h>
++#include <sys/stat.h>
++#include <linux/fs.h>
++#include "zgetdump.h"
++
++#define SYSFS_BUSDIR "/sys/bus/ccw/devices"
++#define MAX_VOLUMES 32
++
++/*
++ * Parameter for DASD multi-volume dump
++ */
++struct vol_parm {
++ u16 devno;
++ u32 start_blk;
++ u32 end_blk;
++ u8 blk_size;
++ u8 end_sec;
++ u8 num_heads;
++} __attribute__ ((packed));
++
++struct vol_parm_table {
++ u64 timestamp;
++ u16 vol_cnt;
++ struct vol_parm vol_parm[MAX_VOLUMES];
++} __attribute__ ((packed));
++
++/*
++ * Device signature
++ */
++enum dev_sign {
++ SIGN_INVALID = 0, /* No dumper installed */
++ SIGN_VALID = 1, /* dumper installed, but volume not used */
++ SIGN_ACTIVE = 2, /* dumper installed and volume userd */
++};
++
++static char *dev_sign_str[] = {"invalid", "valid", "active"};
++#define dev_sign_str(x) (dev_sign_str[x])
++
++/*
++ * Device status
++ */
++enum dev_status {
++ DEV_ONLINE = 0,
++ DEV_OFFLINE = 1,
++ DEV_UNDEFINED = 2,
++};
++
++static char *dev_status_str[] = {"online", "offline", "undefined"};
++#define dev_status_str(x) (dev_status_str[x])
++
++/*
++ * Volume information
++ */
++struct vol {
++ dev_t dev;
++ struct zg_fh *fh;
++ char *devnode;
++ enum dev_status status;
++ enum dev_sign sign;
++ off_t part_off;
++ u64 part_size;
++ u64 mem_start;
++ u64 mem_end;
++ char bus_id[9];
++ u32 nr;
++ u16 blk_size;
++ struct df_s390_dumper dumper;
++ struct df_s390_hdr hdr;
++};
++
++/*
++ * File local static data
++ */
++static struct {
++ struct df_s390_hdr hdr;
++ struct df_s390_em em;
++ struct vol vol_vec[MAX_VOLUMES];
++ struct vol_parm_table table;
++ int blk_size;
++ struct df_s390_dumper dumper;
++ int dump_incomplete;
++} l;
++
++/*
++ * Read volume parameter table
++ */
++static void table_read(struct zg_fh *fh, u16 blk_size,
++ struct vol_parm_table *table)
++{
++ int off;
++
++ off = DF_S390_MAGIC_BLK_ECKD * blk_size + df_s390_dumper_size(l.dumper);
++ zg_seek(fh, off, ZG_CHECK);
++ zg_read(fh, table, sizeof(*table), ZG_CHECK);
++}
++
++/*
++ * Initialize dump end marker
++ */
++static void em_init(struct vol *vol)
++{
++ off_t em_off;
++
++ em_off = vol->part_off + (vol->mem_end + 1 - vol->mem_start) +
++ DF_S390_HDR_SIZE;
++ zg_seek(vol->fh, em_off, ZG_CHECK);
++ zg_read(vol->fh, &l.em, sizeof(l.em), ZG_CHECK);
++ if (df_s390_em_verify(&l.em, &l.hdr) != 0)
++ l.dump_incomplete = 1;
++}
++
++/*
++ * Check sysfs, whether a device specified by its bus ID is defined and online.
++ * Find out the corresponding dev_t
++ */
++static enum dev_status dev_from_busid(char *bus_id, dev_t *dev)
++{
++ char tmp_file[PATH_MAX], dev_file[PATH_MAX];
++ struct dirent *direntp;
++ int fh, minor, major;
++ char buf[10];
++ DIR *fh_dir;
++
++ snprintf(dev_file, PATH_MAX, "%s/%s", SYSFS_BUSDIR, bus_id);
++ fh_dir = opendir(dev_file);
++ if (!fh_dir)
++ return DEV_UNDEFINED;
++
++ snprintf(tmp_file, PATH_MAX, "%s/online", dev_file);
++ fh = open(tmp_file, O_RDONLY);
++ if (read(fh, buf, 1) == -1)
++ ERR_EXIT_ERRNO("Could not read online attribute");
++ close(fh);
++
++ if (buf[0] != '1')
++ return DEV_OFFLINE;
++
++ while ((direntp = readdir(fh_dir)))
++ if (strncmp(direntp->d_name, "block:", 6) == 0)
++ break;
++ closedir(fh_dir);
++
++ if (direntp == NULL) {
++ snprintf(dev_file, PATH_MAX, "%s/%s/block", SYSFS_BUSDIR,
++ bus_id);
++ fh_dir = opendir(dev_file);
++ if (!fh_dir)
++ ERR_EXIT_ERRNO("Could not open \"%s\"", dev_file);
++ while ((direntp = readdir(fh_dir)))
++ if (strncmp(direntp->d_name, "dasd", 4) == 0)
++ break;
++ closedir(fh_dir);
++ if (direntp == NULL)
++ ERR_EXIT("Problem with contents of \"%s\"", dev_file);
++ }
++ snprintf(tmp_file, PATH_MAX, "%s/%s/dev", dev_file, direntp->d_name);
++ fh = open(tmp_file, O_RDONLY);
++ if (read(fh, buf, sizeof(buf)) == -1)
++ ERR_EXIT_ERRNO("Could not read dev file");
++ close(fh);
++ if (sscanf(buf, "%i:%i", &major, &minor) != 2)
++ ERR_EXIT("Malformed content of \"%s\": %s", tmp_file, buf);
++ *dev = makedev(major, minor);
++ return DEV_ONLINE;
++}
++
++/*
++ * Check whether dump table on user specified dump device is
++ * identical to the one found on this device.
++ */
++static void check_vol_table(struct vol *vol)
++{
++ struct vol_parm_table vol_table;
++
++ table_read(vol->fh, vol->blk_size, &vol_table);
++ if (memcmp(&vol_table, &l.table, sizeof(vol_table)))
++ ERR_EXIT("Orphaned multi-volume dump device '%s'",
++ g.opts.device);
++}
++
++/*
++ * Read dump tool, multi-volume dump parameter table, and dump header from the
++ * input dump volume. Check input dump volume for:
++ * - identical dump parameter table (that is it belongs to the same dump set)
++ * - valid magic number in the dump tool
++ * - valid dump sign in the dump header
++ *
++ * We read partition data via the device node. If another process
++ * has changed partition data via the partition node, the corresponding
++ * device node might still have old data in its buffers. Flush buffers
++ * to keep things in sync.
++ */
++void vol_read(struct vol *vol)
++{
++ zg_ioctl(vol->fh, BLKFLSBUF, NULL, "BLKFLSBUF", ZG_CHECK);
++ df_s390_dumper_read(vol->fh, vol->blk_size, &vol->dumper);
++ check_vol_table(vol);
++ zg_seek(vol->fh, vol->part_off, ZG_CHECK);
++ zg_read(vol->fh, &vol->hdr, DF_S390_HDR_SIZE, ZG_CHECK);
++}
++
++/*
++ * Read memory
++ */
++static void df_s390mv_mem_read(struct dfi_mem_chunk *mem_chunk, u64 off,
++ void *buf, u64 cnt)
++{
++ struct vol *vol = mem_chunk->data;
++
++ zg_seek(vol->fh, vol->part_off + off + DF_S390_HDR_SIZE, ZG_CHECK);
++ zg_read(vol->fh, buf, cnt, ZG_CHECK);
++}
++
++/*
++ * Initilize DASD volume
++ */
++static void vol_init(struct vol *vol, struct vol_parm *vol_parm, u64 *mem_off)
++{
++ u64 blk_cnt = vol_parm->end_blk - vol_parm->start_blk + 1;
++
++ sprintf(vol->bus_id, "0.0.%04x", vol_parm->devno);
++ vol->blk_size = vol_parm->blk_size << 8;
++ vol->part_off = vol_parm->start_blk * vol->blk_size;
++ vol->part_size = blk_cnt * vol->blk_size;
++ vol->status = dev_from_busid(vol->bus_id, &vol->dev);
++ vol->sign = SIGN_VALID;
++
++ if (vol->status != DEV_ONLINE)
++ return;
++
++ vol->devnode = zg_devnode_create(vol->dev);
++ vol->fh = zg_open(vol->devnode, O_RDONLY, ZG_CHECK);
++
++ vol_read(vol);
++
++ if ((vol->hdr.volnr == vol->nr) && (vol->hdr.mem_size != 0))
++ vol->sign = SIGN_ACTIVE;
++
++ if (vol->hdr.mvdump_sign != DF_S390_MAGIC) {
++ vol->sign = SIGN_INVALID;
++ l.dump_incomplete = 1;
++ }
++
++ if (strncmp(df_s390_dumper_magic(vol->dumper), "ZMULT64", 7) != 0) {
++ vol->sign = SIGN_INVALID;
++ l.dump_incomplete = 1;
++ }
++
++ if (vol->nr == 0)
++ l.hdr = vol->hdr;
++
++ if (*mem_off == l.hdr.mem_size) {
++ /* Unused volume */
++ vol->mem_start = 0;
++ vol->mem_end = 0;
++ if (vol->sign == SIGN_ACTIVE)
++ vol->sign = SIGN_VALID;
++ } else {
++ /* Used volume */
++ vol->mem_start = *mem_off;
++ vol->mem_end = *mem_off + PAGE_ALIGN(vol->part_size) -
++ DF_S390_HDR_SIZE - 1;
++ vol->mem_end = MIN(vol->mem_end, l.hdr.mem_size - 1);
++ if (vol->mem_end == l.hdr.mem_size - 1)
++ em_init(vol);
++ *mem_off += vol->mem_end - vol->mem_start + 1;
++ }
++}
++
++/*
++ * Print volume information
++ */
++static void vol_print(struct vol *vol)
++{
++ STDERR(" Volume %i: %s (%s", vol->nr, vol->bus_id,
++ dev_status_str(vol->status));
++ if (vol->status == DEV_ONLINE)
++ STDERR("/%s)\n", dev_sign_str(vol->sign));
++ else
++ STDERR(")\n");
++}
++
++/*
++ * Print information for all volumes
++ */
++static void vol_print_all(void)
++{
++ unsigned int i;
++
++ for (i = 0; i < l.table.vol_cnt; i++)
++ vol_print(&l.vol_vec[i]);
++}
++
++/*
++ * Add memory chunks
++ */
++static void mem_chunks_add(void)
++{
++ unsigned int i;
++
++ for (i = 0; i < l.table.vol_cnt; i++) {
++ struct vol *vol = &l.vol_vec[i];
++ if (vol->sign != SIGN_ACTIVE)
++ continue;
++ dfi_mem_chunk_add(vol->mem_start,
++ vol->mem_end - vol->mem_start + 1,
++ vol, df_s390mv_mem_read);
++ }
++}
++
++/*
++ * Print hint for setting all offline volumes online
++ */
++static void vol_offline_msg(void)
++{
++ unsigned int i, first = 1;
++
++ STDERR("\n");
++ STDERR("Set all devices online using:\n");
++ STDERR("# chccwdev -e ");
++ for (i = 0; i < l.table.vol_cnt; i++) {
++ if (l.vol_vec[i].status == DEV_OFFLINE) {
++ if (first)
++ first = 0;
++ else
++ STDERR(",");
++ STDERR("%s", l.vol_vec[i].bus_id);
++ }
++ }
++ STDERR("\n");
++}
++
++/*
++ * Print error for all undefined volumes
++ */
++static void vol_undefined_msg(void)
++{
++ unsigned int i;
++
++ STDERR("\n");
++ STDERR("Ensure that the following devices are available to the"
++ "system:\n");
++ for (i = 0; i < l.table.vol_cnt; i++) {
++ if (l.vol_vec[i].status == DEV_UNDEFINED)
++ STDERR("* %s\n", l.vol_vec[i].bus_id);
++ }
++}
++
++/*
++ * Check that all volumes are in online state
++ */
++static int vol_online_check(void)
++{
++ unsigned int i, offline = 0, undefined = 0;
++
++ for (i = 0; i < l.table.vol_cnt; i++) {
++ if (l.vol_vec[i].status == DEV_OFFLINE)
++ offline = 1;
++ if (l.vol_vec[i].status == DEV_UNDEFINED)
++ undefined = 1;
++ }
++ if (!offline && !undefined)
++ return 0;
++
++ STDERR("Found multi-volume dump tool:\n\n");
++ vol_print_all();
++ if (offline)
++ vol_offline_msg();
++ if (undefined)
++ vol_undefined_msg();
++ return -ENODEV;
++}
++
++/*
++ * Check if on device is a multi-volume dump
++ */
++static int mvdump_hdr_check(const char *file)
++{
++ struct df_s390_hdr hdr;
++ struct zg_fh *fh;
++ int rc = -ENODEV;
++
++ fh = zg_open(file, O_RDONLY, ZG_CHECK);
++ zg_read(fh, &hdr, sizeof(hdr), ZG_CHECK);
++ if (hdr.magic != DF_S390_MAGIC)
++ goto fail;
++ if (hdr.mvdump_sign != DF_S390_MAGIC)
++ goto fail;
++ rc = 0;
++fail:
++ zg_close(fh);
++ return rc;
++}
++
++/*
++ * Check if sysfs is available
++ */
++static void check_sysfs(void)
++{
++ DIR *fh_dir;
++
++ fh_dir = opendir(SYSFS_BUSDIR);
++ if (!fh_dir)
++ ERR_EXIT_ERRNO("Could not open %s\n", SYSFS_BUSDIR);
++ closedir(fh_dir);
++}
++
++/*
++ * Print dump information (dfi operation)
++ */
++static void dfi_s390mvfo_dump(void)
++{
++ vol_print_all();
++}
++
++/*
++ * Read dump tool from DASD and check if we have a multi-volume dump tool
++ */
++static int mv_dumper_read(void)
++{
++ if (zg_ioctl(g.fh, BLKSSZGET, &l.blk_size, "BLKSSZGET",
++ ZG_CHECK_NONE) == -1)
++ return -ENODEV;
++ df_s390_dumper_read(g.fh, l.blk_size, &l.dumper);
++ if (memcmp(df_s390_dumper_magic(l.dumper), "ZMULT64", 7) != 0)
++ return -ENODEV;
++ table_read(g.fh, l.blk_size, &l.table);
++ return 0;
++}
++
++/*
++ * Initialize all volumes
++ */
++static void volumes_init(void)
++{
++ u64 mem_off = 0;
++ unsigned int i;
++
++ check_sysfs();
++
++ for (i = 0; i < l.table.vol_cnt; i++) {
++ l.vol_vec[i].nr = i;
++ vol_init(&l.vol_vec[i], &l.table.vol_parm[i], &mem_off);
++ }
++ if (mem_off != l.hdr.mem_size)
++ l.dump_incomplete = 1;
++}
++
++/*
++ * Open dump - If partition is specified open device instead
++ */
++static int open_dump(void)
++{
++ const struct stat *stat = zg_stat(g.fh);
++ unsigned dev_minor;
++ enum zg_type type;
++ char *path;
++
++ type = zg_type(g.fh);
++ if (type != ZG_TYPE_DASD && type != ZG_TYPE_DASD_PART)
++ return -ENODEV;
++
++ if (type == ZG_TYPE_DASD_PART) {
++ dev_minor = minor(stat->st_rdev) - (minor(stat->st_rdev) % 4);
++ if (mvdump_hdr_check(zg_path(g.fh)) != 0)
++ return -ENODEV;
++ path = zg_devnode_create(makedev(major(stat->st_rdev),
++ dev_minor));
++ zg_close(g.fh);
++ g.fh = zg_open(path, O_RDONLY, ZG_CHECK);
++ }
++ if (mv_dumper_read() != 0)
++ return -ENODEV;
++ return 0;
++}
++
++/*
++ * Initialize s390 multi-volume input dump format
++ */
++static int dfi_s390mv_init(void)
++{
++ if (open_dump() != 0)
++ return -ENODEV;
++ volumes_init();
++ if (vol_online_check() != 0)
++ zg_exit(1);
++ if (l.hdr.mem_size == 0)
++ return -ENODEV;
++ df_s390_hdr_add(&l.hdr);
++ mem_chunks_add();
++ if (l.dump_incomplete)
++ return -EINVAL;
++ df_s390_cpu_info_add(&l.hdr, l.hdr.mem_end);
++ df_s390_em_add(&l.em);
++ return 0;
++}
++
++/*
++ * Initialize s390 multi-volume dump tool (for -d option)
++ */
++int dt_s390mv_init(void)
++{
++ if (open_dump() != 0)
++ return -ENODEV;
++ volumes_init();
++ dt_arch_set(DFI_ARCH_64);
++ dt_version_set(df_s390_dumper_version(l.dumper));
++ if (df_s390_dumper_mem(l.dumper) != U64_MAX)
++ dt_attr_mem_limit_set(df_s390_dumper_mem(l.dumper));
++
++ dt_attr_force_set(df_s390_dumper_force(l.dumper));
++ return 0;
++}
++
++/*
++ * s390 multi-volume dump tool info function (for -d option)
++ */
++void dt_s390mv_info(void)
++{
++ vol_print_all();
++}
++
++/*
++ * S390 multi-volume DFI operations
++ */
++struct dfi dfi_s390mv = {
++ .name = "s390mv",
++ .init = dfi_s390mv_init,
++ .info_dump = dfi_s390mvfo_dump,
++ .feat_bits = DFI_FEAT_COPY | DFI_FEAT_SEEK,
++};
+diff --git a/zdump/dfi_s390tape.c b/zdump/dfi_s390tape.c
+new file mode 100644
+index 0000000..8528cce
+--- /dev/null
++++ b/zdump/dfi_s390tape.c
+@@ -0,0 +1,198 @@
++/*
++ * zgetdump - Tool for copying and converting System z dumps
++ *
++ * S390 tape dump input format
++ *
++ * Copyright IBM Corp. 2001, 2010
++ * Author(s): Michael Holzheu <holzheu at linux.vnet.ibm.com>
++ */
++
++#include <string.h>
++#include <time.h>
++#include <fcntl.h>
++#include <stdio.h>
++#include <unistd.h>
++#include <sys/types.h>
++#include <sys/stat.h>
++#include <sys/mtio.h>
++#include "zgetdump.h"
++
++#define TAPE_BLK_SIZE 32768 /* Defined by zipl tape dumper */
++
++/*
++ * File local static data
++ *
++ * blk_buf_addr: Memory address of last read memory block
++ * blk_buf: Content of the last read memory block
++ * blk: The next block number that will be read relative to blk_start
++ * blk_start: The absolute block number on the tape where the dump starts
++ */
++static struct {
++ char blk_buf[TAPE_BLK_SIZE];
++ u64 blk_buf_addr;
++ u64 blk;
++ int blk_start;
++} l;
++
++/*
++ * MT ioctls
++ */
++struct mtioctl {
++ int op;
++ const char *desc;
++};
++
++static struct mtioctl mt_fsfm = {MTFSFM, "forward space file"};
++static struct mtioctl mt_bsr = {MTBSR, "backward space record"};
++static struct mtioctl mt_tell = {MTTELL, "tell"};
++static struct mtioctl mt_seek = {MTSEEK, "seek"};
++
++/*
++ * Do MT ioctl with count argument
++ */
++static int mtioctl(struct mtioctl *op, int cnt, enum zg_check check)
++{
++ struct mtop mtop;
++
++ mtop.mt_count = cnt;
++ mtop.mt_op = op->op;
++ return zg_ioctl(g.fh, MTIOCTOP, &mtop, op->desc, check);
++}
++
++/*
++ * Verify end marker
++ */
++static int em_verify(struct df_s390_em *em)
++{
++ if ((memcmp(em->str, "DUMP_END", 8) == 0)) {
++ df_s390_em_add(em);
++ return 0;
++ } else {
++ return -EINVAL;
++ }
++}
++
++/*
++ * Verify dump header
++ */
++static void hdr_verify(struct df_s390_hdr *hdr)
++{
++ if (hdr->magic != DF_S390_MAGIC)
++ ERR_EXIT("No valid dump found on tape");
++ if (hdr->volnr != 0) {
++ STDERR_PR("Found volume number: %d\n", hdr->volnr);
++ ERR_EXIT("Multi-volume dumps are no longer supported");
++ }
++}
++
++/*
++ * Seek to relative block number in dump (block 0 is the dump header)
++ */
++static void seek_blk(u64 blk)
++{
++ if (l.blk == blk)
++ return;
++ mtioctl(&mt_seek, l.blk_start + blk, ZG_CHECK);
++ l.blk = blk;
++}
++
++/*
++ * Read memory from cartridge
++ */
++static void df_s390tape_mem_read(struct dfi_mem_chunk *mem_chunk, u64 addr,
++ void *buf, u64 cnt)
++{
++ unsigned int copied = 0, size;
++ (void) mem_chunk;
++ u64 blk, off;
++
++ do {
++ blk = addr / TAPE_BLK_SIZE + 1;
++ if (addr >= l.blk_buf_addr + TAPE_BLK_SIZE ||
++ addr < l.blk_buf_addr) {
++ seek_blk(blk);
++ zg_read(g.fh, l.blk_buf, sizeof(l.blk_buf),
++ ZG_CHECK);
++ l.blk_buf_addr = (l.blk - 1) * TAPE_BLK_SIZE;
++ l.blk++;
++ }
++ off = addr - l.blk_buf_addr;
++ size = MIN(cnt - copied, TAPE_BLK_SIZE - off);
++ memcpy(buf + copied, &l.blk_buf[off], size);
++ addr += size;
++ copied += size;
++ } while (copied != cnt);
++}
++
++/*
++ * Initialize cache for memory read (block 0 is the dump header)
++ */
++static void mem_read_init(void)
++{
++ mtioctl(&mt_seek, l.blk_start + 1, ZG_CHECK);
++ zg_read(g.fh, l.blk_buf, sizeof(l.blk_buf), ZG_CHECK);
++ l.blk_buf_addr = 0;
++ l.blk = 2;
++}
++
++/*
++ * Init a new tape volume
++ */
++static int vol_init(void)
++{
++ struct df_s390_hdr hdr;
++ struct df_s390_em em;
++ int rc;
++
++ STDERR("Checking tape, this can take a while...\n");
++ /* Init dump header */
++ l.blk_start = mtioctl(&mt_tell, 1, ZG_CHECK);
++ zg_read(g.fh, &hdr, sizeof(hdr), ZG_CHECK);
++ hdr_verify(&hdr);
++ df_s390_hdr_add(&hdr);
++ dfi_mem_chunk_add(0, hdr.mem_size, NULL, df_s390tape_mem_read);
++
++ /* Init end marker */
++ mtioctl(&mt_fsfm, 1, ZG_CHECK_NONE);
++ mtioctl(&mt_bsr, 1, ZG_CHECK);
++ rc = zg_read(g.fh, &em, sizeof(em), ZG_CHECK_ERR);
++ if (rc != 8 && rc != 16)
++ return -EINVAL;
++ if (em_verify(&em) != 0)
++ return -EINVAL;
++
++ /* Init memory read & CPU info */
++ mem_read_init();
++ df_s390_cpu_info_add(&hdr, hdr.mem_size - 1);
++ return 0;
++}
++
++/*
++ * Exit function: Seek to block 0
++ */
++static void dfi_s390tape_exit(void)
++{
++ seek_blk(0);
++}
++
++/*
++ * Initialize s390 tape DFI
++ */
++static int dfi_s390tape_init(void)
++{
++ if (zg_type(g.fh) != ZG_TYPE_TAPE)
++ return -ENODEV;
++ if (vol_init() != 0)
++ return -EINVAL;
++ zg_atexit(dfi_s390tape_exit);
++ return 0;
++}
++
++/*
++ * S390 tape DFI operations
++ */
++struct dfi dfi_s390tape = {
++ .name = "s390tape",
++ .init = dfi_s390tape_init,
++ .feat_bits = DFI_FEAT_SEEK | DFI_FEAT_COPY,
++};
+diff --git a/zdump/dfo.c b/zdump/dfo.c
+new file mode 100644
+index 0000000..333f5f2
+--- /dev/null
++++ b/zdump/dfo.c
+@@ -0,0 +1,204 @@
++/*
++ * zgetdump - Tool for copying and converting System z dumps
++ *
++ * Generic output dump format functions (DFO - Dump Format Output)
++ *
++ * Copyright IBM Corp. 2001, 2010
++ * Author(s): Michael Holzheu <holzheu at linux.vnet.ibm.com>
++ */
++
++#include <time.h>
++#include "zgetdump.h"
++
++#define dfo_chunk_iterate(dfo_chunk) \
++ list_iterate(dfo_chunk, &l.dump.chunk_list, list)
++
++/*
++ * DFO vector
++ */
++static struct dfo *dfo_vec[] = {
++ &dfo_s390,
++ &dfo_elf,
++ NULL,
++};
++
++/*
++ * Dump (output) information
++ */
++struct dump {
++ u64 off; /* Current file offset in dump */
++ u64 size; /* Size of dump in bytes */
++ unsigned int chunk_cnt; /* Number of dump chunks */
++ struct list chunk_list; /* DFO chunk list */
++};
++
++/*
++ * File local static data
++ */
++static struct {
++ struct dump dump;
++ struct dfo *dfo;
++} l;
++
++/*
++ * Add dump chunk
++ */
++void dfo_chunk_add(u64 start, u64 size, void *data, dfo_chunk_read_fn read_fn)
++{
++ struct dfo_chunk *dfo_chunk;
++
++ dfo_chunk = zg_alloc(sizeof(*dfo_chunk));
++ dfo_chunk->start = start;
++ dfo_chunk->end = start + size - 1;
++ dfo_chunk->size = size;
++ dfo_chunk->data = data;
++ dfo_chunk->read_fn = read_fn;
++ list_add(&dfo_chunk->list, &l.dump.chunk_list);
++ l.dump.chunk_cnt++;
++ l.dump.size = MAX(l.dump.size, dfo_chunk->end + 1);
++}
++
++/*
++ * Dump chunk function: Copy zero pages for chunk
++ */
++void dfo_chunk_zero_fn(struct dfo_chunk *dfo_chunk, u64 off, void *buf, u64 cnt)
++{
++ (void) dfo_chunk;
++ (void) off;
++
++ memset(buf, 0, cnt);
++}
++
++/*
++ * Dump chunk function: Copy given buffer for chunk
++ */
++void dfo_chunk_buf_fn(struct dfo_chunk *dfo_chunk, u64 off, void *buf, u64 cnt)
++{
++ memcpy(buf, dfo_chunk->data + off, cnt);
++}
++
++/*
++ * Dump chunk function: Copy given memory range for chunk
++ */
++void dfo_chunk_mem_fn(struct dfo_chunk *dfo_chunk, u64 off, void *buf, u64 cnt)
++{
++ struct dfi_mem_chunk *mem_chunk = dfo_chunk->data;
++
++ mem_chunk->read_fn(mem_chunk, off, buf, cnt);
++}
++
++/*
++ * Get DFO name
++ */
++const char *dfo_name(void)
++{
++ return l.dfo->name;
++}
++
++/*
++ * Set DFO by name
++ */
++int dfo_set(const char *dfo_name)
++{
++ struct dfo *dfo;
++ int i = 0;
++
++ while ((dfo = dfo_vec[i])) {
++ if (strcmp(dfo->name, dfo_name) == 0) {
++ l.dfo = dfo;
++ return 0;
++ }
++ i++;
++ }
++ return -ENODEV;
++}
++
++/*
++ * Initialize output dump format
++ */
++void dfo_init(void)
++{
++ if (!l.dfo)
++ ABORT("DFO not set");
++ list_init(&l.dump.chunk_list);
++ l.dfo->init();
++}
++
++/*
++ * Find dump chunk for offset "off"
++ *
++ * This function is a bit hacky. DFO chunks can overlap. If two DFO chunks
++ * overlap, the last registered chunk wins. The dfo_chunk_find() function
++ * reflects that by returning the first memory chunk that is found in
++ * the dfo chunk list.
++ *
++ * In addition to that it calculates the "virtual end" of that chunk. An
++ * overlapping chunk can limit the "virtual end" of an underlying chunk so
++ * that the "virtual end" of that chunk is lower than the "real end".
++ *
++ * Example:
++ *
++ * chunk 1.: |------|
++ * chunk 2.: |---------------------|
++ * off.....: ^
++ * virt end: ^
++ * real end: ^
++ *
++ * In this case chunk 2 will be returned and "end" is set to the start of
++ * chunk 1.
++ */
++static struct dfo_chunk *dfo_chunk_find(u64 off, u64 *end)
++{
++ struct dfo_chunk *dfo_chunk;
++
++ *end = U64_MAX;
++ dfo_chunk_iterate(dfo_chunk) {
++ if (dfo_chunk->start <= off && dfo_chunk->end >= off) {
++ *end = MIN(*end, dfo_chunk->end);
++ return dfo_chunk;
++ } else if (dfo_chunk->start > off) {
++ *end = MIN(*end, dfo_chunk->start - 1);
++ }
++ }
++ return NULL;
++}
++
++/*
++ * Seek to output dump offset "off"
++ */
++void dfo_seek(u64 off)
++{
++ l.dump.off = off;
++}
++
++/*
++ * Read "cnt" bytes of output dump at current offest
++ */
++u64 dfo_read(void *buf, u64 cnt)
++{
++ struct dfo_chunk *dfo_chunk;
++ u64 copied = 0, end, size;
++ u64 off = l.dump.off;
++
++ while (copied != cnt) {
++ dfo_chunk = dfo_chunk_find(off, &end);
++ if (!dfo_chunk)
++ goto out;
++ size = MIN(cnt - copied, end - off + 1);
++ dfo_chunk->read_fn(dfo_chunk, off - dfo_chunk->start,
++ buf + copied, size);
++ copied += size;
++ off += size;
++ }
++out:
++ l.dump.off = off;
++ return copied;
++}
++
++/*
++ * Return output dump size
++ */
++u64 dfo_size(void)
++{
++ return l.dump.size;
++}
+diff --git a/zdump/dfo.h b/zdump/dfo.h
+new file mode 100644
+index 0000000..70ca318
+--- /dev/null
++++ b/zdump/dfo.h
+@@ -0,0 +1,54 @@
++/*
++ * zgetdump - Tool for copying and converting System z dumps
++ *
++ * Generic output dump format functions (DFO - Dump Format Output)
++ *
++ * Copyright IBM Corp. 2001, 2010
++ * Author(s): Michael Holzheu <holzheu at linux.vnet.ibm.com>
++ */
++
++#ifndef DFO_H
++#define DFO_H
++
++#include "list.h"
++#include "zg.h"
++
++struct dfo_chunk;
++
++typedef void (*dfo_chunk_read_fn)(struct dfo_chunk *chunk, u64 off,
++ void *buf, u64 cnt);
++
++struct dfo_chunk {
++ struct list list;
++ u64 start;
++ u64 end;
++ u64 size;
++ dfo_chunk_read_fn read_fn;
++ void *data;
++};
++
++extern void dfo_chunk_zero_fn(struct dfo_chunk *chunk, u64 off, void *buf,
++ u64 cnt);
++extern void dfo_chunk_buf_fn(struct dfo_chunk *chunk, u64 off, void *buf,
++ u64 cnt);
++extern void dfo_chunk_mem_fn(struct dfo_chunk *chunk, u64 off, void *buf,
++ u64 cnt);
++extern void dfo_chunk_add(u64 start, u64 size, void *data,
++ dfo_chunk_read_fn read_fn);
++
++extern u64 dfo_read(void *buf, u64 cnt);
++extern void dfo_seek(u64 addr);
++extern u64 dfo_size(void);
++extern const char *dfo_name(void);
++extern void dfo_init(void);
++extern int dfo_set(const char *dfo_name);
++
++/*
++ * DFO operations
++ */
++struct dfo {
++ const char *name;
++ void (*init)(void);
++};
++
++#endif /* DFO_H */
+diff --git a/zdump/dfo_elf.c b/zdump/dfo_elf.c
+new file mode 100644
+index 0000000..bf3bc13
+--- /dev/null
++++ b/zdump/dfo_elf.c
+@@ -0,0 +1,299 @@
++/*
++ * zgetdump - Tool for copying and converting System z dumps
++ *
++ * ELF core dump output format
++ *
++ * Copyright IBM Corp. 2001, 2010
++ * Author(s): Michael Holzheu <holzheu at linux.vnet.ibm.com>
++ */
++
++#include <elf.h>
++#include <stdio.h>
++#include <string.h>
++#include <stdlib.h>
++#include <unistd.h>
++#include <assert.h>
++#include "zgetdump.h"
++
++#define HDR_PER_CPU_SIZE 0x200
++#define HDR_PER_MEMC_SIZE 0x100
++#define HDR_BASE_SIZE 0x2000
++
++/*
++ * File local static data
++ */
++static struct {
++ void *hdr;
++ u32 hdr_size;
++} l;
++
++/*
++ * Initialize ELF header
++ */
++static void *ehdr_init(Elf64_Ehdr *ehdr)
++{
++ memcpy(ehdr->e_ident, ELFMAG, SELFMAG);
++ ehdr->e_ident[EI_CLASS] = ELFCLASS64;
++ ehdr->e_ident[EI_DATA] = ELFDATA2MSB;
++ ehdr->e_ident[EI_VERSION] = EV_CURRENT;
++ ehdr->e_ident[EI_OSABI] = ELFOSABI_SYSV;
++ ehdr->e_ident[EI_ABIVERSION] = 0;
++ memset(ehdr->e_ident+EI_PAD, 0, EI_NIDENT-EI_PAD);
++ ehdr->e_type = ET_CORE;
++ ehdr->e_machine = EM_S390;
++ ehdr->e_version = EV_CURRENT;
++ ehdr->e_entry = 0;
++ ehdr->e_phoff = sizeof(Elf64_Ehdr);
++ ehdr->e_shoff = 0;
++ ehdr->e_flags = 0;
++ ehdr->e_ehsize = sizeof(Elf64_Ehdr);
++ ehdr->e_phentsize = sizeof(Elf64_Phdr);
++ ehdr->e_phnum = dfi_mem_chunk_cnt() + 1;
++ ehdr->e_shentsize = 0;
++ ehdr->e_shnum = 0;
++ ehdr->e_shstrndx = 0;
++ return ehdr + 1;
++}
++
++/*
++ * Initialize ELF loads
++ */
++static u64 loads_init(Elf64_Phdr *phdr, u64 loads_offset)
++{
++ struct dfi_mem_chunk *mem_chunk;
++ u64 mem_size = 0;
++
++ dfi_mem_chunk_iterate(mem_chunk) {
++ phdr->p_type = PT_LOAD;
++ phdr->p_offset = loads_offset;
++ phdr->p_vaddr = mem_chunk->start;
++ phdr->p_paddr = mem_chunk->start;
++ phdr->p_filesz = mem_chunk->end - mem_chunk->start + 1;
++ phdr->p_memsz = phdr->p_filesz;
++ phdr->p_flags = PF_R | PF_W | PF_X;
++ phdr->p_align = PAGE_SIZE;
++ loads_offset += phdr->p_filesz;
++ mem_size += phdr->p_memsz;
++ phdr++;
++ }
++ return mem_size;
++}
++
++/*
++ * Initialize ELF note
++ */
++static void *nt_init(void *buf, Elf64_Word type, void *desc, int d_len,
++ const char *name)
++{
++ Elf64_Nhdr *note;
++ u64 len;
++
++ note = (Elf64_Nhdr *)buf;
++ note->n_namesz = strlen(name) + 1;
++ note->n_descsz = d_len;
++ note->n_type = type;
++ len = sizeof(Elf64_Nhdr);
++
++ memcpy(buf + len, name, note->n_namesz);
++ len = ROUNDUP(len + note->n_namesz, 4);
++
++ memcpy(buf + len, desc, note->n_descsz);
++ len = ROUNDUP(len + note->n_descsz, 4);
++
++ return PTR_ADD(buf, len);
++}
++
++/*
++ * Initialize prstatus note
++ */
++static void *nt_prstatus(void *ptr, struct dfi_cpu *cpu)
++{
++ struct nt_prstatus_64 nt_prstatus;
++ static int cpu_nr = 1;
++
++ memset(&nt_prstatus, 0, sizeof(nt_prstatus));
++ memcpy(&nt_prstatus.gprs, cpu->gprs, sizeof(cpu->gprs));
++ memcpy(&nt_prstatus.psw, cpu->psw, sizeof(cpu->psw));
++ memcpy(&nt_prstatus.acrs, cpu->acrs, sizeof(cpu->acrs));
++ nt_prstatus.pr_pid = cpu_nr;
++ cpu_nr++;
++
++ return nt_init(ptr, NT_PRSTATUS, &nt_prstatus, sizeof(nt_prstatus),
++ "CORE");
++}
++
++/*
++ * Initialize fpregset (floating point) note
++ */
++static void *nt_fpregset(void *ptr, struct dfi_cpu *cpu)
++{
++ struct nt_fpregset_64 nt_fpregset;
++
++ memset(&nt_fpregset, 0, sizeof(nt_fpregset));
++ memcpy(&nt_fpregset.fpc, &cpu->fpc, sizeof(cpu->fpc));
++ memcpy(&nt_fpregset.fprs, &cpu->fprs, sizeof(cpu->fprs));
++
++ return nt_init(ptr, NT_FPREGSET, &nt_fpregset, sizeof(nt_fpregset),
++ "CORE");
++}
++
++/*
++ * Initialize timer note
++ */
++static void *nt_s390_timer(void *ptr, struct dfi_cpu *cpu)
++{
++ return nt_init(ptr, NT_S390_TIMER, &cpu->timer, sizeof(cpu->timer),
++ "LINUX");
++}
++
++/*
++ * Initialize TOD clock comparator note
++ */
++static void *nt_s390_tod_cmp(void *ptr, struct dfi_cpu *cpu)
++{
++ return nt_init(ptr, NT_S390_TODCMP, &cpu->todcmp,
++ sizeof(cpu->todcmp), "LINUX");
++}
++
++/*
++ * Initialize TOD programmable register note
++ */
++static void *nt_s390_tod_preg(void *ptr, struct dfi_cpu *cpu)
++{
++ return nt_init(ptr, NT_S390_TODPREG, &cpu->todpreg,
++ sizeof(cpu->todpreg), "LINUX");
++}
++
++/*
++ * Initialize control register note
++ */
++static void *nt_s390_ctrs(void *ptr, struct dfi_cpu *cpu)
++{
++ return nt_init(ptr, NT_S390_CTRS, &cpu->ctrs, sizeof(cpu->ctrs),
++ "LINUX");
++}
++
++/*
++ * Initialize prefix register note
++ */
++static void *nt_s390_prefix(void *ptr, struct dfi_cpu *cpu)
++{
++ return nt_init(ptr, NT_S390_PREFIX, &cpu->prefix,
++ sizeof(cpu->prefix), "LINUX");
++}
++
++/*
++ * Initialize prpsinfo note
++ */
++static void *nt_prpsinfo(void *ptr)
++{
++ struct nt_prpsinfo_64 prpsinfo;
++
++ memset(&prpsinfo, 0, sizeof(prpsinfo));
++ prpsinfo.pr_state = 0;
++ prpsinfo.pr_sname = 'R';
++ prpsinfo.pr_zomb = 0;
++ strcpy(prpsinfo.pr_fname, "vmlinux");
++
++ return nt_init(ptr, NT_PRPSINFO, &prpsinfo, sizeof(prpsinfo), "CORE");
++}
++
++/*
++ * Initialize notes
++ */
++static void *notes_init(Elf64_Phdr *phdr, void *ptr, u64 notes_offset)
++{
++ void *ptr_start = ptr;
++ struct dfi_cpu *cpu;
++
++ ptr = nt_prpsinfo(ptr);
++
++ if (dfi_cpu_content() != DFI_CPU_CONTENT_ALL)
++ goto out;
++
++ dfi_cpu_iterate(cpu) {
++ ptr = nt_prstatus(ptr, cpu);
++ ptr = nt_fpregset(ptr, cpu);
++ ptr = nt_s390_timer(ptr, cpu);
++ ptr = nt_s390_tod_cmp(ptr, cpu);
++ ptr = nt_s390_tod_preg(ptr, cpu);
++ ptr = nt_s390_ctrs(ptr, cpu);
++ ptr = nt_s390_prefix(ptr, cpu);
++ }
++out:
++ memset(phdr, 0, sizeof(*phdr));
++ phdr->p_type = PT_NOTE;
++ phdr->p_offset = notes_offset;
++ phdr->p_filesz = (unsigned long) PTR_SUB(ptr, ptr_start);
++ return ptr;
++}
++
++/*
++ * Setup dump chunks
++ */
++static void dump_chunks_init(void)
++{
++ struct dfi_mem_chunk *mem_chunk;
++ u64 off = 0;
++
++ dfo_chunk_add(0, l.hdr_size, l.hdr, dfo_chunk_buf_fn);
++ off = l.hdr_size;
++ dfi_mem_chunk_iterate(mem_chunk) {
++ dfo_chunk_add(off, mem_chunk->size, mem_chunk,
++ dfo_chunk_mem_fn);
++ off += mem_chunk->size;
++ }
++}
++
++/*
++ * ELF DFO is only supported for 64 bit (s390x)
++ */
++static void ensure_s390x(void)
++{
++ if (dfi_arch() != DFI_ARCH_64)
++ ERR_EXIT("Error: The ELF dump format is only supported for "
++ "s390x source dumps");
++ df_elf_ensure_s390x();
++}
++
++/*
++ * Initialize ELF output dump format
++ */
++static void dfo_elf_init(void)
++{
++ Elf64_Phdr *phdr_notes, *phdr_loads;
++ u64 mem_size, hdr_off;
++ u32 alloc_size;
++ void *ptr;
++
++ ensure_s390x();
++ alloc_size = HDR_BASE_SIZE +
++ dfi_cpu_cnt() * HDR_PER_CPU_SIZE +
++ dfi_mem_chunk_cnt() * HDR_PER_MEMC_SIZE;
++ l.hdr = zg_alloc(alloc_size);
++ /* Init elf header */
++ ptr = ehdr_init(l.hdr);
++ /* Init program headers */
++ phdr_notes = ptr;
++ ptr = PTR_ADD(ptr, sizeof(Elf64_Phdr));
++ phdr_loads = ptr;
++ ptr = PTR_ADD(ptr, sizeof(Elf64_Phdr) * dfi_mem_chunk_cnt());
++ /* Init notes */
++ hdr_off = PTR_DIFF(ptr, l.hdr);
++ ptr = notes_init(phdr_notes, ptr, hdr_off);
++ /* Init loads */
++ hdr_off = PTR_DIFF(ptr, l.hdr);
++ mem_size = loads_init(phdr_loads, hdr_off);
++ l.hdr_size = hdr_off;
++ if (l.hdr_size > alloc_size)
++ ABORT("hdr_size=%u alloc_size=%u", l.hdr_size, alloc_size);
++ dump_chunks_init();
++}
++
++/*
++ * ELF DFO operations
++ */
++struct dfo dfo_elf = {
++ .name = "elf",
++ .init = dfo_elf_init,
++};
+diff --git a/zdump/dfo_s390.c b/zdump/dfo_s390.c
+new file mode 100644
+index 0000000..7f51605
+--- /dev/null
++++ b/zdump/dfo_s390.c
+@@ -0,0 +1,200 @@
++/*
++ * zgetdump - Tool for copying and converting System z dumps
++ *
++ * S390 dump output format
++ *
++ * Copyright IBM Corp. 2001, 2010
++ * Author(s): Michael Holzheu <holzheu at linux.vnet.ibm.com>
++ */
++
++#include <string.h>
++#include <time.h>
++#include <fcntl.h>
++#include <stdio.h>
++#include <unistd.h>
++#include <sys/types.h>
++#include <sys/stat.h>
++#include "zgetdump.h"
++
++/*
++ * File local static data
++ */
++struct {
++ struct df_s390_hdr hdr;
++ struct df_s390_em em;
++} l;
++
++/*
++ * Copy internal register set to 64 bit lowcore
++ */
++static void cpu2lc_64(void *lc_64, struct dfi_cpu *cpu)
++{
++ struct dfi_lowcore_64 *lc = lc_64;
++ memcpy(lc->gpregs_save_area, &cpu->gprs, sizeof(cpu->gprs));
++ memcpy(lc->cregs_save_area, &cpu->ctrs, sizeof(cpu->ctrs));
++ memcpy(lc->access_regs_save_area, &cpu->acrs, sizeof(cpu->acrs));
++ memcpy(lc->floating_pt_save_area, &cpu->fprs, sizeof(cpu->fprs));
++ memcpy(&lc->fpt_creg_save_area, &cpu->fpc, sizeof(cpu->fpc));
++ memcpy(lc->st_status_fixed_logout, &cpu->psw, sizeof(cpu->psw));
++ memcpy(&lc->prefixreg_save_area, &cpu->prefix, sizeof(cpu->prefix));
++ memcpy(lc->timer_save_area, &cpu->timer, sizeof(cpu->timer));
++ memcpy(lc->clock_comp_save_area, &cpu->todcmp, sizeof(cpu->todcmp));
++}
++
++/*
++ * Copy internal register set to 32 bit lowcore
++ */
++static void cpu2lc_32(void *lc_32, struct dfi_cpu *cpu_64)
++{
++ struct dfi_lowcore_32 *lc = lc_32;
++ struct dfi_cpu_32 cpu;
++
++ dfi_cpu_64_to_32(&cpu, cpu_64);
++ memcpy(lc->gpregs_save_area, &cpu.gprs, sizeof(cpu.gprs));
++ memcpy(lc->cregs_save_area, &cpu.ctrs, sizeof(cpu.ctrs));
++ memcpy(lc->access_regs_save_area, &cpu.acrs, sizeof(cpu.acrs));
++ memcpy(lc->floating_pt_save_area, &cpu.fprs, sizeof(cpu.fprs));
++ memcpy(lc->st_status_fixed_logout, &cpu.psw, sizeof(cpu.psw));
++ memcpy(&lc->prefixreg_save_area, &cpu.prefix, sizeof(cpu.prefix));
++ memcpy(lc->timer_save_area, &cpu.timer, sizeof(cpu.timer));
++ memcpy(lc->clock_comp_save_area, &cpu.todcmp, sizeof(cpu.todcmp));
++}
++
++/*
++ * Convert timeval to s390 TOD clock
++ */
++static void timeval2tod(u64 *tod, struct timeval *xtime)
++{
++ u64 us = xtime->tv_sec * 1000000 + xtime->tv_usec;
++ *tod = (us << 12);
++ *tod += 0x8126d60e46000000LL - (0x3c26700LL * 1000000 * 4096);
++}
++
++/*
++ * Setup lowcore array in dump header
++ */
++static void lc_setup(struct df_s390_hdr *dh)
++{
++ struct dfi_cpu *cpu;
++ unsigned int i = 0;
++
++ dfi_cpu_iterate(cpu) {
++ if (i > DF_S390_CPU_MAX)
++ ERR_EXIT("Too many CPUs in source dump (%i)", i);
++ dh->lc_vec[i] = cpu->prefix;
++ i++;
++ }
++}
++
++/*
++ * Copy register set to prefix page
++ */
++static void dfo_s390_dump_chunk_lc_fn(struct dfo_chunk *dump_chunk,
++ u64 off, void *buf, u64 cnt)
++{
++ struct dfi_cpu *cpu = dump_chunk->data;
++ char lc[0x2000];
++
++ dfi_mem_read(cpu->prefix + off, &lc[off], cnt);
++ if (dfi_arch() == DFI_ARCH_64)
++ cpu2lc_64(lc, cpu);
++ else
++ cpu2lc_32(lc, cpu);
++ memcpy(buf, &lc[off], cnt);
++}
++
++/*
++ * Add register set to dump layout. We copy the register sets to the
++ * lowcore pages.
++ */
++static void add_cpu_to_dfo(struct dfi_cpu *cpu)
++{
++ if (dfi_cpu_content() != DFI_CPU_CONTENT_ALL)
++ return;
++
++ dfo_chunk_add(cpu->prefix + DF_S390_HDR_SIZE,
++ dfi_lc_size(dfi_arch()), cpu,
++ dfo_s390_dump_chunk_lc_fn);
++}
++
++/*
++ * Add memory chunk to dump layout
++ */
++static void add_mem_chunk_to_dfo(struct dfi_mem_chunk *mem_chunk)
++{
++ struct dfi_mem_chunk *mem_chunk_prev = dfi_mem_chunk_prev(mem_chunk);
++
++ if (mem_chunk_prev && (mem_chunk_prev->end + 1 != mem_chunk->start))
++ dfo_chunk_add(mem_chunk_prev->end + 1 + DF_S390_HDR_SIZE,
++ mem_chunk->start - mem_chunk_prev->end - 1,
++ NULL, dfo_chunk_zero_fn);
++
++ dfo_chunk_add(mem_chunk->start + DF_S390_HDR_SIZE, mem_chunk->size,
++ mem_chunk, dfo_chunk_mem_fn);
++}
++
++/*
++ * Setup dump chunks
++ */
++static void dump_chunks_init(void)
++{
++ struct dfi_mem_chunk *mem_chunk;
++ struct dfi_cpu *cpu;
++
++ dfo_chunk_add(0, DF_S390_HDR_SIZE, &l.hdr, dfo_chunk_buf_fn);
++ dfi_mem_chunk_iterate(mem_chunk)
++ add_mem_chunk_to_dfo(mem_chunk);
++ dfi_cpu_iterate(cpu)
++ add_cpu_to_dfo(cpu);
++ dfo_chunk_add(dfi_mem_range() + DF_S390_HDR_SIZE,
++ DF_S390_EM_SIZE,
++ &l.em, dfo_chunk_buf_fn);
++}
++
++/*
++ * Initialize s390 output dump format
++ */
++static void df_s390_dump_init(void)
++{
++ struct df_s390_hdr *dh = &l.hdr;
++ struct df_s390_em *em = &l.em;
++
++ dh->magic = DF_S390_MAGIC;
++ dh->hdr_size = DF_S390_HDR_SIZE;
++ dh->page_size = PAGE_SIZE;
++ dh->dump_level = 4;
++ if (dfi_cpu_content() == DFI_CPU_CONTENT_NONE)
++ dh->version = 4;
++ else
++ dh->version = 5;
++ dh->mem_start = 0;
++ dh->mem_size = dh->mem_end = dfi_mem_range();
++ dh->num_pages = dh->mem_size / PAGE_SIZE;
++ dh->arch = df_s390_from_dfi_arch(dfi_arch());
++ if (dfi_attr_build_arch())
++ dh->build_arch = df_s390_from_dfi_arch(*dfi_attr_build_arch());
++ dh->cpu_cnt = dfi_cpu_cnt();
++ if (dfi_attr_real_cpu_cnt())
++ dh->real_cpu_cnt = *dfi_attr_real_cpu_cnt();
++ if (dfi_attr_cpu_id())
++ dh->cpu_id = *dfi_attr_cpu_id();
++ if (dfi_attr_mem_size_real())
++ dh->mem_size_real = *dfi_attr_mem_size_real();
++ if (dfi_attr_time()) {
++ timeval2tod(&dh->tod, dfi_attr_time());
++ timeval2tod(&em->tod, dfi_attr_time());
++ }
++ if (dfi_attr_time_end())
++ timeval2tod(&em->tod, dfi_attr_time_end());
++ lc_setup(dh);
++ memcpy(em->str, DF_S390_EM_STR, sizeof(em->str));
++ dump_chunks_init();
++}
++
++/*
++ * S390 DFO operations
++ */
++struct dfo dfo_s390 = {
++ .name = "s390",
++ .init = df_s390_dump_init,
++};
+diff --git a/zdump/dt.c b/zdump/dt.c
+new file mode 100644
+index 0000000..b19aa80
+--- /dev/null
++++ b/zdump/dt.c
+@@ -0,0 +1,131 @@
++/*
++ * zgetdump - Tool for copying and converting System z dumps
++ *
++ * Dump tool info generic functions
++ *
++ * Copyright IBM Corp. 2001, 2010
++ * Author(s): Michael Holzheu <holzheu at linux.vnet.ibm.com>
++ */
++
++#include "zgetdump.h"
++
++/*
++ * Supported dump tools
++ */
++static struct dt *dt_vec[] = {
++ &dt_s390mv,
++ &dt_s390sv,
++ NULL,
++};
++
++/*
++ * Dumper attribute information
++ */
++struct attr {
++ int *force;
++ u64 *mem_limit;
++ char *dasd_type;
++};
++
++/*
++ * File local static data
++ */
++struct {
++ int version;
++ enum dfi_arch arch;
++ struct attr attr;
++ struct dt *dt;
++} l;
++
++/*
++ * Init dump tool backends
++ */
++void dt_init(void)
++{
++ struct dt *dt;
++ int i = 0;
++
++ while ((dt = dt_vec[i])) {
++ g.fh = zg_open(g.opts.device, O_RDONLY, ZG_CHECK);
++ if (zg_type(g.fh) != ZG_TYPE_DASD)
++ ERR_EXIT("Please specify DASD device node (e.g. "
++ "/dev/dasdd)");
++ if (dt->init() == 0) {
++ l.dt = dt;
++ return;
++ }
++ zg_close(g.fh);
++ i++;
++ }
++ ERR_EXIT("No dump tool found on \"%s\"", g.opts.device);
++}
++
++/*
++ * Print info about dump tool
++ */
++void dt_info_print(void)
++{
++ STDERR("Dump device info:\n");
++ STDERR(" Dump tool.........: %s\n", l.dt->desc);
++ STDERR(" Version...........: %d\n", l.version);
++ STDERR(" Architecture......: %s\n", dfi_arch_str(l.arch));
++ if (l.attr.dasd_type)
++ STDERR(" DASD type.........: %s\n", l.attr.dasd_type);
++ if (l.attr.mem_limit)
++ STDERR(" Dump size limit...: %lld MB\n",
++ TO_MIB(*l.attr.mem_limit));
++ else
++ STDERR(" Dump size limit...: none\n");
++ if (l.attr.force) {
++ if (*l.attr.force == 0)
++ STDERR(" Force specified...: no\n");
++ else
++ STDERR(" Force specified...: yes\n");
++ }
++ if (l.dt->info) {
++ STDERR("\n");
++ l.dt->info();
++ }
++}
++
++/*
++ * Set DT architecture
++ */
++void dt_arch_set(enum dfi_arch arch)
++{
++ l.arch = arch;
++}
++
++/*
++ * Set DT version
++ */
++void dt_version_set(int version)
++{
++ l.version = version;
++}
++
++/*
++ * Set DT memory limit attribute
++ */
++void dt_attr_mem_limit_set(u64 mem_limit)
++{
++ l.attr.mem_limit = zg_alloc(sizeof(*l.attr.mem_limit));
++ *l.attr.mem_limit = mem_limit;
++}
++
++/*
++ * Set DT force attribute
++ */
++void dt_attr_force_set(int force)
++{
++ l.attr.force = zg_alloc(sizeof(*l.attr.force));
++ *l.attr.force = force;
++}
++
++/*
++ * Set DT DASD type attribute
++ */
++void dt_attr_dasd_type_set(const char *dasd_type)
++{
++ l.attr.dasd_type = zg_strdup(dasd_type);
++}
+diff --git a/zdump/dt.h b/zdump/dt.h
+new file mode 100644
+index 0000000..44e0a09
+--- /dev/null
++++ b/zdump/dt.h
+@@ -0,0 +1,29 @@
++/*
++ * zgetdump - Tool for copying and converting System z dumps
++ *
++ * Dump tool info generic functions
++ *
++ * Copyright IBM Corp. 2001, 2010
++ * Author(s): Michael Holzheu <holzheu at linux.vnet.ibm.com>
++ */
++
++#ifndef DT_H
++#define DT_H
++
++#include "dfi.h"
++
++struct dt {
++ const char *desc;
++ int (*init)(void);
++ void (*info)(void);
++};
++
++extern void dt_init(void);
++extern void dt_info_print(void);
++extern void dt_arch_set(enum dfi_arch arch);
++extern void dt_version_set(int version);
++extern void dt_attr_mem_limit_set(u64 mem_limit);
++extern void dt_attr_force_set(int value);
++extern void dt_attr_dasd_type_set(const char *dasd_type);
++
++#endif
+diff --git a/zdump/dt_s390mv.c b/zdump/dt_s390mv.c
+new file mode 100644
+index 0000000..17ac1fc
+--- /dev/null
++++ b/zdump/dt_s390mv.c
+@@ -0,0 +1,19 @@
++/*
++ * zgetdump - Tool for copying and converting System z dumps
++ *
++ * S390 multi-volume DASD dump tool
++ *
++ * Copyright IBM Corp. 2001, 2010
++ * Author(s): Michael Holzheu <holzheu at linux.vnet.ibm.com>
++ */
++
++#include "zgetdump.h"
++
++/*
++ * DT operations
++ */
++struct dt dt_s390mv = {
++ .desc = "Multi-volume DASD dump tool",
++ .init = dt_s390mv_init,
++ .info = dt_s390mv_info,
++};
+diff --git a/zdump/dt_s390sv.c b/zdump/dt_s390sv.c
+new file mode 100644
+index 0000000..22966cd
+--- /dev/null
++++ b/zdump/dt_s390sv.c
+@@ -0,0 +1,129 @@
++/*
++ * zgetdump - Tool for copying and converting System z dumps
++ *
++ * S390 single-volume DASD dump tool
++ *
++ * Copyright IBM Corp. 2001, 2010
++ * Author(s): Michael Holzheu <holzheu at linux.vnet.ibm.com>
++ */
++
++#include <linux/fs.h>
++#include "zgetdump.h"
++
++#define HEXINSTR "\x0d\x10\x47\xf0" /* BASR + 1st halfword of BC */
++
++/*
++ * File local static data
++ */
++static struct {
++ struct df_s390_dumper dumper;
++ enum dfi_arch dumper_arch;
++} l;
++
++/*
++ * Read dump tool from ECKD DASD device
++ */
++static int dumper_read_eckd(int blk_size)
++{
++ df_s390_dumper_read(g.fh, blk_size, &l.dumper);
++
++ if (strncmp(l.dumper.magic, "ZECKD31", 7) == 0) {
++ l.dumper_arch = DFI_ARCH_32;
++ } else if (strncmp(l.dumper.magic, "ZECKD64", 7) == 0) {
++ l.dumper_arch = DFI_ARCH_64;
++ } else if ((memcmp(l.dumper.magic, HEXINSTR, 4) == 0) &&
++ (l.dumper.d.v1.code[0] == '\x0d') &&
++ (l.dumper.d.v1.code[1] == '\xd0')) {
++ /* We found basr r13,0 (old dumper) */
++ l.dumper.version = 0;
++ l.dumper_arch = DFI_ARCH_UNKNOWN;
++ } else {
++ return -ENODEV;
++ }
++ return 0;
++}
++
++/*
++ * Read dump tool from FBA DASD device
++ */
++static void dumper_read_fba_gen(int size, void *buffer)
++{
++ zg_seek_end(g.fh, -size, ZG_CHECK);
++ zg_read(g.fh, buffer, size, ZG_CHECK);
++}
++
++/*
++ * Read dump tool on FBA disk and check its magic number
++ */
++int dumper_check_fba(void)
++{
++ if (strncmp(l.dumper.magic, "ZDFBA31", 7) == 0) {
++ l.dumper_arch = DFI_ARCH_32;
++ } else if (strncmp(l.dumper.magic, "ZDFBA64", 7) == 0) {
++ l.dumper_arch = DFI_ARCH_64;
++ } else if ((memcmp(l.dumper.magic, HEXINSTR, 4) == 0) &&
++ (l.dumper.d.v1.code[0] == '\x0d') &&
++ (l.dumper.d.v1.code[1] == '\xd0')) {
++ /* We found basr r13,0 (old dumper) */
++ l.dumper.version = 0;
++ l.dumper_arch = DFI_ARCH_UNKNOWN;
++ } else {
++ return -ENODEV;
++ }
++ return 0;
++}
++
++/*
++ * Read dump tool on FBA disk and check its magic number
++ */
++static int dumper_read_fba(void)
++{
++ dumper_read_fba_gen(DF_S390_DUMPER_SIZE_V1, &l.dumper);
++ if (dumper_check_fba() == 0)
++ return 0;
++ dumper_read_fba_gen(DF_S390_DUMPER_SIZE_V2, &l.dumper);
++ if (dumper_check_fba() == 0)
++ return 0;
++ return -ENODEV;
++}
++
++/*
++ * Read single volume dumper from disk
++ */
++static int sv_dumper_read(void)
++{
++ int blk_size;
++
++ zg_ioctl(g.fh, BLKSSZGET, &blk_size, "BLKSSZGET", ZG_CHECK);
++ if (dumper_read_eckd(blk_size) == 0) {
++ dt_attr_dasd_type_set("ECKD");
++ return 0;
++ }
++ if (dumper_read_fba() == 0) {
++ dt_attr_dasd_type_set("FBA");
++ return 0;
++ }
++ return -ENODEV;
++}
++
++/*
++ * Initialize s390 single-volume dump tool (for -d option)
++ */
++static int dt_s390sv_init(void)
++{
++ if (sv_dumper_read() != 0)
++ return -ENODEV;
++ dt_arch_set(l.dumper_arch);
++ dt_version_set(df_s390_dumper_version(l.dumper));
++ if (df_s390_dumper_mem(l.dumper) != U64_MAX)
++ dt_attr_mem_limit_set(df_s390_dumper_mem(l.dumper));
++ return 0;
++}
++
++/*
++ * s390 single-volume DT operations
++ */
++struct dt dt_s390sv = {
++ .desc = "Single-volume DASD dump tool",
++ .init = dt_s390sv_init,
++};
+diff --git a/zdump/opts.c b/zdump/opts.c
+new file mode 100644
+index 0000000..d00f5b1
+--- /dev/null
++++ b/zdump/opts.c
+@@ -0,0 +1,242 @@
++/*
++ * zgetdump - Tool for copying and converting System z dumps
++ *
++ * Option parsing
++ *
++ * Copyright IBM Corp. 2001, 2010
++ * Author(s): Michael Holzheu <holzheu at linux.vnet.ibm.com>
++ */
++
++#include <getopt.h>
++#include <stdio.h>
++#include <string.h>
++#include <stdlib.h>
++#include "zgetdump.h"
++#include "zt_common.h"
++
++/*
++ * Text for --help option
++ */
++static char help_text[] =
++"Usage: zgetdump [OPTIONS] [DUMPDEV] [DIR]\n"
++"\n"
++"The zgetdump tool takes as source a dump device or dump file (DUMPDEV)\n"
++"and writes its contents to standard output, which you can redirect to a\n"
++"specific file. Alternatively you can also mount the dump content.\n"
++"Because zgetdump is able to read and write different dump formats, it\n"
++"can be used to convert a dump from one format to another. zgetdump can\n"
++"also verify if a dump is valid or check, whether a DASD device contains\n"
++"a valid dump tool.\n"
++"\n"
++"-h, --help Print this help, then exit.\n"
++"-v, --version Print version information, then exit.\n"
++"-m, --mount Mount dump to mount point DIR\n"
++"-u, --umount Unmount dump from mount point DIR\n"
++"-f, --fmt <FMT> Specify target dump format FMT (\"elf\" or \"s390\")\n"
++"-i, --info Print dump information\n"
++"-d, --device Print dump device information\n";
++
++static const char copyright_str[] = "Copyright IBM Corp. 2001, 2010";
++
++/*
++ * Initialize default settings
++ */
++static void init_defaults(void)
++{
++ g.prog_name = "zgetdump";
++ g.opts.action = ZG_ACTION_STDOUT;
++ g.opts.fmt = "s390";
++ dfo_set(g.opts.fmt);
++}
++
++/*
++ * Print "help" hint
++ */
++static void print_usage_exit(void)
++{
++ STDERR("Try '%s --help' for more information.\n", g.prog_name);
++ zg_exit(1);
++}
++
++/*
++ * Print help text
++ */
++static void print_help_exit(void)
++{
++ STDOUT(help_text);
++ zg_exit(0);
++}
++
++/*
++ * Print version information
++ */
++static void print_version_exit(void)
++{
++ STDOUT("%s: Tool for copying and converting dumps version %s\n",
++ g.prog_name, RELEASE_STRING);
++ STDOUT("%s\n", copyright_str);
++ zg_exit(0);
++}
++
++/*
++ * Set "--fmt" option
++ */
++static void fmt_set(const char *fmt)
++{
++ if (dfo_set(fmt) != 0)
++ ERR_EXIT("Invalid target format \"%s\" specified", fmt);
++ g.opts.fmt_specified = 1;
++ g.opts.fmt = fmt;
++}
++
++/*
++ * Set mount point
++ */
++static void mount_point_set(const char *mount_point)
++{
++ g.opts.mount_point = zg_strdup(mount_point);
++}
++
++/*
++ * Set device
++ */
++static void device_set(const char *path)
++{
++ g.opts.device = zg_strdup(path);
++}
++
++/*
++ * Set FUSE debug options
++ */
++static void argv_fuse_set(char **argv, int argc)
++{
++ int i;
++
++ g.opts.argv_fuse = argv;
++ g.opts.argc_fuse = argc;
++
++ STDERR_PR("Fuse Options: ");
++ for (i = 0; i < argc; i++)
++ STDERR("%s ", g.opts.argv_fuse[i]);
++ STDERR("\n");
++}
++
++/*
++ * Set action
++ */
++static void action_set(enum zg_action action)
++{
++ if (g.opts.action_specified)
++ ERR_EXIT("Please specifiy only one of the \"-i\", \"-d\", "
++ "\"-m\" or \"-u\" option");
++ g.opts.action = action;
++ g.opts.action_specified = 1;
++}
++
++/*
++ * Verify option combinations
++ */
++static void verify_opts(void)
++{
++ if (!g.opts.fmt_specified)
++ return;
++
++ if (g.opts.action == ZG_ACTION_DUMP_INFO)
++ ERR_EXIT("The \"--fmt\" option cannot be specified "
++ "together with \"--info\"");
++ if (g.opts.action == ZG_ACTION_DEVICE_INFO)
++ ERR_EXIT("The \"--fmt\" option cannot be specified "
++ "together with \"--device\"");
++ if (g.opts.action == ZG_ACTION_UMOUNT)
++ ERR_EXIT("The \"--fmt\" option cannot be specified "
++ "together with \"--umount\"");
++}
++
++/*
++ * Parse positional arguments
++ */
++static void parse_pos_args(char *argv[], int argc)
++{
++ int pos_args = argc - optind;
++
++ switch (g.opts.action) {
++ case ZG_ACTION_STDOUT:
++ case ZG_ACTION_DUMP_INFO:
++ case ZG_ACTION_DEVICE_INFO:
++ if (pos_args == 0)
++ ERR_EXIT("No device or dump specified");
++ if (pos_args > 1 && !g.opts.debug_specified)
++ ERR_EXIT("Too many positional paramters specified");
++ device_set(argv[optind]);
++ break;
++ case ZG_ACTION_MOUNT:
++ if (pos_args == 0)
++ ERR_EXIT("No dump specified");
++ if (pos_args == 1)
++ ERR_EXIT("No mount point specified");
++ if (pos_args > 2 && !g.opts.debug_specified)
++ ERR_EXIT("Too many positional paramters specified");
++ device_set(argv[optind]);
++ mount_point_set(argv[optind + 1]);
++ if (g.opts.debug_specified && pos_args > 2)
++ argv_fuse_set(&argv[optind + 2], pos_args - 2);
++ break;
++ case ZG_ACTION_UMOUNT:
++ if (pos_args == 0)
++ ERR_EXIT("No mount point specified");
++ mount_point_set(argv[optind]);
++ break;
++ }
++}
++
++/*
++ * Main command line parsing function
++ */
++void opts_parse(int argc, char *argv[])
++{
++ int opt, idx;
++ static struct option long_opts[] = {
++ {"help", no_argument, NULL, 'h'},
++ {"version", no_argument, NULL, 'v'},
++ {"info", no_argument, NULL, 'i'},
++ {"device", no_argument, NULL, 'd'},
++ {"mount", no_argument, NULL, 'm'},
++ {"umount", no_argument, NULL, 'u'},
++ {"fmt", required_argument, NULL, 'f'},
++ {"debug", no_argument, NULL, 'X'},
++ {0, 0, 0, 0 }
++ };
++ static const char optstr[] = "hvidmuf:X";
++
++ init_defaults();
++ while ((opt = getopt_long(argc, argv, optstr, long_opts, &idx)) != -1) {
++ switch (opt) {
++ case 'h':
++ print_help_exit();
++ case 'v':
++ print_version_exit();
++ case 'i':
++ action_set(ZG_ACTION_DUMP_INFO);
++ break;
++ case 'd':
++ action_set(ZG_ACTION_DEVICE_INFO);
++ break;
++ case 'm':
++ action_set(ZG_ACTION_MOUNT);
++ break;
++ case 'u':
++ action_set(ZG_ACTION_UMOUNT);
++ break;
++ case 'f':
++ fmt_set(optarg);
++ break;
++ case 'X':
++ g.opts.debug_specified = 1;
++ break;
++ default:
++ print_usage_exit();
++ }
++ }
++ parse_pos_args(argv, argc);
++ verify_opts();
++}
+diff --git a/zdump/stdout.c b/zdump/stdout.c
+new file mode 100644
+index 0000000..1c3722d
+--- /dev/null
++++ b/zdump/stdout.c
+@@ -0,0 +1,38 @@
++/*
++ * zgetdump - Tool for copying and converting System z dumps
++ *
++ * Write dump to standard output (stdout)
++ *
++ * Copyright IBM Corp. 2001, 2010
++ * Author(s): Michael Holzheu <holzheu at linux.vnet.ibm.com>
++ */
++
++#include "zgetdump.h"
++
++int stdout_write_dump(void)
++{
++ u64 cnt, written = 0;
++ char buf[32768];
++ ssize_t rc;
++
++ if (!dfi_feat_copy())
++ ERR_EXIT("Copying not possible for %s dumps", dfi_name());
++ STDERR("Format Info:\n");
++ STDERR(" Source: %s\n", dfi_name());
++ STDERR(" Target: %s\n", dfo_name());
++ STDERR("\n");
++ zg_progress_init("Copying dump", dfo_size());
++ do {
++ cnt = dfo_read(buf, sizeof(buf));
++ rc = write(STDOUT_FILENO, buf, cnt);
++ if (rc == -1)
++ ERR_EXIT_ERRNO("Error: Write failed");
++ if (rc != (ssize_t) cnt)
++ ERR_EXIT("Error: Could not write full block");
++ written += cnt;
++ zg_progress(written);
++ } while (written != dfo_size());
++ STDERR("\n");
++ STDERR("Success: Dump has been copied\n");
++ return 0;
++}
+diff --git a/zdump/zfuse.c b/zdump/zfuse.c
+new file mode 100644
+index 0000000..d86e1c0
+--- /dev/null
++++ b/zdump/zfuse.c
+@@ -0,0 +1,238 @@
++/*
++ * zgetdump - Tool for copying and converting System z dumps
++ *
++ * FUSE functions
++ *
++ * Copyright IBM Corp. 2010
++ * Author(s): Michael Holzheu <holzheu at linux.vnet.ibm.com>
++ */
++
++#define FUSE_USE_VERSION 25
++
++#include <fuse.h>
++#include <stdio.h>
++#include <string.h>
++#include <errno.h>
++#include <fcntl.h>
++#include <unistd.h>
++#include <time.h>
++#include <limits.h>
++#include <sys/types.h>
++#include <sys/stat.h>
++#include "zgetdump.h"
++
++#define DUMP_PATH_MAX 100
++
++/*
++ * File local static data
++ */
++static struct {
++ char path[DUMP_PATH_MAX];
++ struct stat stat_root;
++ struct stat stat_dump;
++} l;
++
++/*
++ * Initialize default values for stat buffer
++ */
++static void stat_default_init(struct stat *stat)
++{
++ if (dfi_attr_time()) {
++ stat->st_mtime = dfi_attr_time()->tv_sec;
++ stat->st_ctime = dfi_attr_time()->tv_sec;
++ stat->st_atime = dfi_attr_time()->tv_sec;
++ } else {
++ stat->st_mtime = zg_stat(g.fh)->st_mtime;
++ stat->st_ctime = zg_stat(g.fh)->st_ctime;
++ stat->st_atime = zg_stat(g.fh)->st_atime;
++ }
++ stat->st_uid = geteuid();
++ stat->st_gid = getegid();
++}
++
++/*
++ * Initialize stat buffer for root directory
++ */
++static void stat_root_init(void)
++{
++ stat_default_init(&l.stat_root);
++ l.stat_root.st_mode = S_IFDIR | 0500;
++ l.stat_root.st_nlink = 2;
++}
++
++/*
++ * Initialize stat buffer for dump
++ */
++static void stat_dump_init(void)
++{
++ stat_default_init(&l.stat_dump);
++ l.stat_dump.st_mode = S_IFREG | 0400;
++ l.stat_dump.st_nlink = 1;
++ l.stat_dump.st_size = dfo_size();
++ l.stat_dump.st_blksize = 4096;
++ l.stat_dump.st_blocks = l.stat_dump.st_size / 4096;
++}
++
++/*
++ * FUSE callback: Getattr
++ */
++static int zfuse_getattr(const char *path, struct stat *stat)
++{
++ if (strcmp(path, "/") == 0) {
++ *stat = l.stat_root;
++ return 0;
++ }
++ if (strcmp(path, l.path) == 0) {
++ *stat = l.stat_dump;
++ return 0;
++ }
++ return -ENOENT;
++}
++
++/*
++ * FUSE callback: Readdir - Return ".", ".." and dump file
++ */
++static int zfuse_readdir(const char *path, void *buf, fuse_fill_dir_t filler,
++ off_t offset, struct fuse_file_info *fi)
++{
++ (void) offset;
++ (void) fi;
++
++ if (strcmp(path, "/") != 0)
++ return -ENOENT;
++
++ filler(buf, ".", NULL, 0);
++ filler(buf, "..", NULL, 0);
++ filler(buf, &l.path[1], NULL, 0);
++ return 0;
++}
++
++/*
++ * FUSE callback: Open
++ */
++static int zfuse_open(const char *path, struct fuse_file_info *fi)
++{
++ if (strcmp(path, l.path) != 0)
++ return -ENOENT;
++ if ((fi->flags & 3) != O_RDONLY)
++ return -EACCES;
++ l.stat_dump.st_atime = time(NULL);
++ return 0;
++}
++
++/*
++ * FUSE callback: Read
++ */
++static int zfuse_read(const char *path, char *buf, size_t size, off_t offset,
++ struct fuse_file_info *fi)
++{
++ (void) fi;
++
++ if (strcmp(path, l.path) != 0)
++ return -ENOENT;
++ dfo_seek(offset);
++ dfo_read(buf, size);
++ return size;
++}
++
++/*
++ * FUSE callback: Statfs
++ */
++static int zfuse_statfs(const char *path, struct statvfs *buf)
++{
++ (void) path;
++
++ buf->f_bsize = buf->f_frsize = 4096;
++ buf->f_blocks = dfo_size() / 4096;
++ buf->f_bfree = buf->f_bavail = 0;
++ buf->f_files = 1;
++ buf->f_ffree = 0;
++ buf->f_namemax = strlen(l.path) + 1;
++ return 0;
++}
++
++/*
++ * FUSE operations
++ */
++static struct fuse_operations zfuse_ops = {
++ .getattr = zfuse_getattr,
++ .readdir = zfuse_readdir,
++ .open = zfuse_open,
++ .read = zfuse_read,
++ .statfs = zfuse_statfs,
++};
++
++/*
++ * Add additional FUSE arguments
++ */
++static void add_argv_fuse(struct fuse_args *args)
++{
++ int i;
++
++ if (g.opts.argc_fuse == 0)
++ return;
++ STDERR("Adding Fuse options: ");
++ for (i = 0; i < g.opts.argc_fuse; i++) {
++ STDERR("%s ", g.opts.argv_fuse[i]);
++ fuse_opt_add_arg(args, g.opts.argv_fuse[i]);
++ }
++ STDERR("\n");
++}
++
++/*
++ * Mount dump
++ *
++ * Add additional FUSE options:
++ * - s....................: Disable multi-threaded operation
++ * - o fsname.............: File system name (used for umount)
++ * - o ro.................: Read only
++ * - o default_permissions: Enable permission checking by kernel
++ */
++int zfuse_mount_dump(void)
++{
++ struct fuse_args args = FUSE_ARGS_INIT(0, NULL);
++ char tmp_str[PATH_MAX];
++
++ if (!dfi_feat_seek())
++ ERR_EXIT("Mounting not possible for %s dumps", dfi_name());
++ fuse_opt_add_arg(&args, "zgetdump");
++ fuse_opt_add_arg(&args, "-s");
++ snprintf(tmp_str, sizeof(tmp_str),
++ "-ofsname=%s,ro,default_permissions,nonempty",
++ g.opts.device);
++ fuse_opt_add_arg(&args, tmp_str);
++ fuse_opt_add_arg(&args, g.opts.mount_point);
++ add_argv_fuse(&args);
++ stat_root_init();
++ stat_dump_init();
++ snprintf(l.path, sizeof(l.path), "/dump.%s", dfo_name());
++ return fuse_main(args.argc, args.argv, &zfuse_ops);
++}
++
++/*
++ * Unmount dump
++ */
++void zfuse_umount(void)
++{
++ char umount_cmd[PATH_MAX];
++ char *umount_tool;
++ struct stat sbuf;
++ int rc;
++
++ if (stat("/usr/bin/fusermount", &sbuf) == 0)
++ umount_tool = "/usr/bin/fusermount -u";
++ else if (stat("/bin/fusermount", &sbuf) == 0)
++ umount_tool = "/bin/fusermount -u";
++ else
++ umount_tool = "umount";
++
++ snprintf(umount_cmd, sizeof(umount_cmd), "%s %s", umount_tool,
++ g.opts.mount_point);
++ rc = system(umount_cmd);
++
++ if (rc == -1)
++ ERR_EXIT_ERRNO("\"%s\" failed", umount_cmd);
++ if (rc > 0)
++ ERR_EXIT("\"%s\" failed", umount_cmd);
++ exit(0);
++}
+diff --git a/zdump/zg.c b/zdump/zg.c
+new file mode 100644
+index 0000000..e249011
+--- /dev/null
++++ b/zdump/zg.c
+@@ -0,0 +1,411 @@
++/*
++ * zgetdump - Tool for copying and converting System z dumps
++ *
++ * Helper functions
++ *
++ * Copyright IBM Corp. 2001, 2010
++ * Author(s): Michael Holzheu <holzheu at linux.vnet.ibm.com>
++ */
++
++#include <stdlib.h>
++#include <limits.h>
++#include "zgetdump.h"
++
++#define MAX_EXIT_FN 10
++#define MAX_DEV_RETRIES 1000
++
++/*
++ * Progress information
++ */
++struct prog {
++ u64 addr_next;
++ u64 mem_size;
++};
++
++/*
++ * At exit information
++ */
++struct atexit {
++ zg_atexit_fn_t fn_vec[MAX_EXIT_FN];
++ unsigned int cnt;
++};
++
++/*
++ * Temp devnode information
++ */
++struct devnode {
++ char **vec;
++ int cnt;
++};
++
++/*
++ * File local static data
++ */
++static struct {
++ struct atexit atexit;
++ struct prog prog;
++ struct devnode devnode;
++} l;
++
++/*
++ * Call all registered exit handlers
++ */
++static void exit_fn(void)
++{
++ unsigned int i;
++
++ for (i = 0; i < l.atexit.cnt; i++)
++ l.atexit.fn_vec[i]();
++}
++
++/*
++ * Register exit handler
++ */
++void zg_atexit(zg_atexit_fn_t fn)
++{
++ if (l.atexit.cnt >= MAX_EXIT_FN)
++ ABORT("Too many atexit handlers (%d)", l.atexit.cnt + 1);
++ l.atexit.fn_vec[l.atexit.cnt] = fn;
++ if (l.atexit.cnt == 0)
++ atexit(exit_fn);
++ l.atexit.cnt++;
++}
++
++/*
++ * Exit function (For having exit gdb break point)
++ */
++void zg_exit(int rc)
++{
++ exit(rc);
++}
++
++/*
++ * Alloc memory and check for errors
++ */
++void *zg_alloc(unsigned int size)
++{
++ void *ptr = calloc(size, 1);
++ if (!ptr)
++ ERR_EXIT("Alloc: Out of memory (%i KiB)", TO_KIB(size));
++ return ptr;
++}
++
++/*
++ * Realloc memory and check for errors
++ */
++void *zg_realloc(void *ptr, unsigned int size)
++{
++ void *new_ptr = realloc(ptr, size);
++ if (!new_ptr)
++ ERR_EXIT("Realloc: Out of memory (%i KiB)", TO_KIB(size));
++ return new_ptr;
++}
++
++/*
++ * Create duplicate for string
++ */
++char *zg_strdup(const char *str)
++{
++ char *new_str = strdup(str);
++
++ if (!new_str)
++ ERR_EXIT("Strdup: Out of memory (%s)\n", str);
++ return new_str;
++}
++
++/*
++ * Free memory
++ */
++void zg_free(void *ptr)
++{
++ free(ptr);
++}
++
++/*
++ * Return path name of open file
++ */
++const char *zg_path(struct zg_fh *zg_fh)
++{
++ return zg_fh->path;
++}
++
++/*
++ * Return stat buffer of open file
++ */
++const struct stat *zg_stat(struct zg_fh *zg_fh)
++{
++ return &zg_fh->sb;
++}
++
++/*
++ * Open file
++ */
++struct zg_fh *zg_open(const char *path, int flags, enum zg_check check)
++{
++ struct zg_fh *zg_fh = zg_alloc(sizeof(*zg_fh));
++
++ zg_fh->fh = open(path, flags);
++ if (zg_fh->fh == -1) {
++ if (check == ZG_CHECK_NONE)
++ goto fail;
++ ERR_EXIT_ERRNO("Could not open \"%s\"", path);
++ }
++ if (stat(path, &zg_fh->sb) == -1) {
++ if (check == ZG_CHECK_NONE)
++ goto fail;
++ ERR_EXIT_ERRNO("Could not access file \"%s\"", path);
++ }
++ zg_fh->path = zg_strdup(path);
++ return zg_fh;
++
++fail:
++ zg_free(zg_fh);
++ return NULL;
++}
++
++/*
++ * Close file
++ */
++void zg_close(struct zg_fh *zg_fh)
++{
++ close(zg_fh->fh);
++ free(zg_fh);
++}
++
++/*
++ * Read file
++ */
++ssize_t zg_read(struct zg_fh *zg_fh, void *buf, size_t cnt, enum zg_check check)
++{
++ size_t copied = 0;
++ ssize_t rc;
++
++ do {
++ rc = read(zg_fh->fh, buf + copied, cnt - copied);
++ if (rc == -1) {
++ if (check == ZG_CHECK_NONE)
++ return rc;
++ ERR_EXIT_ERRNO("Could not read \"%s\"", zg_fh->path);
++ }
++ if (rc == 0) {
++ if (check != ZG_CHECK)
++ return copied;
++ ERR_EXIT("Unexpected end of file for \"%s\"",
++ zg_fh->path);
++ }
++ copied += rc;
++ } while (copied != cnt);
++ return copied;
++}
++
++/*
++ * Return file size
++ */
++u64 zg_size(struct zg_fh *zg_fh)
++{
++ return zg_fh->sb.st_size;
++}
++
++/*
++ * Return file position
++ */
++off_t zg_tell(struct zg_fh *zg_fh, enum zg_check check)
++{
++ off_t rc;
++
++ rc = lseek(zg_fh->fh, 0, SEEK_CUR);
++ if (rc == -1 && check != ZG_CHECK_NONE)
++ ERR_EXIT_ERRNO("Could not get file position for \"%s\"",
++ zg_fh->path);
++ return rc;
++}
++
++/*
++ * Seek to "off" relative to END
++ */
++off_t zg_seek_end(struct zg_fh *zg_fh, off_t off, enum zg_check check)
++{
++ off_t rc;
++
++ rc = lseek(zg_fh->fh, off, SEEK_END);
++ if (rc == -1 && check != ZG_CHECK_NONE)
++ ERR_EXIT_ERRNO("Could not seek \"%s\"", zg_fh->path);
++ return rc;
++}
++
++/*
++ * Seek to "off" in file
++ */
++off_t zg_seek(struct zg_fh *zg_fh, off_t off, enum zg_check check)
++{
++ off_t rc;
++
++ rc = lseek(zg_fh->fh, off, SEEK_SET);
++ if (rc == -1 && check != ZG_CHECK_NONE)
++ ERR_EXIT_ERRNO("Could not seek \"%s\"", zg_fh->path);
++ if (rc != off && check == ZG_CHECK)
++ ERR_EXIT("Could not seek \"%s\"", zg_fh->path);
++ return rc;
++}
++
++/*
++ * Seek from current position
++ */
++off_t zg_seek_cur(struct zg_fh *zg_fh, off_t off, enum zg_check check)
++{
++ off_t rc;
++
++ rc = lseek(zg_fh->fh, off, SEEK_CUR);
++ if (rc == -1 && check != ZG_CHECK_NONE)
++ ERR_EXIT_ERRNO("Could not seek \"%s\"", zg_fh->path);
++ return rc;
++}
++
++/*
++ * Do ioctl and exit in case of an error
++ */
++int zg_ioctl(struct zg_fh *zg_fh, int rq, void *data, const char *op,
++ enum zg_check check)
++{
++ int rc;
++
++ rc = ioctl(zg_fh->fh, rq, data);
++ if (rc == -1 && check != ZG_CHECK_NONE)
++ ERR_EXIT_ERRNO("Operation \"%s\" failed on \"%s\"", op,
++ zg_fh->path);
++ return rc;
++}
++
++/*
++ * Return file type
++ */
++enum zg_type zg_type(struct zg_fh *zg_fh)
++{
++ struct mtop mtop;
++ struct stat *sb = &zg_fh->sb;
++
++ if (S_ISREG(sb->st_mode))
++ return ZG_TYPE_FILE;
++ if (S_ISBLK(sb->st_mode)) {
++ if (minor(sb->st_rdev) % 4 == 0)
++ return ZG_TYPE_DASD;
++ else
++ return ZG_TYPE_DASD_PART;
++ }
++ if (S_ISCHR(sb->st_mode)) {
++ mtop.mt_count = 1;
++ mtop.mt_op = MTTELL;
++ if (zg_ioctl(zg_fh, MTIOCTOP, &mtop, "MTIOCTOP",
++ ZG_CHECK_NONE) != -1)
++ return ZG_TYPE_TAPE;
++ }
++ return ZG_TYPE_UNKNOWN;
++}
++
++/*
++ * Initialize progress messages
++ */
++void zg_progress_init(const char *msg, u64 mem_size)
++{
++ STDERR("%s:\n", msg);
++ l.prog.addr_next = 0;
++ l.prog.mem_size = mem_size;
++}
++
++/*
++ * Print progress
++ */
++void zg_progress(u64 addr)
++{
++ if (addr < l.prog.addr_next)
++ return;
++
++ STDERR(" %08Lu / %08Lu MB\n", TO_MIB(addr), TO_MIB(l.prog.mem_size));
++ l.prog.addr_next += l.prog.mem_size / 6;
++ l.prog.addr_next = MIN(l.prog.addr_next, l.prog.mem_size);
++}
++
++/*
++ * Try to create device node in "dir"
++ */
++static char *devnode_create_dir(const char *dir, dev_t dev)
++{
++ char file_path[PATH_MAX];
++ int i, fh, rc;
++
++ for (i = 0; i < MAX_DEV_RETRIES; i++) {
++ snprintf(file_path, PATH_MAX, "%s/zgetdump.%04d", dir, i);
++ rc = mknod(file_path, S_IFBLK | S_IRWXU, dev);
++ if (rc == -1) {
++ if (errno == EEXIST)
++ continue;
++ else
++ break;
++ }
++
++ /* Need this test to cover 'nodev'-mounted filesystems */
++ fh = open(file_path, O_RDWR);
++ if (fh == -1) {
++ remove(file_path);
++ break;
++ }
++ close(fh);
++ return zg_strdup(file_path);
++ }
++ return NULL;
++}
++
++/*
++ * Delete temporary device node
++ */
++static void devnode_remove(char *dev_node)
++{
++ if (remove(dev_node))
++ ERR("Warning: Could not remove temporary file %s: %s",
++ dev_node, strerror(errno));
++ zg_free(dev_node);
++}
++
++/*
++ * Remove all temporary device nodes
++ */
++static void devnode_remove_all(void)
++{
++ int i;
++
++ for (i = 0; i < l.devnode.cnt; i++)
++ devnode_remove(l.devnode.vec[i]);
++ if (l.devnode.vec) {
++ zg_free(l.devnode.vec);
++ l.devnode.vec = NULL;
++ }
++ l.devnode.cnt = 0;
++}
++
++/*
++ * Make temporary device node for input dev identified by its dev_t
++ */
++char *zg_devnode_create(dev_t dev)
++{
++ char *dir_vec[] = {getenv("TMPDIR"), "/tmp", getenv("HOME"), ".", "/"};
++ char *file_path;
++ unsigned int i;
++
++ for (i = 0; i < ARRAY_ELEMENT_CNT(dir_vec); i++) {
++ if (dir_vec[i] == NULL)
++ continue;
++ file_path = devnode_create_dir(dir_vec[i], dev);
++ if (file_path)
++ goto found;
++ }
++ ERR_EXIT_ERRNO("Unable to create temporary dev node");
++ return NULL;
++found:
++ l.devnode.cnt++;
++ l.devnode.vec = zg_realloc(l.devnode.vec, l.devnode.cnt *
++ sizeof(void *));
++ l.devnode.vec[l.devnode.cnt - 1] = file_path;
++ if (l.devnode.cnt == 1)
++ zg_atexit(devnode_remove_all);
++ return file_path;
++}
+diff --git a/zdump/zg.h b/zdump/zg.h
+new file mode 100644
+index 0000000..2532146
+--- /dev/null
++++ b/zdump/zg.h
+@@ -0,0 +1,185 @@
++/*
++ * zgetdump - Tool for copying and converting System z dumps
++ *
++ * Helper functions
++ *
++ * Copyright IBM Corp. 2001, 2010
++ * Author(s): Michael Holzheu <holzheu at linux.vnet.ibm.com>
++ */
++
++#ifndef ZG_H
++#define ZG_H
++
++#include <errno.h>
++#include <string.h>
++#include <unistd.h>
++#include <stdlib.h>
++#include <stdio.h>
++#include <fcntl.h>
++#include <assert.h>
++#include <sys/ioctl.h>
++#include <sys/types.h>
++#include <sys/stat.h>
++#include <sys/mtio.h>
++#include "zt_common.h"
++
++#define U64_MAX ((u64) -1)
++#define U32_MAX ((u32) -1)
++#define U16_MAX ((u16) -1)
++#define U8_MAX ((u8) -1)
++
++/*
++ * IEC definitions
++ */
++#define KIB_DIFF (1024)
++#define MIB_DIFF (1024 * 1024)
++#define GIB_DIFF (1024 * 1024 * 1024)
++
++#define TO_MIB(x) ((x + (MIB_DIFF / 2)) / MIB_DIFF)
++#define TO_KIB(x) ((x + (KIB_DIFF / 2)) / KIB_DIFF)
++
++/*
++ * Memory functions
++ */
++extern void *zg_alloc(unsigned int size);
++extern void *zg_realloc(void *ptr, unsigned int size);
++extern void zg_free(void *ptr);
++extern char *zg_strdup(const char *str);
++
++/*
++ * At exit functions
++ */
++typedef void (*zg_atexit_fn_t)(void);
++extern void zg_atexit(zg_atexit_fn_t fn);
++extern void zg_exit(int rc) __attribute__ ((__noreturn__));
++
++/*
++ * Temporary device node functions
++ */
++extern char *zg_devnode_create(dev_t dev);
++
++/*
++ * Progress bar functions
++ */
++extern void zg_progress_init(const char *msg, u64 mem_size);
++extern void zg_progress(u64 addr);
++
++/*
++ * Error and print functions
++ */
++#define ERR(x...) \
++do { \
++ fprintf(stderr, "%s: ", "zgetdump"); \
++ fprintf(stderr, x); \
++ fprintf(stderr, "\n"); \
++} while (0)
++
++#define ERR_EXIT(x...) \
++do { \
++ ERR(x); \
++ zg_exit(1); \
++} while (0)
++
++#define ABORT(x...) \
++do { \
++ ERR("Internal Error: " x); \
++ abort(); \
++} while (0)
++
++#define ERR_EXIT_ERRNO(x...) \
++ do { \
++ fflush(stdout); \
++ fprintf(stderr, "%s: ", "zgetdump"); \
++ fprintf(stderr, x); \
++ fprintf(stderr, " (%s)", strerror(errno)); \
++ fprintf(stderr, "\n"); \
++ zg_exit(1); \
++ } while (0)
++
++#define STDERR(x...) \
++do { \
++ fprintf(stderr, x); \
++ fflush(stderr); \
++} while (0)
++
++#define STDERR_PR(x...) \
++do { \
++ fprintf(stderr, "\r%s: ", "zgetdump"); \
++ fprintf(stderr, x); \
++} while (0)
++
++#define STDOUT(x...) \
++do { \
++ fprintf(stdout, x); \
++ fflush(stdout); \
++} while (0)
++
++/*
++ * Misc
++ */
++#define PAGE_SIZE 4096
++#define ALIGN(x, a) __ALIGN_MASK(x, (typeof(x))(a)-1)
++#define __ALIGN_MASK(x, mask) (((x) + (mask)) & ~(mask))
++#define PAGE_ALIGN(addr) ALIGN(addr, PAGE_SIZE)
++#define MIN(x, y) ((x) < (y) ? (x) : (y))
++#define MAX(x, y) ((x) > (y) ? (x) : (y))
++#define ARRAY_ELEMENT_CNT(x) (sizeof(x) / sizeof(x[0]))
++#define ROUNDUP(x, y) ((((x) + ((y) - 1)) / (y)) * (y))
++
++/*
++ * Pointer atrithmetic
++ */
++#define PTR_SUB(x, y) (((char *) (x)) - ((unsigned long) (y)))
++#define PTR_ADD(x, y) (((char *) (x)) + ((unsigned long) (y)))
++#define PTR_DIFF(x, y) ((unsigned long)(((char *) (x)) - ((unsigned long) (y))))
++
++/*
++ * File functions
++ */
++struct zg_fh {
++ const char *path;
++ int fh;
++ struct stat sb;
++};
++
++enum zg_type {
++ ZG_TYPE_DASD,
++ ZG_TYPE_DASD_PART,
++ ZG_TYPE_FILE,
++ ZG_TYPE_TAPE,
++ ZG_TYPE_UNKNOWN,
++};
++
++enum zg_check {
++ ZG_CHECK,
++ ZG_CHECK_ERR,
++ ZG_CHECK_NONE,
++};
++
++extern const char *zg_path(struct zg_fh *zg_fh);
++extern const struct stat *zg_stat(struct zg_fh *zg_fh);
++extern struct zg_fh *zg_open(const char *path, int flags, enum zg_check check);
++extern void zg_close(struct zg_fh *zg_fh);
++extern ssize_t zg_read(struct zg_fh *zg_fh, void *buf, size_t cnt,
++ enum zg_check check);
++extern u64 zg_size(struct zg_fh *zg_fh);
++extern off_t zg_tell(struct zg_fh *zg_fh, enum zg_check check);
++extern off_t zg_seek(struct zg_fh *zg_fh, off_t off, enum zg_check check);
++extern off_t zg_seek_end(struct zg_fh *zg_fh, off_t off, enum zg_check check);
++extern off_t zg_seek_cur(struct zg_fh *zg_fh, off_t off, enum zg_check check);
++extern int zg_ioctl(struct zg_fh *zg_fh, int rq, void *data, const char *op,
++ enum zg_check check);
++extern enum zg_type zg_type(struct zg_fh *zg_fh);
++
++/*
++ * zgetdump actions
++ */
++enum zg_action {
++ ZG_ACTION_STDOUT,
++ ZG_ACTION_DUMP_INFO,
++ ZG_ACTION_DEVICE_INFO,
++ ZG_ACTION_MOUNT,
++ ZG_ACTION_UMOUNT,
++};
++
++#endif /* ZG_H */
+diff --git a/zdump/zgetdump.8 b/zdump/zgetdump.8
+index 81ea801..a9230b4 100644
+--- a/zdump/zgetdump.8
++++ b/zdump/zgetdump.8
+@@ -1,69 +1,317 @@
+-.TH ZGETDUMP 8 "Apr 2006" "s390-tools"
++.TH ZGETDUMP 8 "Jan 2010" "s390-tools"
+ .SH NAME
+-zgetdump \- tool for copying dumps.
++zgetdump \- Tool for copying and converting System z dumps
+ .SH SYNOPSIS
+-\fBzgetdump\fR [-d] [-h] [-i] [-a] [-v] \fIdumpdevice\fR
++\fBzgetdump\fR [OPTIONS] [DUMP/DUMPDEV] [DIR]
+ .SH DESCRIPTION
+-\fBzgetdump\fR takes as input the dump device and writes its contents
+-to standard output, which you can redirect to a specific file.
+-.br
+-\fBzgetdump\fR can also check, whether a DASD device contains a valid dumper.
++The \fBzgetdump\fR tool reads or converts a dump. The dump can be located
++either on a dump device or on a file system. By default the dump content is
++written to standard output, which you can redirect to a specific file. You
++can also mount the dump content, print dump information, or check
++whether a DASD device contains a valid dump tool.
+ .SH OPTIONS
+ .TP
+-\fB-d\fR
+-Check DASD device \fIdumpdevice\fR for valid dumper.
++.BR "\-h" " or " "\-\-help"
++Print usage information, then exit.
++
++.TP
++.BR "\-v" " or " "\-\-version"
++Print version information, then exit.
++
++.TP
++.BR "\-m <DUMP> <DIR>" " or " "\-\-mount <DUMP> <DIR>"
++Mount the DUMP to mount point DIR and generate a virtual target
++dump file instead of writing the content to standard output. The virtual dump
++file gets the name "dump.FMT", where FMT is the name of the specified
++dump format (see "--fmt" option).
++
++.TP
++.BR "\-u <DIR>" " or " "\-\-umount <DIR>"
++Unmount the dump that is mounted at mount point DIR. This option is a wrapper
++for "fusermount -u". Instead of DIR also the the DUMP (e.g. /dev/dasdd1)
++can be specified.
++
++.TP
++.BR "\-d <DUMPDEV>" " or " "\-\-device <DUMPDEV>"
++Check DASD device DUMPDEV for valid dump tool and print information about it.
++
++.TP
++.BR "\-i <DUMP>" " or " "\-\-info <DUMP>"
++Print the dump header information reading from the DUMP and check if
++the dump is valid. See chapter DUMP INFORMATION below for more information.
++.TP
++.BR "\-f <FMT>" " or " "\-\-fmt <FMT>"
++Use the specified target dump format FMT when writing or mounting the dump.
++The following target dump formats are supported:
++
++.BR "- elf:"
++Executable and Linking Format core dump (64 bit only)
++
++.BR "- s390:"
++s390 dump (default)
++
++.TP
++\fBDUMP\fR
++This parameter specifies the file, partition or tape device node where the
++dump is located:
++.TP
++.BR
++- Regular dump file (e.g. /testdir/dump.0)
++.TP
++.BR
++- DASD partition device node (e.g. /dev/dasdc1)
++.TP
++.BR
++- DASD device node for multi-volume dump (e.g. /dev/dasdc)
++.TP
++.BR
++- Tape device node (e.g. /dev/ntibm0)
++
++Note: For DASD multi-volume dump it is sufficient to specify only one of the
++multi-volume DASD partitions as DUMP.
++
++.TP
++\fBDUMPDEV\fR
++When using the "--device" option, DUMPDEV must be the DASD device node of
++the dump disk that should be verified.
++
++.SH COPY DUMP
++The default action of zgetdump is to copy the DUMP to standard output. Read
++the examples section below for more information.
++
++.SH MOUNT DUMP
++Instead of writing the dump content to standard output you can also mount the
++dump using the "--mount" option. With that option it is possible to convert
++the dump without the need of copying it. The zgetdump tool generates a
++virtual target dump file that contains the dump in the requested target
++format. The virtual dump file is generated by mounting the source dump as a
++user space file system to the directory specified by the "--mount" option.
++The virtual target dump file is called dump.<FMT> where FMT denotes
++the format of the target dump. The virtual dump file exists as long as the
++directory containing the file is not unmounted.
++
++Mounting can be useful when you want to process the dump with a tool that
++cannot read the original dump format. To do this, mount the dump and
++specify the required target dump format with the "--fmt" option. Mounting is
++also for useful for multi-volume DASD dumps. After a multi-volume dump has been
++mounted, it is shown as a single dump file that can be accessed directly with
++dump processing tools like "makedumpfile", "crash" or "lcrash".
++
++Mounting is implemented with "fuse" (file system in user space). Therefore the
++"fuse" kernel module must to be loaded on the system before the "--mount"
++option can be used.
++
++A DASD dump can be mounted e.g. with "zgetdump /dev/dasdd1 -m
++/mnt" and unmounted with either "zgetdump -u /mnt", "fusermount -u /mnt" or
++"umount /mnt" (root only).
++
++.SH DUMP FORMATS
++zgetdump supports the following dump formats:
++.TP
++.BR "s390"
++This dump format is System z specific and is used for DASD and tape dumps.
++.TP
++.BR "elf"
++Executable and Linking Format core dump. This dump format is also used for
++Linux user space core dumps. The zgetdump tool supports this dump format only
++for 64 bit.
++.TP
++.BR "lkcd"
++This dump format has been used by the Linux Kernel Crash Dumps (LKCD) project
++and is used on System z for the vmconvert and zfcp (SCSI) dump tool. The
++zgetdump tool supports "lkcd" only as source format.
++
++.TP
++The default target format of zgetdump is "s390". Use the "--fmt" option to change the target format.
++
++.SH DUMP INFORMATION
++When calling zgetdump with the "--info" option depending on the dump format
++the following dump attributes are available:
++.TP
++.BR "Dump format"
++Name of the dump format.
++.TP
++.BR Version
++Version number of the dump format.
++.TP
++.BR "Dump created/ended"
++Time when the dump process was started or ended. The dump time information is
++printed in your local time zone. E.g. "Wed, 03 Feb 2010 10:47:37 +0100" shows
++the time at your location. The meaning of "+0100" is that your time zone is one
++hour behind GMT (Greenwich Mean Time). You can use the "TZ" environment
++variable or use the "tzselect" tool to change the time zone. For example, if you
++know that the dump has been created in Hawaii, you can get the correct
++time information with:
++.br
++
++# TZ='Pacific/Honolulu' zgetdump -i DUMP
++.TP
++.BR "Dump CPU ID"
++Identifier of the CPU that executed the dump tool.
++.TP
++.BR "Build arch"
++Architecture (s390 or s390x) on which the dump tool was built.
++.TP
++.BR "System arch"
++Architecture (s390 or s390x) of the dumped Linux system.
++.TP
++.BR "CPU count (online)"
++Number of online CPUs.
++.TP
++.BR "CPU count (real)"
++Number of total CPUs (online and offline).
++.TP
++.BR "Dump memory range"
++Memory range that was dumped. This value is the difference between the last
++dumped and the first dumped memory address.
++.TP
++.BR "Real memory range"
++Memory range that was available on system. This value is the difference
++between the last and the first memory address of the dumped system.
++The "real memory range" can differ from the "dump memory range" when
++the SIZE parameter was used when preparing the dump device with the zipl
++tool (see man zipl).
++.TP
++.BR "Memory map"
++Available memory chunks in the dump. Depending on the dump tool there
++can be multiple memory chunks, when a system with memory holes is dumped.
++
++.SH DUMP DEVICE INFORMATION
++When calling zgetdump with the "--device" option depending on the dump tool
++the following attributes are available:
++.TP
++.BR "Dump tool"
++Name of the dump tool.
++.TP
++.BR "Version"
++Version of the dump tool.
+ .TP
+-\fB-h\fR
+-Print usage and exit.
++.BR "Architecture"
++Architecture (s390 or s390x) of the dump tool.
+ .TP
+-\fB-i\fR
+-Print the dump header information reading from the \fIdumpdevice\fR and
+-check if the dump is valid.
++.BR "DASD type"
++Type of the DASD where the dump tool is installed (ECKD or FBA).
+ .TP
+-\fB-i -a\fR
+-Print the dump header information and check if the dump is valid when
+-\fIdumpdevice\fR is a multi-volume tape.
+-(Mount and check all cartridges in sequence.)
++.BR "Dump size limit"
++If this attribute is set, the dump tool will dump memory only up to that
++limit even if there is more memory available.
+ .TP
+-\fB-v\fR
+-Output version information and exit.
++.BR "Force specified"
++If that attribute is set to "yes", the multi-volume DASD dump tool will not
++verify the dump signature on dump partitions. This can be useful, if the dump
++partition is also used for swap.
++
++.SH EXAMPLES
+ .TP
+-\fBdumpdevice\fR
+-This parameter specifies the device or partition where the dump is located.
+-.SH EXAMPLE
+-1. Scenario: DASD partition /dev/dasdx1 was prepared for dump by means of
++.B Copy single volume DASD dump
++
++The DASD partition /dev/dasdx1 was prepared for dump with:
+ .br
+- zipl -d /dev/dasdx1
++
++ # zipl -d /dev/dasdx1
++
+ .br
+-The corresponding single-volume dump tool was IPLed.
+-.RB "The respective " "zgetdump " "call to copy the dump from the DASD
+-partition to file dump_file is:
++The corresponding single-volume dump tool was IPLed. The respective zgetdump
++call to copy the dump from the DASD partition to file dump.s390 is:
+ .br
+
+- zgetdump /dev/dasdx1 > dump_file
++ # zgetdump /dev/dasdx1 > dump.s390
+
+-2. Scenario: DASD partitions /dev/dasdx1 and /dev/dasdy1 contained in file
+-dump_list_conf were prepared for dump by means of
++.TP
++.B Copy multi-volume DASD dump
++
++DASD partitions /dev/dasdx1 and /dev/dasdy1 contained in file dev_list.conf
++were prepared for multi-volume dump with:
+ .br
+- zipl -M dump_list_conf
++
++ # zipl -M dev_list.conf
++
+ .br
+-The corresponding multi-volume dump tool was IPLed.
+-.RB "The respective " "zgetdump " "call to copy the dump from the DASD
+-partitions to file dump_file is:
++The corresponding multi-volume dump tool was IPLed. The respective zgetdump
++call to copy the dump from the DASD partitions to file dump.s390 is:
+ .br
+
+- zgetdump /dev/dasdx > dump_file or equivalent
++ # zgetdump /dev/dasdx > dump.s390
++
+ .br
+- zgetdump /dev/dasdy > dump_file
++.TP
++.B Copy tape dump
+
+-3. Scenario: Tape device /dev/ntibm0 was prepared for dump by means of
++Tape device /dev/ntibm0 was prepared with:
+ .br
+- zipl -d /dev/ntibm0
++
++ # zipl -d /dev/ntibm0
++
++.br
++The corresponding tape dump tool was IPLed. The respective zgetdump call to
++copy the dump from the tape to file dump.s390 is:
++.br
++
++ # zgetdump /dev/ntibm0 > dump.s390
++
++.br
++.TP
++.B Using pipes for network transfer
++
++You can redirect standard output to tools like ftp or ssh in order to
++transfer the dump over the network without copying it into the file system
++first.
++
++Copy DASD dump using ssh:
++.br
++
++ # zgetdump /dev/dasdd1 | ssh user at host "cat > dump.s390"
++
+ .br
+-The corresponding tape dump tool was IPLed.
+-.RB "The respective " "zgetdump " "call to copy the dump from the tape
+-to file dump_file is:
++Copy and compress DASD dump using ftp and gzip (note that not all ftp clients
++can do this):
+ .br
+
+- zgetdump /dev/ntibm0 > dump_file
++ # ftp host
++ ftp> put |"zgetdump /dev/dasdd1 | gzip" dump.s390.gz
+
++.br
++The same effect can also be achieved by using the "--mount" option and run
++scp or ftp directly on the mounted virtual dump file.
++
++.TP
++.B Using the "--mount" option
++
++Mount multi-volume DASD dump, process it with the "crash" tool and unmout
++it with zgetdump afterwards.
++.br
++
++ # zgetdump -m -f elf /dev/dasdx /dumps
++ # crash vmlinux /dumps/dump.elf
++ # zgetdump -u /dumps
++
++.br
++Convert an ELF dump to an s390 dump by mounting it with the "--fmt" option,
++process it with lcrash and unmount it with fusermount afterwards.
++.br
++
++ # zgetdump -m -f s390 dump.elf /dumps
++ # lcrash System.map /dumps/dump.s390 Kerntypes
++ # fusermount -u /dumps
++
++.br
++.TP
++.B Print dump information (--info)
++
++Print information on DASD dump on /dev/dasdd1:
++.br
++
++ # zgetdump -i /dev/dasdd1
++
++.br
++.TP
++.B Print DASD dump tool information (--device)
++
++Print information on DASD dump tool on /dev/dasdd:
++.br
++
++ # zgetdump -d /dev/dasdd
++
++.br
++.SH SEE ALSO
++.BR zipl (8), crash (8), lcrash (8), dumpconf (8), vmconvert (8), vmur (8)
+diff --git a/zdump/zgetdump.c b/zdump/zgetdump.c
+index 1ef312c..b3db463 100644
+--- a/zdump/zgetdump.c
++++ b/zdump/zgetdump.c
+@@ -1,1360 +1,159 @@
+ /*
+- * zgetdump
+- * Description: The zgetdump tool takes as input the dump device
+- * and writes its contents to standard output,
+- * which you can redirect to a specific file.
++ * zgetdump - Tool for copying and converting System z dumps
+ *
+- * Copyright IBM Corp. 2001, 2006.
+- * Author(s): Despina Papadopoulou
+- * Frank Munzert <munzert at de.ibm.com>
++ * Main functions
++ *
++ * Copyright IBM Corp. 2001, 2010
++ * Author(s): Michael Holzheu <holzheu at linux.vnet.ibm.com>
++ * Frank Munzert <munzert at de.ibm.com>
++ * Despina Papadopoulou
+ */
+
+-#include "zgetdump.h"
+-#include "zt_common.h"
+ #include <stdio.h>
+ #include <unistd.h>
+ #include <stdlib.h>
+-#include <sys/ioctl.h>
+-#include <sys/types.h>
+-#include <sys/stat.h>
+-#include <sys/time.h>
+-#include <sys/utsname.h>
+ #include <fcntl.h>
+ #include <ctype.h>
+ #include <string.h>
+ #include <time.h>
+-#include <getopt.h>
+ #include <limits.h>
+-#include <dirent.h>
+ #include <errno.h>
++#include <signal.h>
++#include <sys/ioctl.h>
++#include <sys/types.h>
++#include <sys/stat.h>
++#include <sys/time.h>
+ #include <sys/mtio.h>
++#include <linux/fs.h>
++#include "zgetdump.h"
+
+-/* from linux/fs.h */
+-#define BLKSSZGET _IO(0x12,104)
+-#define BLKFLSBUF _IO(0x12,97)
+-
+-#define HEADER_SIZE 4096
+-#define BLOCK_SIZE 32768
+-#define MVDUMPER_SIZE 4096
+-#define PARTN_MASK ((1 << 2) - 1)
+-#define MAGIC_BLOCK_OFFSET_ECKD 3
+-#define MAGIC_OFFSET_FBA -0x1000
+-#define HEXINSTR "\x0d\x10\x47\xf0" /* BASR + 1st halfword of BC */
+-#define ARCH_S390 1
+-#define ARCH_S390X 2
+-#define VERSION_NO_DUMP_DEVICE -1
+-
+-#define SYSFS_BUSDIR "/sys/bus/ccw/devices"
+-
+-#if defined(__s390x__)
+- #define FMT64 "l"
+-#else
+- #define FMT64 "ll"
+-#endif
+-
+-/* definitions */
+-
+-char *help_text =
+-"The zgetdump tool takes as input the dump device and writes its contents\n"\
+-"to standard output, which you can redirect to a specific file.\n"\
+-"zgetdump can also check, whether a DASD device contains a valid dumper.\n\n"\
+-"Usage:\n"\
+-"Copy dump from <dumpdevice> to stdout:\n"\
+-" > zgetdump <dumpdevice>\n"\
+-"Print dump header and check if dump is valid - for single tape or DASD:\n"\
+-" > zgetdump [-i | --info] <dumpdevice>\n"\
+-"Print dump header and check if dump is valid - for all volumes of a\n"
+-"multi-volume tape dump:\n"\
+-" > zgetdump [-i | --info] [-a | --all] <dumpdevice>\n"\
+-"Check dump device:\n"\
+-" > zgetdump [-d | --device] <dasd_device>\n"\
+-"Print version info:\n"\
+-" > zgetdump [-v | --version]\n"\
+-"Print this text:\n"\
+-" > zgetdump [-h | --help]\n\n"\
+-"Examples for single-volume DASD:\n"\
+-"> zgetdump -d /dev/dasdc\n"\
+-"> zgetdump -i /dev/dasdc1\n"\
+-"> zgetdump /dev/dasdc1 > dump_file\n";
+-
+-char *usage_note =
+-"Usage:\n"\
+-"> zgetdump <dumpdevice>\n"\
+-"> zgetdump -i <dumpdevice>\n"\
+-"> zgetdump -i -a <dumpdevice>\n"\
+-"> zgetdump -d <device>\n"\
+-"More info:\n"\
+-"> zgetdump -h\n";
+-
+-/* Version info */
+-static const char version_text[] = "zgetdump: version "RELEASE_STRING;
+-
+-/* Copyright notice */
+-static const char copyright_notice[] = "Copyright IBM Corp. 2001, 2008";
+-
+-/* global variables */
+-
+-s390_dump_header_t header;
+-s390_dump_end_marker_t end_marker;
+-char read_buffer[BLOCK_SIZE];
+-struct timeval h_time_begin, h_time_end;
+-
+-int option_a_set;
+-int option_i_set;
+-int option_d_set;
+-char dump_device[PATH_MAX];
+-
+-/* end of definitions */
+-
+-/* Use uname to check whether we run s390x kernel */
+-int check_kernel_mode()
+-{
+- struct utsname uname_struct;
+- if (uname(&uname_struct)) {
+- fprintf(stderr, "Unable to get name and information about "
+- "current kernel. \n");
+- perror("");
+- return 1;
+- }
+- if (strncmp(uname_struct.machine, "s390x", 5) == 0) {
+- fprintf(stderr, "=========================================="
+- "=======\n");
+- fprintf(stderr, "WARNING: You are running an s390x (ESAME) "
+- "kernel.\n");
+- fprintf(stderr, " Your dump tool however is s390 "
+- "(ESA).\n");
+- fprintf(stderr, "=========================================="
+- "=======\n");
+- }
+- return 0;
+-}
+-
+-/* Read dump tool from DASD device */
+-int read_dumper(int fd, int32_t offset, struct dump_tool *buffer, int whence)
+-{
+- if (lseek(fd, offset, whence) == -1) {
+- perror("Cannot seek on device");
+- return 1;
+- }
+- if (read(fd, buffer, sizeof(struct dump_tool)) !=
+- sizeof(struct dump_tool)) {
+- perror("Cannot read dump tool from device");
+- return 1;
+- }
+- return 0;
+-}
+-
+-/* Use stat to check whether user provided input is a block device or a
+- * partition */
+-enum devnode_type check_device(char *device, int print)
+-{
+- struct stat stat_struct;
+-
+- if (stat(device, &stat_struct)) {
+- fprintf(stderr, "Unable to get device status for "
+- "'%s'. \n", device);
+- perror("");
+- return IS_NOBLOCK;
+- }
+- if (!(S_ISBLK(stat_struct.st_mode))) {
+- fprintf(stderr, "'%s' is not a block device. \n", dump_device);
+- return IS_NOBLOCK;
+- }
+- if (minor(stat_struct.st_rdev) & PARTN_MASK) {
+- if (print)
+- fprintf(stderr, "Partition '%s' (%d/%d) specified where"
+- " device is required.\n", dump_device,
+- (unsigned short) major(stat_struct.st_rdev),
+- (unsigned short) minor(stat_struct.st_rdev));
+- return IS_PARTITION;
+- }
+- return IS_DEVICE;
+-}
+-
+-/* Allocate SIZE bytes of memory. Upon success, return pointer to memory.
+- * Return NULL otherwise. */
+-void *misc_malloc(size_t size)
+-{
+- void* result;
+-
+- result = malloc(size);
+- if (result == NULL) {
+- fprintf(stderr, "Could not allocate %lld bytes of memory",
+- (unsigned long long) size);
+- }
+- return result;
+-}
+-
+-char* misc_make_path(char* dirname, char* filename)
+-{
+- char* result;
+- size_t len;
+-
+- len = strlen(dirname) + strlen(filename) + 2;
+- result = (char *) misc_malloc(len);
+- if (result == NULL)
+- return NULL;
+- sprintf(result, "%s/%s", dirname, filename);
+- return result;
+-}
+-
+-#define TEMP_DEV_MAX_RETRIES 1000
+-
+-/* Make temporary device node for input device identified by its dev_t */
+-int make_temp_devnode(dev_t dev, char** device_node)
+-{
+- char* result;
+- char* pathname[] = { getenv("TMPDIR"), "/tmp",
+- getenv("HOME"), "." , "/"};
+- char filename[] = "zgetdump0000";
+- mode_t mode;
+- unsigned int path;
+- int retry;
+- int rc;
+- int fd;
+-
+- mode = S_IFBLK | S_IRWXU;
+- /* Try several locations as directory for the temporary device
+- * node. */
+- for (path=0; path < sizeof(pathname) / sizeof(pathname[0]); path++) {
+- if (pathname[path] == NULL)
+- continue;
+- for (retry=0; retry < TEMP_DEV_MAX_RETRIES; retry++) {
+- sprintf(filename, "zgetdump%04d", retry);
+- result = misc_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_RDWR);
+- if (fd != -1) {
+- close(fd);
+- *device_node = result;
+- return 0;
+- }
+- remove(result);
+- retry = TEMP_DEV_MAX_RETRIES;
+- } else if (errno != EEXIST)
+- retry = TEMP_DEV_MAX_RETRIES;
+- free(result);
+- }
+- }
+- fprintf(stderr, "Unable to create temporary device node: %s",
+- strerror(errno));
+- return 1;
+-}
+-
+-/* Delete temporary device node and free memory allocated for device name. */
+-void free_temp_devnode(char* device_node)
+-{
+- if (remove(device_node)) {
+- fprintf(stderr, "Warning: Could not remove "
+- "temporary file %s: %s",
+- device_node, strerror(errno));
+- }
+- free(device_node);
+-}
+-
+-
+-int open_block_device(char *device)
+-{
+- int fd;
+-
+- if (check_device(device, 1) != IS_DEVICE)
+- return -1;
+- fd = open(device, O_RDONLY);
+- if (fd == -1) {
+- fprintf(stderr, "Cannot open device '%s'. \n", device);
+- perror("");
+- }
+- return fd;
+-}
+-
+-/* Check sysfs, whether a device specified by its bus id is defined and online.
+- * Find out the corresponding dev_t */
+-enum device_status get_device_from_busid(char* bus_id, dev_t *device)
+-{
+- char dev_file[PATH_MAX];
+- char temp_file[PATH_MAX];
+- char buffer[10];
+- struct dirent *direntp;
+- int fd, minor, major;
+- DIR *fd1;
+-
+- fd1 = opendir(SYSFS_BUSDIR);
+- if (!fd1) {
+- fprintf(stderr, "Could not open %s (err = %i).\n",
+- SYSFS_BUSDIR, errno);
+- exit(1); /* sysfs info not available */
+- }
+- closedir(fd1);
+- snprintf(dev_file, PATH_MAX, "%s/%s", SYSFS_BUSDIR, bus_id);
+- fd1 = opendir(dev_file);
+- if (!fd1)
+- return UNDEFINED; /* device with devno does not exist */
+- snprintf(temp_file, PATH_MAX, "%s/online", dev_file);
+- fd = open(temp_file, O_RDONLY);
+- if (read(fd, buffer, 1) == -1) {
+- perror("Could not read online attribute.");
+- exit(1);
+- }
+- close(fd);
+- if (buffer[0] != '1')
+- return OFFLINE; /* device with devno is not online */
+- while ((direntp = readdir(fd1)))
+- if (strncmp(direntp->d_name, "block:", 6) == 0)
+- break;
+- closedir(fd1);
+- if (direntp == NULL) {
+- snprintf(dev_file, PATH_MAX, "%s/%s/block", SYSFS_BUSDIR,
+- bus_id);
+- fd1 = opendir(dev_file);
+- if (!fd1) {
+- fprintf(stderr, "Could not open %s (err = %i).\n",
+- dev_file, errno);
+- exit(1);
+- }
+- while ((direntp = readdir(fd1)))
+- if (strncmp(direntp->d_name, "dasd", 4) == 0)
+- break;
+- closedir(fd1);
+- if (direntp == NULL) {
+- fprintf(stderr, "Problem with contents of %s.\n",
+- dev_file);
+- exit(1);
+- }
+- }
+- snprintf(temp_file, PATH_MAX, "%s/%s/dev", dev_file, direntp->d_name);
+- fd = open(temp_file, O_RDONLY);
+- if (read(fd, buffer, sizeof(buffer)) == -1) {
+- perror("Could not read dev file.");
+- exit(1);
+- }
+- close(fd);
+- if (sscanf(buffer, "%i:%i", &major, &minor) != 2) {
+- fprintf(stderr, "Malformed content of %s: %s\n",
+- temp_file, buffer);
+- exit(1);
+- }
+- *device = makedev(major, minor);
+- return ONLINE;
+-}
+-
+-/* Read dump tool, multi-volume dump parameter table, and dump header from the
+- * input dump volume. Check input dump volume for
+- * - identical dump parameter table (that is it belongs to the same dump set)
+- * - valid magic number in the dump tool
+- * - valid dump signature in the dump header
+- * and set the volume's signature accordingly */
+-int get_mvdump_volume_info(struct disk_info *vol, uint32_t vol_nr, off_t offset,
+- struct mvdump_parm_table *table)
+-{
+- int fd, rc;
+- ssize_t n_read;
+- char* temp_devnode;
+- struct dump_tool dumper;
+- struct mvdump_parm_table vol_table;
+-
+- vol->signature = INVALID;
+- rc = make_temp_devnode(vol->device, &temp_devnode);
+- if (rc)
+- return 1;
+- fd = open_block_device(temp_devnode);
+- if (fd == -1) {
+- free_temp_devnode(temp_devnode);
+- return 1;
+- }
+- /* We read partition data via the device node. If another process
+- * has changed partition data via the partition node, the corresponding
+- * device node might still have old data in its buffers. Flush buffers
+- * to keep things in sync */
+- if (ioctl(fd, BLKFLSBUF, 0)) {
+- perror("BLKFLSBUF failed");
+- goto out;
+- }
+- if (read_dumper(fd, offset, &dumper, SEEK_SET))
+- goto out;
+- if (lseek(fd, offset + MVDUMPER_SIZE, SEEK_SET) !=
+- offset + MVDUMPER_SIZE) {
+- perror("Cannot seek on device");
+- goto out;
+- }
+- n_read = read(fd, &vol_table, sizeof(vol_table));
+- if (n_read == -1) {
+- perror("Cannot read multi-volume dump table");
+- goto out;
+- }
+- /* Check whether dump table on user specified dump device is
+- * identical to the one found on this device */
+- if (memcmp(&vol_table, table, sizeof(vol_table))) {
+- printf("ERROR: Orphaned multi-volume dump device '%s'\n",
+- dump_device);
+- goto out;
+- }
+- if (lseek(fd, vol->start_offset, SEEK_SET) != vol->start_offset) {
+- perror("Cannot seek on device");
+- goto out;
+- }
+- n_read = read(fd, &header, HEADER_SIZE);
+- if (n_read == -1) {
+- perror("Cannot read dump header");
+- goto out;
+- }
+- free_temp_devnode(temp_devnode);
+- close(fd);
+- if ((header.dh_mvdump_signature == DUMP_MAGIC_S390) &&
+- (strncmp(dumper.magic, "ZMULT64", 7) == 0)) {
+- vol->signature = VALID;
+- if ((header.dh_volnr == vol_nr) && (header.dh_memory_size != 0))
+- vol->signature = ACTIVE;
+- }
+- return 0;
+-out:
+- free_temp_devnode(temp_devnode);
+- close(fd);
+- return 1;
+-}
+-
+-/* Read multi-volume dump parameter table from dump device and fill in the
+- * fields of the disk_info array */
+-int get_mvdump_info(int fd, int block_size, int *count,
+- struct disk_info vol[])
+-{
+- int i, rc = 0;
+- off_t offset;
+- ssize_t n_read;
+- struct mvdump_parm_table table;
+-
+- offset = MAGIC_BLOCK_OFFSET_ECKD * block_size + MVDUMPER_SIZE;
+- if (lseek(fd, offset, SEEK_SET) != offset) {
+- fprintf(stderr, "Cannot seek on device '%s'.\n",
+- dump_device);
+- perror("");
+- return 1;
+- }
+- n_read = read(fd, &table, sizeof(table));
+- if (n_read == -1) {
+- perror("Cannot read multi-volume dump table");
+- return 1;
+- }
+- *count = table.num_param;
+- for (i = 0; i < table.num_param; i++) {
+- sprintf(vol[i].bus_id, "0.0.%04x", table.param[i].devno);
+- vol[i].start_offset = table.param[i].start_blk;
+- vol[i].start_offset *= table.param[i].blocksize << 8;
+- vol[i].part_size = (table.param[i].end_blk -
+- table.param[i].start_blk + 1);
+- vol[i].part_size *= table.param[i].blocksize << 8;
+- vol[i].status = get_device_from_busid(vol[i].bus_id,
+- &vol[i].device);
+- if (vol[i].status == ONLINE) {
+- offset = MAGIC_BLOCK_OFFSET_ECKD *
+- table.param[i].blocksize << 8;
+- rc = get_mvdump_volume_info(&vol[i], i, offset,
+- &table);
+- if (rc)
+- return rc;
+- }
+- }
+- return 0;
+-}
+-
+-/* Print dump size limit as specified in zipl -d or zipm -M */
+-void print_size_limit_info(uint64_t memory)
+-{
+- fprintf(stderr, "Dump size limit: ");
+- if (memory == (uint64_t) -1)
+- fprintf(stderr, "none\n");
+- else
+- fprintf(stderr, "%lldMB\n", (unsigned long long) memory /
+- (1024LL * 1024LL));
+-}
++/*
++ * Globals
++ */
++struct zgetdump_globals g;
+
+-/* Print multi-volume dump device information for --device option */
+-void print_mvdump_info(int version, int count, struct disk_info vol[],
+- uint64_t memory, int force)
++/*
++ * Signal handler for exiting zgetdump (the atexit handler will do the work)
++ */
++static void sig_exit(int sig)
+ {
+- int i;
++ (void) sig;
+
+- fprintf(stderr, "'%s' is part of Version %i multi-volume dump,\n"
+- "which is spread along the following DASD volumes:\n",
+- dump_device, version);
+- for (i = 0; i < count; i++) {
+- switch(vol[i].status) {
+- case UNDEFINED:
+- fprintf(stderr, "%s (not defined)\n", vol[i].bus_id);
+- break;
+- case OFFLINE:
+- fprintf(stderr, "%s (offline)\n", vol[i].bus_id);
+- break;
+- case ONLINE:
+- fprintf(stderr, "%s (online, ", vol[i].bus_id);
+- if (vol[i].signature == INVALID)
+- fprintf(stderr, "invalid)\n");
+- else
+- fprintf(stderr, "valid)\n");
+- break;
+- }
+- }
+- print_size_limit_info(memory);
+- fprintf(stderr, "Force option specified: ");
+- if (force)
+- fprintf(stderr, "yes\n");
+- else
+- fprintf(stderr, "no\n");
++ STDERR("\n"); /* E.g. to get newline after '^C' */
++ ERR_EXIT("Got signal %i, exiting...", sig);
+ }
+
+-/* Print single-volume dump device information for --device option */
+-int print_dump_info(int version, int dumper_arch, uint64_t memory)
+-{
+- int rc = 0;
+-
+- if (version > 0) {
+- if (dumper_arch == ARCH_S390) {
+- fprintf(stderr, "'%s' is Version %i s390 (ESA) "
+- "dump device.\n", dump_device, version);
+- if (check_kernel_mode())
+- rc = 1;
+- } else
+- fprintf(stderr, "'%s' is Version %i s390x (ESAME) "
+- "dump device.\n", dump_device, version);
+- } else
+- fprintf(stderr, "'%s' is Version 0 dump device. \n",
+- dump_device);
+- print_size_limit_info(memory);
+- return rc;
++/*
++ * Install signal handler
++ */
++static void sig_handler_init(void)
++{
++ struct sigaction sigact;
++
++ /* Ignore signals SIGUSR1 and SIGUSR2 */
++ if (sigemptyset(&sigact.sa_mask) < 0)
++ goto fail;
++ 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 = 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");
+ }
+
+-/* Read dump tool on FBA disk and check its magic number */
+-int check_dump_tool_fba(int fd, int *version, int *arch, uint64_t *memory)
++/*
++ * Run "--umount" action
++ */
++static int do_umount(void)
+ {
+- struct dump_tool dumper;
+-
+- if (read_dumper(fd, MAGIC_OFFSET_FBA, &dumper, SEEK_END))
+- return 1;
+- *memory = dumper.mem;
+- if (strncmp(dumper.magic, "ZDFBA31", 7) == 0) {
+- *version = dumper.version;
+- *arch = ARCH_S390;
+- } else if (strncmp(dumper.magic, "ZDFBA64", 7) == 0) {
+- *version = dumper.version;
+- *arch = ARCH_S390X;
+- } else if ((memcmp(dumper.magic, HEXINSTR, 4) == 0) &&
+- (dumper.code[0] == '\x0d') && (dumper.code[1] == '\xd0'))
+- /* We found basr r13,0 (old dumper) */
+- *version = 0;
+- else
+- *version = VERSION_NO_DUMP_DEVICE;
++ zfuse_umount();
+ return 0;
+ }
+
+-/* Read dump tool on ECKD disk and check its magic number */
+-int check_dump_tool_eckd(int fd, int *version, int *arch, int *dasd_mv_flag,
+- int *block_size, int *force_specified,
+- uint64_t *memory)
++/*
++ * Run "--device" action
++ */
++static int do_device_info(void)
+ {
+- struct dump_tool dumper;
+-
+- if (ioctl(fd, BLKSSZGET, block_size)) {
+- fprintf(stderr, "Cannot get blocksize of device %s.\n",
+- dump_device);
+- perror("");
+- return 1;
+- }
+- if (read_dumper(fd, MAGIC_BLOCK_OFFSET_ECKD * *block_size, &dumper,
+- SEEK_SET))
+- return 1;
+- *memory = dumper.mem;
+- if (strncmp(dumper.magic, "ZECKD31", 7) == 0) {
+- *version = dumper.version;
+- *arch = ARCH_S390;
+- } else if (strncmp(dumper.magic, "ZECKD64", 7) == 0) {
+- *version = dumper.version;
+- *arch = ARCH_S390X;
+- } else if (strncmp(dumper.magic, "ZMULT64", 7) == 0) {
+- *version = dumper.version;
+- *arch = ARCH_S390X;
+- *dasd_mv_flag = 1;
+- *force_specified = dumper.force;
+- } else if ((memcmp(dumper.magic, HEXINSTR, 4) == 0) &&
+- (dumper.code[0] == '\x0d') && (dumper.code[1] == '\xd0'))
+- /* We found basr r13,0 (old dumper) */
+- *version = 0;
+- else
+- *version = VERSION_NO_DUMP_DEVICE;
++ dt_init();
++ dt_info_print();
+ return 0;
+ }
+
+-void s390_tod_to_timeval(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;
+-}
+-
+-
+-int open_dump(char *pathname)
+-{
+- int fd;
+-
+- fd = open(pathname, O_RDONLY);
+- if (fd == -1) {
+- perror("Cannot open dump device");
+- exit(1);
+- } else
+- fprintf(stderr, "Dump device: %s\n", pathname);
+- return fd;
+-}
+-
+-
+-/* check if device is dasd or tape */
+-enum dump_type dev_type(int fd)
+-{
+- struct mtget mymtget;
+-
+- if (ioctl(fd, MTIOCGET, &mymtget) == -1)
+- return IS_DASD;
+- else
+- return IS_TAPE;
+-}
+-
+-/* print lkcd header information */
+-void print_lkcd_header(int fd)
+-{
+- dump_header_4_1_t dump_header;
+-
+- lseek(fd, 0, SEEK_SET);
+- if (read(fd, &dump_header, sizeof(dump_header)) == -1) {
+- perror("Could not read dump header.");
+- exit(1);
+- }
+- fprintf(stderr, "\nThis is a lkcd dump:\n\n");
+- fprintf(stderr,
+- "Memory start : 0x%"FMT64"x\n", dump_header.dh_memory_start);
+- fprintf(stderr,
+- "Memory end : 0x%"FMT64"x\n", dump_header.dh_memory_end);
+- fprintf(stderr,
+- "Physical memory: %"FMT64"d\n", dump_header.dh_memory_size);
+- fprintf(stderr,
+- "Panic string : %s\n", dump_header.dh_panic_string);
+- fprintf(stderr,
+- "Number of pages: %d\n", dump_header.dh_num_dump_pages);
+- fprintf(stderr,
+- "Page size : %d\n", dump_header.dh_dump_page_size);
+- fprintf(stderr,
+- "Magic number : 0x%"FMT64"x\n", dump_header.dh_magic_number);
+- fprintf(stderr,
+- "Version number : %d\n", dump_header.dh_version);
+-}
+-
+-void print_s390_header(enum dump_type d_type)
+-{
+- s390_tod_to_timeval(header.dh_tod, &h_time_begin);
+-
+-/* as from version 2 of the dump tools */
+-/* volume numbers are used */
+-
+- if ((d_type == IS_TAPE) && (header.dh_version >= 2)) {
+- fprintf(stderr, "\nTape Volume %i", header.dh_volnr);
+- if (header.dh_volnr != 0)
+- fprintf(stderr, " of a multi volume dump.\n");
+- else
+- fprintf(stderr, "\n");
+- }
+-
+-/* don't print header */
+-/* for all subsequent tapes/disks */
+-/* of a multi-volume tape/disk dump */
+-
+- if ((d_type == IS_DASD) || (header.dh_volnr == 0)) {
+- if (header.dh_magic_number != DUMP_MAGIC_S390) {
+- fprintf(stderr, "===================================="
+- "===============\n");
+- fprintf(stderr, "WARNING: This does not look like a "
+- "valid s390 dump!\n");
+- fprintf(stderr, "===================================="
+- "===============\n");
+- }
+- fprintf(stderr, "\n>>> Dump header information <<<\n");
+- fprintf(stderr, "Dump created on: %s\n",
+- ctime(&h_time_begin.tv_sec));
+- fprintf(stderr, "Magic number:\t 0x%"FMT64"x\n",
+- header.dh_magic_number);
+- fprintf(stderr, "Version number:\t %d\n", header.dh_version);
+- fprintf(stderr, "Header size:\t %d\n", header.dh_header_size);
+- fprintf(stderr, "Page size:\t %d\n", header.dh_page_size);
+- fprintf(stderr, "Dumped memory:\t %"FMT64"d\n",
+- header.dh_memory_size);
+- fprintf(stderr, "Dumped pages:\t %u\n", header.dh_num_pages);
+- if (header.dh_version >= 3) {
+- fprintf(stderr, "Real memory:\t %"FMT64"d\n",
+- header.dh_real_memory_size);
+- }
+- fprintf(stderr, "cpu id:\t\t 0x%"FMT64"x\n", header.dh_cpu_id);
+- if (header.dh_version >= 2) {
+- switch (header.dh_arch) {
+- case 1: fprintf(stderr, "System Arch:\t s390 (ESA)\n");
+- break;
+- case 2: fprintf(stderr,
+- "System Arch:\t s390x (ESAME)\n");
+- break;
+- default:
+- fprintf(stderr, "System Arch:\t <unknown>\n");
+- break;
+- }
+- switch (header.dh_build_arch) {
+- case 1: fprintf(stderr, "Build Arch:\t s390 (ESA)\n");
+- break;
+- case 2: fprintf(stderr,
+- "Build Arch:\t s390x (ESAME)\n");
+- break;
+- default:
+- fprintf(stderr, "Build Arch:\t <unknown>\n");
+- break;
+- }
+- }
+- fprintf(stderr, ">>> End of Dump header <<<\n\n");
+- }
+-}
+-
+-/* print header information */
+-void get_header(int fd)
+-{
+- ssize_t n_read;
+-
+- n_read = read(fd, &header, HEADER_SIZE);
+- if (n_read == -1) {
+- perror("Cannot read dump header");
+- close(fd);
+- exit(1);
+- }
+-}
+-
+-/* copy header to stdout */
+-void write_header()
++/*
++ * Run "--info" action
++ */
++static int do_dump_info(void)
+ {
+- ssize_t rc;
+-
+- memcpy(read_buffer, &header, sizeof(header));
+- rc = write(STDOUT_FILENO, read_buffer, header.dh_header_size);
+- if (rc == -1) {
+- perror("\nwrite failed");
+- exit(1);
+- }
+- if (rc < header.dh_header_size) {
+- fprintf(stderr, "\nwrite failed: No space left on device\n");
+- exit(1);
++ if (dfi_init() != 0) {
++ dfi_info_print();
++ STDERR("\nERROR: Dump is not complete\n");
++ zg_exit(1);
+ }
+-}
+-
+-/* copy partition containing multi-volume dump data to stdout */
+-int mvdump_copy(int fd, uint64_t partsize, uint64_t *totalsize)
+-{
+- ssize_t n_read, n_written;
+- uint64_t part_offset;
+- int done = 0;
+- size_t count;
+-
+- part_offset = HEADER_SIZE;
+- do {
+- count = MIN(header.dh_memory_size - *totalsize, BLOCK_SIZE);
+- if (count < BLOCK_SIZE)
+- done = 1;
+- if (partsize - part_offset < count) {
+- count = partsize - part_offset;
+- done = 1;
+- }
+- n_read = read(fd, read_buffer, count);
+- if (n_read == -1) {
+- perror("\nread failed");
+- return 1;
+- }
+- n_read = (n_read >> 12) << 12;
+- n_written = write(STDOUT_FILENO, read_buffer, n_read);
+- if (n_written == -1) {
+- perror("\nwrite failed");
+- return 1;
+- }
+- if (n_written < n_read) {
+- fprintf(stderr, "\nwrite failed: "
+- "No space left on device\n");
+- return 1;
+- }
+- part_offset += n_written;
+- *totalsize += n_written;
+- if (part_offset % (header.dh_memory_size / 32) == HEADER_SIZE)
+- fprintf(stderr, ".");
+- } while (!done);
+- fprintf(stderr, "\n");
++ dfi_info_print();
+ return 0;
+ }
+
+-/* copy the dump to stdout */
+-int get_dump(int fd, int d_type)
+-{
+- int ret, bsr;
+- ssize_t n_read, n_written;
+- struct mtop mymtop;
+- uint64_t i;
+-
+- ret = 0;
+- if (d_type == IS_DASD) {
+- i = 0;
+- do {
+- n_read = read(fd, read_buffer, BLOCK_SIZE);
+- n_written = write(STDOUT_FILENO, read_buffer, n_read);
+- if (n_written == -1) {
+- perror("\nwrite failed");
+- exit(1);
+- }
+- if (n_written < n_read) {
+- fprintf(stderr, "\nwrite failed: "
+- "No space left on device\n");
+- exit(1);
+- }
+- i += n_read;
+- if (i % (header.dh_memory_size / 32) == 0)
+- fprintf(stderr, ".");
+- } while (i < header.dh_memory_size && n_read != 0
+- && n_written >= 0);
+- } else if (d_type == IS_TAPE) {
+- /* write to stdout while not ENDOFVOL or DUMP_END */
+- if (header.dh_volnr != 0)
+- fprintf(stderr, "Reading dump content ");
+- for (i = 0; i < (header.dh_memory_size/BLOCK_SIZE); i++) {
+- n_read = read(fd, read_buffer, BLOCK_SIZE);
+- if (i % ((header.dh_memory_size/BLOCK_SIZE) / 32) == 0)
+- fprintf(stderr, ".");
+- if (strncmp(read_buffer, "ENDOFVOL", 8) == 0) {
+- fprintf(stderr, "\nEnd of Volume reached.\n");
+- ret = 1;
+- break;
+- } else if (strncmp(read_buffer, "DUMP_END", 8) == 0) {
+- ret = 2;
+- break;
+- } else {
+- n_written = write(STDOUT_FILENO, read_buffer,
+- n_read);
+- if (n_written == -1) {
+- perror("\nwrite failed");
+- exit(1);
+- }
+- if (n_written < n_read) {
+- fprintf(stderr, "\nwrite failed: "
+- "No space left on device\n");
+- exit(1);
+- }
+- }
+- }
+- if (ret == 2) {
+- /* we go back a record, so dump_end_times gets called */
+- mymtop.mt_count = 1;
+- mymtop.mt_op = MTBSR;
+- bsr = ioctl(fd, MTIOCTOP, &mymtop);
+- if (bsr != 0) {
+- fprintf(stderr,
+- "Tape operation MTBSR failed.\n");
+- exit(1);
+- }
+- }
+- }
+- return ret;
+-}
+-
+-/* check for DUMP_END and see */
+-/* if dump ended after it started (!!!) */
+-int dump_end_times(int fd)
+-{
+- int ret;
+-
+- if (read(fd, &end_marker, sizeof(end_marker)) == -1) {
+- perror("Could not read end marker.");
+- exit(1);
+- }
+- s390_tod_to_timeval(end_marker.end_time, &h_time_end);
+- if ((strncmp(end_marker.end_string, "DUMP_END", 8) == 0) &&
+- ((h_time_end.tv_sec - h_time_begin.tv_sec) >= 0)) {
+- fprintf(stderr, "\nDump ended on:\t %s\n",
+- ctime(&h_time_end.tv_sec));
+- ret = 0;
+- } else
+- ret = -1;
+- return ret;
+-}
+-
+-int check_and_write_end_marker(int fd)
+-{
+- if (dump_end_times(fd) == 0) {
+- ssize_t rc;
+- rc = write(STDOUT_FILENO, &end_marker,
+- sizeof(end_marker));
+- if (rc == -1) {
+- perror("\nwrite failed");
+- return 1;
+- }
+- if (rc < (ssize_t) sizeof(end_marker)) {
+- fprintf(stderr, "\nwrite failed: "
+- "No space left on device\n");
+- return 1;
+- }
+- fprintf(stderr, "\nDump End Marker found: "
+- "this dump is valid.\n");
+- return 0;
+- } else {
+- fprintf(stderr, "\nThis dump is NOT valid.\n");
+- return 1;
+- }
+-}
+-
+-/* if a tape is part of the dump (not the last) */
+-/* it should have and ENDOFVOL marker */
+-int vol_end(void)
+-{
+- int ret;
+-
+- ret = strncmp(end_marker.end_string, "ENDOFVOL", 8);
+- return ret;
+-}
+-
+-/* position the tape in front of an end marker */
+-/* with FSFM and BSR */
+-void tape_forwards(int fd)
+-{
+- int ret;
+- struct mtop mymtop;
+-
+- mymtop.mt_count = 1;
+- mymtop.mt_op = MTFSFM;
+- ret = ioctl(fd, MTIOCTOP, &mymtop);
+- if (ret != 0) {
+- fprintf(stderr, "Tape operation FSFM failed.\n");
+- exit(1);
+- }
+-
+- mymtop.mt_count = 1;
+- mymtop.mt_op = MTBSR;
+- ret = ioctl(fd, MTIOCTOP, &mymtop);
+- if (ret != 0) {
+- fprintf(stderr, "Tape operation BSR failed.\n");
+- exit(1);
+- }
+-}
+-
+-/* put current tape offline */
+-/* load & rewind next tape */
+-void load_next(int fd)
++/*
++ * Run "--mount" action
++ */
++static int do_mount(void)
+ {
+- int ret;
+- struct mtop mymtop;
+-
+- mymtop.mt_count = 1;
+- mymtop.mt_op = MTOFFL;
+- ret = ioctl(fd, MTIOCTOP, &mymtop);
+- if (ret != 0) {
+- fprintf(stderr, "Tape operation OFFL failed.\n");
+- exit(1);
+- }
+-
+- mymtop.mt_count = 1;
+- mymtop.mt_op = MTLOAD;
+- ret = ioctl(fd, MTIOCTOP, &mymtop);
+- if (ret != 0) {
+- fprintf(stderr, "Tape operation LOAD failed.\n");
+- exit(1);
+- } else
+- fprintf(stderr, "done\n");
+-
+- mymtop.mt_count = 1;
+- mymtop.mt_op = MTREW;
+- ret = ioctl(fd, MTIOCTOP, &mymtop);
+- if (ret != 0) {
+- fprintf(stderr, "Tape operation REW failed.\n");
+- exit(1);
+- }
++ if (dfi_init() != 0)
++ ERR_EXIT("Dump cannot be processed (is not complete)");
++ dfo_init();
++ return zfuse_mount_dump();
+ }
+
+-/* parse the commandline options */
+-void parse_opts(int argc, char *argv[])
+-{
+- int opt, index;
+- static struct option long_options[] = {
+- {"info", no_argument, 0, 'i'},
+- {"help", no_argument, 0, 'h'},
+- {"version", no_argument, 0, 'v'},
+- {"all", no_argument, 0, 'a'},
+- {"device", no_argument, 0, 'd'},
+- {0, 0, 0, 0 }
+- };
+- static const char option_string[] = "iavhd";
+-
+- while ((opt = getopt_long(argc, argv, option_string, long_options,
+- &index)) != -1) {
+- switch (opt) {
+- case 'd':
+- option_d_set = 1;
+- break;
+- case 'a':
+- option_a_set = 1;
+- break;
+- case 'i':
+- option_i_set = 1;
+- break;
+- case 'h':
+- printf(help_text);
+- exit(0);
+- case 'v':
+- printf("%s\n", version_text);
+- printf("%s\n", copyright_notice);
+- exit(0);
+- default:
+- fprintf(stderr, "Try 'zgetdump --help' for more"
+- " information.\n");
+- exit(1);
+- }
+- }
+-
+- /* check if -a and -i options are used correctly and check */
+- /* if devicename has been specified */
+-
+- if ((option_a_set && !option_i_set) || (optind != argc-1)
+- || (option_d_set && option_i_set)) {
+- printf(help_text);
+- exit(1);
+-
+-
+- }
+- strcpy(dump_device, argv[optind]);
+-}
+-
+-/* Loop along all involved volumes (dump partitions) and either check (for
+- * option --info) or pick up dump data */
+-int mvdump_check_or_copy(int vol_count, struct disk_info vol[])
++/*
++ * Run "copy to stdout" action
++ */
++static int do_stdout(void)
+ {
+- int i, fd, rc = 1;
+- uint64_t data_size, total_size = 0;
+- char* temp_devnode;
+-
+- for (i = 0; i < vol_count; i++) {
+- if (vol[i].status != ONLINE) {
+- fprintf(stderr, "============================="
+- "=======================\n");
+- fprintf(stderr, "ERROR: Dump device %s is not "
+- "available.\n", vol[i].bus_id);
+- fprintf(stderr, "============================="
+- "=======================\n");
+- return 1;
+- }
+- if (vol[i].signature != ACTIVE) {
+- fprintf(stderr, "============================="
+- "=======================\n");
+- fprintf(stderr, "ERROR: Invalid dump data on "
+- "%s.\n", vol[i].bus_id);
+- fprintf(stderr, "============================="
+- "=======================\n");
+- return 1;
+- }
+- if (make_temp_devnode(vol[i].device, &temp_devnode))
+- return 1;
+- fd = open_block_device(temp_devnode);
+- if (fd == -1) {
+- free_temp_devnode(temp_devnode);
+- return 1;
+- }
+- if (lseek(fd, vol[i].start_offset, SEEK_SET) !=
+- vol[i].start_offset) {
+- perror("Cannot seek on device");
+- goto out;
+- }
+- get_header(fd);
+- print_s390_header(IS_MULT_DASD);
+- fprintf(stderr, "\nMulti-volume dump: Disk %i (of %i)\n",
+- i + 1, vol_count);
+- if (option_i_set) {
+- data_size = ((vol[i].part_size >> 12) << 12) -
+- HEADER_SIZE;
+- if (total_size + data_size > header.dh_memory_size) {
+- if (lseek(fd, header.dh_memory_size -
+- total_size, SEEK_CUR) == -1) {
+- perror("Cannot seek on device");
+- goto out;
+- }
+- fprintf(stderr, "Checking dump contents on "
+- "%s\n", vol[i].bus_id);
+- if (dump_end_times(fd) == 0) {
+- fprintf(stderr, "Dump End Marker "
+- "found: "
+- "this dump is valid.\n\n");
+- rc = 0;
+- goto out;
+- } else {
+- fprintf(stderr, "Dump End Marker not "
+- "found: "
+- "this dump is NOT valid.\n\n");
+- goto out;
+- }
+- } else if (i == vol_count - 1) {
+- fprintf(stderr, "Dump End Marker not found: "
+- "this dump is NOT valid.\n\n");
+- goto out;
+- }
+- total_size += data_size;
+- fprintf(stderr, "Skipping dump contents on %s\n",
+- vol[i].bus_id);
+- } else {
+- if (i == 0)
+- write_header();
+- fprintf(stderr, "Reading dump contents from %s",
+- vol[i].bus_id);
+- if (mvdump_copy(fd, vol[i].part_size, &total_size))
+- goto out;
+- if ((i == vol_count - 1) ||
+- (total_size == header.dh_memory_size)) {
+- rc = check_and_write_end_marker(fd);
+- goto out;
+- }
+- }
+- free_temp_devnode(temp_devnode);
+- close(fd);
+- }
+- return 0;
+-out:
+- free_temp_devnode(temp_devnode);
+- close(fd);
+- return rc;
++ if (dfi_init() != 0)
++ ERR_EXIT("Dump cannot be processed (is not complete)");
++ dfo_init();
++ return stdout_write_dump();
+ }
+
++/*
++ * The zgetdump main function
++ */
+ int main(int argc, char *argv[])
+ {
+- uint64_t cur_time, size_limit;
+- int vol_count, fd = -1;
+- int version, dumper_arch, dasd_mv_flag = 0, block_size, rc;
+- int force_specified = 0;
+- enum dump_type d_type;
+- enum devnode_type type;
+- struct disk_info vol[MAX_DUMP_VOLUMES];
+- uint32_t cur_volnr;
+-
+- rc = 0;
+- parse_opts(argc, argv);
+-
+- if (option_d_set) {
+- fd = open_block_device(dump_device);
+- if (fd == -1) {
+- rc = 1;
+- goto out;
+- }
+- rc = check_dump_tool_fba(fd, &version, &dumper_arch,
+- &size_limit);
+- if (rc)
+- goto out;
+- if (version >= 0)
+- goto is_dump_device;
+- else
+- rc = check_dump_tool_eckd(fd, &version, &dumper_arch,
+- &dasd_mv_flag, &block_size,
+- &force_specified,
+- &size_limit);
+- if (rc)
+- goto out;
+- if (version >= 0)
+- goto is_dump_device;
+- fprintf(stderr, "'%s' is no dump device.\n", dump_device);
+- rc = 1;
+- goto out;
+-
+-is_dump_device:
+- if (dasd_mv_flag) {
+- rc = get_mvdump_info(fd, block_size, &vol_count, vol);
+- if (rc)
+- goto out;
+- print_mvdump_info(version, vol_count, vol, size_limit,
+- force_specified);
+- } else
+- rc = print_dump_info(version, dumper_arch,
+- size_limit);
+- goto out; /* do not consider any other options */
+- }
+-
+- fd = open_dump(dump_device);
+- get_header(fd);
+- d_type = dev_type(fd);
+- if ((d_type == IS_DASD) &&
+- ((header.dh_magic_number == DUMP_MAGIC_LKCD)
+- || (header.dh_magic_number == DUMP_MAGIC_LIVE))) {
+- print_lkcd_header(fd);
+- exit(0);
+- }
+- if (d_type != IS_TAPE) {
+- type = check_device(dump_device, 0);
+- if (type == IS_DEVICE) {
+- /* This is a valid block device node, no partition */
+- rc = check_dump_tool_eckd(fd, &version, &dumper_arch,
+- &dasd_mv_flag, &block_size,
+- &force_specified,
+- &size_limit);
+- if (rc)
+- goto out;
+- if (!dasd_mv_flag) {
+- fprintf(stderr, "Device '%s' specified where"
+- " partition is required.\n", dump_device);
+- rc = 1;
+- goto out;
+- } else
+- d_type = IS_MULT_DASD;
+- } else if ((type == IS_PARTITION) &&
+- (header.dh_mvdump_signature == DUMP_MAGIC_S390)) {
+- fprintf(stderr, "'%s' is a multi-volume dump "
+- "partition.\nSpecify the corresponding device "
+- "node instead.\n", dump_device);
+- rc = 1;
+- goto out;
+- }
+- }
+-
+- if (dasd_mv_flag) {
+- rc = get_mvdump_info(fd, block_size, &vol_count, vol);
+- if (rc)
+- goto out;
+- rc = mvdump_check_or_copy(vol_count, vol);
+- goto out;
+- }
+-
+- if (!option_i_set) { /* copy the dump to stdout */
+- print_s390_header(d_type);
+- write_header();
+- fprintf(stderr, "Reading dump content ");
+-
+- /* now get_dump returns 1 for all */
+- /* except the last tape of a multi-volume dump */
+-
+- while (get_dump(fd, d_type) == 1) {
+- fprintf(stderr, "\nWaiting for next volume to be "
+- "loaded... ");
+- load_next(fd);
+- get_header(fd);
+- print_s390_header(d_type);
+- }
+-
+- /* if dev is DASD and dump is copied */
+- /* check if the dump is valid */
+-
+- if (d_type == IS_DASD)
+- lseek(fd, header.dh_header_size + header.dh_memory_size,
+- SEEK_SET);
+-
+- if (!check_and_write_end_marker(fd))
+- goto out;
+- } else if (!option_a_set) { /* "-i" option */
+- fprintf(stderr, "\n> \"zgetdump -i\" checks if a dump on "
+- "either\n");
+- fprintf(stderr, "> a dasd volume or single tape is valid.\n");
+- fprintf(stderr, "> If the tape is part of a multi-volume tape "
+- "dump,\n");
+- fprintf(stderr, "> it checks if it is a valid portion of "
+- "the dump.\n");
+- print_s390_header(d_type);
+- if (d_type == IS_DASD)
+- lseek(fd,
+- header.dh_header_size + header.dh_memory_size,
+- SEEK_SET);
+- else {
+- fprintf(stderr, "Checking if the dump is valid - "
+- "this might take a while...\n");
+- tape_forwards(fd);
+- }
+- if (dump_end_times(fd) == 0) {
+- fprintf(stderr, "Dump End Marker found: ");
+- if (header.dh_volnr != 0)
+- fprintf(stderr, "this is a valid part of "
+- "a dump.\n\n");
+- else
+- fprintf(stderr, "this dump is valid.\n\n");
+- goto out;
+- } else if (d_type == IS_DASD) {
+- fprintf(stderr, "Dump End Marker not found: "
+- "this dump is NOT valid.\n\n");
+- rc = 1;
+- goto out;
+- } else
+- fprintf(stderr, "Checking for End of Volume...\n");
+- if (vol_end() != 0) {
+- fprintf(stderr, "End of Volume not found: "
+- "this dump is NOT valid.\n\n");
+- rc = 1;
+- goto out;
+- } else {
+- fprintf(stderr, "Reached End of Volume %i of a "
+- "multi-volume tape dump.\n", header.dh_volnr);
+- fprintf(stderr, "This part of the dump is valid.\n\n");
+- goto out;
+- }
+- } else { /* "-i -a" option */
+- fprintf(stderr, "\n> \"zgetdump -i -a\" checks if a "
+- "multi-volume tape dump is valid.\n");
+- fprintf(stderr, "> Please make sure that all volumes are "
+- "loaded in sequence.\n");
+- if (d_type == IS_DASD) {
+- fprintf(stderr, "\"-i -a\" is used for validation of "
+- "multi-volume tape dumps.\n\n");
+- rc = 1;
+- goto out;
+- }
+- print_s390_header(d_type);
+- cur_volnr = header.dh_volnr;
+- cur_time = header.dh_tod;
+- fprintf(stderr, "\nChecking if the dump is valid - "
+- "this might take a while...\n");
+- tape_forwards(fd);
+- if (dump_end_times(fd) == 0) {
+- fprintf(stderr, "Dump End Marker found: "
+- "this dump is valid.\n\n");
+- goto out;
+- } else if (vol_end() != 0) {
+- fprintf(stderr, "End of Volume not found: "
+- "this dump is NOT valid.\n\n");
+- rc = 1;
+- goto out;
+- }
+- while (vol_end() == 0) {
+- cur_volnr += 1;
+- fprintf(stderr, "Reached End of Volume %i.\n",
+- header.dh_volnr);
+- fprintf(stderr, "Waiting for Volume %i to be "
+- "loaded... ", cur_volnr);
+- load_next(fd);
+- get_header(fd);
+- print_s390_header(d_type);
+- if (header.dh_volnr != cur_volnr) {
+- fprintf(stderr, "This is not Volume %i\n",
+- cur_volnr);
+- rc = 1;
+- goto out;
+- } else if (header.dh_tod != cur_time) {
+- fprintf(stderr, "Time stamp of this volume "
+- "does not match the previous one.\n");
+- rc = 1;
+- goto out;
+- }
+- tape_forwards(fd);
+- if (dump_end_times(fd) == 0) {
+- fprintf(stderr, "Dump End found: "
+- "this dump is valid.\n\n");
+- goto out;
+- } else if (vol_end() != 0) {
+- fprintf(stderr, "End of Volume not found: "
+- "this dump is NOT valid.\n\n");
+- rc = 1;
+- goto out;
+- }
+- }
+- }
+-out:
+- if (fd != -1)
+- close(fd);
+- return(rc);
++ sig_handler_init();
++ opts_parse(argc, argv);
++
++ switch (g.opts.action) {
++ case ZG_ACTION_STDOUT:
++ return do_stdout();
++ case ZG_ACTION_DUMP_INFO:
++ return do_dump_info();
++ case ZG_ACTION_DEVICE_INFO:
++ return do_device_info();
++ case ZG_ACTION_MOUNT:
++ return do_mount();
++ case ZG_ACTION_UMOUNT:
++ return do_umount();
++ }
++ ABORT("Invalid action: %i", g.opts.action);
+ }
+diff --git a/zdump/zgetdump.h b/zdump/zgetdump.h
+index 46b427d..daf0ea1 100644
+--- a/zdump/zgetdump.h
++++ b/zdump/zgetdump.h
+@@ -1,194 +1,90 @@
+ /*
+- * header file for zgetdump
+- * Copyright IBM Corp. 2001, 2006.
+- * Author(s): Despina Papadopoulou
++ * zgetdump - Tool for copying and converting System z dumps
++ *
++ * Main include file - Should be included by all source files
++ *
++ * Copyright IBM Corp. 2001, 2010
++ * Author(s): Michael Holzheu <holzheu at linux.vnet.ibm.com>
++ * Frank Munzert <munzert at de.ibm.com>
++ * Despina Papadopoulou
+ */
+
+-/* This header file holds the architecture specific crash dump header */
+-#ifndef _ZGETDUMP_H
+-#define _ZGETDUMP_H
++#ifndef ZGETDUMP_H
++#define ZGETDUMP_H
+
+-#include <sys/time.h>
+-#include <stdint.h>
+-#include <sys/types.h>
+-
+-/* definitions (this has to match with vmdump.h of lcrash */
+-
+-#define DUMP_MAGIC_S390 0xa8190173618f23fdULL /* s390 magic number */
+-#define DUMP_MAGIC_LKCD 0xa8190173618f23edULL /* lkcd magic number */
+-#define DUMP_MAGIC_LIVE 0xa8190173618f23cdULL /* live magic number */
+-
+-#define S390_DUMP_HEADER_SIZE 4096
+-#define MAX_DUMP_VOLUMES 32
+-#define DUMP_ASM_MAGIC_NUMBER 0xdeaddeadULL /* magic number */
+-
+-#define MIN(x, y) ((x) < (y) ? (x) : (y))
++#include "zg.h"
++#include "dfo.h"
++#include "dfi.h"
++#include "dt.h"
++#include "list.h"
++#include "df_s390.h"
++#include "df_elf.h"
++#include "df_lkcd.h"
+
+ /*
+- * Structure: s390_dump_header_t
+- * Function: This is the header dumped at the top of every valid s390 crash
+- * dump.
++ * zgetdump options
+ */
+-
+-typedef struct _s390_dump_header_s {
+- /* the dump magic number -- unique to verify dump is valid */
+- uint64_t dh_magic_number; /* 0x000 */
+-
+- /* the version number of this dump */
+- uint32_t dh_version; /* 0x008 */
+-
+- /* the size of this header (in case we can't read it) */
+- uint32_t dh_header_size; /* 0x00c */
+-
+- /* the level of this dump (just a header?) */
+- uint32_t dh_dump_level; /* 0x010 */
+-
+- /* the size of a Linux memory page (4K, 8K, 16K, etc.) */
+- uint32_t dh_page_size; /* 0x014 */
+-
+- /* the size of all physical memory */
+- uint64_t dh_memory_size; /* 0x018 */
+-
+- /* the start of physical memory */
+- uint64_t dh_memory_start; /* 0x020 */
+-
+- /* the end of physical memory */
+- uint64_t dh_memory_end; /* 0x028 */
+-
+- /* the number of pages in this dump specifically */
+- uint32_t dh_num_pages; /* 0x030 */
+-
+- /* ensure that dh_tod and dh_cpu_id are 8 byte aligned */
+- uint32_t dh_pad; /* 0x034 */
+-
+- /* the time of the dump generation using stck */
+- uint64_t dh_tod; /* 0x038 */
+-
+- /* cpu id */
+- uint64_t dh_cpu_id; /* 0x040 */
+-
+- /* arch */
+- uint32_t dh_arch; /* 0x048 */
+-
+- /* volume number */
+- uint32_t dh_volnr; /* 0x04c */
+-
+- /* build arch */
+- uint32_t dh_build_arch; /* 0x050 */
+-
+- /* real mem size */
+- uint64_t dh_real_memory_size; /* 0x054 */
+-
+- /* multi-volume dump indicator */
+- uint8_t dh_mvdump; /* 0x05c */
+-
+- /* fill up to 512 bytes */
+- unsigned char end_pad1[0x200-0x05d]; /* 0x05d */
+-
+- /* the dump signature to verify a multi-volume dump partition */
+- uint64_t dh_mvdump_signature; /* 0x200 */
+-
+- /* the time the partition was prepared for multi-volume dump */
+- uint64_t dh_mvdump_zipl_time; /* 0x208 */
+-
+- /* fill up to 4096 byte */
+- unsigned char end_pad2[0x1000-0x210]; /* 0x210 */
+-
+-} __attribute__((packed)) s390_dump_header_t;
++struct options {
++ int action_specified;
++ enum zg_action action;
++ char *device;
++ char *mount_point;
++ int fmt_specified;
++ const char *fmt;
++ int debug_specified;
++ char **argv_fuse;
++ int argc_fuse;
++};
+
+ /*
+- * Structure: s390_dump_end_marker_t
+- * Function: This end marker should be at the end of every valid s390 crash
+- * dump.
++ * zgetdump globals
+ */
+-
+-typedef struct _s390_dump_end_marker_{
+- char end_string[8];
+- unsigned long long end_time;
+-} __attribute__((packed)) s390_dump_end_marker_t;
++extern struct zgetdump_globals {
++ struct zg_fh *fh;
++ const char *prog_name;
++ struct options opts;
++} g;
+
+ /*
+- * Structure: lkcd 4.1 dump header
++ * Misc fuctions
+ */
++extern void opts_parse(int argc, char *argv[]);
++extern int stdout_write_dump(void);
++
++#ifndef WITHOUT_FUSE
++extern int zfuse_mount_dump(void);
++extern void zfuse_umount(void);
++#else
++static inline int zfuse_mount_dump(void)
++{
++ ERR_EXIT("Program compiled without fuse support");
++}
++static inline void zfuse_umount(void)
++{
++ ERR_EXIT("Program compiled without fuse support");
++}
++#endif
+
+-typedef struct _dump_header_s {
+- uint64_t dh_magic_number;
+- uint32_t dh_version;
+- uint32_t dh_header_size;
+- uint32_t dh_dump_level;
+- uint32_t dh_dump_page_size;
+- uint64_t dh_memory_size;
+- uint64_t dh_memory_start;
+- uint64_t dh_memory_end;
+- uint32_t dh_num_dump_pages;
+- char dh_panic_string[0x100];
+- struct timeval dh_time;
+- char dh_utsname_sysname[65];
+- char dh_utsname_nodename[65];
+- char dh_utsname_release[65];
+- char dh_utsname_version[65];
+- char dh_utsname_machine[65];
+- char dh_utsname_domainname[65];
+- void *dh_current_task;
+- uint32_t dh_dump_compress;
+- uint32_t dh_dump_flags;
+- uint32_t dh_dump_device;
+-} dump_header_4_1_t;
+-
+-struct dump_tool {
+- char magic[7];
+- uint8_t version;
+- char code[0xff7 - 0x8];
+- uint8_t force;
+- uint64_t mem;
+-} __attribute__ ((packed));
+-
+-struct mvdump_param {
+- uint16_t devno;
+- uint32_t start_blk;
+- uint32_t end_blk;
+- uint8_t blocksize;
+- uint8_t end_sec;
+- uint8_t num_heads;
+-} __attribute__ ((packed));
+-
+-struct mvdump_parm_table {
+- uint64_t timestamp;
+- uint16_t num_param;
+- struct mvdump_param param[MAX_DUMP_VOLUMES];
+-} __attribute__ ((packed));
+-
+-enum dump_type {
+- IS_TAPE = 0,
+- IS_DASD = 1,
+- IS_MULT_DASD = 2,
+-};
+-
+-enum devnode_type {
+- IS_DEVICE = 0,
+- IS_PARTITION = 1,
+- IS_NOBLOCK = 2,
+-};
+-
+-enum device_status {
+- ONLINE = 0,
+- OFFLINE = 1,
+- UNDEFINED = 2,
+-};
++/*
++ * Supported DFI dump formats
++ */
++extern struct dfi dfi_s390tape;
++extern struct dfi dfi_s390mv;
++extern struct dfi dfi_s390;
++extern struct dfi dfi_lkcd;
++extern struct dfi dfi_elf;
++extern struct dfi dfi_kdump;
+
+-enum device_signature {
+- INVALID = 0,
+- VALID = 1,
+- ACTIVE = 2,
+-};
++/*
++ * Supported DFO dump formats
++ */
++extern struct dfo dfo_s390;
++extern struct dfo dfo_elf;
+
+-struct disk_info {
+- dev_t device;
+- enum device_status status;
+- enum device_signature signature;
+- off_t start_offset;
+- uint64_t part_size;
+- char bus_id[9];
+-};
++/*
++ * Supported s390 dumpers
++ */
++extern struct dt dt_s390mv;
++extern struct dt dt_s390sv;
+
+-#endif /* _ASM_VMDUMP_H */
++#endif /* ZGETDUMP_H */
+diff --git a/zipl/boot/dumpcommon.S b/zipl/boot/dumpcommon.S
+index d70473c..b37017c 100644
+--- a/zipl/boot/dumpcommon.S
++++ b/zipl/boot/dumpcommon.S
+@@ -47,23 +47,27 @@
+
+ #define __LC_ARCH_MODE_ID 163 /* here is the arch flag in the lowcore */
+ #define __LC_IPIB 0xe00 /* IPL Parameter Information Block */
++#define __LC_CPU_ADDRESS 0x0084 /* CPU address in lowcore */
+ #define DIAG308_IPL 3 /* Subcode 3 - Perform Load Clear */
+ #define DIAG308_SET 5 /* Subcode 5 - Set IPL Parameters */
+
+-#define PARAM_START 0x3000 /* 8-byte time stamp plus 2-byte count */
++#define PARAM_START 0x4000 /* 8-byte time stamp plus 2-byte count */
+ /* plus 32 13-byte entries */
+-#define IDA_LIST_START 0x3200 /* 64 8-byte IDAW's */
+-#define CCW_CHAIN_START 0x3400 /* chained write CCW's */
+-#define ZERO_MEM_START 0x4000
+-#define ZERO_MEM_SIZE 0x3000
++#define IDA_LIST_START 0x4200 /* 64 8-byte IDAW's */
++#define CCW_CHAIN_START 0x4400 /* chained write CCW's */
++#define ZERO_MEM_START 0x6000 /* Zero pages start */
++#define ZERO_MEM_SIZE 0x3000 /* Init three zero pages */
+
+ #define SCPINCR1_OFF 8
+ #define SCPA1_OFF 10
+ #define SCPA2_OFF 100
+ #define SCPINCR2_OFF 104
+
+-#define ZERO_PAGE_START 0x5000
+-#define TMP_PAGE_START 0x6000
++
++#define ZERO_PAGE_START 0x6000 /* Zero page */
++#define HEADER_PAGE_START 0x5000 /* Dump header page */
++#define PREFIX_ARR_START 0x5800 /* Prefix page array in dump header */
++#define TMP_PAGE_START 0x7000 /* Page for temp storage */
+
+ ################################################################################
+ # MACRO: dump_header
+@@ -77,7 +81,7 @@
+ #
+ .Ldh_dumpheader:
+ .Ldh_magic_number:.long S390_DUMP_MAGIC
+-.Ldh_version: .long 0x00000004
++.Ldh_version: .long 0x00000005
+ .Ldh_header_size: .long HEADER_SIZE
+ .Ldh_dump_level: .long 0x00000004 # DUMP_ALL
+ .Ldh_page_size: .long PAGE_SIZE
+@@ -97,6 +101,8 @@
+ #endif
+ .Ldh_real_mem_size: .long 0x00000000,0x00000000
+ .Ldh_mvdump: .byte 0x00
++.Ldh_cpu_cnt: .byte 0x00,0x00
++.Ldh_real_cpu_cnt:.byte 0x00,0x00
+
+ #
+ # Dump End Marker
+@@ -258,10 +264,13 @@ _ssch_64:
+ lgr %r3,%r10 # and irb address as parameters
+ bas %r14,_wait4de_64-0b(%r13) # wait until DE or error
+ tm 9(%r10),0xff # test channel status
+- bnz 5f-0b(%r13)
+- tm 8(%r10),0xd2 # test device status
++ bz 3f-0b(%r13)
++ bct %r9,1b-0b(%r13) # something went wrong, retry.
++3: tm 8(%r10),0xd2 # test device status
+ bz 4f-0b(%r13)
+ bct %r9,1b-0b(%r13) # something went wrong, retry.
++ j 5f # retries failed -> panic
++
+ 4: lmg %r6,%r15,248(%r15)
+ br %r14
+
+@@ -364,7 +373,8 @@ _take_dump_64:
+ spt .Lcpu_timer-.Lbase(%r13) # set cpu timer to future
+
+ lghi %r6,ZERO_MEM_START # clear memory
+- lghi %r7,ZERO_MEM_START + ZERO_MEM_SIZE
++ lgr %r7,%r6
++ aghi %r7,ZERO_MEM_SIZE
+ sgr %r7,%r6
+ sgr %r8,%r8
+ sgr %r9,%r9
+@@ -395,6 +405,14 @@ _take_dump_64:
+
+ bas %r14,_count_mem_64-.Lbase(%r13)
+
++ # copy dump header
++
++ stck .Ldh_time-.Lbase(%r13) # store time
++ stidp .Ldh_cpuid-.Lbase(%r13) # store CPU ID
++
++ lghi %r3,HEADER_PAGE_START
++ mvc 0(256,%r3),.Ldh_dumpheader-.Lbase(%r13)
++
+ # dump memory
+
+ llgf %r14,.Ldump_mem_64-.Lbase(%r13)
+@@ -448,7 +466,8 @@ _count_mem_64:
+ mlgr %r2,%r1 # mem size in bytes in %r3
+
+ stg %r3,.Ldh_real_mem_size-0b(%r13)
+- lg %r6,.Lmem_upper_limit-0b(%r13) # check if we have an upper limit
++ larl %r7,.Lmem_upper_limit
++ lg %r6,0(%r7) # check if we have an upper limit
+ clgr %r3,%r6
+ bl .Lsavemem-0b(%r13)
+ lgr %r3,%r6 # upper mem limit set -> use it!
+@@ -458,6 +477,11 @@ _count_mem_64:
+ srlg %r12,%r3,12 # calculate page count (/ 4096)
+ st %r12,.Ldh_num_pages-0b(%r13) # store page count
+
++ clgr %r6,%r3
++ bne .Lexit-0b(%r13)
++ larl %r2,.Lmsg_mem_limit_set # print mem limit warning
++ bras %r14,_sclp_print
++.Lexit:
+ lmg %r6,%r15,248(%r15)
+ br %r14
+ .Lonemb:
+@@ -473,29 +497,31 @@ _store_status_64:
+ stmg %r6,%r15,48(%r15)
+ basr %r13,0 # base register
+ 0: aghi %r15,-200
+- lghi %r7,0x0 # base register for 0 page
+-
+- ######## move lowcore info (assume user has made store ########
+- ######## status) to prefix-page ########
+-
+- bas %r14,_copy_lowcore_64-0b(%r13)
+
+ ######## stop all cpus and store status in prefix pages ########
+
+ lghi %r8,0 # first cpu
+ stap .Lcurrent_cpu_64+2-0b(%r13) # store current cpu address
++ llgh %r6,.Lcurrent_cpu_64+2-0b(%r13)
++
++ ######## move lowcore info (assume user has made store ########
++ ######## status) to prefix-page ########
++
++ sth %r6,__LC_CPU_ADDRESS(%r0)
++ bas %r14,_copy_lowcore_64-0b(%r13)
+
+ 1:
+ cl %r8,.Lcurrent_cpu_64-0b(%r13) # is ipl cpu ?
+ be 4f-0b(%r13) # if yes get next cpu
+ 2:
+- lgr %r9,%r7
++ lghi %r9,0
+ sigp %r9,%r8,0x9 # store status of cpu
+ bc 8,3f-0b(%r13) # accepted
+ bc 4,4f-0b(%r13) # status stored: next cpu
+ bc 2,2b-0b(%r13) # busy: try again
+ bc 1,4f-0b(%r13) # not op: next cpu
+ 3:
++ sth %r8,__LC_CPU_ADDRESS(%r0)
+ bas %r14,_copy_lowcore_64-0b(%r13)
+ 4:
+ aghi %r8,1 # next cpu (r8 +=1)
+@@ -534,8 +560,17 @@ _copy_lowcore_64:
+ ###### copy lowcore ######
+
+ llgf %r3,792(%r2) # get prefix page of current cpu
+- lghi %r5,0x1000 # first page
+- agr %r3,%r5 # get base register for second
++
++ lgh %r8,.Ldh_cpu_cnt-0b(%r13) # Save lowcore pointer (32 bit)
++ sll %r8,2 # in dump header
++ lghi %r9,PREFIX_ARR_START
++ agr %r9,%r8
++ st %r3,0(%r9)
++
++ lgh %r8,__LC_CPU_ADDRESS(%r0) # copy cpu address
++ sth %r8,__LC_CPU_ADDRESS(%r3) # to lowcore
++
++ aghi %r3,0x1000 # get base register for second
+ # page of prefix pages
+
+ # |-----------------------------------------------------------|
+@@ -562,7 +597,14 @@ _copy_lowcore_64:
+ mvc 804(20,%r3),804(%r2) # 4900
+ mvc 832(192,%r3),832(%r2) # 4928
+
++ lgh %r8,.Ldh_cpu_cnt-0b(%r13) # Increment (online) CPU count
++ aghi %r8,1
++ sth %r8,.Ldh_cpu_cnt-0b(%r13)
+ .Lcpy_locore_exit_64:
++ lgh %r10,.Ldh_real_cpu_cnt-0b(%r13) # Increment real CPU count
++ aghi %r10,1
++ sth %r10,.Ldh_real_cpu_cnt-0b(%r13)
++
+ lmg %r6,%r15,248(%r15)
+ br %r14 # return to caller
+ .Lpage_align_64:
+@@ -755,10 +797,12 @@ _ssch_32:
+ lr %r3,%r10 # and irb address as parameters
+ bas %r14,_wait4de_32-0b(%r13) # wait until DE or error
+ tm 9(%r10),0xff # test channel status
+- bnz 5f-0b(%r13)
+- tm 8(%r10),0xd2 # f3 test device status
++ bz 3f-0b(%r13)
++ bct %r9,1b-0b(%r13) # something went wrong, retry.
++3: tm 8(%r10),0xd2 # test device status
+ bz 4f-0b(%r13)
+ bct %r9,1b-0b(%r13) # something went wrong, retry.
++ j 5f # retries failed -> panic
+
+ 4: lm %r6,%r15,120(%r15)
+ br %r14
+@@ -864,7 +908,8 @@ _take_dump_32:
+ spt .Lcpu_timer-.Lbase(%r13) # set cpu timer to future
+
+ lhi %r6,ZERO_MEM_START # clear memory
+- lhi %r7,ZERO_MEM_START + ZERO_MEM_SIZE
++ lhi %r7,ZERO_MEM_START
++ ahi %r7,ZERO_MEM_SIZE
+ sr %r7,%r6
+ sr %r8,%r8
+ sr %r9,%r9
+@@ -897,6 +942,14 @@ _take_dump_32:
+
+ bas %r14,_count_mem_32-.Lbase(%r13)
+
++ # copy dump header
++
++ stck .Ldh_time-.Lbase(%r13) # store time
++ stidp .Ldh_cpuid-.Lbase(%r13) # store CPU ID
++
++ lhi %r3,HEADER_PAGE_START
++ mvc 0(256,%r3),.Ldh_dumpheader-.Lbase(%r13)
++
+ # dump memory
+
+ l %r14,.Ldump_mem_32-.Lbase(%r13)
+@@ -952,15 +1005,22 @@ _count_mem_32:
+ mr %r2,%r1 # mem size in bytes in %r3
+
+ st %r3,.Ldh_real_mem_size+4-0b(%r13)
+- cl %r6,.Lmem_upper_limit+4-0b(%r13) # check if we have an upper limit
++ larl %r7,.Lmem_upper_limit+4
++ l %r6,0(%r7) # check if we have an upper limit
++ clr %r3,%r6
+ bl .Lsavemem-0b(%r13)
+- l %r3,.Lmem_upper_limit+4-0b(%r13) # upper mem limit set -> use it!
++ lr %r3,%r6 # upper mem limit set -> use it!
+ .Lsavemem:
+ st %r3,.Ldh_mem_size+4-0b(%r13) # store memory size
+ st %r3,.Ldh_mem_end+4-0b(%r13) # store memory end
+ srl %r3,12 # calculate page count (/ 4096)
+ st %r3,.Ldh_num_pages-0b(%r13) # store page count
+
++ clr %r6,%r3
++ bne .Lexit-0b(%r13)
++ larl %r2,.Lmsg_mem_limit_set # print mem limit warning
++ bras %r14,_sclp_print
++.Lexit:
+ lm %r6,%r15,120(%r15)
+ br %r14
+ .Lonemb:
+@@ -1112,6 +1172,15 @@ _copy_lowcore_32:
+
+ ###### copy lowcore ######
+
++ lh %r8,.Ldh_cpu_cnt-0b(%r13) # Save lowcore pointer (32 bit)
++ sll %r8,2 # in dump header
++ lhi %r9,PREFIX_ARR_START
++ ar %r9,%r8
++ st %r3,0(%r9)
++
++ lh %r8,__LC_CPU_ADDRESS(%r0) # copy cpu address
++ sth %r8,__LC_CPU_ADDRESS(%r3) # to lowcore
++
+ # |-----------------------------------------------------------|
+ # | Decimal | Length | Data |
+ # | Address | in Bytes | |
+@@ -1131,7 +1200,14 @@ _copy_lowcore_32:
+ mvc 256(12,%r3),256(%r0)
+ mvc 288(224,%r3),288(%r0)
+
++ lh %r8,.Ldh_cpu_cnt-0b(%r13) # Increment (online) CPU count
++ ahi %r8,1
++ sth %r8,.Ldh_cpu_cnt-0b(%r13)
+ .Lcpy_locore_exit:
++ lh %r10,.Ldh_real_cpu_cnt-0b(%r13) # Increment real CPU count
++ ahi %r10,1
++ sth %r10,.Ldh_real_cpu_cnt-0b(%r13)
++
+ lm %r6,%r15,120(%r15)
+ br %r14 # return to caller
+ .Lpage_align:
+@@ -1303,6 +1379,14 @@ _print_exit_message:
+ .byte 0xf4, 0x40, 0x82, 0x89, 0xa3, 0x40, 0xd6, 0xe2
+ .byte 0x00
+
++# INFO: Using memory limit
++
++.Lmsg_mem_limit_set:
++ .byte 0xc9, 0xd5, 0xc6, 0xd6, 0x7a
++ .byte 0x40, 0xe4, 0xa2, 0x89, 0x95, 0x87, 0x40, 0x94
++ .byte 0x85, 0x94, 0x96, 0x99, 0xa8, 0x40, 0x93, 0x89
++ .byte 0x94, 0x89, 0xa3, 0x25, 0x00
++
+ # "00000000 / 00000000 MB"
+
+ .Lmsg_progress_mb:
+diff --git a/zipl/boot/eckd2dump.S b/zipl/boot/eckd2dump.S
+index bb5a6f5..5c437bc 100644
+--- a/zipl/boot/eckd2dump.S
++++ b/zipl/boot/eckd2dump.S
+@@ -21,7 +21,7 @@
+
+ /* General defines */
+
+-#define IPL_BS 0x1000
++#define IPL_BS 0x2000
+ #define BLOCKS_PER_WRITE 64 /* makes 256K with 4K blksize */
+
+ ################################################################################
+@@ -32,9 +32,9 @@
+ ################################################################################
+
+ #if defined(__s390x__)
+-dump_magic: .long 0x5a45434b, 0x44363401 # "ZECKD64", version 1
++dump_magic: .long 0x5a45434b, 0x44363402 # "ZECKD64", version 2
+ #else
+-dump_magic: .long 0x5a45434b, 0x44333101 # "ZECKD31", version 1
++dump_magic: .long 0x5a45434b, 0x44333102 # "ZECKD31", version 2
+ #endif
+
+ #if defined(__s390x__)
+@@ -153,20 +153,14 @@ _dump_mem_64:
+ # write header
+
+ .Lheaders: # write dump headers
+- stck .Ldh_time-0b(%r13) # store time
+- stidp .Ldh_cpuid-0b(%r13) # store cpu id
+-
+ llgf %r11,.Ldev_start_blk-0b(%r13) # start block
+
+ lgr %r2,%r11
+- lghi %r3,TMP_PAGE_START
+- mvc 0(256,%r3),.Ldh_dumpheader-0b(%r13)
+- # copy dump header to page
+- # boundary
+- llgf %r4,.Lheader_size-0b(%r13)
++ lghi %r3,HEADER_PAGE_START
++ lghi %r4,HEADER_SIZE
+ srda %r4,32 # shift ==> 64 bit number
+ llgf %r6,.Ldev_blk_size-0b(%r13) # get blocksize
+-
++
+ dr %r4,%r6 # nr of blocks for header =
+ # HEADER_SIZE / BLOCKSIZE = r5
+ lgr %r4,%r5
+@@ -208,7 +202,6 @@ _dump_mem_64:
+ lmg %r6,%r15,248(%r15)
+ br %r14 # return to caller
+ .Lbytes_per_write: .long 0x00000000
+-.Lheader_size: .long HEADER_SIZE
+ .Lblocks_per_write: .word BLOCKS_PER_WRITE
+
+ ################################################################################
+@@ -503,20 +496,14 @@ _dump_mem_32:
+ # write header
+
+ .Lheaders: # write dump headers
+- stck .Ldh_time-0b(%r13) # store time
+- stidp .Ldh_cpuid-0b(%r13) # store cpu id
+-
+ l %r11,.Ldev_start_blk-0b(%r13) # start block
+
+ lr %r2,%r11
+- lhi %r3,TMP_PAGE_START
+- mvc 0(256,%r3),.Ldh_dumpheader-0b(%r13)
+- # copy dump header to page
+- # boundary
+- l %r4,.Lheader_size-0b(%r13)
++ lhi %r3,HEADER_PAGE_START
++ lhi %r4,HEADER_SIZE
+ srda %r4,32 # shift ==> 64 bit number
+ l %r6,.Ldev_blk_size-0b(%r13) # get blocksize
+-
++
+ dr %r4,%r6 # nr of blocks for header =
+ # HEADER_SIZE / BLOCKSIZE = r5
+ lr %r4,%r5
+@@ -559,7 +546,6 @@ _dump_mem_32:
+ lm %r6,%r15,120(%r15)
+ br %r14 # return to caller
+ .Lbytes_per_write: .long 0x00000000
+-.Lheader_size: .long HEADER_SIZE
+ .Lblocks_per_write: .word BLOCKS_PER_WRITE
+
+ ################################################################################
+diff --git a/zipl/boot/eckd2mvdump.S b/zipl/boot/eckd2mvdump.S
+index 2b1be43..17b959d 100644
+--- a/zipl/boot/eckd2mvdump.S
++++ b/zipl/boot/eckd2mvdump.S
+@@ -13,7 +13,7 @@
+
+ /* General defines */
+
+-#define MVDUMP_TOOL_SIZE 0x1000 /* length of dump tool without parmtable */
++#define MVDUMP_TOOL_SIZE 0x2000 /* length of dump tool without parmtable */
+ #define BLOCKS_PER_WRITE 64 /* makes 256K with 4Ki blksize */
+ #define PTE_LENGTH 13 /* length of parameter table entry */
+ #define MAGIC_BLOCK_OFFSET 3 /* dump tool starts on track 0, block 3 */
+@@ -25,7 +25,7 @@
+ # %r4 : load address
+ ################################################################################
+
+-dump_magic: .long 0x5a4d554c, 0x54363401 # "ZMULT64", version 1
++dump_magic: .long 0x5a4d554c, 0x54363402 # "ZMULT64", version 1
+
+ /******************************** 64 BIT only**********************************/
+
+@@ -102,8 +102,6 @@ _dump_mem_64:
+ bras %r14,_init_print_progress_64
+
+ # prepare dump header info
+- stck .Ldh_time-0b(%r13) # store time
+- stidp .Ldh_cpuid-0b(%r13) # store cpu id
+ mvi .Ldh_mvdump-0b(%r13),0x01 # store mvdump indicator
+
+ .Lnextvol:
+@@ -133,7 +131,9 @@ _dump_mem_64:
+ lghi %r2,CCW_CHAIN_START # point to 1st CCW in chain
+ mvi 16(%r2),0x86 # move read opcode into CCW
+ lhi %r2,MAGIC_BLOCK_OFFSET # start block of dump tool
+- ar %r2,%r5 # start block of parameter table
++ lgr %r10,%r5 # mvdumptool size = 0x2000
++ sll %r10,1 # (header size * 2)
++ ar %r2,%r10 # start block of parameter table
+ lghi %r3,TMP_PAGE_START # destination of read operation
+ lghi %r4,1 # number of blocks to read
+ bas %r14,_ioblock_64-0b(%r13) # read parameter table
+@@ -152,7 +152,8 @@ _dump_mem_64:
+ # The dump signature is located at offset 512 relative to the partition start
+
+ .Lcheck_sign:
+- tm .Lforce-0b(%r13),0x01 # was zipl --force specified?
++ larl %r7,.Lforce
++ tm 0(%r7),0x01 # was zipl --force specified?
+ bo .Lheaders-0b(%r13) # yes, skip signature check
+ llgf %r2,.Ldev_start_blk-0b(%r13) # start block of partition
+ lghi %r3,TMP_PAGE_START # destination of read operation
+@@ -178,9 +179,7 @@ _dump_mem_64:
+ llgf %r11,.Ldev_start_blk-0b(%r13) # start block
+
+ lgr %r2,%r11
+- lghi %r3,TMP_PAGE_START
+- mvc 0(256,%r3),.Ldh_dumpheader-0b(%r13) # copy dump header to page
+- # boundary
++ lghi %r3,HEADER_PAGE_START
+ mvc 512(8,%r3),.Ldh_magic_number-0b(%r13) # preserve signature
+ lgr %r4,%r5
+ lgr %r12,%r5 # save nr of blocks
+@@ -198,6 +197,8 @@ _dump_mem_64:
+ l %r12,.Ldh_vol_nr-0b(%r13) # get current volume number
+ ahi %r12,1 # increment volume number
+ st %r12,.Ldh_vol_nr-0b(%r13) # store next volume number
++ lghi %r3,HEADER_PAGE_START
++ mvc 0(256,%r3),.Ldh_dumpheader-0b(%r13)
+ lhi %r11,PARAM_START #
+ ch %r12,8(%r11) # last dump target?
+ bl .Lmloop2-0b(%r13) # no, initialize next target
+diff --git a/zipl/boot/fba0.S b/zipl/boot/fba0.S
+index 71f508f..14e65dc 100644
+--- a/zipl/boot/fba0.S
++++ b/zipl/boot/fba0.S
+@@ -35,9 +35,25 @@ _start:
+ .long 0x43000000+.Llo6+512,0x40000008 # locate record 6
+ .long 0x42002C00,0x60000200 # bytes 3072-3584 of 2nd stage
+ .long 0x43000000+.Llo7+512,0x40000008 # locate record 7
+- .long 0x42002E00,0x20000200 # bytes 3584-4096 of 2nd stage
+-# offset 2 in .Llo[0-7]: block count (unsigned short) = 1
+-# offset 4 in .Llo[0-7]: block number (unsigned long)
++ .long 0x42002E00,0x60000200 # bytes 3584-4096 of 2nd stage
++ .long 0x43000000+.Llo8+512,0x40000008 # locate record 8
++ .long 0x42003000,0x60000200 # bytes 4096-4608 of 2nd stage
++ .long 0x43000000+.Llo9+512,0x40000008 # locate record 9
++ .long 0x42003200,0x60000200 # bytes 4608-5120 of 2nd stage
++ .long 0x43000000+.Llo10+512,0x40000008 # locate record 10
++ .long 0x42003400,0x60000200 # bytes 5120-5632 of 2nd stage
++ .long 0x43000000+.Llo11+512,0x40000008 # locate record 11
++ .long 0x42003600,0x60000200 # bytes 5632-6144 of 2nd stage
++ .long 0x43000000+.Llo12+512,0x40000008 # locate record 12
++ .long 0x42003800,0x60000200 # bytes 6144-6656 of 2nd stage
++ .long 0x43000000+.Llo13+512,0x40000008 # locate record 13
++ .long 0x42003A00,0x60000200 # bytes 6656-7168 of 2nd stage
++ .long 0x43000000+.Llo14+512,0x40000008 # locate record 14
++ .long 0x42003C00,0x60000200 # bytes 7168-7680 of 2nd stage
++ .long 0x43000000+.Llo15+512,0x40000008 # locate record 15
++ .long 0x42003E00,0x20000200 # bytes 7680-8192 of 2nd stage
++# offset 2 in .Llo[0-15]: block count (unsigned short) = 1
++# offset 4 in .Llo[0-15]: block number (unsigned long)
+ .Llo0: .long 0x06000001,0x00000000
+ .Llo1: .long 0x06000001,0x00000000
+ .Llo2: .long 0x06000001,0x00000000
+@@ -46,4 +62,12 @@ _start:
+ .Llo5: .long 0x06000001,0x00000000
+ .Llo6: .long 0x06000001,0x00000000
+ .Llo7: .long 0x06000001,0x00000000
++.Llo8: .long 0x06000001,0x00000000
++.Llo9: .long 0x06000001,0x00000000
++.Llo10: .long 0x06000001,0x00000000
++.Llo11: .long 0x06000001,0x00000000
++.Llo12: .long 0x06000001,0x00000000
++.Llo13: .long 0x06000001,0x00000000
++.Llo14: .long 0x06000001,0x00000000
++.Llo15: .long 0x06000001,0x00000000
+ .Lend:
+diff --git a/zipl/boot/fba2dump.S b/zipl/boot/fba2dump.S
+index e14d047..c0366b6 100644
+--- a/zipl/boot/fba2dump.S
++++ b/zipl/boot/fba2dump.S
+@@ -20,7 +20,7 @@
+
+ /* General defines */
+
+-#define IPL_BS 0x1000
++#define IPL_BS 0x2000
+ #define BLOCKS_PER_WRITE 64
+ #define FBA_BLK_SIZE 0x200
+ #define STAGE2_DESC 0x218
+@@ -33,9 +33,9 @@
+ ################################################################################
+
+ #if defined(__s390x__)
+-dump_magic: .long 0x5a444642, 0x41363401 # ZDFBA64, version 1
++dump_magic: .long 0x5a444642, 0x41363402 # ZDFBA64, version 2
+ #else
+-dump_magic: .long 0x5a444642, 0x41333101 # ZDFBA31, version 1
++dump_magic: .long 0x5a444642, 0x41333102 # ZDFBA31, version 2
+ #endif
+
+ #if defined(__s390x__)
+@@ -129,18 +129,14 @@ _dump_mem_64:
+
+ # write header
+
+- stck .Ldh_time-0b(%r13) # store time
+- stidp .Ldh_cpuid-0b(%r13) # store cpu id
+-
+ llgf %r11,.Ldev_start_blk-0b(%r13) # start block
+
+ lgr %r2,%r11
+- lghi %r3, TMP_PAGE_START
+- mvc 0(256,%r3),.Ldh_dumpheader-0b(%r13) # move to 4k boundary
+- llgf %r4,.Lheader_size-0b(%r13)
++ lghi %r3,HEADER_PAGE_START
++ lghi %r4,HEADER_SIZE
+ srda %r4,32 # shift ==> 64 bit number
+ llgf %r6,.Ldev_blk_size-0b(%r13) # get blocksize
+-
++
+ dr %r4,%r6 # nr of blocks for header =
+ # HEADER_SIZE / BLOCKSIZE = r5
+ lgr %r4,%r5
+@@ -181,7 +177,6 @@ _dump_mem_64:
+ lmg %r6,%r15,248(%r15)
+ br %r14 # return to caller
+ .Lbytes_per_write: .long 0x00000000
+-.Lheader_size: .long HEADER_SIZE
+ .Lblocks_per_write: .word BLOCKS_PER_WRITE
+
+ ################################################################################
+@@ -363,20 +358,14 @@ _dump_mem_32:
+ st %r11,.Lbytes_per_write-0b(%r13)
+
+ # write header
+-
+- stck .Ldh_time-0b(%r13) # store time
+- stidp .Ldh_cpuid-0b(%r13) # store cpu id
+-
+ l %r11,.Ldev_start_blk-0b(%r13) # start block
+
+ lr %r2,%r11
+- lhi %r3, TMP_PAGE_START
+- mvc 0(256,%r3),.Ldh_dumpheader-0b(%r13) # move to 4k boundary
+-
+- l %r4,.Lheader_size-0b(%r13)
++ lhi %r3,HEADER_PAGE_START
++ lhi %r4,HEADER_SIZE
+ srda %r4,32 # shift ==> 64 bit number
+ l %r6,.Ldev_blk_size-0b(%r13) # get blocksize
+-
++
+ dr %r4,%r6 # nr of blocks for header =
+ # HEADER_SIZE / BLOCKSIZE = r5
+ lr %r4,%r5
+@@ -418,7 +407,6 @@ _dump_mem_32:
+ lm %r6,%r15,120(%r15)
+ br %r14 # return to caller
+ .Lbytes_per_write: .long 0x00000000
+-.Lheader_size: .long HEADER_SIZE
+ .Lblocks_per_write: .word BLOCKS_PER_WRITE
+
+ ################################################################################
+diff --git a/zipl/boot/tapedump.S b/zipl/boot/tapedump.S
+index f7f25e6..aded0a8 100644
+--- a/zipl/boot/tapedump.S
++++ b/zipl/boot/tapedump.S
+@@ -21,20 +21,15 @@
+ #endif
+ #include "sclp.S"
+
+-#define IPL_BS 0x1000
++#define IPL_BS 0x2000
+ #define BLOCK_SIZE 0x8000 /* 32 KB */
+ #define DUMP_TOOL_START 0x2000 /* tool is loaded to this address in order */
+ /* not to overwrite page 0 */
+-#define EOV_MARKER_SIZE 8
+-#define EOV_LABEL 0x454e444f,0x46564f4c /* ENDOFVOL */
+-
+ /* Tape display messages */
+
+ #ifdef LOWER_CASE
+-#define DISP_NEXT_VOL 0x409585a7,0xa35ca596,0x93000000 /* next*vol */
+ #define DISP_DUMP_END 0x2084a494,0x975c8595,0x84000000 /* dump*end */
+ #else
+-#define DISP_NEXT_VOL 0x40d5c5e7,0xe35ce5d6,0xd3000000 /* NEXT*VOL */
+ #define DISP_DUMP_END 0x20c4e4d4,0xd75cc5d5,0xc4000000 /* DUMP*END */
+ #endif
+
+@@ -73,18 +68,16 @@
+ _start:
+ basr %r13,0
+ .Linit_base:
+- la %r9,0
+- st %r9,.Ldh_arch-.Linit_base(%r13) # init arch
++ xc .Ldh_arch-.Linit_base(4,%r13),.Ldh_arch-.Linit_base(%r13)
+ l %r15,1f-.Linit_base(%r13) # load end of stack address
+- tm __LC_ARCH_MODE_ID(%r9),0x01 # check arch mode
++ tm __LC_ARCH_MODE_ID(%r0),0x01 # check arch mode
+ bnz .Larch_64-.Linit_base(%r13)
+
+ /* 32 bit store status */
+
+- l %r14,.Lstore_status_32-.Linit_base(%r13)
++ larl %r14,.Lstore_status_32
+ basr %r14,%r14
+- la %r10,ARCH_S390_ID
+- st %r10,.Ldh_arch-.Linit_base(%r13)
++ mvi .Ldh_arch+3-.Linit_base(%r13),ARCH_S390_ID
+ .Larch_64:
+ la %r7,2 # first try code 2:
+ la %r6,0 # 64 bit psws are restored
+@@ -103,16 +96,16 @@ _start:
+
+ /* 64 bit store status */
+
+- llgf %r14,.Lstore_status_64-0b(%r13)
++ larl %r14,_store_status_64
+ basr %r14,%r14
+ lghi %r10,ARCH_S390X_ID
+ st %r10,.Ldh_arch-0b(%r13)
+-.Larch_32:
++.Larch_32:
+ llgf %r2,IPL_SC # load ipl device subchannel id
+- llgf %r14,.Lenable_device_64-0b(%r13)
++ larl %r14,_enable_device_64
+ basr %r14,%r14
+- bas %r14,_get_device_characteristics_64-0b(%r13)
+- llgf %r14,.Ltake_dump_64-0b(%r13)
++ bas %r14,_get_device_characteristics_64-0b(%r13)
++ larl %r14,_take_dump_64
+ basr %r14,%r14
+ 1: .long 0x10000-128 # end of stack
+
+@@ -138,11 +131,8 @@ _dump_mem_64:
+ #
+ # write header
+ #
+- stck .Ldh_time-0b(%r13) # store time
+- stidp .Ldh_cpuid-0b(%r13) # store cpu id
+- lghi %r2, TMP_PAGE_START
+- mvc 0(256,%r2),.Ldh_dumpheader-0b(%r13) # move to 4k boundary
+- llgf %r3,.Lheader_size-0b(%r13) # size of header
++ lghi %r2,HEADER_PAGE_START
++ lghi %r3,HEADER_SIZE # size of header
+ lgr %r4,%r3 # blocksize
+ bas %r14,_writer_64-0b(%r13)
+
+@@ -153,24 +143,21 @@ _dump_mem_64:
+ lgr %r12,%r10 # save mem size
+ lghi %r11,0 # start
+
+-1:
+ lgr %r2,%r11 # start
+ lgr %r3,%r10 # length
+ llgf %r4,.Lblock_size-0b(%r13) # blocksize
+ bas %r14,_writer_64-0b(%r13) # write page
+-
++
+ clgr %r2,%r12
+- bhe 2f-0b(%r13)
++ bhe 1f-0b(%r13)
+
+- # Next Volume
++ # Cartridge full
+
+- lgr %r11,%r2 # save next start addr
+- bas %r14,_next_vol_64-0b(%r13)
+- lgr %r10,%r12 # update length:
+- sgr %r10,%r11 # memsize-act written
+- b 1b-0b(%r13)
++ la %r2,EMEM
++ larl %r14,_panik_64
++ basr %r14,%r14
+
+-2: # All memory written
++1: # All memory written
+
+ #
+ # write end marker
+@@ -191,56 +178,12 @@ _dump_mem_64:
+
+ lmg %r6,%r15,248(%r15)
+ br %r14 # return to caller
+-.Lheader_size:
+- .long HEADER_SIZE
+ .Lblock_size:
+ .long BLOCK_SIZE
+ .Lend_text:
+ .long DISP_DUMP_END
+
+ ################################################################################
+-# _next_vol
+-# - no parameters
+-################################################################################
+-
+-_next_vol_64:
+- stmg %r6,%r15,48(%r15)
+- basr %r13,0 # base register
+-0: aghi %r15,-200 # create stack frame
+-
+- /* write end of volume marker */
+-
+- lghi %r2, TMP_PAGE_START
+- mvc 0(256,%r2),.Leov_marker-0b(%r13) # move to 4k boundary
+- lghi %r3,EOV_MARKER_SIZE
+- lghi %r4,EOV_MARKER_SIZE
+- bas %r14,_writer_64-0b(%r13)
+-
+- /* write two tape marks (End of Tape) */
+-
+- bas %r14,_tapemark_64-0b(%r13)
+- bas %r14,_tapemark_64-0b(%r13)
+-
+- /* rewind unload */
+-
+- bas %r14,_rewind_unload_64-0b(%r13)
+-
+- /* write header to next volume */
+-
+- l %r10,.Ldh_vol_nr-0b(%r13)
+- ahi %r10,1
+- st %r10,.Ldh_vol_nr-0b(%r13)
+- la %r2,.Ldh_dumpheader-0b(%r13)
+- llgf %r3,.Ldh_header_size-0b(%r13)
+- llgf %r4,.Ldh_header_size-0b(%r13)
+- bas %r14,_writer_64-0b(%r13)
+-
+- lmg %r6,%r15,248(%r15)
+- br %r14 # return to caller
+-.Leov_marker:
+- .long EOV_LABEL
+-
+-################################################################################
+ # subroutine for writing to tape
+ # Parameters:
+ # -r2: start address
+@@ -303,8 +246,7 @@ _writer_64:
+
+ /* build error code: first byte ERA, last byte our error code */
+
+- lghi %r2,0
+- ic %r2,.Ltmp_data+3-0b(%r13) # get ERA
++ llgc %r2,.Ltmp_data+3-0b(%r13) # get ERA
+ sll %r2,24 # move it to first byte
+ ahi %r2,ETAPE_WRITE
+
+@@ -318,8 +260,7 @@ _writer_64:
+
+ # unit exception: We reached End of Tape
+
+- lgr %r2,%r10 # r2 contains
+- agr %r2,%r12 # next write addr
++ la %r2,0(%r12,%r10) # r2 contains next write addr
+ b 3f-0b(%r13) # return
+
+ 1:
+@@ -356,6 +297,9 @@ _writer_64:
+ .Lorbwrite:
+ .long 0x00000000,0x0082ff00,.Lccwwrite
+ .align 8
++.Lorbsense:
++ .long 0x00000000,0x0080ff00,.Lccwsense
++ .align 8
+ .Lccwwrite_compressed: /* note that 3480 does not support IDRC */
+ .long 0xdb400001,.Lmodsetbyte
+ .Lccwwrite:
+@@ -365,7 +309,8 @@ _writer_64:
+ .Lmodsetbyte:
+ .long 0x08000000
+ .align 8
+-
++.Lccwsense:
++ .long 0x04200020,.Ltmp_data
+ ################################################################################
+ # Translate binary hex to decimal ebcdic
+ # -r2: value (bin)
+@@ -385,125 +330,6 @@ _hex_to_ebcdic_64:
+ .long 0x0,0x0
+
+ ################################################################################
+-# rewind unload tape
+-# - no parameters
+-################################################################################
+-
+-_rewind_unload_64:
+- stmg %r6,%r15,48(%r15)
+- basr %r13,0 # base register
+-0: aghi %r15,-200 # create stack frame
+-
+- /* 3480/3490/3590: rewind unload */
+-
+- llgf %r2,IPL_SC # subchannel id
+- la %r3,.Lorbrew_unload-0b(%r13)
+- la %r4,.Lirb-0b(%r13)
+- lghi %r5,1 # 1 retries
+- bas %r14,_ssch_64-0b(%r13) # do the rewind unload
+-
+- /* check for 3590 */
+-
+- lh %r9,.Ltape_type-0b(%r13)
+- chi %r9,TAPE_3590
+- bne .Lnot3590-0b(%r13)
+-
+- tm .Lirb+8-0b(%r13),0x2 # unit check?
+- bz 3f-0b(%r13) # no unit check: rewunl worked
+-
+- /* 3590: retry rewind unload */
+-
+- llgf %r2,IPL_SC
+- la %r3,.Lorbrew_unload-0b(%r13)
+- la %r4,.Lirb-0b(%r13)
+- la %r5,1
+- bas %r14,_ssch_64-0b(%r13)
+-
+- b 3f-0b(%r13)
+-
+- /* 3480/90 */
+-
+-.Lnot3590:
+-
+- /* 3480/3490 sense */
+-
+- llgf %r2,IPL_SC
+- la %r3,.Lorbsense-0b(%r13)
+- la %r4,.Lirb-0b(%r13)
+- lghi %r5,1
+- bas %r14,_ssch_64-0b(%r13)
+-
+- cli .Ltmp_data+3-0b(%r13),0x2b # check load sense byte 3
+- # 3480: ERA: rewunl completed (2b)
+- be 3f-0b(%r13)
+-
+- lghi %r2,ETAPE_REWUNL_1
+- cli .Ltmp_data+3-0b(%r13),0x51 # check load sense byte 3
+- # 3490: ERA: EOV (51)
+- bne 2f-0b(%r13)
+-
+- /* 3490: retry rewind unload */
+-
+- llgf %r2,IPL_SC
+- la %r3,.Lorbrew_unload-0b(%r13)
+- la %r4,.Lirb-0b(%r13)
+- la %r5,1
+- bas %r14,_ssch_64-0b(%r13)
+-
+- /* 3490: sense */
+-
+- l %r2,IPL_SC
+- la %r3,.Lorbsense-0b(%r13)
+- la %r4,.Lirb-0b(%r13)
+- lghi %r5,1
+- bas %r14,_ssch_64-0b(%r13)
+-
+- cli .Ltmp_data+3-0b(%r13),0x52 # check load sense byte 3
+- # 3490: ERA: EOV Complete (52)
+- be 3f-0b(%r13)
+-
+- lghi %r2,ETAPE_REWUNL_2
+-
+-2:
+- /* Something went wrong --> panik */
+-
+- l %r14,.Lpanik_64-0b(%r13)
+- basr %r14,%r14
+-
+-3:
+- /* Tell operator to insert next cartridge */
+-
+- la %r2,.Lnext_vol_text-0b(%r13)
+- bas %r14,_load_display_64-0b(%r13)
+-
+- /* wait for UException/DE/Attention (85) */
+-
+-.Lwait_loop:
+- llgf %r2,IPL_SC
+- la %r3,.Lirb-0b(%r13)
+- bas %r14,_wait4de_64-0b(%r13)
+- cli .Lirb+8-0b(%r13),0x85
+- bne .Lwait_loop-0b(%r13)
+-
+-4: lmg %r6,%r15,248(%r15)
+- br %r14
+- .align 8
+-.Lorbsense:
+- .long 0x00000000,0x0080ff00,.Lccwsense
+- .align 8
+-.Lorbrew_unload:
+- .long 0x00000000,0x0080ff00,.Lccwrew_unload
+- .align 8
+-.Lccwrew_unload:
+- .long 0x0f200000,0x00000000
+- .align 8
+-.Lnext_vol_text:
+- .long DISP_NEXT_VOL
+- .align 8
+-.Lccwsense:
+- .long 0x04200020,.Ltmp_data
+-
+-################################################################################
+ # subroutine for reading the device characteristics
+ ################################################################################
+
+@@ -682,11 +508,9 @@ _dump_mem_32:
+ #
+ # write header
+ #
+- stck .Ldh_time-0b(%r13) # store time
+- stidp .Ldh_cpuid-0b(%r13) # store cpu id
+- lhi %r2, TMP_PAGE_START
++ lhi %r2,HEADER_PAGE_START
+ mvc 0(256,%r2),.Ldh_dumpheader-0b(%r13) # move to 4k boundary
+- l %r3,.Lheader_size-0b(%r13) # size of header
++ lhi %r3,HEADER_SIZE # size of header
+ lr %r4,%r3 # blocksize
+ bas %r14,_writer_32-0b(%r13)
+
+@@ -697,25 +521,21 @@ _dump_mem_32:
+ lr %r12,%r10 # save mem size
+ la %r11,0 # start
+
+-1:
+-
+ lr %r2,%r11 # start
+ lr %r3,%r10 # length
+ l %r4,.Lblock_size-0b(%r13) # blocksize
+ bas %r14,_writer_32-0b(%r13) # write page
+
+ clr %r2,%r12
+- bhe 2f-0b(%r13)
++ bhe 1f-0b(%r13)
+
+- # Next Volume
++ # Cartridge full
+
+- lr %r11,%r2 # save next start addr
+- bas %r14,_next_vol_32-0b(%r13)
+- lr %r10,%r12 # update length:
+- sr %r10,%r11 # memsize-act written
+- b 1b-0b(%r13)
+-
+-2: # All memory written
++ la %r2,EMEM
++ larl %r14,_panik_32
++ basr %r14,%r14
++
++1: # All memory written
+ #
+ # write end marker
+ #
+@@ -735,8 +555,6 @@ _dump_mem_32:
+
+ lm %r6,%r15,120(%r15)
+ br %r14 # return to caller
+-.Lheader_size:
+- .long HEADER_SIZE
+ .Lblock_size:
+ .long BLOCK_SIZE
+ .Lend_text:
+@@ -767,47 +585,6 @@ _init_tape_32:
+ .long 0x00000000 /* buffered mode + IDRC */
+
+ ################################################################################
+-# _next_vol
+-################################################################################
+-
+-_next_vol_32:
+- stm %r6,%r15,24(%r15)
+- basr %r13,0 # base register
+-0: ahi %r15,-96 # create stack frame
+-
+- /* write end of volume marker */
+- la %r2,.Leov_marker-0b(%r13)
+- lhi %r2, TMP_PAGE_START
+- mvc 0(256,%r2),.Leov_marker-0b(%r13) # move to 4k boundary
+- la %r3,EOV_MARKER_SIZE
+- la %r4,EOV_MARKER_SIZE
+- bas %r14,_writer_32-0b(%r13)
+-
+- /* write two tape marks (End of Tape) */
+-
+- bas %r14,_tapemark_32-0b(%r13)
+- bas %r14,_tapemark_32-0b(%r13)
+-
+- /* rewind unload */
+-
+- bas %r14,_rewind_unload_32-0b(%r13)
+-
+- /* write header to next volume */
+-
+- l %r10,.Ldh_vol_nr-0b(%r13)
+- ahi %r10,1
+- st %r10,.Ldh_vol_nr-0b(%r13)
+- la %r2,.Ldh_dumpheader-0b(%r13)
+- l %r3,.Ldh_header_size-0b(%r13)
+- l %r4,.Ldh_header_size-0b(%r13)
+- bas %r14,_writer_32-0b(%r13)
+-
+- lm %r6,%r15,120(%r15)
+- br %r14 # return to caller
+-.Leov_marker:
+- .long EOV_LABEL
+-
+-################################################################################
+ # subroutine for writing to tape
+ # Parameters:
+ # -r2: start address
+@@ -925,6 +702,9 @@ _writer_32:
+ .Lorbwrite:
+ .long 0x00000000,0x0080ff00,.Lccwwrite
+ .align 8
++.Lorbsense:
++ .long 0x00000000,0x0080ff00,.Lccwsense
++ .align 8
+ .Lccwwrite_compressed: /* note that 3480 does not support IDRC */
+ .long 0xdb400001,.Lmodsetbyte
+ .Lccwwrite:
+@@ -933,6 +713,8 @@ _writer_32:
+ .long 0x20000000,0x00000000,0x00000000
+ .Lmodsetbyte:
+ .long 0x08000000
++.Lccwsense:
++ .long 0x04200020,.Ltmp_data
+
+ ################################################################################
+ # Translate binary hex to decimal ebcdic
+@@ -952,100 +734,6 @@ _hex_to_ebcdic_32:
+ .Lout_packed:
+ .long 0x0,0x0
+
+-################################################################################
+-# rewind unload tape
+-# - no parameters
+-################################################################################
+-
+-_rewind_unload_32:
+- stm %r6,%r15,24(%r15)
+- basr %r13,0 # base register
+-0: ahi %r15,-96 # create stack frame
+-
+- /* 3480/3490: rewind unload */
+-
+- l %r2,IPL_SC # subchannel id
+- la %r3,.Lorbrew_unload-0b(%r13)
+- la %r4,.Lirb-0b(%r13)
+- la %r5,1 # no retries
+- bas %r14,_ssch_32-0b(%r13) # do the rewind unload
+-
+- /* 3480/3490: sense */
+-
+- l %r2,IPL_SC
+- la %r3,.Lorbsense-0b(%r13)
+- la %r4,.Lirb-0b(%r13)
+- la %r5,1
+- bas %r14,_ssch_32-0b(%r13)
+-
+- cli .Ltmp_data+3-0b(%r13),0x2b # check load sense byte 3
+- # 3480: ERA: rewunl completed (2b)
+- be 3f-0b(%r13)
+-
+- la %r2,ETAPE_REWUNL_1
+- cli .Ltmp_data+3-0b(%r13),0x51 # check load sense byte 3
+- # 3490: ERA: EOV (51)
+- bne 2f-0b(%r13)
+-
+- /* 3490: retry rewind unload */
+-
+- l %r2,IPL_SC
+- la %r3,.Lorbrew_unload-0b(%r13)
+- la %r4,.Lirb-0b(%r13)
+- la %r5,1
+- bas %r14,_ssch_32-0b(%r13)
+-
+- /* 3490: sense */
+-
+- l %r2,IPL_SC
+- la %r3,.Lorbsense-0b(%r13)
+- la %r4,.Lirb-0b(%r13)
+- la %r5,1
+- bas %r14,_ssch_32-0b(%r13)
+-
+- cli .Ltmp_data+3-0b(%r13),0x52 # check load sense byte 3
+- # 3490: ERA: EOV Complete (52)
+- be 3f-0b(%r13)
+-
+- la %r2,ETAPE_REWUNL_2
+-
+-2:
+- /* Something went wrong --> panik */
+-
+- l %r14,.Lpanik_32-0b(%r13)
+- basr %r14,%r14
+-
+-3:
+- /* Tell operator to insert next cartridge */
+-
+- la %r2,.Lnext_vol_text-0b(%r13)
+- bas %r14,_load_display_32-0b(%r13)
+-
+- /* wait for UException/DE/Attention (85) */
+-
+- l %r2,IPL_SC
+- la %r3,.Lirb-0b(%r13)
+- bas %r14,_wait4de_32-0b(%r13)
+-
+-
+-4: lm %r6,%r15,120(%r15)
+- br %r14
+- .align 8
+-.Lorbsense:
+- .long 0x00000000,0x0080ff00,.Lccwsense
+- .align 8
+-.Lorbrew_unload:
+- .long 0x00000000,0x0080ff00,.Lccwrew_unload
+- .align 8
+-.Lccwrew_unload:
+- .long 0x0f200000,0x00000000
+- .align 8
+-.Lnext_vol_text:
+- .long DISP_NEXT_VOL
+- .align 8
+-.Lccwsense:
+- .long 0x04200020,.Ltmp_data
+-
+ ################################################################################# subroutine for reading the device characteristics
+ ################################################################################
+ _get_device_characteristics_32:
+diff --git a/zipl/include/boot.h b/zipl/include/boot.h
+index a3cac61..e27c93a 100644
+--- a/zipl/include/boot.h
++++ b/zipl/include/boot.h
+@@ -39,8 +39,8 @@ struct boot_fba_stage0 {
+ uint64_t TIC;
+ uint64_t param1;
+ uint64_t param2;
+- struct boot_fba_locread locread[8];
+- struct boot_fba_locdata locdata[8];
++ struct boot_fba_locread locread[16];
++ struct boot_fba_locdata locdata[16];
+ } __attribute__ ((packed));
+
+
+diff --git a/zipl/src/boot.c b/zipl/src/boot.c
+index 8fe0b36..8c6314c 100644
+--- a/zipl/src/boot.c
++++ b/zipl/src/boot.c
+@@ -107,7 +107,7 @@ boot_init_fba_stage0(struct boot_fba_stage0* stage0,
+ /* Initialize stage 0 data */
+ memcpy(stage0, DATA_ADDR(fba0), sizeof(struct boot_fba_stage0));
+ /* Fill in blocklist for stage 2 loader */
+- if (stage2_count > 8) {
++ if (stage2_count > 16) {
+ error_reason("Not enough room for FBA stage 2 loader "
+ "(try larger block size)");
+ return -1;
+diff --git a/zipl/src/install.c b/zipl/src/install.c
+index ec84821..3f72ff5 100644
+--- a/zipl/src/install.c
++++ b/zipl/src/install.c
+@@ -645,8 +645,12 @@ overwrite_partition_start(int fd, struct disk_info* info, int mv_dump_magic)
+ return 0;
+ }
+
+-
+-static int check_partition_bounds(struct disk_info* info)
++/*
++ * Ensure that end block is within bounds.
++ * Force block size of 4KiB because otherwise there is not enough space
++ * to write the dump tool.
++ */
++static int check_eckd_dump_partition(struct disk_info* info)
+ {
+ unsigned long long end_blk = info->geo.start + info->phy_blocks - 1;
+
+@@ -658,6 +662,11 @@ static int check_partition_bounds(struct disk_info* info)
+ info->phy_block_size) >> 20);
+ return -1;
+ }
++ if (info->phy_block_size != 4096) {
++ error_reason("unsupported DASD block size %d (should be 4096)",
++ info->phy_block_size);
++ return -1;
++ }
+ return 0;
+ }
+
+@@ -863,7 +872,7 @@ static int
+ install_dump_fba(int fd, struct disk_info* info, uint64_t mem)
+ {
+ struct boot_fba_stage0 stage0;
+- disk_blockptr_t stage2_list[8];
++ disk_blockptr_t stage2_list[16];
+ blocknum_t block;
+ blocknum_t count;
+ void* buffer;
+@@ -880,7 +889,7 @@ install_dump_fba(int fd, struct disk_info* info, uint64_t mem)
+ if (rc)
+ return rc;
+ count = (size + info->phy_block_size - 1) / info->phy_block_size;
+- if (count > 8) {
++ if (count > 16) {
+ error_reason("FBA dump record is too large");
+ free(buffer);
+ return -1;
+@@ -1017,7 +1026,7 @@ install_dump(const char* device, struct job_target_data* target, uint64_t mem)
+ switch (info->type) {
+ case disk_type_eckd_classic:
+ case disk_type_eckd_compatible:
+- if (check_partition_bounds(info)) {
++ if (check_eckd_dump_partition(info)) {
+ error_text("Dump target '%s'", device);
+ rc = -1;
+ break;
+@@ -1128,7 +1137,7 @@ install_mvdump(char* const device[], struct job_target_data* target, int count,
+ info[i]->device, device[i]);
+ if (rc)
+ goto out;
+- if (check_partition_bounds(info[i])) {
++ if (check_eckd_dump_partition(info[i])) {
+ error_text("Dump target '%s'", device[i]);
+ rc = -1;
+ goto out;
+--
+1.7.3.5
+
diff --git a/0055-znetconf-support-for-OSA-CHPID-types-OSX-and-OSM.patch b/0055-znetconf-support-for-OSA-CHPID-types-OSX-and-OSM.patch
new file mode 100644
index 0000000..68fadd0
--- /dev/null
+++ b/0055-znetconf-support-for-OSA-CHPID-types-OSX-and-OSM.patch
@@ -0,0 +1,91 @@
+From 7700e2333725199a42d929ceb7af803c609df196 Mon Sep 17 00:00:00 2001
+From: =?UTF-8?q?Dan=20Hor=C3=A1k?= <dan at danny.cz>
+Date: Fri, 28 Jan 2011 14:22:16 +0100
+Subject: [PATCH 55/61] znetconf: support for OSA CHPID types OSX and OSM
+
+Summary: znetconf: support for OSA CHPID types OSX and OSM
+Description: New feature to enable znetconf to support the
+ new OSA CHPID types OSX and OSM.
+---
+ zconf/lsznet.raw | 12 ++++++++++++
+ zconf/znetcontrolunits | 6 +++++-
+ 2 files changed, 17 insertions(+), 1 deletions(-)
+
+diff --git a/zconf/lsznet.raw b/zconf/lsznet.raw
+index 9821ba3..a9f1247 100755
+--- a/zconf/lsznet.raw
++++ b/zconf/lsznet.raw
+@@ -41,6 +41,8 @@ readonly -a CU_CARDTYPE=(
+ "LCS OSA"
+ "LCS CLAW"
+ "OSN"
++ "OSX"
++ "OSM"
+ )
+
+ readonly -a CU_DEVNAME=(
+@@ -53,6 +55,8 @@ readonly -a CU_DEVNAME=(
+ eth
+ eth
+ osn
++ eth
++ eth
+ )
+
+ readonly -a CU_GROUPCHANNELS=(
+@@ -65,6 +69,8 @@ readonly -a CU_GROUPCHANNELS=(
+ 2
+ 2
+ 3
++ 3
++ 3
+ )
+
+ readonly -a CHPIDTYPES=(
+@@ -72,6 +78,8 @@ readonly -a CHPIDTYPES=(
+ [0x11]=OSD
+ [0x15]=OSN
+ [0x24]=IQD
++ [0x30]=OSM
++ [0x31]=OSX
+ )
+
+ # whitelist of network devices for TCP/IP stack, e.g. for Linux installers
+@@ -83,6 +91,10 @@ readonly -a CU_TCPIP=(
+ 3088/1e
+ 3088/01
+ 3088/60
++ 3088/61
++ 1731/06
++ 1731/02
++ 1731/02
+ )
+
+ readonly PREFIXFORMAT=[[:xdigit:]]*
+diff --git a/zconf/znetcontrolunits b/zconf/znetcontrolunits
+index e54e34b..7ee65f7 100644
+--- a/zconf/znetcontrolunits
++++ b/zconf/znetcontrolunits
+@@ -23,6 +23,8 @@ readonly -a CU=(
+ 3088/60
+ 3088/61
+ 1731/06
++ 1731/02
++ 1731/02
+ )
+
+ readonly -a CU_DEVDRV=(
+@@ -33,7 +35,9 @@ readonly -a CU_DEVDRV=(
+ ctcm
+ lcs
+ lcs
+- lcs
++ claw
++ qeth
++ qeth
+ qeth
+ )
+
+--
+1.7.3.5
+
diff --git a/0056-iucvtty-do-not-specify-z-VM-user-ID-as-argument-to-l.patch b/0056-iucvtty-do-not-specify-z-VM-user-ID-as-argument-to-l.patch
new file mode 100644
index 0000000..108100a
--- /dev/null
+++ b/0056-iucvtty-do-not-specify-z-VM-user-ID-as-argument-to-l.patch
@@ -0,0 +1,68 @@
+From ed12fec98c2365593e3b8bac14701112051028ad Mon Sep 17 00:00:00 2001
+From: =?UTF-8?q?Dan=20Hor=C3=A1k?= <dan at danny.cz>
+Date: Fri, 28 Jan 2011 14:24:23 +0100
+Subject: [PATCH 56/61] iucvtty: do not specify z/VM user ID as argument to login -h
+
+Description: iucvtty: do not specify z/VM user ID as argument to login -h
+Symptom: When establishing a terminal connection to an iucvtty
+ instance on a target system, iucvconn disconnects due to a
+ timeout of the login program. Users are not able to log in.
+Problem: iucvtty passes the z/VM user ID of the originating guest
+ virtual machine as argument to the -h option of the login
+ program. The -h option specifies a host name that is used
+ when writing [uw]tmp records. Depending on the implementation
+ of the login program, login calls gethostbyname() to get the
+ FQDN of the hostname. If the target system does not have any
+ working network connection, the DNS query times out. The DNS
+ timeout might be greater than the timeout the login program
+ waits for user input. The login timeout is caused by a SIGALRM
+ signal that is registered before login calls gethostbyname().
+ If the DNS timeout is greater, login exits.
+Solution: Do not specify the z/VM user ID when iucvtty starts the login
+ program. The workaround to avoid timeouts is to explicitly
+ specify the login program when starting iucvtty.
+ For example, use "iucvtty TERMID -- /bin/login".
+---
+ iucvterm/src/iucvtty.c | 12 +++---------
+ 1 files changed, 3 insertions(+), 9 deletions(-)
+
+diff --git a/iucvterm/src/iucvtty.c b/iucvterm/src/iucvtty.c
+index b9a2754..ef7e212 100644
+--- a/iucvterm/src/iucvtty.c
++++ b/iucvterm/src/iucvtty.c
+@@ -48,21 +48,15 @@ static void sig_handler(int sig)
+ /**
+ * exec_login_prog() - execute a login program
+ * @cmd: Path to the (login) program executable
+- * @host: Originator host that is passed to /bin/login
+ */
+-static int exec_login_prog(char *cmd[], const char *host)
++static int exec_login_prog(char *cmd[])
+ {
+ int rc;
+
+ if (cmd != NULL)
+ rc = execv(cmd[0], cmd);
+ else
+- /* for root only: write hostname to [uw]tmp if set */
+- if (geteuid() == 0 && host != NULL)
+- rc = execl("/bin/login", "/bin/login", "-h", host,
+- (char *) NULL);
+- else
+- rc = execl("/bin/login", "/bin/login", (char *) NULL);
++ rc = execl("/bin/login", "/bin/login", (char *) NULL);
+ return rc;
+ }
+
+@@ -110,7 +104,7 @@ static int iucvtty_worker(int client, int master, int slave,
+ exit(2);
+ }
+ setenv("TERM", term_env, 1);
+- if (exec_login_prog(cfg->cmd_parms, host)) {
++ if (exec_login_prog(cfg->cmd_parms)) {
+ print_error("Running the login program failed");
+ iucvtty_tx_error(client, ERR_CANNOT_EXEC_LOGIN);
+ }
+--
+1.7.3.5
+
diff --git a/0057-tunedasd-add-new-option-Q-query_reserve.patch b/0057-tunedasd-add-new-option-Q-query_reserve.patch
new file mode 100644
index 0000000..097cad4
--- /dev/null
+++ b/0057-tunedasd-add-new-option-Q-query_reserve.patch
@@ -0,0 +1,340 @@
+From a3fd27a0ce5920b66afbf89cb83a9b61db9460c7 Mon Sep 17 00:00:00 2001
+From: =?UTF-8?q?Dan=20Hor=C3=A1k?= <dan at danny.cz>
+Date: Fri, 28 Jan 2011 14:26:53 +0100
+Subject: [PATCH 57/61] tunedasd: add new option -Q / --query_reserve
+
+Summary: tunedasd: add new option -Q / --query_reserve
+Description: The new option -Q / --query_reserve uses the BIODASDSNID ioctl to
+ determine the device reservation status for the given device and
+ prints it to stdout.
+---
+ tunedasd/include/disk.h | 1 +
+ tunedasd/man/tunedasd.8 | 22 +++++++++-
+ tunedasd/src/disk.c | 65 +++++++++++++++++++++++++++++
+ tunedasd/src/tunedasd.c | 104 +++++++++++++++++++++++++++--------------------
+ 4 files changed, 147 insertions(+), 45 deletions(-)
+
+diff --git a/tunedasd/include/disk.h b/tunedasd/include/disk.h
+index 4b98fb0..ece8cb5 100644
+--- a/tunedasd/include/disk.h
++++ b/tunedasd/include/disk.h
+@@ -25,6 +25,7 @@ int disk_set_cache (char* device, char* cache, char* no_cyl);
+ int disk_reserve (char* device);
+ int disk_release (char* device);
+ int disk_slock (char* device);
++int disk_query_reserve_status(char* device);
+ int disk_profile (char* device, char* prof_item);
+ int disk_reset_prof (char* device);
+
+diff --git a/tunedasd/man/tunedasd.8 b/tunedasd/man/tunedasd.8
+index 2d99bdc..a96913e 100644
+--- a/tunedasd/man/tunedasd.8
++++ b/tunedasd/man/tunedasd.8
+@@ -66,7 +66,6 @@ Enterprise Storage Servers (ESS):
+ (Record Access)
+ .IP "" 7
+ More details about caching can be found in the 'Storage Control Reference' of the attached storage server.
+-
+ .TP
+ .BR "\-n" " or " "\-\-no_cyl" " <n> "
+ Number of cylinders to be cached (only valid together with --cache).
+@@ -91,6 +90,27 @@ Profile info must be available and enabled
+ ('echo set on > /proc/dasd/statistics')
+ in the kernel to get valid results out of the profile commands.
+ .TP
++.BR "\-Q" " or " "\-\-query_reserve"
++Query the current reserve status of the device.
++The following states are defined:
++.br
++.IP " \(bu" 12
++.I none:
++The device is not reserved.
++.IP " \(bu" 12
++.I implicit:
++The device is not reserved, but there is a contingent or implicit
++allegiance to this Linux instance.
++.IP " \(bu" 12
++.I other:
++The device is reserved to another operating system instance.
++.IP " \(bu" 12
++.I reserved:
++The device is reserved to this Linux instance.
++.IP "" 7
++More details about reserve/release can be found in the 'Storage Control Reference' of the attached storage server.
++
++.TP
+ .BR "\-I" " or " "\-\-prof_item <row> "
+ Print single profile item data row (without header).
+ .br
+diff --git a/tunedasd/src/disk.c b/tunedasd/src/disk.c
+index afbe851..3eaa4f3 100644
+--- a/tunedasd/src/disk.c
++++ b/tunedasd/src/disk.c
+@@ -68,6 +68,25 @@ typedef struct attrib_data_t {
+ #define DASD_REC_ACCESS 0x5
+
+ /*
++ * Data returned by Sense Path Group ID (SNID)
++ */
++struct dasd_snid_data {
++ struct {
++ __u8 group:2;
++ __u8 reserve:2;
++ __u8 mode:1;
++ __u8 res:3;
++ } __attribute__ ((packed)) path_state;
++ __u8 pgid[11];
++} __attribute__ ((packed));
++
++struct dasd_snid_ioctl_data {
++ struct dasd_snid_data data;
++ __u8 path_mask;
++} __attribute__ ((packed));
++
++
++/*
+ * DASD-IOCTLs (copied from dasd.h)
+ */
+ /* Issue a reserve/release command, rsp. */
+@@ -85,6 +104,9 @@ typedef struct attrib_data_t {
+ /* Set Attributes (cache operations) */
+ #define BIODASDSATTR _IOW (DASD_IOCTL_LETTER,2,attrib_data_t)
+
++/* Get Sense Path Group ID (SNID) data */
++#define BIODASDSNID _IOWR(DASD_IOCTL_LETTER, 1, struct dasd_snid_ioctl_data)
++
+
+ /* id definition for profile items */
+ enum prof_id {
+@@ -378,6 +400,49 @@ disk_slock (char* device)
+ return 0;
+ }
+
++
++/*
++ * Uses the Sense Path Group ID (SNID) ioctl to find out if
++ * a device is reserved to it's path group.
++ */
++int
++disk_query_reserve_status(char* device)
++{
++ int fd;
++ struct dasd_snid_ioctl_data snid;
++
++ /* Open device file */
++ fd = open (device, O_RDONLY);
++ if (fd == -1) {
++ error_print ("<%s> - %s", device, strerror (errno));
++ return -1;
++ }
++ snid.path_mask = 0;
++ /* Release device */
++ if (ioctl(fd, BIODASDSNID, &snid)) {
++ error_print("Could not read reserve status"
++ " for device <%s>", device);
++ close (fd);
++ return -1;
++ }
++ switch (snid.data.path_state.reserve) {
++ case 0:
++ printf("none\n");
++ break;
++ case 1:
++ printf("implicit\n");
++ break;
++ case 2:
++ printf("other\n");
++ break;
++ case 3:
++ printf("reserved\n");
++ break;
++ }
++ close (fd);
++ return 0;
++}
++
+ int
+ disk_profile_summary (dasd_profile_info_t dasd_profile_info)
+ {
+diff --git a/tunedasd/src/tunedasd.c b/tunedasd/src/tunedasd.c
+index 05e0344..fbb7a1e 100644
+--- a/tunedasd/src/tunedasd.c
++++ b/tunedasd/src/tunedasd.c
+@@ -47,6 +47,7 @@ static const char* usage_text[] = {
+ "-O, --slock Unconditional reserve device",
+ " Note: Use with care, this breaks an existing "
+ "lock",
++ "-Q, --query_reserve Print reserve status of device ",
+ "-P, --profile Print profile info of device",
+ "-I, --prof_item Print single profile item",
+ " (reqs/sects/sizes/total/totsect/start/irq/",
+@@ -54,21 +55,22 @@ static const char* usage_text[] = {
+ "-R, --reset_prof Reset profile info of device"
+ };
+
+-#define CMD_KEYWORD_NUM 11
++#define CMD_KEYWORD_NUM 12
+ #define DEVICES_NUM 256
+
+ enum cmd_keyword_id {
+- cmd_keyword_help = 0,
+- cmd_keyword_version = 1,
+- cmd_keyword_get_cache = 2,
+- cmd_keyword_cache = 3,
+- cmd_keyword_no_cyl = 4,
+- cmd_keyword_reserve = 5,
+- cmd_keyword_release = 6,
+- cmd_keyword_slock = 7,
+- cmd_keyword_profile = 8,
+- cmd_keyword_prof_item = 9,
+- cmd_keyword_reset_prof = 10,
++ cmd_keyword_help = 0,
++ cmd_keyword_version = 1,
++ cmd_keyword_get_cache = 2,
++ cmd_keyword_cache = 3,
++ cmd_keyword_no_cyl = 4,
++ cmd_keyword_reserve = 5,
++ cmd_keyword_release = 6,
++ cmd_keyword_slock = 7,
++ cmd_keyword_profile = 8,
++ cmd_keyword_prof_item = 9,
++ cmd_keyword_reset_prof = 10,
++ cmd_keyword_query_reserve = 11,
+ };
+
+
+@@ -77,17 +79,18 @@ static const struct {
+ char* keyword;
+ enum cmd_keyword_id id;
+ } keyword_list[] = {
+- { "help", cmd_keyword_help },
+- { "version", cmd_keyword_version },
+- { "get_cache", cmd_keyword_get_cache },
+- { "cache", cmd_keyword_cache },
+- { "no_cyl", cmd_keyword_no_cyl },
+- { "reserve", cmd_keyword_reserve },
+- { "release", cmd_keyword_release },
+- { "slock", cmd_keyword_slock },
+- { "profile", cmd_keyword_profile },
+- { "prof_item", cmd_keyword_prof_item },
+- { "reset_prof", cmd_keyword_reset_prof }
++ { "help", cmd_keyword_help },
++ { "version", cmd_keyword_version },
++ { "get_cache", cmd_keyword_get_cache },
++ { "cache", cmd_keyword_cache },
++ { "no_cyl", cmd_keyword_no_cyl },
++ { "reserve", cmd_keyword_reserve },
++ { "release", cmd_keyword_release },
++ { "slock", cmd_keyword_slock },
++ { "profile", cmd_keyword_profile },
++ { "prof_item", cmd_keyword_prof_item },
++ { "reset_prof", cmd_keyword_reset_prof },
++ { "query_reserve", cmd_keyword_query_reserve }
+ };
+
+
+@@ -100,21 +103,22 @@ enum cmd_key_state {
+
+ /* Determines which combination of keywords are valid */
+ enum cmd_key_state cmd_key_table[CMD_KEYWORD_NUM][CMD_KEYWORD_NUM] = {
+- /* help vers get_ cach no_c rese rele sloc prof prof rese
+- * ion cach e yl rve ase k ile _ite t_pr
+- * e m of
+- */
+- /* help */ { req, opt, opt, opt, opt, opt, opt, opt, opt, opt, opt },
+- /* version */ { inv, req, inv, inv, inv, inv, inv, inv, inv, inv, inv },
+- /* get_cache */ { opt, opt, req, inv, inv, inv, inv, inv, inv, inv, inv },
+- /* cache */ { opt, opt, inv, req, opt, inv, inv, inv, inv, inv, inv },
+- /* no_cyl */ { opt, opt, inv, req, req, inv, inv, inv, inv, inv, inv },
+- /* reserve */ { opt, opt, inv, inv, inv, req, inv, inv, inv, inv, inv },
+- /* release */ { opt, opt, inv, inv, inv, inv, req, inv, inv, inv, inv },
+- /* slock */ { opt, opt, inv, inv, inv, inv, inv, req, inv, inv, inv },
+- /* profile */ { opt, opt, inv, inv, inv, inv, inv, inv, req, opt, inv },
+- /* prof_item */ { opt, opt, inv, inv, inv, inv, inv, inv, req, req, inv },
+- /* reset_prof */ { opt, opt, inv, inv, inv, inv, inv, inv, inv, inv, req },
++ /* help vers get_ cach no_c rese rele sloc prof prof rese quer
++ * ion cach e yl rve ase k ile _ite t_pr y_re
++ * e m of serv
++ */
++ /* help */ { req, opt, opt, opt, opt, opt, opt, opt, opt, opt, opt, inv },
++ /* version */ { inv, req, inv, inv, inv, inv, inv, inv, inv, inv, inv, inv },
++ /* get_cache */ { opt, opt, req, inv, inv, inv, inv, inv, inv, inv, inv, inv },
++ /* cache */ { opt, opt, inv, req, opt, inv, inv, inv, inv, inv, inv, inv },
++ /* no_cyl */ { opt, opt, inv, req, req, inv, inv, inv, inv, inv, inv, inv },
++ /* reserve */ { opt, opt, inv, inv, inv, req, inv, inv, inv, inv, inv, inv },
++ /* release */ { opt, opt, inv, inv, inv, inv, req, inv, inv, inv, inv, inv },
++ /* slock */ { opt, opt, inv, inv, inv, inv, inv, req, inv, inv, inv, inv },
++ /* profile */ { opt, opt, inv, inv, inv, inv, inv, inv, req, opt, inv, inv },
++ /* prof_item */ { opt, opt, inv, inv, inv, inv, inv, inv, req, req, inv, inv },
++ /* reset_prof */ { opt, opt, inv, inv, inv, inv, inv, inv, inv, inv, req, inv },
++ /* query_reserve */ { inv, inv, inv, inv, inv, inv, inv, inv, inv, inv, inv, req },
+ };
+
+ struct parameter {
+@@ -141,11 +145,12 @@ static struct option options[] = {
+ { "profile", no_argument, NULL, 'P'},
+ { "prof_item", required_argument, NULL, 'I'},
+ { "reset_prof", no_argument, NULL, 'R'},
++ { "query_reserve", no_argument, NULL, 'Q'},
+ { NULL, 0, NULL, 0 }
+ };
+
+ /* Command line option abbreviations */
+-static const char option_string[] = "hvgc:n:SLOPI:R";
++static const char option_string[] = "hvgc:n:SLOPI:RQ";
+
+
+ /* Error message string */
+@@ -372,6 +377,11 @@ get_command_line (int argc, char* argv[], struct command_line* line)
+ rc = store_option (&cmdline, cmd_keyword_reset_prof,
+ optarg);
+ break;
++ case 'Q':
++ rc = store_option (&cmdline, cmd_keyword_query_reserve,
++ optarg);
++ break;
++
+ case -1:
+ /* End of options string - start of devices list */
+ cmdline.device_id = optind;
+@@ -431,8 +441,11 @@ do_command (char* device, struct command_line cmdline)
+ rc = disk_reset_prof (device);
+ break;
+ case cmd_keyword_prof_item:
+- break;
+- default:
++ break;
++ case cmd_keyword_query_reserve:
++ rc = disk_query_reserve_status(device);
++ break;
++ default:
+ error_print ("Unknown command '%s' specified",
+ get_keyword_name (i));
+ break;
+@@ -449,7 +462,7 @@ int
+ main (int argc, char* argv[])
+ {
+ struct command_line cmdline;
+- int rc;
++ int rc, finalrc;
+
+ /* Find out what we're supposed to do */
+ rc = get_command_line (argc, argv, &cmdline);
+@@ -484,9 +497,12 @@ main (int argc, char* argv[])
+ return 1;
+ }
+
+- while (cmdline.device_id < argc) {
++ finalrc = 0;
++ while (cmdline.device_id < argc) {
+ rc = do_command (argv[cmdline.device_id], cmdline);
++ if (rc && !finalrc)
++ finalrc = rc;
+ cmdline.device_id++;
+ }
+- return 0;
++ return finalrc;
+ }
+--
+1.7.3.5
+
diff --git a/0058-fdasd-dasdfmt-fix-format-7-label.patch b/0058-fdasd-dasdfmt-fix-format-7-label.patch
new file mode 100644
index 0000000..5b234ec
--- /dev/null
+++ b/0058-fdasd-dasdfmt-fix-format-7-label.patch
@@ -0,0 +1,96 @@
+From f877ca62c13e475d55f6fe3fac5c9732ed44b49e Mon Sep 17 00:00:00 2001
+From: =?UTF-8?q?Dan=20Hor=C3=A1k?= <dan at danny.cz>
+Date: Fri, 28 Jan 2011 14:27:39 +0100
+Subject: [PATCH 58/61] fdasd/dasdfmt: fix format 7 label
+
+Description: fdasd/dasdfmt: fix format 7 label
+Symptom: Backups of Linux on System z disks from z/OS do not work
+ when the disk is not fully partitioned.
+Problem: The format 7 label written by fdasd and dasdfmt is incorrect.
+ The extend for free space has one track less than required
+ which is recognized as inconsistent vtoc state by z/OS tools.
+Solution: Fix libvtoc to write the format 7 label correctly.
+---
+ libvtoc/vtoc.c | 22 +++++++++++++---------
+ 1 files changed, 13 insertions(+), 9 deletions(-)
+
+diff --git a/libvtoc/vtoc.c b/libvtoc/vtoc.c
+index cebd5a4..36269a4 100644
+--- a/libvtoc/vtoc.c
++++ b/libvtoc/vtoc.c
+@@ -1204,7 +1204,7 @@ void vtoc_update_format7_label_add (format7_label_t *f7, int verbose,
+ if ((ext->a + ext->b) == 0x00000000)
+ continue;
+
+- if ((ext->b + 1) == tmp->a) {
++ if ((ext->b) == tmp->a) {
+ /* this extent precedes the new one */
+ ext->b = tmp->b;
+ bzero(tmp, sizeof(ds7ext_t));
+@@ -1216,7 +1216,7 @@ void vtoc_update_format7_label_add (format7_label_t *f7, int verbose,
+ continue;
+ }
+
+- if (ext->a == (tmp->b + 1))
++ if (ext->a == (tmp->b))
+ {
+ /* this extent succeeds the new one */
+ ext->a = tmp->a;
+@@ -1240,7 +1240,7 @@ void vtoc_update_format7_label_del (format7_label_t *f7, int verbose,
+ {
+ ds7ext_t *ext;
+ int i, counter=0;
+-
++
+ for (i=0; i<16; i++) {
+ if (i<5)
+ ext = &f7->DS7EXTNT[i];
+@@ -1258,7 +1258,7 @@ void vtoc_update_format7_label_del (format7_label_t *f7, int verbose,
+
+ if ((a == ext->a) && (b < ext->b)) {
+ /* left-bounded in free space gap */
+- ext->a = b + 1;
++ ext->a = b;
+ if (verbose)
+ printf("FMT7 add extent: left-bounded\n");
+ counter++;
+@@ -1267,7 +1267,7 @@ void vtoc_update_format7_label_del (format7_label_t *f7, int verbose,
+
+ if ((a > ext->a) && (b == ext->b)) {
+ /* right-bounded in free space gap */
+- ext->b = a - 1;
++ ext->b = a;
+ if (verbose)
+ printf("FMT7 add extent: right-bounded\n");
+ counter++;
+@@ -1277,8 +1277,8 @@ void vtoc_update_format7_label_del (format7_label_t *f7, int verbose,
+ if ((a > ext->a) && (b < ext->b)) {
+ /* partition devides free space into 2 pieces */
+ vtoc_update_format7_label_add(f7, verbose,
+- b+1, ext->b);
+- ext->b = a - 1;
++ b, ext->b);
++ ext->b = a;
+ if (verbose)
+ printf("FMT7 add extent: 2 pieces\n");
+ counter++;
+@@ -1311,10 +1311,14 @@ void vtoc_set_freespace(format4_label_t *f4, format5_label_t *f5,
+ {
+ if ((cyl * trk) > BIG_DISK_SIZE) {
+ if (ch == '+') {
+- vtoc_update_format7_label_add(f7, verbose, start,stop);
++ vtoc_update_format7_label_add(f7, verbose, start,
++ /* ds7ext RTA + 1 */
++ stop + 1);
+ }
+ else if (ch == '-') {
+- vtoc_update_format7_label_del(f7, verbose, start,stop);
++ vtoc_update_format7_label_del(f7, verbose, start,
++ /* ds7ext RTA + 1 */
++ stop + 1);
+ }
+ else {
+ printf("BUG: syntax error in vtoc_set_freespace.\n");
+--
+1.7.3.5
+
diff --git a/0059-cpuplugd-cmm_pages-not-set-and-restored-correctly.patch b/0059-cpuplugd-cmm_pages-not-set-and-restored-correctly.patch
new file mode 100644
index 0000000..0ea4cac
--- /dev/null
+++ b/0059-cpuplugd-cmm_pages-not-set-and-restored-correctly.patch
@@ -0,0 +1,69 @@
+From f127d0df43f5fe5709f068e0c79bef0b759cb6fe Mon Sep 17 00:00:00 2001
+From: =?UTF-8?q?Dan=20Hor=C3=A1k?= <dan at danny.cz>
+Date: Fri, 28 Jan 2011 14:28:35 +0100
+Subject: [PATCH 59/61] cpuplugd: cmm_pages not set and restored correctly
+
+Description: cpuplugd: cmm_pages not set and restored correctly
+Symptom: /proc/sys/vm/cmm_pages will be restored to 0 after program exit,
+ regardless of previous value, if cpuplugd is run with an invalid
+ memory configuration, e.g. cmm_min commented out. Also, cmm_pages
+ will not correctly reach a cmm_min of 0 during run-time, in a case
+ where cmm_pages is equal to cmm_inc.
+Problem: Incorrect checks on program exit, and in the memplug function.
+Solution: Fix checks on program exit and memplug.
+---
+ cpuplugd/config.c | 1 +
+ cpuplugd/daemon.c | 4 ++--
+ cpuplugd/mem.c | 2 +-
+ 3 files changed, 4 insertions(+), 3 deletions(-)
+
+diff --git a/cpuplugd/config.c b/cpuplugd/config.c
+index e853ea7..bda7780 100644
+--- a/cpuplugd/config.c
++++ b/cpuplugd/config.c
+@@ -358,6 +358,7 @@ void check_config(struct config *cfg)
+ if (foreground == 0)
+ syslog(LOG_INFO, "No valid memory hotplug "
+ "configuration detected\n");
++ memory = 0;
+ } else {
+ memory = 1;
+ /*
+diff --git a/cpuplugd/daemon.c b/cpuplugd/daemon.c
+index 391acba..4dab372 100644
+--- a/cpuplugd/daemon.c
++++ b/cpuplugd/daemon.c
+@@ -125,7 +125,7 @@ void clean_up()
+ syslog(LOG_INFO, "terminated\n");
+ remove(pid_file);
+ reactivate_cpus();
+- if (check_cmmfiles() == 0)
++ if (memory)
+ cleanup_cmm();
+ exit(1);
+ }
+@@ -139,7 +139,7 @@ void kill_daemon(int a)
+ syslog(LOG_INFO, "shutting down\n");
+ remove(pid_file);
+ reactivate_cpus();
+- if (check_cmmfiles() == 0)
++ if (memory)
+ cleanup_cmm();
+ exit(0);
+ }
+diff --git a/cpuplugd/mem.c b/cpuplugd/mem.c
+index 13f902d..b15c7e2 100644
+--- a/cpuplugd/mem.c
++++ b/cpuplugd/mem.c
+@@ -163,7 +163,7 @@ int memplug(int size)
+ * new value: previous value - size
+ */
+ new_size = old_size - size;
+- if (new_size <= 0)
++ if (new_size < 0)
+ return -1;
+ filp = fopen("/proc/sys/vm/cmm_pages", "w");
+ if (!filp) {
+--
+1.7.3.5
+
diff --git a/0060-lsluns-Fix-LUN-reporting-for-SAN-volume-controller-S.patch b/0060-lsluns-Fix-LUN-reporting-for-SAN-volume-controller-S.patch
new file mode 100644
index 0000000..e5803da
--- /dev/null
+++ b/0060-lsluns-Fix-LUN-reporting-for-SAN-volume-controller-S.patch
@@ -0,0 +1,69 @@
+From bdc3b89850bb437f48cb2f9fa31a8f51d3cd88b5 Mon Sep 17 00:00:00 2001
+From: =?UTF-8?q?Dan=20Hor=C3=A1k?= <dan at danny.cz>
+Date: Fri, 28 Jan 2011 14:30:22 +0100
+Subject: [PATCH 60/61] lsluns: Fix LUN reporting for SAN volume controller (SVC)
+
+Description: lsluns: Fix LUN reporting for SAN volume controller (SVC)
+Symptom: lsluns fails to report LUNs from SVC
+Problem: The SCSI Architecture Model (SAM) specifies that a storage
+ server only has to support the REPORT LUNs command for LUN 0
+ or the "REPORT LUNS Well-Known-LUN" (WLUN). The SAN Volume
+ Controller (SVC) only supports REPORT LUNS on LUN 0. The
+ approach of lsluns of sending the REPORT LUNS to any attached
+ LUN does not work for the SVC.
+Solution: Change the strategy of lsluns to check if the LUN 0 or the
+ WLUN is already available. If both LUNs are not available,
+ first try to attach LUN 0 and issue the command; if this
+ fails, try the WLUN.
+---
+ zconf/lsluns | 14 +++++++++-----
+ 1 files changed, 9 insertions(+), 5 deletions(-)
+
+diff --git a/zconf/lsluns b/zconf/lsluns
+index 769b846..acbdcd7 100755
+--- a/zconf/lsluns
++++ b/zconf/lsluns
+@@ -45,16 +45,16 @@ sub list_luns
+ next;
+ }
+ if (!defined($lun_hash{$a}{$p})) {
+- `echo $wlun >> $drv_dir/$a/$p/unit_add 2>/dev/null`;
++ `echo $lun0 >> $drv_dir/$a/$p/unit_add 2>/dev/null`;
+ select(undef, undef, undef, 0.1);
+ %lun_hash = get_lun_hash();
+ if (!defined($lun_hash{$a}{$p})) {
+- `echo $wlun >> $drv_dir/$a/$p/unit_remove 2>/dev/null`;
+- `echo $lun0 >> $drv_dir/$a/$p/unit_add 2>/dev/null`;
++ `echo $lun0 >> $drv_dir/$a/$p/unit_remove 2>/dev/null`;
++ `echo $wlun >> $drv_dir/$a/$p/unit_add 2>/dev/null`;
+ select(undef, undef, undef, 0.1);
+ %lun_hash = get_lun_hash();
+ if (!defined($lun_hash{$a}{$p})) {
+- `echo $lun0 >> $drv_dir/$a/$p/unit_remove 2>/dev/null`;
++ `echo $wlun >> $drv_dir/$a/$p/unit_remove 2>/dev/null`;
+ print"\tat port $p:\n";
+ print "\t\tCannot attach WLUN / LUN0 for scanning.\n";
+ next;
+@@ -92,6 +92,8 @@ sub list_luns
+ }
+ }
+
++# Look only for LUN0 and the REPORT LUNs WLUN. SAM specifies that the storage
++# only has to response on one of those to the REPORT LUNs command.
+ sub get_lun_hash
+ {
+ my %lun_hash;
+@@ -105,7 +107,9 @@ sub get_lun_hash
+ $p =~ s/(0x\w{16})*\n/$1/;
+ chomp($a);
+
+- $lun_hash{$a}{$p}{$l} = ${[split('/', $device)]}[-1];
++ if ($l eq $lun0 or $l eq $wlun) {
++ $lun_hash{$a}{$p}{$l} = ${[split('/', $device)]}[-1];
++ }
+ }
+ return %lun_hash;
+ }
+--
+1.7.3.5
+
diff --git a/0061-lsluns-Accept-uppercase-and-lowercase-hex-digits.patch b/0061-lsluns-Accept-uppercase-and-lowercase-hex-digits.patch
new file mode 100644
index 0000000..4a82c8b
--- /dev/null
+++ b/0061-lsluns-Accept-uppercase-and-lowercase-hex-digits.patch
@@ -0,0 +1,39 @@
+From 7fd37ae55c104cf62f8d62da79d89a4c59087a6d Mon Sep 17 00:00:00 2001
+From: =?UTF-8?q?Dan=20Hor=C3=A1k?= <dan at danny.cz>
+Date: Fri, 28 Jan 2011 14:31:06 +0100
+Subject: [PATCH 61/61] lsluns: Accept uppercase and lowercase hex digits
+
+Description: lsluns: Accept uppercase and lowercase hex digits
+Symptom: lsluns does not accept uppercase letters in hex-digits for
+ FCP device id and WWPN.
+Problem: lsluns compares the FCP device id and the WWPN with the
+ sysfs entries that are always lowercase.
+Solution: Convert the input from the command line to lowercase, so
+ that lsluns accepts both, uppercase and lowercase for the
+ hex digits in the FCP device and the WWPN.
+---
+ zconf/lsluns | 7 +++++++
+ 1 files changed, 7 insertions(+), 0 deletions(-)
+
+diff --git a/zconf/lsluns b/zconf/lsluns
+index acbdcd7..436ea34 100755
+--- a/zconf/lsluns
++++ b/zconf/lsluns
+@@ -252,7 +252,14 @@ GetOptions('c|ccw=s' => \@adapter,
+ };
+
+ @adapter = split(',', join(',', @adapter));
++foreach (@adapter) {
++ $_ =~ tr/A-Z/a-z/;
++}
++
+ @port = split(',', join(',', @port));
++foreach (@port) {
++ $_ =~ tr/A-Z/a-z/;
++}
+
+ %res_hash = get_env_list(\@adapter, \@port);
+
+--
+1.7.3.5
+
diff --git a/0062-dumpconf-Add-DELAY_MINUTES-description-to-man-page.patch b/0062-dumpconf-Add-DELAY_MINUTES-description-to-man-page.patch
new file mode 100644
index 0000000..a24a939
--- /dev/null
+++ b/0062-dumpconf-Add-DELAY_MINUTES-description-to-man-page.patch
@@ -0,0 +1,225 @@
+From 9e35e49ec56880c9f62cce2ff79849e1e409bc2b Mon Sep 17 00:00:00 2001
+From: =?UTF-8?q?Dan=20Hor=C3=A1k?= <dan at danny.cz>
+Date: Mon, 14 Feb 2011 11:03:03 +0100
+Subject: [PATCH] dumpconf: Add DELAY_MINUTES description to man page
+
+Description: dumpconf: Add DELAY_MINUTES description to man page
+Symptom: User has no online documentation for the DELAY_MINUTES keyword.
+Problem: Description of the DELAY_MINUTES keyword is missing in dumpconf
+ man page.
+Solution: Add description of DELAY_MINUTES keyword to dumpconf man page.
+ Also add some minor man page cleanups from upstream version.
+---
+ man/dumpconf.8 | 104 +++++++++++++++++++++++--------------------------------
+ 1 files changed, 44 insertions(+), 60 deletions(-)
+
+diff --git a/man/dumpconf.8 b/man/dumpconf.8
+index b8dcd00..c795568 100644
+--- a/man/dumpconf.8
++++ b/man/dumpconf.8
+@@ -1,7 +1,7 @@
+-.TH DUMPCONF 8 "Nov 2006" "s390-tools"
++.TH DUMPCONF 8 "Nov 2009" "s390-tools"
+
+ .SH NAME
+-dumpconf \- Configure automatic dump for Linux on z/Series.
++dumpconf \- Configure an ON_PANIC action for Linux on System z.
+
+ .SH SYNOPSIS
+ .br
+@@ -10,8 +10,8 @@ dumpconf \- Configure automatic dump for Linux on z/Series.
+ \fBdumpconf\fR [-h|-v]
+
+ .SH DESCRIPTION
+-\fBdumpconf\fR reads /etc/sysconfig/dumpconf and initializes the automatic dump
+-feature according to the configuration file.
++\fBdumpconf\fR reads the /etc/sysconfig/dumpconf file
++and establishes the action to be taken in case a kernel panic occurs.
+
+ The following keywords can be used in the dumpconf file:
+
+@@ -20,21 +20,22 @@ The following keywords can be used in the dumpconf file:
+ Shutdown action in case of a kernel panic. Possible values are 'dump', 'reipl', 'dump_reipl', 'stop' and 'vmcmd':
+ .br
+
+-dump: trigger dump according to configuration in /etc/sysconfig/dumpconf.
++dump: trigger dump according to the configuration in /etc/sysconfig/dumpconf.
+ .br
+
+-reipl: trigger re-IPL according to configuration under /sys/firmware/reipl.
++reipl: trigger re-IPL according to the configuration under /sys/firmware/reipl.
+ .br
+
+-dump_reipl: first trigger dump according to configuration in
+-/etc/sysconfig/dumpconf, then trigger re-IPL according to configuration under
+-/sys/firmware/reipl.
++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 vm command according to 'VMCMD_X' configuration in /etc/sysconfig/dumpconf.
++vmcmd: trigger CP command according to the 'VMCMD_X' configuration in
++/etc/sysconfig/dumpconf.
+
+ .TP
+ \fB - DUMP_TYPE:\fR
+@@ -46,15 +47,15 @@ Device number of dump device.
+
+ .TP
+ \fB - WWPN\fR
+-WWPN for scsi dump device.
++WWPN for SCSI dump device.
+
+ .TP
+ \fB - LUN\fR
+-LUN for scsi dump device.
++LUN for SCSI dump device.
+
+ .TP
+ \fB - BOOTPROG:\fR
+-Boot program selector
++Boot program selector.
+
+ .TP
+ \fB - BR_LBA:\fR
+@@ -64,46 +65,25 @@ Boot record logical block address.
+ \fB - VMCMD_1, VMCMD_2 ... VMCMD_5:\fR
+ Up to five CP commands, which are triggered in case of a kernel panic.
+
+-.SH Reboot loop considerations
+-
+-If you select the shutdown actions "reipl" or "dump_reipl", in rare cases a
+-"reboot loop" can occur, if the Linux kernel crashes everytime after the
+-reboot. If you want to prevent that scenario, one of the following two
+-approaches can be taken:
+-
+-1. Manual activation of dumpconf
+-
+-Ensure that the dumpconf service is not active by default:
+-.br
+-# chkconfig --del dumpconf
+-
+-Start dumpconf service manually:
+-.br
+-# service dumpconf start
+-
+-When your Linux system crashes, the system will be rebooted (after creating
+-a dump in case of dump_reipl). Because the dumpconf script will then not be
+-activated automatically, a second crash will stop the system.
+-
+-2. Automatic delayed activation of dumpconf
+-
+-Ensure that the dumpconf service is not active by default:
+-.br
+-# chkconfig --del dumpconf
+-
+-To enable delayed activation one of the following methods can be used:
+- a) Use a init script (e.g. /etc/rc.d/boot.local):
+- (sleep 10m; /sbin/service dumpconf start) &
+-
+- b) Use a init script (e.g. /etc/rc.d/boot.local) together with "at":
+- echo 'echo /sbin/service dumpconf start' |at now+10min
+-
+- c) Use the following crontab entry:
+- @reboot sleep 10m && /sbin/service dumpconf start
+-
+-In these examples, when your Linux system crashes within 10 minutes after
+-the reboot, the dumpconf script is not active and a second crash will stop
+-the system.
++.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
+@@ -128,12 +108,12 @@ Print usage information, then exit.
+ Print version information, then exit.
+
+ .SH EXAMPLES:
+-The following are examples for /etc/sysconfig/dumpconf:
++The following are examples of the /etc/sysconfig/dumpconf file:
+ .br
+
+ #
+ .br
+-# Example config for CCW dump device (DASD)
++# Example configuration for a CCW dump device (DASD)
+ .br
+ #
+ .br
+@@ -141,12 +121,14 @@ ON_PANIC=dump_reipl
+ .br
+ DUMP_TYPE=ccw
+ .br
+-DEVICE=0.0.4714
++DEVICE=0.0.1234
++.br
++DELAY_MINUTES=5
+ .br
+
+ #
+ .br
+-# Example config for FCP dump device (SCSI Disk)
++# Example configuration for an FCP dump device (SCSI Disk)
+ .br
+ #
+ .br
+@@ -154,7 +136,7 @@ ON_PANIC=dump
+ .br
+ DUMP_TYPE=fcp
+ .br
+-DEVICE=0.0.4711
++DEVICE=0.0.2345
+ .br
+ WWPN=0x5005076303004712
+ .br
+@@ -167,7 +149,7 @@ BR_LBA=0
+
+ #
+ .br
+-# Example config for vm command on panic
++# Example configuration for CP commands on panic
+ .br
+ #
+ .br
+@@ -177,7 +159,7 @@ VMCMD_1="MESSAGE * Starting VMDUMP"
+ .br
+ VMCMD_2="VMDUMP"
+ .br
+-VMCMD_3="IPL 4711"
++VMCMD_3="IPL 3456"
+
+ #
+ .br
+@@ -186,6 +168,8 @@ VMCMD_3="IPL 4711"
+ #
+ .br
+ ON_PANIC=reipl
++.br
++DELAY_MINUTES=5
+
+ .SH SEE ALSO
+ Linux on zSeries: Using the Dump Tools
+--
+1.7.4
+
diff --git a/0063-cmsfs-fuse-fix-read-and-write-errors-in-text-mode.patch b/0063-cmsfs-fuse-fix-read-and-write-errors-in-text-mode.patch
new file mode 100644
index 0000000..05c4c9e
--- /dev/null
+++ b/0063-cmsfs-fuse-fix-read-and-write-errors-in-text-mode.patch
@@ -0,0 +1,164 @@
+From c55983415ae3bd360deb04ede20bc482bd2c41b7 Mon Sep 17 00:00:00 2001
+From: =?UTF-8?q?Dan=20Hor=C3=A1k?= <dan at danny.cz>
+Date: Mon, 7 Mar 2011 15:13:45 +0100
+Subject: [PATCH] cmsfs-fuse: fix read and write errors in text mode
+
+Description: cmsfs-fuse: fix read and write errors in text mode.
+Symptom: Copying a file in text mode fails with read or write errors.
+Problem: Miscalculation of file size in text mode and off-by-one error
+ in record length check for fixed files.
+Solution: Correct the calculation of the file size by using the displacement
+ value for the last block of a variable file and by limiting
+ the size of the last record of a fixed file to the actual size.
+ Additionally scan for the correct length of a fixed record in text
+ mode.
+Problem-ID: 70230
+---
+ cmsfs-fuse/cmsfs-fuse.c | 67 ++++++++++++++++++++++++++---------------------
+ cmsfs-fuse/dasd.c | 2 +-
+ 2 files changed, 38 insertions(+), 31 deletions(-)
+
+diff --git a/cmsfs-fuse/cmsfs-fuse.c b/cmsfs-fuse/cmsfs-fuse.c
+index 6c5b0b5..fd87774 100644
+--- a/cmsfs-fuse/cmsfs-fuse.c
++++ b/cmsfs-fuse/cmsfs-fuse.c
+@@ -917,6 +917,33 @@ static void set_record_extension(struct file *f, int *record, off_t addr,
+ f->record_scan_state = RSS_DATA_BLOCK_EXT;
+ }
+
++/* check for file end via byte count and return count of bytes left */
++static size_t crop_file_end(struct file *f, int record, size_t done,
++ int first)
++{
++ size_t filesize = f->fst->nr_records * f->fst->record_len;
++ struct record *rec = &f->rlist[record];
++ struct record_ext *rext = rec->ext;
++
++ /* done already includes the complete record length incl. extensions */
++ done -= rec->total_len;
++ /* remove possible linefeeds before comparing with on-disk file size */
++ if (f->linefeed && record)
++ done -= record;
++ done += rec->first_block_len;
++
++ /* add length of all existing extensions */
++ while (rext != NULL) {
++ done += rext->len;
++ rext = rext->next;
++ }
++
++ if (done + first > filesize)
++ first = filesize - done;
++ return first;
++}
++
++/* check for file end by record number */
+ static int end_of_file(struct file *f, int record)
+ {
+ if (record == f->fst->nr_records)
+@@ -936,6 +963,8 @@ static void walk_fixed_data_block(struct file *f, off_t addr, int *record,
+
+ if (first) {
+ BUG(first > left);
++
++ first = crop_file_end(f, *record, *total, first);
+ set_record_extension(f, record, addr, first, block);
+ left -= first;
+ if (addr != NULL_BLOCK)
+@@ -1572,28 +1601,6 @@ static ssize_t get_file_size_fixed(struct fst_entry *fst)
+ return fst->nr_records * fst->record_len;
+ }
+
+-static ssize_t get_file_size_variable_slow(struct fst_entry *fst)
+-{
+- struct record *rec;
+- ssize_t total = 0;
+- int rc = 0;
+- struct file *f = create_file_object(fst, &rc);
+-
+- if (f == NULL)
+- return rc;
+-
+- rec = get_record(f, f->fst->nr_records - 1);
+- total = rec->file_start + rec->total_len;
+-
+- /*
+- * Note: need to add header bytes since the record information does
+- * not contain them but get_file_size_logical will remove them...
+- */
+- total += f->fst->nr_records * VAR_RECORD_HEADER_SIZE;
+- destroy_file_object(f);
+- return total;
+-}
+-
+ static ssize_t get_file_size_variable(struct fst_entry *fst)
+ {
+ struct var_ptr vptr;
+@@ -1608,11 +1615,11 @@ static ssize_t get_file_size_variable(struct fst_entry *fst)
+ return rc;
+ if (vptr.next == 0) {
+ /*
+- * Last block is a null block. Cannot scan that block,
+- * need to scan the whole file instead...
++ * Last block is a null block. No more records can
++ * follow, so the displacement value points to EOF.
+ */
+- total = get_file_size_variable_slow(fst);
+- goto skip;
++ total = vptr.disp;
++ goto skip_scan;
+ }
+ ptr = ABS(vptr.next);
+ if (vptr.disp != VAR_RECORD_SPANNED) {
+@@ -1638,7 +1645,6 @@ skip_scan:
+ */
+ if (fst->nr_blocks)
+ total += (fst->nr_blocks - 1) * cmsfs.blksize;
+-skip:
+ return total;
+ }
+
+@@ -3896,7 +3902,8 @@ static int do_write(struct file *f, const char *buf, size_t size, off_t offset)
+ return rc;
+ f->ptr_dirty = 0;
+ } else
+- if (f->fst->levels > 0) {
++ 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)
+@@ -3962,7 +3969,7 @@ static int cmsfs_write(const char *path, const char *buf, size_t size,
+ return do_write(f, buf, size, offset);
+
+ /* remove already comitted bytes */
+- offset -= f->wcache_used;
++ 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)
+@@ -3981,7 +3988,7 @@ static int cmsfs_write(const char *path, const char *buf, size_t size,
+ return -EINVAL;
+ } else {
+ if (f->fst->record_format == RECORD_LEN_FIXED &&
+- f->wcache_commited + scan_len >= f->fst->record_len) {
++ f->wcache_commited + scan_len > f->fst->record_len) {
+ purge_wcache(f);
+ return -EINVAL;
+ }
+diff --git a/cmsfs-fuse/dasd.c b/cmsfs-fuse/dasd.c
+index d79d34d..1b9af9a 100644
+--- a/cmsfs-fuse/dasd.c
++++ b/cmsfs-fuse/dasd.c
+@@ -196,7 +196,7 @@ int get_device_info(struct cmsfs *cmsfs)
+ */
+ fd = open(cmsfs->device, O_RDWR);
+ if (fd < 0) {
+- if (errno == EROFS) {
++ if (errno == EROFS || errno == EACCES) {
+ cmsfs->readonly = 1;
+ fd = open(cmsfs->device, O_RDONLY);
+ if (fd < 0)
+--
+1.7.4
+
diff --git a/0064-switch-to-using-udevadm-settle.patch b/0064-switch-to-using-udevadm-settle.patch
new file mode 100644
index 0000000..0d9a65b
--- /dev/null
+++ b/0064-switch-to-using-udevadm-settle.patch
@@ -0,0 +1,25 @@
+From c4a38de57376a6ddf03906afeac142525837aab0 Mon Sep 17 00:00:00 2001
+From: =?UTF-8?q?Dan=20Hor=C3=A1k?= <dan at danny.cz>
+Date: Fri, 18 Mar 2011 16:35:17 +0100
+Subject: [PATCH 64/66] switch to using udevadm settle
+
+---
+ etc/init.d/mon_statd | 2 +-
+ 1 files changed, 1 insertions(+), 1 deletions(-)
+
+diff --git a/etc/init.d/mon_statd b/etc/init.d/mon_statd
+index 60bcf00..b6699c7 100755
+--- a/etc/init.d/mon_statd
++++ b/etc/init.d/mon_statd
+@@ -39,7 +39,7 @@ load_kernel_module()
+ if [ $? -ne 0 ]; then
+ exit 1
+ fi
+- udevsettle
++ udevadm settle
+ if [ $? -ne 0 ]; then
+ exit 1
+ fi
+--
+1.7.4
+
diff --git a/0065-hyptop-Fix-man-page-typo-for-current-weight.patch b/0065-hyptop-Fix-man-page-typo-for-current-weight.patch
new file mode 100644
index 0000000..ab9fcde
--- /dev/null
+++ b/0065-hyptop-Fix-man-page-typo-for-current-weight.patch
@@ -0,0 +1,30 @@
+From 983f8cd4337de2ca5377ed64121c55d27367beca Mon Sep 17 00:00:00 2001
+From: =?UTF-8?q?Dan=20Hor=C3=A1k?= <dan at danny.cz>
+Date: Fri, 18 Mar 2011 16:37:15 +0100
+Subject: [PATCH 65/66] hyptop: Fix man page typo for "current weight"
+
+Description: hyptop: Fix man page typo for "current weight"
+Symptom: The hyptop man page says that the 't' character is used for
+ "current weight" under z/VM.
+Problem: The correct character for "current weight" is 'r'.
+Solution: Document the correct character 'r' for "current weight".
+---
+ hyptop/hyptop.8 | 2 +-
+ 1 files changed, 1 insertions(+), 1 deletions(-)
+
+diff --git a/hyptop/hyptop.8 b/hyptop/hyptop.8
+index 99a729c..325613b 100644
+--- a/hyptop/hyptop.8
++++ b/hyptop/hyptop.8
+@@ -127,7 +127,7 @@ The following fields are available under z/VM:
+ 'u' - Used memory
+ 'a' - Maximum memory
+ 'n' - Minimum weight
+- 't' - Current weight
++ 'r' - Current weight
+ 'x' - Maximum weight
+
+ In "sys" window:
+--
+1.7.4
+
diff --git a/0066-fdasd-buffer-overflow-when-writing-to-read-only-devi.patch b/0066-fdasd-buffer-overflow-when-writing-to-read-only-devi.patch
new file mode 100644
index 0000000..2bfa24a
--- /dev/null
+++ b/0066-fdasd-buffer-overflow-when-writing-to-read-only-devi.patch
@@ -0,0 +1,61 @@
+From f5a80bd5d3d478354d6044b6d2b9951fd29a8d59 Mon Sep 17 00:00:00 2001
+From: =?UTF-8?q?Dan=20Hor=C3=A1k?= <dan at danny.cz>
+Date: Fri, 18 Mar 2011 16:37:54 +0100
+Subject: [PATCH 66/66] fdasd: buffer overflow when writing to read-only device
+
+Description: fdasd: buffer overflow when writing to read-only device
+Symptom: When fdasd tries to write to a read-only disk, it fails with
+ a '*** buffer overflow detected ***' error message.
+Problem: It is expected that fdasd cannot write to a read-only disk, and
+ such an operation should end with a proper error message. The
+ libvtoc code, which writes this message, contains the bug that
+ causes the buffer overflow.
+Solution: Directly print the error message, without formatting it first in
+ a buffer.
+---
+ libvtoc/vtoc.c | 15 +++++----------
+ 1 files changed, 5 insertions(+), 10 deletions(-)
+
+diff --git a/libvtoc/vtoc.c b/libvtoc/vtoc.c
+index 36269a4..ae1de8c 100644
+--- a/libvtoc/vtoc.c
++++ b/libvtoc/vtoc.c
+@@ -146,30 +146,25 @@ static char buffer[85];
+ */
+ static void vtoc_error(enum failure why, char *s1, char *s2)
+ {
+- char error[LINE_LENGTH];
+-
+ switch (why) {
+ case unable_to_open:
+- sprintf(error, "%s opening device '%s' failed.\n%s\n",
++ fprintf(stderr, "\n%s opening device '%s' failed.\n%s\n",
+ VTOC_ERROR, s1, s2);
+ break;
+ case unable_to_seek:
+- sprintf(error, "%s seeking device '%s' failed.\n%s\n",
++ fprintf(stderr, "\n%s seeking device '%s' failed.\n%s\n",
+ VTOC_ERROR, s1, s2);
+ break;
+ case unable_to_write:
+- sprintf(error, "%s writing to device '%s' failed,\n%s\n",
++ fprintf(stderr, "\n%s writing to device '%s' failed,\n%s\n",
+ VTOC_ERROR, s1, s2);
+ break;
+ case unable_to_read:
+- sprintf(error, "%s reading from device '%s' failed.\n%s\n",
++ fprintf(stderr, "\n%s reading from device '%s' failed.\n%s\n",
+ VTOC_ERROR, s1, s2);
+ break;
+- default: sprintf(error, "Fatal error\n");
++ default: fprintf(stderr, "\nFatal error\n");
+ }
+-
+- fputc('\n', stderr);
+- fputs(error, stderr);
+ exit(1);
+ }
+
+--
+1.7.4
+
diff --git a/cmsfs-1.1.8-use-detected-filesystem-block-size-on-FBA-devices.patch b/cmsfs-1.1.8-use-detected-filesystem-block-size-on-FBA-devices.patch
new file mode 100644
index 0000000..275d371
--- /dev/null
+++ b/cmsfs-1.1.8-use-detected-filesystem-block-size-on-FBA-devices.patch
@@ -0,0 +1,31 @@
+From 25442f958a12b428b7d063b927ac48965dcd8164 Mon Sep 17 00:00:00 2001
+From: =?UTF-8?q?Dan=20Hor=C3=A1k?= <dan at danny.cz>
+Date: Fri, 28 Jan 2011 16:11:19 +0100
+Subject: [PATCH] use detected filesystem block size on FBA devices
+
+If a FBA device is not properly formated, then the CMS file system can
+have a different block size. The cmsfs tools were able to detect the file
+system block size, but in fact they still used default 512 instead. And
+using the default was causing crashes. Now the detected value is used.
+
+https://bugzilla.redhat.com/show_bug.cgi?id=651012
+---
+ cmsfsany.c | 2 +-
+ 1 files changed, 1 insertions(+), 1 deletions(-)
+
+diff --git a/cmsfsany.c b/cmsfsany.c
+index 55bcfdc..18efffb 100644
+--- a/cmsfsany.c
++++ b/cmsfsany.c
+@@ -102,7 +102,7 @@ int cmsfs_find_label(struct CMSSUPER *vol,struct CMSFSADT *adt)
+ cmsfs_error(cmsfs_ermsg);
+ }
+ vol->flags = CMSFSFBA;
+- vol->blksz = 512;
++ vol->blksz = blksz;
+ return vol->blksz;
+ } }
+
+--
+1.7.3.5
+
diff --git a/lib-zfcp-hbaapi-2.1-module.patch b/lib-zfcp-hbaapi-2.1-module.patch
new file mode 100644
index 0000000..e9bc461
--- /dev/null
+++ b/lib-zfcp-hbaapi-2.1-module.patch
@@ -0,0 +1,26 @@
+diff -up lib-zfcp-hbaapi-2.1/Makefile.am.module lib-zfcp-hbaapi-2.1/Makefile.am
+--- lib-zfcp-hbaapi-2.1/Makefile.am.module 2010-07-21 09:55:20.000000000 +0200
++++ lib-zfcp-hbaapi-2.1/Makefile.am 2011-01-14 10:42:06.000000000 +0100
+@@ -69,6 +69,10 @@ libzfcphbaapi_la_LDFLAGS = \
+ -lpthread -Wl,-init,_initvlib,-fini,_finivlib \
+ -export-symbols $(SYMFILE)
+
++if VENDORLIB
++libzfcphbaapi_la_LDFLAGS += -module -avoid-version -release $(VERSION)
++endif
++
+ bin_PROGRAMS = zfcp_ping zfcp_show
+
+ zfcp_ping_SOURCES = fc_tools/zfcp_ping.c
+diff -up lib-zfcp-hbaapi-2.1/Makefile.in.module lib-zfcp-hbaapi-2.1/Makefile.in
+--- lib-zfcp-hbaapi-2.1/Makefile.in.module 2010-09-17 13:17:17.000000000 +0200
++++ lib-zfcp-hbaapi-2.1/Makefile.in 2011-01-14 10:42:44.000000000 +0100
+@@ -279,6 +279,8 @@ libzfcphbaapi_la_LDFLAGS = \
+ -lpthread -Wl,-init,_initvlib,-fini,_finivlib \
+ -export-symbols $(SYMFILE)
+
++ at VENDORLIB_TRUE@libzfcphbaapi_la_LDFLAGS += -module -avoid-version -release $(VERSION)
++
+ zfcp_ping_SOURCES = fc_tools/zfcp_ping.c
+ zfcp_ping_LDADD = -lzfcphbaapi
+ zfcp_show_SOURCES = fc_tools/zfcp_show.c
diff --git a/lib-zfcp-hbaapi-2.1-u8.patch b/lib-zfcp-hbaapi-2.1-u8.patch
new file mode 100644
index 0000000..afd2ead
--- /dev/null
+++ b/lib-zfcp-hbaapi-2.1-u8.patch
@@ -0,0 +1,13 @@
+diff -up lib-zfcp-hbaapi-2.1/vlib_sg_io.c.u8 lib-zfcp-hbaapi-2.1/vlib_sg_io.c
+--- lib-zfcp-hbaapi-2.1/vlib_sg_io.c.u8 2011-01-14 11:57:51.000000000 +0100
++++ lib-zfcp-hbaapi-2.1/vlib_sg_io.c 2011-01-14 11:58:05.000000000 +0100
+@@ -24,6 +24,9 @@
+ #include <sys/types.h>
+ #include <sys/ioctl.h>
+ #include <sys/time.h>
++
++typedef __u8 u8;
++
+ #include <scsi/scsi.h>
+ #include <scsi/sg.h>
+
diff --git a/lib-zfcp-hbaapi-2.1-vendorlib.patch b/lib-zfcp-hbaapi-2.1-vendorlib.patch
new file mode 100644
index 0000000..acc4994
--- /dev/null
+++ b/lib-zfcp-hbaapi-2.1-vendorlib.patch
@@ -0,0 +1,37 @@
+diff -up lib-zfcp-hbaapi-2.1/Makefile.am.vendorlib lib-zfcp-hbaapi-2.1/Makefile.am
+--- lib-zfcp-hbaapi-2.1/Makefile.am.vendorlib 2011-01-14 12:10:56.000000000 +0100
++++ lib-zfcp-hbaapi-2.1/Makefile.am 2011-01-14 12:12:02.000000000 +0100
+@@ -76,9 +76,15 @@ endif
+ bin_PROGRAMS = zfcp_ping zfcp_show
+
+ zfcp_ping_SOURCES = fc_tools/zfcp_ping.c
+-zfcp_ping_LDADD = -lzfcphbaapi
+ zfcp_show_SOURCES = fc_tools/zfcp_show.c
++
++if VENDORLIB
++zfcp_ping_LDADD = -lHBAAPI
++zfcp_show_LDADD = -lHBAAPI
++else
++zfcp_ping_LDADD = -lzfcphbaapi
+ zfcp_show_LDADD = -lzfcphbaapi
++endif
+
+
+ if DOCS
+diff -up lib-zfcp-hbaapi-2.1/Makefile.in.vendorlib lib-zfcp-hbaapi-2.1/Makefile.in
+--- lib-zfcp-hbaapi-2.1/Makefile.in.vendorlib 2011-01-14 12:11:01.000000000 +0100
++++ lib-zfcp-hbaapi-2.1/Makefile.in 2011-01-14 12:13:05.000000000 +0100
+@@ -282,9 +282,11 @@ libzfcphbaapi_la_LDFLAGS = \
+ @VENDORLIB_TRUE at libzfcphbaapi_la_LDFLAGS += -module -avoid-version -release $(VERSION)
+
+ zfcp_ping_SOURCES = fc_tools/zfcp_ping.c
+-zfcp_ping_LDADD = -lzfcphbaapi
+ zfcp_show_SOURCES = fc_tools/zfcp_show.c
+-zfcp_show_LDADD = -lzfcphbaapi
++ at VENDORLIB_TRUE@zfcp_ping_LDADD = -lHBAAPI
++ at VENDORLIB_TRUE@zfcp_show_LDADD = -lHBAAPI
++ at VENDORLIB_FALSE@zfcp_ping_LDADD = -lzfcphbaapi
++ at VENDORLIB_FALSE@zfcp_show_LDADD = -lzfcphbaapi
+ @DOCS_FALSE at man_MANS = zfcp_show.8 zfcp_ping.8 libzfcphbaapi.3
+ @DOCS_TRUE at man_MANS = libzfcphbaapi.3 dox/man/man3/SupportedHBAAPIs.3 \
+ @DOCS_TRUE@ dox/man/man3/UnSupportedHBAAPIs.3 dox/man/man3/hbaapi.h.3 \
diff --git a/s390utils.spec b/s390utils.spec
index bf51522..588fcb8 100644
--- a/s390utils.spec
+++ b/s390utils.spec
@@ -1,6 +1,6 @@
%define cmsfsver 1.1.8c
%define vipaver 2.0.4
-%define hbaapiver 2.0
+%define hbaapiver 2.1
%{!?_initddir: %define _initddir %{_initrddir}}
@@ -8,7 +8,7 @@ Name: s390utils
Summary: Utilities and daemons for IBM System/z
Group: System Environment/Base
Version: 1.8.2
-Release: 30%{?dist}
+Release: 31%{?dist}
Epoch: 2
License: GPLv2 and GPLv2+ and CPL
Buildroot: %{_tmppath}/%{name}-%{version}-%{release}-root-%(%{__id_u} -n)
@@ -81,16 +81,41 @@ Patch40: 0040-cpuplugd-set-cpu_min-to-1-by-default.patch
Patch41: 0041-fix-dates-option-on-zfcpdbf.patch
Patch42: 0042-lsluns-uninitialized-value-on-adapter-offline.patch
Patch43: 0043-zfcpdbf-Fix-Use-of-uninitialized-value-and-output-is.patch
+Patch44: 0044-xcec-bridge-fix-multicast-forwarding.patch
+Patch45: 0045-ziomon-wrong-return-codes.patch
+Patch46: 0046-qethconf-process-devices-with-non-zero-subchannel.patch
+Patch47: 0047-wait-for-completion-of-any-pending-actions-affecting.patch
+Patch48: 0048-add-infrastructure-code-for-new-features.patch
+Patch49: 0049-hyptop-Show-hypervisor-performance-data-on-System-z.patch
+Patch50: 0050-cmsfs-fuse-support-for-CMS-EDF-filesystems-via-fuse.patch
+Patch51: 0051-lsmem-chmem-Tools-to-manage-memory-hotplug.patch
+Patch52: 0052-dumpconf-Prevent-re-IPL-loop-for-dump-on-panic.patch
+Patch53: 0053-ttyrun-run-a-program-if-a-terminal-device-is-availab.patch
+Patch54: 0054-zgetdump-zipl-Add-ELF-dump-support-needed-for-makedu.patch
+Patch55: 0055-znetconf-support-for-OSA-CHPID-types-OSX-and-OSM.patch
+Patch56: 0056-iucvtty-do-not-specify-z-VM-user-ID-as-argument-to-l.patch
+Patch57: 0057-tunedasd-add-new-option-Q-query_reserve.patch
+Patch58: 0058-fdasd-dasdfmt-fix-format-7-label.patch
+Patch59: 0059-cpuplugd-cmm_pages-not-set-and-restored-correctly.patch
+Patch60: 0060-lsluns-Fix-LUN-reporting-for-SAN-volume-controller-S.patch
+Patch61: 0061-lsluns-Accept-uppercase-and-lowercase-hex-digits.patch
+Patch62: 0062-dumpconf-Add-DELAY_MINUTES-description-to-man-page.patch
+Patch63: 0063-cmsfs-fuse-fix-read-and-write-errors-in-text-mode.patch
+Patch64: 0064-switch-to-using-udevadm-settle.patch
+Patch65: 0065-hyptop-Fix-man-page-typo-for-current-weight.patch
+Patch66: 0066-fdasd-buffer-overflow-when-writing-to-read-only-devi.patch
Patch1000: 1000-ziomon-linker.patch
Patch100: cmsfs-1.1.8-warnings.patch
Patch101: cmsfs-1.1.8-kernel26.patch
+Patch102: cmsfs-1.1.8-use-detected-filesystem-block-size-on-FBA-devices.patch
Patch200: src_vipa-2.0.4-locations.patch
-Patch300: lib-zfcp-hbaapi-2.0-sgutils.patch
-Patch301: lib-zfcp-hbaapi-2.0-module.patch
+Patch301: lib-zfcp-hbaapi-2.1-module.patch
+Patch302: lib-zfcp-hbaapi-2.1-u8.patch
+Patch303: lib-zfcp-hbaapi-2.1-vendorlib.patch
Requires: s390utils-base = %{epoch}:%{version}-%{release}
Requires: s390utils-osasnmpd = %{epoch}:%{version}-%{release}
@@ -241,6 +266,75 @@ be used together with the zSeries (s390) Linux kernel and device drivers.
# zfcpdbf: Fix 'Use of uninitialized value' and output issues (#612622)
%patch43 -p1 -b .zfcpdbf-uninitialized-value
+# xcec-bridge: fix multicast forwarding (#619504)
+%patch44 -p1 -b .xcec-bridge-multicast
+
+# ziomon: wrong return codes (#623250)
+%patch45 -p1 -b .ziomon-return-codes
+
+# qethconf: process devices with non-zero subchannel (#627692)
+%patch46 -p1 -b .qetgconf-nonzero-subchannel
+
+# wait for completion of any pending actions affecting device (#631527)
+%patch47 -p1 -b .cio_settle
+
+# add infrastructure code for new features (#631541)
+%patch48 -p1 -b .feature-infrastructure
+
+# hyptop: Show hypervisor performance data on System z (#631541)
+%patch49 -p1 -b .hyptop
+
+# cmsfs-fuse: support for CMS EDF filesystems via fuse (#631546)
+%patch50 -p1 -b .cmsfs-fuse
+
+# lsmem/chmem: Tools to manage memory hotplug (#631561)
+%patch51 -p1 -b .lsmem-chmem
+
+# dumpconf: Prevent re-IPL loop for dump on panic (#633411)
+%patch52 -p1 -b .dumpconf-reipl
+
+# ttyrun: run a program if a terminal device is available (#633420)
+%patch53 -p1 -b .ttyrun
+
+# zgetdump/zipl: Add ELF dump support (needed for makedumpfile) (#633437)
+%patch54 -p1 -b .elf-dump
+
+# znetconf: support for OSA CHPID types OSX and OSM (#633534)
+%patch55 -p1 -b .znetconf-osx-osm
+
+# iucvtty: do not specify z/VM user ID as argument to login -h (#636204)
+%patch56 -p1 -b .iucvtty-login
+
+# tunedasd: add new option -Q / --query_reserve (#644935)
+%patch57 -p1 -b .tunedasd-q
+
+# fdasd/dasdfmt: fix format 7 label (#649787)
+%patch58 -p1 -b .vtoc-format-7
+
+# cpuplugd: cmm_pages not set and restored correctly (#658517)
+%patch59 -p1 -b .cpuplugd-cmm_pages
+
+# lsluns: Fix LUN reporting for SAN volume controller (SVC) (#659828)
+%patch60 -p1 -b .lsluns-svc
+
+# lsluns: Accept uppercase and lowercase hex digits (#660361)
+%patch61 -p1 -b .lsluns-ignore-case
+
+# dumpconf: Add DELAY_MINUTES description to man page (#676706)
+%patch62 -p1 -b .dumpconf-update-man
+
+# cmsfs-fuse: fix read and write errors in text mode (#680465)
+%patch63 -p1 -b .cmsfs-fuse-text-mode-errors
+
+# mon_statd: switch to using udevadm settle (#688140)
+%patch64 -p1 -b .mon_statd-udevadm-settle
+
+# hyptop: Fix man page typo for "current weight" (#684244)
+%patch65 -p1 -b .hyptop-man-page-typo
+
+# fdasd: buffer overflow when writing to read-only device (#688340)
+%patch66 -p1 -b .fdasd-buffer-overflow
+
# Fix linking with --no-add-needed
%patch1000 -p1 -b .linker
@@ -253,6 +347,9 @@ pushd cmsfs-%{cmsfsver}
# build on kernel-2.6, too
%patch101 -p1 -b .cmsfs26
+
+# use detected filesystem block size (#651012)
+%patch102 -p1 -b .use-detected-block-size
popd
#
@@ -267,11 +364,14 @@ popd
# lib-zfcp-hbaapi
#
pushd lib-zfcp-hbaapi-%{hbaapiver}
-# fix for newer sg3_utils and missing function declarations
-%patch300 -p1 -b .sgutils
-
# build the library as a module
%patch301 -p1 -b .module
+
+# kernel headers need u8 type
+%patch302 -p1 -b .u8
+
+# fix linking of the tools when using vendor library mode
+%patch303 -p1 -b .vendorlib
popd
# remove --strip from install
@@ -306,10 +406,13 @@ pushd src_vipa-%{vipaver}
make CC_FLAGS="$RPM_OPT_FLAGS -fPIC" LIBDIR=%{_libdir}
popd
+%ifarch s390x
pushd lib-zfcp-hbaapi-%{hbaapiver}
-%configure --disable-static
+export CPPFLAGS=-I/usr/src/kernels/$(rpm -q --qf="%{VERSION}-%{RELEASE}.%{ARCH}" kernel-devel)/include
+%configure --disable-static --enable-vendor-lib
make EXTRA_CFLAGS="$RPM_OPT_FLAGS -fno-strict-aliasing"
popd
+%endif
%install
@@ -365,6 +468,7 @@ pushd src_vipa-%{vipaver}
make install LIBDIR=%{_libdir} SBINDIR=%{_bindir} INSTROOT=$RPM_BUILD_ROOT
popd
+%ifarch s390x
# lib-zfcp-hbaapi
pushd lib-zfcp-hbaapi-%{hbaapiver}
%makeinstall docdir=$RPM_BUILD_ROOT%{_docdir}/lib-zfcp-hbaapi-%{hbaapiver}
@@ -373,6 +477,7 @@ popd
rm -rf $RPM_BUILD_ROOT%{_docdir}/lib-zfcp-hbaapi-%{hbaapiver}/latex
# remove unwanted files
rm -f $RPM_BUILD_ROOT%{_libdir}/libzfcphbaapi.*
+%endif
# install usefull headers for devel subpackage
mkdir -p $RPM_BUILD_ROOT%{_includedir}/%{name}
@@ -420,6 +525,7 @@ Requires: sg3_utils
Requires(pre): chkconfig
Requires(preun): chkconfig
Requires(preun): initscripts
+BuildRequires: ncurses-devel
%description base
@@ -525,6 +631,8 @@ s390 base tools. This collection provides the following utilities:
adapters.
- cio_ignore: Query and modify the contents of the CIO device driver
blacklist.
+ - lsmem: Display the online status of the available memory.
+ - chmem: Set hotplug memory online or offline.
* dumpconf:
Allows to configure the dump device used for system dump in case a kernel
@@ -590,19 +698,23 @@ fi
/sbin/qethconf
/sbin/tape390_display
/sbin/tape390_crypt
+/sbin/ttyrun
/sbin/tunedasd
/sbin/vmcp
/sbin/zgetdump
/sbin/znetconf
/sbin/dbginfo.sh
+%{_sbindir}/lsmem
%{_sbindir}/lsreipl
%{_sbindir}/lsshut
+%{_sbindir}/chmem
%{_sbindir}/chreipl
%{_sbindir}/chshut
%{_sbindir}/ip_watcher.pl
%{_sbindir}/start_hsnc.sh
%{_sbindir}/vmur
%{_sbindir}/xcec-bridge
+%{_sbindir}/hyptop
%{_bindir}/vmconvert
%{_initddir}/dumpconf
%config(noreplace) %{_sysconfdir}/sysconfig/dumpconf
@@ -612,6 +724,7 @@ fi
%{_mandir}/man5/zipl.conf.5*
%{_mandir}/man8/chccwdev.8*
%{_mandir}/man8/chchp.8*
+%{_mandir}/man8/chmem.8*
%{_mandir}/man8/chreipl.8*
%{_mandir}/man8/chshut.8*
%{_mandir}/man8/chzcrypt.8*
@@ -621,9 +734,11 @@ fi
%{_mandir}/man8/dasdview.8*
%{_mandir}/man8/dumpconf.8*
%{_mandir}/man8/fdasd.8*
+%{_mandir}/man8/hyptop.8*
%{_mandir}/man8/lschp.8*
%{_mandir}/man8/lscss.8*
%{_mandir}/man8/lsdasd.8*
+%{_mandir}/man8/lsmem.8*
%{_mandir}/man8/lsluns.8*
%{_mandir}/man8/lsqeth.8*
%{_mandir}/man8/lsreipl.8*
@@ -635,6 +750,7 @@ fi
%{_mandir}/man8/qethconf.8*
%{_mandir}/man8/tape390_crypt.8*
%{_mandir}/man8/tape390_display.8*
+%{_mandir}/man8/ttyrun.8*
%{_mandir}/man8/tunedasd.8*
%{_mandir}/man8/vmconvert.8*
%{_mandir}/man8/vmcp.8*
@@ -806,6 +922,7 @@ Tool set to collect data for zfcp performance analysis and report.
License: GPLv2
Summary: z/VM IUCV terminal applications
Group: Applications/System
+Requires(pre): shadow-utils
BuildRequires: gettext
%description iucvterm
@@ -860,6 +977,7 @@ fi
#
# *********************** libzfcphbaapi package ***********************
#
+%ifarch s390x
%package libzfcphbaapi
License: CPL
Summary: ZFCP HBA API Library -- HBA API for the zfcp device driver
@@ -868,6 +986,8 @@ URL: http://www.ibm.com/developerworks/linux/linux390/zfcp-hbaapi.html
BuildRequires: automake autoconf
BuildRequires: doxygen libsysfs-devel
BuildRequires: sg3_utils-devel
+BuildRequires: kernel-devel
+BuildRequires: libhbaapi-devel
Requires: libhbaapi
Obsoletes: %{name}-libzfcphbaapi-devel < 2:1.8.2-4
@@ -883,10 +1003,14 @@ the zfcp device driver.
%doc lib-zfcp-hbaapi-%{hbaapiver}/ChangeLog
%doc lib-zfcp-hbaapi-%{hbaapiver}/AUTHORS
%doc lib-zfcp-hbaapi-%{hbaapiver}/LICENSE
+%{_bindir}/zfcp_ping
+%{_bindir}/zfcp_show
%{_libdir}/libzfcphbaapi-%{hbaapiver}.so
%{_mandir}/man3/libzfcphbaapi.3*
%{_mandir}/man3/SupportedHBAAPIs.3*
%{_mandir}/man3/UnSupportedHBAAPIs.3*
+%{_mandir}/man8/zfcp_ping.8*
+%{_mandir}/man8/zfcp_show.8*
%exclude %{_mandir}/man3/hbaapi.h.3*
#
@@ -908,6 +1032,8 @@ Documentation for the ZFCP HBA API Library.
%docdir %{_docdir}/lib-zfcp-hbaapi-%{hbaapiver}
%{_docdir}/lib-zfcp-hbaapi-%{hbaapiver}/
+%endif
+
#
# *********************** cmsfs package ***********************
#
@@ -935,6 +1061,26 @@ This package contains the CMS file system tools.
%{_mandir}/man8/cmsfsvol.8*
#
+# *********************** cmsfs-fuse package ***********************
+#
+%package cmsfs-fuse
+License: GPLv2
+Summary: CMS file system based on FUSE
+Group: System Environment/Base
+BuildRequires: fuse-devel
+Requires: fuse
+
+%description cmsfs-fuse
+This package contains the CMS file system based on FUSE.
+
+%files cmsfs-fuse
+%defattr(-,root,root,-)
+%dir %{_sysconfdir}/cmsfs-fuse
+%config(noreplace) %{_sysconfdir}/cmsfs-fuse/filetypes.conf
+%{_bindir}/cmsfs-fuse
+%{_mandir}/man1/cmsfs-fuse.1*
+
+#
# *********************** devel package ***********************
#
%package devel
@@ -951,6 +1097,40 @@ User-space development files for the s390/s390x architecture.
%changelog
+* Fri Mar 18 2011 Dan Horák <dhorak at redhat.com> 2:1.8.2-31
+- mon_statd: switch to using udevadm settle (#688140)
+- hyptop: Fix man page typo for "current weight" (#684244)
+- fdasd: buffer overflow when writing to read-only device (#688340)
+- cmsfs-fuse: fix read and write errors in text mode (#680465)
+- cmsfs-fuse needs fuse (#631546)
+- dumpconf: Add DELAY_MINUTES description to man page (#676706)
+- iucvterm scriptlet need shadow-utils (#677247)
+- use lower-case in udev rules (#597360)
+- add support for the 1731/02 OSM/OSX network device (#636849)
+- xcec-bridge: fix multicast forwarding (#619504)
+- ziomon: wrong return codes (#623250)
+- qethconf: process devices with non-zero subchannel (#627692)
+- wait for completion of any pending actions affecting device (#631527)
+- add infrastructure code for new features (#631541)
+- hyptop: Show hypervisor performance data on System z (#631541)
+- cmsfs-fuse: support for CMS EDF filesystems via fuse (#631546)
+- lsmem/chmem: Tools to manage memory hotplug (#631561)
+- dumpconf: Prevent re-IPL loop for dump on panic (#633411)
+- ttyrun: run a program if a terminal device is available (#633420)
+- zgetdump/zipl: Add ELF dump support (needed for makedumpfile) (#633437)
+- znetconf: support for OSA CHPID types OSX and OSM (#633534)
+- iucvtty: do not specify z/VM user ID as argument to login -h (#636204)
+- tunedasd: add new option -Q / --query_reserve (#644935)
+- fdasd/dasdfmt: fix format 7 label (#649787)
+- cpuplugd: cmm_pages not set and restored correctly (#658517)
+- lsluns: Fix LUN reporting for SAN volume controller (SVC) (#659828)
+- lsluns: Accept uppercase and lowercase hex digits (#660361)
+- cmsfs: use detected filesystem block size (#651012)
+- device_cio_free: use the /proc/cio_settle interface when waiting for devices
+- libzfcphbaapi library needs kernel-devel during build and thus is limited to s390x
+- libzfcphbaapi library rebased to 2.1 (#633414)
+- new zfcp tools added (#633409)
+
* Wed Feb 09 2011 Fedora Release Engineering <rel-eng at lists.fedoraproject.org> - 2:1.8.2-30
- Rebuilt for https://fedoraproject.org/wiki/Fedora_15_Mass_Rebuild
diff --git a/sources b/sources
index ac05290..280ea2d 100644
--- a/sources
+++ b/sources
@@ -1,4 +1,4 @@
856ecdd42ad358433eb3fcc886b58a89 s390-tools-1.8.2.tar.bz2
71a8ee5918f2c44c385fcfe8350cdc98 cmsfs-1.1.8c.tar.gz
-2cbfffca3f07c61420899f45d221d451 lib-zfcp-hbaapi-2.0.tar.gz
+ecf3ff0ac4469db7297ebd6f7607fb48 lib-zfcp-hbaapi-2.1.tar.gz
ba42772e5b305b5e147344442cd70826 src_vipa-2.0.4.tar.gz
More information about the scm-commits
mailing list