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?= +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?= +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?= +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?= +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?= +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@de.ibm.com) ++ * Michael Holzheu ++ */ ++ ++#ifndef LIST_H ++#define LIST_H ++ ++#include ++ ++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?= +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 ++ */ ++ ++#include ++#include ++#include ++#include ++#include ++#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 ++ */ ++ ++#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 ++ */ ++ ++#include ++#include ++#include ++#include ++#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 ++ */ ++ ++#include ++#include ++#include ++#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 ++ * Christian Borntraeger ++ */ ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include "helper.h" ++#include "hyptop.h" ++#include "sd.h" ++ ++/* ++ * Globals ++ */ ++static iconv_t l_iconv_ebcdic_ascii; ++static int l_underline_cnt; ++static int l_reverse_cnt; ++static int l_bold_cnt; ++ ++/* ++ * Print time of day ++ */ ++void ht_print_time(void) ++{ ++ char time_str[40]; ++ struct timeval tv; ++ struct tm *tm; ++ ++ gettimeofday(&tv, NULL); ++ tm = localtime(&tv.tv_sec); ++ strftime(time_str, sizeof(time_str), "%H:%M:%S", tm); ++ hyptop_printf("%s", time_str); ++} ++ ++/* ++ * Alloc uninitialized memory and exit on failure ++ */ ++void *ht_alloc(size_t size) ++{ ++ void *ptr; ++ ++ ptr = malloc(size); ++ if (!ptr) ++ ERR_EXIT("Out of memory (%zu Kb)", size / 1024); ++ return ptr; ++} ++ ++/* ++ * Alloc memory initialized with "0" and exit on failure ++ */ ++void *ht_zalloc(size_t size) ++{ ++ void *ptr; ++ ++ ptr = calloc(1, size); ++ if (!ptr) ++ ERR_EXIT("Out of memory (%zu Kb)", size / 1024); ++ return ptr; ++} ++ ++/* ++ * Realloc memory and exit on failure ++ */ ++void *ht_realloc(void *old_ptr, size_t size) ++{ ++ void *ptr; ++ ++ assert(size != 0); ++ if (old_ptr) ++ ptr = realloc(old_ptr, size); ++ else ++ ptr = calloc(1, size); ++ if (!ptr) ++ ERR_EXIT("Out of memory (%lu Kb)", (unsigned long) size / 1024); ++ return ptr; ++} ++ ++/* ++ * Convert EBCDIC string to ASCII ++ */ ++void ht_ebcdic_to_ascii(char *inout, size_t len) ++{ ++ iconv(l_iconv_ebcdic_ascii, &inout, &len, &inout, &len); ++} ++ ++/* ++ * Get mount point for file system tye "fs_type" ++ */ ++char *ht_mount_point_get(const char *fs_type) ++{ ++ struct mntent *mntbuf; ++ FILE *mounts; ++ ++ mounts = setmntent(_PATH_MOUNTED, "r"); ++ if (!mounts) ++ ERR_EXIT_ERRNO("Could not find \"%s\" mount point", fs_type); ++ while ((mntbuf = getmntent(mounts)) != NULL) { ++ if (strcmp(mntbuf->mnt_type, fs_type) == 0) ++ 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 ++ * Christian Borntraeger ++ */ ++ ++#ifndef HELPER_H ++#define HELPER_H ++ ++#include ++#include ++#include ++#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 " " or " "\-\-window=" ++Select current window. Use the options "--sys", "--fields", and "--sort" to ++modify the current window. The last window specified with the "--window" option ++will be used as start window. The default window is "sys_list". ++.TP ++.BR "\-s ,..." " or " "\-\-sys=,..." ++Select systems for current window. If this option is specified, only the ++selected systems are shown for the window. For window "sys" only one ++system can be specified. ++.TP ++.BR "\-f [:],..." " or " "\-\-fields=[:],..." ++Select fields and units in the current window. "F_LETTER" is the field ++letter that identifies uniquely a field (for example "c" for CPU time). ++"UNIT" is the used entity for displaying data for the field (for example "us" ++for microseconds). See FIELDS and UNITS below for definitions. ++If the "--fields" option is specified, only the selected fields are ++shown. ++.TP ++.BR "\-S " " or " "\-\-sort=" ++Select sort field for current window. To reverse the sort order, specify the ++option twice. See FIELDS below for definitions. ++.TP ++.BR "\-t ,..." " or " "\-\-cpu_types=,..." ++Select CPU types that are used for CPU time calculations. See CPU TYPES ++below for definitions. ++.TP ++.BR "\-b" " or " "\-\-batch_mode" ++Use batch mode (no curses). This can be useful for sending output from hyptop ++to another program, a file, or a line mode terminal. ++In this mode no user input is accepted. ++.TP ++.BR "\-d " " or " "\-\-delay=" ++Specifies the delay between screen updates. ++.TP ++.BR "\-n " " or " "\-\-iterations=" ++Specifies the maximum number of iterations before ending. ++ ++.SH PREREQUISITES ++The following things are required to run hyptop: ++ ++.IP " -" ++The Linux kernel must have the required support to provide the ++performance data. ++.IP " -" ++debugfs has to be mounted. ++.IP " -" ++The hyptop user must have read permission for the required debugfs files. ++ ++.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 ++ */ ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#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 ++ */ ++ ++#ifndef HYPTOP_H ++#define HYPTOP_H ++ ++#include ++#include ++#include ++#include ++#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 ++ */ ++ ++#include ++#include ++#include ++#include "nav_desc.h" ++#include "tbox.h" ++ ++#define L_KEY_LEN 14 ++#define L_KEY_FMT "%-14s" ++ ++/* Select mode */ ++ ++struct nav_desc nav_desc_select_mode_enter = { ++ .desc = "Enter select mode", ++ .keys = {"RIGHT", "l", NULL}, ++}; ++ ++struct nav_desc nav_desc_select_mode_leave = { ++ .desc = "Leave select mode", ++ .keys = {"LEFT", "h", NULL}, ++}; ++ ++/* "sys" Window */ ++ ++struct nav_desc nav_desc_win_enter_sys = { ++ .desc = "Go to the \"sys\" window for selected system", ++ .keys = {"RIGHT", "l", NULL}, ++}; ++ ++struct nav_desc nav_desc_win_leave_sys = { ++ .desc = "Go to the previous window", ++ .keys = {"LEFT", "h", "q", NULL}, ++}; ++ ++struct nav_desc nav_desc_win_leave_sys_fast = { ++ .desc = "Go to the previous window", ++ .keys = {"q", NULL}, ++}; ++ ++/* "fields" window */ ++ ++struct nav_desc nav_desc_win_enter_fields = { ++ .desc = "Go to the \"fields\" window", ++ .keys = {"f", NULL}, ++} ; ++ ++struct nav_desc nav_desc_win_leave_fields = { ++ .desc = "Go to the previous window", ++ .keys = {"LEFT", "ENTER", "h", "f", "q", NULL}, ++}; ++ ++struct nav_desc nav_desc_win_leave_fields_fast = { ++ .desc = "Go to the previous window", ++ .keys = {"f", "q", NULL}, ++}; ++ ++/* "cpu_types" window */ ++ ++struct nav_desc nav_desc_win_enter_cpu_types = { ++ .desc = "Go to the \"cpu_types\" window", ++ .keys = {"t", NULL}, ++}; ++ ++struct nav_desc nav_desc_win_leave_cpu_types = { ++ .desc = "Go to the previous window", ++ .keys = {"LEFT", "ENTER", "h", "t", "q", NULL}, ++}; ++ ++struct nav_desc nav_desc_win_leave_cpu_types_fast = { ++ .desc = "Go to the previous window", ++ .keys = {"t", "q", NULL}, ++}; ++ ++/* Marks */ ++ ++struct nav_desc nav_desc_marks_clear = { ++ .desc = "Clear all marked rows", ++ .keys = {"SPACE", NULL}, ++}; ++ ++struct nav_desc nav_desc_mark_toggle = { ++ .desc = "Toggle mark for selected row", ++ .keys = {"SPACE", NULL}, ++}; ++ ++struct nav_desc nav_desc_mark_toggle_view = { ++ .desc = "Toggle view for marked rows", ++ .keys = {".", NULL}, ++}; ++ ++/* Units */ ++ ++struct nav_desc nav_desc_col_unit_increase = { ++ .desc = "Increase unit type of selected column", ++ .keys = {"+", NULL}, ++}; ++ ++struct nav_desc nav_desc_col_unit_decrease = { ++ .desc = "Decrease unit type of selected column", ++ .keys = {"-", NULL}, ++}; ++ ++struct nav_desc nav_desc_row_unit_increase = { ++ .desc = "Increase unit type of selected row", ++ .keys = {"+", NULL}, ++}; ++ ++struct nav_desc nav_desc_row_unit_decrease = { ++ .desc = "Decrease unit type of selected row", ++ .keys = {"-", NULL}, ++}; ++ ++/* Select columns */ ++ ++struct nav_desc nav_desc_select_col_next = { ++ .desc = "Select next column", ++ .keys = {">", NULL}, ++}; ++ ++struct nav_desc nav_desc_select_col_prev = { ++ .desc = "Select previous column", ++ .keys = {"<", NULL}, ++}; ++ ++struct nav_desc nav_desc_select_col_hotkey = { ++ .desc = "Select column with hotkey", ++ .keys = {"", NULL}, ++}; ++ ++/* Quit */ ++ ++struct nav_desc nav_desc_quit = { ++ .desc = "Quit program", ++ .keys = {"q", NULL}, ++}; ++ ++/* Select rows */ ++ ++struct nav_desc nav_desc_toggle_mark_hotkey = { ++ .desc = "Toggle mark for row with hotkey", ++ .keys = {"", NULL}, ++}; ++ ++/* Navigation */ ++ ++struct nav_desc nav_desc_scroll_up_line = { ++ .desc = "Scroll up one line", ++ .keys = {"UP", "k", NULL}, ++}; ++ ++struct nav_desc nav_desc_scroll_down_line = { ++ .desc = "Scroll down one line", ++ .keys = {"DOWN", "j", NULL}, ++}; ++ ++struct nav_desc nav_desc_scroll_up_page = { ++ .desc = "Scroll up one page", ++ .keys = {"PGUP", NULL}, ++}; ++ ++struct nav_desc nav_desc_scroll_down_page = { ++ .desc = "Scroll down one page", ++ .keys = {"PGDOWN", NULL}, ++}; ++ ++struct nav_desc nav_desc_scroll_up_head = { ++ .desc = "Scroll up to head of window", ++ .keys = {"g", NULL}, ++}; ++ ++struct nav_desc nav_desc_scroll_down_tail = { ++ .desc = "Scroll down to tail of window", ++ .keys = {"G", NULL}, ++}; ++ ++/* ++ * Add navigation descriptons to text box ++ */ ++static void l_nav_desc_add(struct tbox *tb, struct nav_desc *desc) ++{ ++ char keys_str[L_KEY_LEN + 1]; ++ unsigned int i, first; ++ char *key; ++ ++ first = 1; ++ keys_str[0] = 0; ++ for (i = 0; (key = desc->keys[i]); i++) { ++ /* ++ * If we have used the whole space for the keys, ++ * we write the line and begin a new one ++ */ ++ if (strlen(desc->keys[i]) + strlen(keys_str) + 1 > L_KEY_LEN) { ++ tbox_printf(tb, " " L_KEY_FMT ": %s", keys_str, ++ desc->desc); ++ keys_str[0] = 0; ++ first = 1; ++ } ++ if (!first) ++ strcat(keys_str, ","); ++ else ++ first = 0; ++ strcat(keys_str, "'"); ++ strcat(keys_str, desc->keys[i]); ++ strcat(keys_str, "'"); ++ assert(strlen(keys_str) <= L_KEY_LEN); ++ } ++ tbox_printf(tb, " " L_KEY_FMT ": %s", keys_str, desc->desc); ++} ++ ++/* ++ * Add navigation descriptions for "normal", "select" and "general" to text box ++ */ ++void nav_desc_add(struct tbox *tb, ++ struct nav_desc **desc_normal, ++ struct nav_desc **desc_select, ++ struct nav_desc **desc_general) ++{ ++ unsigned int i; ++ ++ tbox_printf(tb, "\\BSupported keys in this window\\B"); ++ tbox_printf(tb, " "); ++ ++ tbox_printf(tb, "NORMAL MODE:"); ++ for (i = 0; (desc_normal[i]); i++) ++ l_nav_desc_add(tb, desc_normal[i]); ++ tbox_printf(tb, " "); ++ tbox_printf(tb, "SELECT MODE:"); ++ for (i = 0; (desc_select[i]); i++) ++ l_nav_desc_add(tb, desc_select[i]); ++ tbox_printf(tb, " "); ++ tbox_printf(tb, "GENERAL:"); ++ for (i = 0; (desc_general[i]); i++) ++ l_nav_desc_add(tb, desc_general[i]); ++ tbox_printf(tb, " "); ++} +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 ++ */ ++ ++#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 ++ */ ++ ++#include ++#include ++#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 ++ */ ++ ++#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 ++ */ ++ ++#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 ++ */ ++ ++#include ++#include ++#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 ++ */ ++ ++#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 ++ */ ++ ++#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 ++ */ ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#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 ++ */ ++ ++#ifndef TABLE_H ++#define TABLE_H ++ ++#include ++#include ++#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 ++ */ ++ ++#include ++#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 ++ */ ++ ++#include ++#include ++#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 ++ */ ++ ++#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 ++ */ ++ ++#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 ++ */ ++ ++#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 ++ */ ++ ++#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 ++ */ ++ ++#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 ++ */ ++ ++#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 ++ */ ++ ++#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 ++ */ ++ ++#include ++#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 ++ */ ++ ++#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?= +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 ++ */ ++ ++#define _GNU_SOURCE ++#include ++#include ++#include ++#include ++#include ++#include ++#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@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 ++ */ ++ ++#define FUSE_USE_VERSION 26 ++#define _GNU_SOURCE ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#ifdef HAVE_SETXATTR ++#include ++#endif ++#include ++#include ++ ++#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 ++ */ ++ ++#ifndef _CMSFS_H ++#define _CMSFS_H ++ ++#define _GNU_SOURCE ++#include ++#include ++#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 ++ */ ++ ++#define _GNU_SOURCE ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#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 ++ */ ++ ++#define _GNU_SOURCE ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#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 ++ */ ++ ++#ifndef _EBCDIC_H ++#define _EBCDIC_H ++ ++#include ++#include ++ ++/* ++ * EBCDIC 037 -> ISO8859-1 ++ * changes: ++ * 0x5f: 0xaa -> 0x5e ^ ++ * 0xad: 0x07 -> 0x5b [ ++ * 0xbd: 0x07 -> 0x5d ] ++ */ ++ ++static char ebc2asc[256] = { ++/* 0x00 */ ++ 0x00, 0x01, 0x02, 0x03, 0x07, 0x09, 0x07, 0x7F, ++ 0x07, 0x07, 0x07, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, ++/* 0x10 */ ++ 0x10, 0x11, 0x12, 0x13, 0x07, 0x0A, 0x08, 0x07, ++ 0x18, 0x19, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, ++/* 0x20 */ ++ 0x07, 0x07, 0x1C, 0x07, 0x07, 0x0A, 0x17, 0x1B, ++ 0x07, 0x07, 0x07, 0x07, 0x07, 0x05, 0x06, 0x07, ++/* 0x30 */ ++ 0x07, 0x07, 0x16, 0x07, 0x07, 0x07, 0x07, 0x04, ++ 0x07, 0x07, 0x07, 0x07, 0x14, 0x15, 0x07, 0x1A, ++/* 0x40 */ ++ 0x20, 0xFF, 0x83, 0x84, 0x85, 0xA0, 0x07, 0x86, ++ 0x87, 0xA4, 0x9B, 0x2E, 0x3C, 0x28, 0x2B, 0x7C, ++/* 0x50 */ ++ 0x26, 0x82, 0x88, 0x89, 0x8A, 0xA1, 0x8C, 0x07, ++ 0x8D, 0xE1, 0x21, 0x24, 0x2A, 0x29, 0x3B, 0x5E, ++/* 0x60 */ ++ 0x2D, 0x2F, 0x07, 0x8E, 0x07, 0x07, 0x07, 0x8F, ++ 0x80, 0xA5, 0x07, 0x2C, 0x25, 0x5F, 0x3E, 0x3F, ++/* 0x70 */ ++ 0x07, 0x90, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, ++ 0x70, 0x60, 0x3A, 0x23, 0x40, 0x27, 0x3D, 0x22, ++/* 0x80 */ ++ 0x07, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, ++ 0x68, 0x69, 0xAE, 0xAF, 0x07, 0x07, 0x07, 0xF1, ++/* 0x90 */ ++ 0xF8, 0x6A, 0x6B, 0x6C, 0x6D, 0x6E, 0x6F, 0x70, ++ 0x71, 0x72, 0xA6, 0xA7, 0x91, 0x07, 0x92, 0x07, ++/* 0xa0 */ ++ 0xE6, 0x7E, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, ++ 0x79, 0x7A, 0xAD, 0xAB, 0x07, 0x5B, 0x07, 0x07, ++/* 0xb0 */ ++ 0x5E, 0x9C, 0x9D, 0xFA, 0x07, 0x07, 0x07, 0xAC, ++ 0xAB, 0x07, 0x5B, 0x5D, 0x07, 0x5D, 0x07, 0x07, ++/* 0xc0 */ ++ 0x7B, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, ++ 0x48, 0x49, 0x07, 0x93, 0x94, 0x95, 0xA2, 0x07, ++/* 0xd0 */ ++ 0x7D, 0x4A, 0x4B, 0x4C, 0x4D, 0x4E, 0x4F, 0x50, ++ 0x51, 0x52, 0x07, 0x96, 0x81, 0x97, 0xA3, 0x98, ++/* 0xe0 */ ++ 0x5C, 0xF6, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, ++ 0x59, 0x5A, 0xFD, 0x07, 0x99, 0x07, 0x07, 0x07, ++/* 0xf0 */ ++ 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, ++ 0x38, 0x39, 0x07, 0x07, 0x9A, 0x07, 0x07, 0x07 ++}; ++ ++/* ISO8859-1 -> EBCDIC 037 */ ++static char asc2ebc[256] = { ++/* 0x00 */ ++ 0x00, 0x01, 0x02, 0x03, 0x37, 0x2D, 0x2E, 0x2F, ++ 0x16, 0x05, 0x15, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, ++/* 0x10 */ ++ 0x10, 0x11, 0x12, 0x13, 0x3C, 0x3D, 0x32, 0x26, ++ 0x18, 0x19, 0x3F, 0x27, 0x22, 0x1D, 0x1E, 0x1F, ++/* 0x20 */ ++ 0x40, 0x5A, 0x7F, 0x7B, 0x5B, 0x6C, 0x50, 0x7D, ++ 0x4D, 0x5D, 0x5C, 0x4E, 0x6B, 0x60, 0x4B, 0x61, ++/* 0x30 */ ++ 0xF0, 0xF1, 0xF2, 0xF3, 0xF4, 0xF5, 0xF6, 0xF7, ++ 0xF8, 0xF9, 0x7A, 0x5E, 0x4C, 0x7E, 0x6E, 0x6F, ++/* 0x40 */ ++ 0x7C, 0xC1, 0xC2, 0xC3, 0xC4, 0xC5, 0xC6, 0xC7, ++ 0xC8, 0xC9, 0xD1, 0xD2, 0xD3, 0xD4, 0xD5, 0xD6, ++/* 0x50 */ ++ 0xD7, 0xD8, 0xD9, 0xE2, 0xE3, 0xE4, 0xE5, 0xE6, ++ 0xE7, 0xE8, 0xE9, 0xBA, 0xE0, 0xBB, 0xB0, 0x6D, ++/* 0x60 */ ++ 0x79, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, ++ 0x88, 0x89, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, ++/* 0x70 */ ++ 0x97, 0x98, 0x99, 0xA2, 0xA3, 0xA4, 0xA5, 0xA6, ++ 0xA7, 0xA8, 0xA9, 0xC0, 0x4F, 0xD0, 0xA1, 0x07, ++/* 0x80 */ ++ 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, ++ 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, ++/* 0x90 */ ++ 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, ++ 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, ++/* 0xa0 */ ++ 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, ++ 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, ++/* 0xb0 */ ++ 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, ++ 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, ++/* 0xc0 */ ++ 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, ++ 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, ++/* 0xd0 */ ++ 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, ++ 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, ++/* 0xe0 */ ++ 0x3F, 0x59, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, ++ 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, ++/* 0xf0 */ ++ 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, ++ 0x90, 0x3F, 0x3F, 0x3F, 0x3F, 0xEA, 0x3F, 0xFF ++}; ++ ++#define EBCDIC_ENCODE 0x0 ++#define EBCDIC_DECODE 0x1 ++ ++static inline void a2e(char *dst, const char *src, int len, int to) ++{ ++ char *conv; ++ int i; ++ ++ if (to == EBCDIC_ENCODE) ++ conv = asc2ebc; ++ else ++ conv = ebc2asc; ++ for (i = 0; i < len; i++) ++ dst[i] = conv[(unsigned int)src[i]]; ++} ++ ++static inline void ebcdic_enc(char *dst, const char *src, int len) ++{ ++ a2e(dst, src, len, EBCDIC_ENCODE); ++} ++ ++static inline void ebcdic_dec(char *dst, const char *src, int len) ++{ ++ a2e(dst, src, len, EBCDIC_DECODE); ++} ++ ++#endif +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 ++ */ ++ ++#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 ++ */ ++ ++#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?= +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 ++############################################################################### ++ ++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 <[m|M|g|G]. With m or M, specifies the memory ++size in MB (1024 x 1024 bytes). With g or G, specifies the memory size ++in GB (1024 x 1024 x 1024 bytes). The default unit is MB. ++ ++Specify RANGE in the form 0x-0x as shown in the output of the ++lsmem command. is the hexadecimal address of the first byte and ++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 [m|M|g|G]. With m or M, specifies the memory ++size in MB (1024 x 1024 bytes). With g or G, 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-0x as shown in the output of the ++lsmem command. is the hexadecimal address of the first byte and ++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 ++############################################################################### ++ ++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 <> 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 < 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?= +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?= +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 ++.\" ------------------------------------------------------------------------- ++.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 ++ */ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#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] []\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?= +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 ++ */ ++ ++#ifndef DF_ELF_H ++#define DF_ELF_H ++ ++#include ++#include ++#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 ++ */ ++ ++#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 ++ */ ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#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 ++ */ ++ ++#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 ++ */ ++ ++#include ++#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 ++ */ ++ ++#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 ++ */ ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include "zgetdump.h" ++ ++/* ++ * Read memory for given memory chunk ++ */ ++static void dfi_elf_mem_chunk_read_fn(struct dfi_mem_chunk *mem_chunk, u64 off, ++ void *buf, u64 cnt) ++{ ++ u64 elf_load_off = *((u64 *) mem_chunk->data); ++ ++ zg_seek(g.fh, elf_load_off + off, ZG_CHECK); ++ zg_read(g.fh, buf, cnt, ZG_CHECK); ++} ++ ++/* ++ * Add load (memory chunk) to DFI dump ++ */ ++static int pt_load_add(Elf64_Phdr *phdr) ++{ ++ u64 *off_ptr; ++ ++ if (phdr->p_paddr != phdr->p_vaddr) { ++ phdr->p_paddr = phdr->p_vaddr; ++ STDERR("Dump file \"%s\" is a user space core dump\n", ++ g.opts.device); ++ } ++ if (phdr->p_filesz == 0) /* Skip null pt loads */ ++ return 0; ++ off_ptr = zg_alloc(sizeof(*off_ptr)); ++ *off_ptr = phdr->p_offset; ++ dfi_mem_chunk_add(phdr->p_paddr, phdr->p_memsz, off_ptr, ++ dfi_elf_mem_chunk_read_fn); ++ if (phdr->p_offset + phdr->p_memsz > zg_size(g.fh)) ++ return -EINVAL; ++ return 0; ++} ++ ++/* ++ * Skip name of note ++ */ ++static void nt_name_skip(Elf64_Nhdr *note) ++{ ++ zg_seek_cur(g.fh, ROUNDUP(note->n_namesz, 4), ZG_CHECK); ++} ++ ++/* ++ * Read note ++ */ ++static int nt_read(Elf64_Nhdr *note, void *buf) ++{ ++ off_t buf_len = ROUNDUP(note->n_descsz, 4); ++ char tmp_buf[buf_len]; ++ ++ nt_name_skip(note); ++ if (zg_read(g.fh, tmp_buf, buf_len, ZG_CHECK_ERR) != buf_len) ++ return -EINVAL; ++ if (buf) ++ memcpy(buf, tmp_buf, note->n_descsz); ++ return 0; ++} ++ ++/* ++ * Skip note ++ */ ++static int nt_skip(Elf64_Nhdr *note) ++{ ++ return nt_read(note, NULL); ++} ++ ++/* ++ * Ensure that CPU is already defined by prstatus note ++ */ ++static void check_cpu(struct dfi_cpu *cpu, const char *note_str) ++{ ++ if (cpu) ++ return; ++ ERR_EXIT("Invalid ELF dump (%s before prstatus found)", note_str); ++} ++ ++/* ++ * Read prstatus note and return new DFI CPU ++ */ ++static struct dfi_cpu *nt_prstatus_read(Elf64_Nhdr *note) ++{ ++ struct dfi_cpu *cpu = dfi_cpu_alloc(); ++ struct nt_prstatus_64 nt_prstatus; ++ ++ if (nt_read(note, &nt_prstatus)) ++ return NULL; ++ ++ memcpy(cpu->gprs, &nt_prstatus.gprs, sizeof(cpu->gprs)); ++ memcpy(cpu->psw, &nt_prstatus.psw, sizeof(cpu->psw)); ++ memcpy(cpu->acrs, &nt_prstatus.acrs, sizeof(cpu->acrs)); ++ ++ dfi_cpu_add(cpu); ++ return cpu; ++} ++ ++/* ++ * Read fpregset note ++ */ ++static int nt_fpregset_read(struct dfi_cpu *cpu, Elf64_Nhdr *note) ++{ ++ struct nt_fpregset_64 nt_fpregset; ++ ++ check_cpu(cpu, "FPREGSET"); ++ if (nt_read(note, &nt_fpregset)) ++ return -EINVAL; ++ ++ memcpy(&cpu->fpc, &nt_fpregset.fpc, sizeof(cpu->fpc)); ++ memcpy(cpu->fprs, &nt_fpregset.fprs, sizeof(cpu->fprs)); ++ return 0; ++} ++ ++/* ++ * Read s390 timer note ++ */ ++static int nt_s390_timer_read(struct dfi_cpu *cpu, Elf64_Nhdr *note) ++{ ++ check_cpu(cpu, "S390_TIMER"); ++ return nt_read(note, &cpu->timer); ++} ++ ++/* ++ * Read s390 todcmp note ++ */ ++static int nt_s390_todcmp_read(struct dfi_cpu *cpu, Elf64_Nhdr *note) ++{ ++ check_cpu(cpu, "S390_TODCMP"); ++ return nt_read(note, &cpu->todcmp); ++} ++ ++/* ++ * Read s390 todpreg note ++ */ ++static int nt_s390_todpreg_read(struct dfi_cpu *cpu, Elf64_Nhdr *note) ++{ ++ check_cpu(cpu, "S390_TODPREG"); ++ return nt_read(note, &cpu->todpreg); ++} ++ ++/* ++ * Read s390 ctrs note ++ */ ++static int nt_s390_ctrs_read(struct dfi_cpu *cpu, Elf64_Nhdr *note) ++{ ++ check_cpu(cpu, "S390_CTRS"); ++ return nt_read(note, &cpu->ctrs); ++} ++ ++/* ++ * Read s390 prefix note ++ */ ++static int nt_s390_prefix_read(struct dfi_cpu *cpu, Elf64_Nhdr *note) ++{ ++ check_cpu(cpu, "S390_PREFIX"); ++ return nt_read(note, &cpu->prefix); ++} ++ ++/* ++ * Add all notes for notes phdr ++ */ ++static int pt_notes_add(Elf64_Phdr *phdr) ++{ ++ u64 start_off = zg_tell(g.fh, ZG_CHECK); ++ struct dfi_cpu *cpu_current = NULL; ++ u64 notes_start_off; ++ Elf64_Nhdr note; ++ int rc; ++ ++ zg_seek(g.fh, phdr->p_offset, ZG_CHECK); ++ notes_start_off = zg_tell(g.fh, ZG_CHECK); ++ while (zg_tell(g.fh, ZG_CHECK) - notes_start_off < phdr->p_filesz) { ++ rc = zg_read(g.fh, ¬e, sizeof(note), ZG_CHECK_ERR); ++ if (rc != sizeof(note)) ++ return -EINVAL; ++ switch (note.n_type) { ++ case NT_PRSTATUS: ++ cpu_current = nt_prstatus_read(¬e); ++ if (!cpu_current) ++ return -EINVAL; ++ break; ++ case NT_FPREGSET: ++ if (nt_fpregset_read(cpu_current, ¬e)) ++ return -EINVAL; ++ break; ++ case NT_S390_TIMER: ++ if (nt_s390_timer_read(cpu_current, ¬e)) ++ return -EINVAL; ++ break; ++ case NT_S390_TODCMP: ++ if (nt_s390_todcmp_read(cpu_current, ¬e)) ++ return -EINVAL; ++ break; ++ case NT_S390_TODPREG: ++ if (nt_s390_todpreg_read(cpu_current, ¬e)) ++ return -EINVAL; ++ break; ++ case NT_S390_CTRS: ++ if (nt_s390_ctrs_read(cpu_current, ¬e)) ++ return -EINVAL; ++ break; ++ case NT_S390_PREFIX: ++ if (nt_s390_prefix_read(cpu_current, ¬e)) ++ return -EINVAL; ++ break; ++ default: ++ if (nt_skip(¬e)) ++ return -EINVAL; ++ break; ++ } ++ } ++ zg_seek(g.fh, start_off, ZG_CHECK); ++ return 0; ++} ++ ++/* ++ * Read ELF header ++ */ ++static int read_elf_hdr(Elf64_Ehdr *ehdr) ++{ ++ if (zg_size(g.fh) < sizeof(*ehdr)) ++ return -ENODEV; ++ zg_read(g.fh, ehdr, sizeof(*ehdr), ZG_CHECK); ++ if (memcmp(ehdr->e_ident, ELFMAG, SELFMAG) != 0) ++ return -ENODEV; ++ if (ehdr->e_type != ET_CORE) ++ return -ENODEV; ++ if (ehdr->e_machine != EM_S390 || ehdr->e_ident[EI_CLASS] != ELFCLASS64) ++ ERR_EXIT("Only s390x (64 bit) core dump files are supported"); ++ return 0; ++} ++ ++/* ++ * Initialize ELF input dump format ++ */ ++static int dfi_elf_init(void) ++{ ++ Elf64_Ehdr ehdr; ++ Elf64_Phdr phdr; ++ int i; ++ ++ if (read_elf_hdr(&ehdr) != 0) ++ return -ENODEV; ++ ++ df_elf_ensure_s390x(); ++ dfi_arch_set(DFI_ARCH_64); ++ dfi_cpu_info_init(DFI_CPU_CONTENT_ALL); ++ ++ for (i = 0; i < ehdr.e_phnum; i++) { ++ zg_read(g.fh, &phdr, sizeof(phdr), ZG_CHECK); ++ switch (phdr.p_type) { ++ case PT_LOAD: ++ if (pt_load_add(&phdr)) ++ return -EINVAL; ++ break; ++ case PT_NOTE: ++ if (pt_notes_add(&phdr)) ++ return -EINVAL; ++ break; ++ default: ++ break; ++ } ++ } ++ dfi_attr_version_set(ehdr.e_ident[EI_VERSION]); ++ return 0; ++} ++ ++/* ++ * ELF DFI operations ++ */ ++struct dfi dfi_elf = { ++ .name = "elf", ++ .init = dfi_elf_init, ++ .feat_bits = DFI_FEAT_COPY | DFI_FEAT_SEEK, ++}; +diff --git a/zdump/dfi_kdump.c b/zdump/dfi_kdump.c +new file mode 100644 +index 0000000..537ea55 +--- /dev/null ++++ b/zdump/dfi_kdump.c +@@ -0,0 +1,122 @@ ++/* ++ * zgetdump - Tool for copying and converting System z dumps ++ * ++ * kdump input format ++ * ++ * Copyright IBM Corp. 2001, 2010 ++ * Author(s): Michael Holzheu ++ */ ++ ++#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 ++ */ ++ ++#include ++#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 ++ */ ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#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 ++ */ ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#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 ++ */ ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#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 ++ */ ++ ++#include ++#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 ++ */ ++ ++#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 ++ */ ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#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 ++ */ ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#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 ++ */ ++ ++#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 ++ */ ++ ++#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 ++ */ ++ ++#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 ++ */ ++ ++#include ++#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 ++ */ ++ ++#include ++#include ++#include ++#include ++#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 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 ++ */ ++ ++#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 ++ */ ++ ++#define FUSE_USE_VERSION 25 ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#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 ++ */ ++ ++#include ++#include ++#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 ++ */ ++ ++#ifndef ZG_H ++#define ZG_H ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#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 " " or " "\-\-mount " ++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 " " or " "\-\-umount " ++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 " " or " "\-\-device " ++Check DASD device DUMPDEV for valid dump tool and print information about it. ++ ++.TP ++.BR "\-i " " or " "\-\-info " ++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 " " or " "\-\-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. 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@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 ++ * Main functions ++ * ++ * Copyright IBM Corp. 2001, 2010 ++ * Author(s): Michael Holzheu ++ * Frank Munzert ++ * Despina Papadopoulou + */ + +-#include "zgetdump.h" +-#include "zt_common.h" + #include + #include + #include +-#include +-#include +-#include +-#include +-#include + #include + #include + #include + #include +-#include + #include +-#include + #include ++#include ++#include ++#include ++#include ++#include + #include ++#include ++#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 to stdout:\n"\ +-" > zgetdump \n"\ +-"Print dump header and check if dump is valid - for single tape or DASD:\n"\ +-" > zgetdump [-i | --info] \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] \n"\ +-"Check dump device:\n"\ +-" > zgetdump [-d | --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 \n"\ +-"> zgetdump -i \n"\ +-"> zgetdump -i -a \n"\ +-"> zgetdump -d \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 \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 \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 ++ * Frank Munzert ++ * 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 +-#include +-#include +- +-/* 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?= +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?= +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?= +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" " " + 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 " + 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?= +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?= +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?= +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?= +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?= +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?= +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?= +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?= +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?= +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?= +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.0-module.patch b/lib-zfcp-hbaapi-2.0-module.patch deleted file mode 100644 index 01ad7a9..0000000 --- a/lib-zfcp-hbaapi-2.0-module.patch +++ /dev/null @@ -1,25 +0,0 @@ -diff -up lib-zfcp-hbaapi-2.0/Makefile.am.orig lib-zfcp-hbaapi-2.0/Makefile.am ---- lib-zfcp-hbaapi-2.0/Makefile.am.orig 2009-11-05 17:22:58.000000000 +0100 -+++ lib-zfcp-hbaapi-2.0/Makefile.am 2009-11-09 14:07:17.000000000 +0100 -@@ -66,6 +66,9 @@ libzfcphbaapi_la_LDFLAGS = \ - -lpthread -Wl,-init,_initvlib,-fini,_finivlib \ - -export-symbols $(SYMFILE) - -+if VENDORLIB -+libzfcphbaapi_la_LDFLAGS += -module -avoid-version -release $(VERSION) -+endif - - if DOCS - man_MANS = libzfcphbaapi.3 dox/man/man3/SupportedHBAAPIs.3 \ -diff -up lib-zfcp-hbaapi-2.0/Makefile.in.orig lib-zfcp-hbaapi-2.0/Makefile.in ---- lib-zfcp-hbaapi-2.0/Makefile.in.orig 2009-11-09 14:06:58.000000000 +0100 -+++ lib-zfcp-hbaapi-2.0/Makefile.in 2009-11-09 14:08:40.000000000 +0100 -@@ -254,6 +254,8 @@ libzfcphbaapi_la_LDFLAGS = \ - -lpthread -Wl,-init,_initvlib,-fini,_finivlib \ - -export-symbols $(SYMFILE) - -+@VENDORLIB_TRUE@libzfcphbaapi_la_LDFLAGS += -module -avoid-version -release $(VERSION) -+ - @DOCS_FALSE@man_MANS = libzfcphbaapi.3 - @DOCS_TRUE@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/lib-zfcp-hbaapi-2.0-sgutils.patch b/lib-zfcp-hbaapi-2.0-sgutils.patch deleted file mode 100644 index a292ae4..0000000 --- a/lib-zfcp-hbaapi-2.0-sgutils.patch +++ /dev/null @@ -1,36 +0,0 @@ -diff -Nrup lib-zfcp-hbaapi-2.0.orig/Makefile.am lib-zfcp-hbaapi-2.0/Makefile.am ---- lib-zfcp-hbaapi-2.0.orig/Makefile.am 2008-11-21 15:08:13.000000000 +0100 -+++ lib-zfcp-hbaapi-2.0/Makefile.am 2009-09-25 12:30:07.000000000 +0200 -@@ -60,7 +60,7 @@ lib_LTLIBRARIES = libzfcphbaapi.la - - libzfcphbaapi_la_SOURCES = vlib.c vlib_callbacks.c vlib_aux.c vlib_sysfs.c \ - vlib_sg.c --libzfcphbaapi_la_LIBADD = -lsysfs -lsgutils -+libzfcphbaapi_la_LIBADD = -lsysfs -lsgutils2 - libzfcphbaapi_la_LDFLAGS = \ - -version-info $(LIB_CURRENT):$(LIB_REVISION):$(LIB_AGE) \ - -lpthread -Wl,-init,_initvlib,-fini,_finivlib \ -diff -Nrup lib-zfcp-hbaapi-2.0.orig/Makefile.in lib-zfcp-hbaapi-2.0/Makefile.in ---- lib-zfcp-hbaapi-2.0.orig/Makefile.in 2008-11-21 15:08:18.000000000 +0100 -+++ lib-zfcp-hbaapi-2.0/Makefile.in 2009-09-25 13:22:04.000000000 +0200 -@@ -248,7 +248,7 @@ lib_LTLIBRARIES = libzfcphbaapi.la - libzfcphbaapi_la_SOURCES = vlib.c vlib_callbacks.c vlib_aux.c vlib_sysfs.c \ - vlib_sg.c - --libzfcphbaapi_la_LIBADD = -lsysfs -lsgutils -+libzfcphbaapi_la_LIBADD = -lsysfs -lsgutils2 - libzfcphbaapi_la_LDFLAGS = \ - -version-info $(LIB_CURRENT):$(LIB_REVISION):$(LIB_AGE) \ - -lpthread -Wl,-init,_initvlib,-fini,_finivlib \ -diff -Nrup lib-zfcp-hbaapi-2.0.orig/vlib_sg.c lib-zfcp-hbaapi-2.0/vlib_sg.c ---- lib-zfcp-hbaapi-2.0.orig/vlib_sg.c 2008-11-20 10:23:34.000000000 +0100 -+++ lib-zfcp-hbaapi-2.0/vlib_sg.c 2009-09-25 13:21:52.000000000 +0200 -@@ -19,6 +19,8 @@ - * @brief All calls that use sg_utils library. - */ - -+#include -+#include - #include "vlib.h" - - #define INTERVAL 10000000 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) + ++@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 + #include + #include ++ ++typedef __u8 u8; ++ + #include + #include + 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@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 ++@VENDORLIB_TRUE@zfcp_ping_LDADD = -lHBAAPI ++@VENDORLIB_TRUE@zfcp_show_LDADD = -lHBAAPI ++@VENDORLIB_FALSE@zfcp_ping_LDADD = -lzfcphbaapi ++@VENDORLIB_FALSE@zfcp_show_LDADD = -lzfcphbaapi + @DOCS_FALSE@man_MANS = zfcp_show.8 zfcp_ping.8 libzfcphbaapi.3 + @DOCS_TRUE@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 *********************** # @@ -934,6 +1060,26 @@ This package contains the CMS file system tools. %{_mandir}/man8/cmsfslst.8* %{_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 *********************** # @@ -951,6 +1097,40 @@ User-space development files for the s390/s390x architecture. %changelog +* Fri Mar 18 2011 Dan HorĂ¡k 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 - 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