[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, &note, sizeof(note), ZG_CHECK_ERR);
++		if (rc != sizeof(note))
++			return -EINVAL;
++		switch (note.n_type) {
++		case NT_PRSTATUS:
++			cpu_current = nt_prstatus_read(&note);
++			if (!cpu_current)
++				return -EINVAL;
++			break;
++		case NT_FPREGSET:
++			if (nt_fpregset_read(cpu_current, &note))
++				return -EINVAL;
++			break;
++		case NT_S390_TIMER:
++			if (nt_s390_timer_read(cpu_current, &note))
++				return -EINVAL;
++			break;
++		case NT_S390_TODCMP:
++			if (nt_s390_todcmp_read(cpu_current, &note))
++				return -EINVAL;
++			break;
++		case NT_S390_TODPREG:
++			if (nt_s390_todpreg_read(cpu_current, &note))
++				return -EINVAL;
++			break;
++		case NT_S390_CTRS:
++			if (nt_s390_ctrs_read(cpu_current, &note))
++				return -EINVAL;
++			break;
++		case NT_S390_PREFIX:
++			if (nt_s390_prefix_read(cpu_current, &note))
++				return -EINVAL;
++			break;
++		default:
++			if (nt_skip(&note))
++				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