Summary:     zipl: Add support for device mapper devices
Description: zipl now allows installation of and booting from a boot record on
             logical devices, i.e. devices managed by device mapper (or similar
             products), e.g. multipath devices.

---
 zipl/include/disk.h                |   25 
 zipl/include/install.h             |    9 
 zipl/include/job.h                 |   17 
 zipl/include/scan.h                |   19 
 zipl/man/zipl.8                    |   88 +++
 zipl/man/zipl.conf.5               |   71 ++
 zipl/src/Makefile                  |    1 
 zipl/src/bootmap.c                 |   69 +-
 zipl/src/disk.c                    |  242 +++++++--
 zipl/src/install.c                 |   11 
 zipl/src/job.c                     |  157 +++++
 zipl/src/scan.c                    |  251 ++++++++-
 zipl/src/zipl.c                    |   13 
 zipl/src/zipl_helper.device-mapper |  716 +++++++++++++++++++++++++++
 14 files changed, 1547 insertions(+), 142 deletions(-)

Index: s390-tools-sles11/zipl/include/disk.h
===================================================================
--- s390-tools-sles11.orig/zipl/include/disk.h
+++ s390-tools-sles11/zipl/include/disk.h
@@ -2,7 +2,7 @@
  * s390-tools/zipl/include/disk.h
  *   Functions to handle disk layout specific operations.
  *
- * Copyright IBM Corp. 2001, 2006.
+ * Copyright IBM Corp. 2001, 2009.
  *
  * Author(s): Carsten Otte <cotte@de.ibm.com>
  *            Peter Oberparleiter <Peter.Oberparleiter@de.ibm.com>
@@ -59,6 +59,19 @@ struct hd_geometry {
 	unsigned long start;
 };
 
+/* Disk information source */
+typedef enum {
+	source_auto,
+	source_user,
+	source_script
+} source_t;
+
+/* targetbase definition */
+typedef enum {
+	defined_as_device,
+	defined_as_name
+} definition_t;
+
 /* Disk information type */
 struct disk_info {
 	disk_type_t type;
@@ -72,11 +85,17 @@ struct disk_info {
 	struct hd_geometry geo;
 	char* name;
 	char* drv_name;
+	source_t source;
+	definition_t targetbase;
 };
 
+struct job_target_data;
 
-int disk_get_info(const char* device, struct disk_info** info);
-int disk_get_info_from_file(const char* filename, struct disk_info** info);
+int disk_get_info(const char* device, struct job_target_data* target,
+		  struct disk_info** info);
+int disk_get_info_from_file(const char* filename,
+			    struct job_target_data* target,
+			    struct disk_info** info);
 void disk_free_info(struct disk_info* info);
 char* disk_get_type_name(disk_type_t type);
 int disk_is_large_volume(struct disk_info* info);
Index: s390-tools-sles11/zipl/include/install.h
===================================================================
--- s390-tools-sles11.orig/zipl/include/install.h
+++ s390-tools-sles11/zipl/include/install.h
@@ -2,7 +2,7 @@
  * s390-tools/zipl/include/install.h
  *   Functions handling the installation of the boot loader code onto disk.
  *
- * Copyright IBM Corp. 2001, 2006.
+ * Copyright IBM Corp. 2001, 2009.
  *
  * Author(s): Carsten Otte <cotte@de.ibm.com>
  *            Peter Oberparleiter <Peter.Oberparleiter@de.ibm.com>
@@ -24,8 +24,9 @@ int install_tapeloader(const char* devic
 		       const char* parmline, const char* ramdisk,
 		       address_t image_addr, address_t parm_addr,
 		       address_t initrd_addr);
-int install_dump(const char* device, uint64_t mem);
-int install_mvdump(char* const device[], int device_count, uint64_t mem,
-		   uint8_t force);
+int install_dump(const char* device, struct job_target_data* target,
+		 uint64_t mem);
+int install_mvdump(char* const device[], struct job_target_data* target,
+		   int device_count, uint64_t mem, uint8_t force);
 
 #endif /* INSTALL_H */
Index: s390-tools-sles11/zipl/include/job.h
===================================================================
--- s390-tools-sles11.orig/zipl/include/job.h
+++ s390-tools-sles11/zipl/include/job.h
@@ -3,7 +3,7 @@
  *   Functions and data structures representing the actual 'job' that the
  *   user wants us to execute.
  *
- * Copyright IBM Corp. 2001, 2006.
+ * Copyright IBM Corp. 2001, 2009.
  *
  * Author(s): Carsten Otte <cotte@de.ibm.com>
  *            Peter Oberparleiter <Peter.Oberparleiter@de.ibm.com>
@@ -13,6 +13,7 @@
 #define JOB_H
 
 #include "zipl.h"
+#include "disk.h"
 
 
 enum job_id {
@@ -27,6 +28,17 @@ enum job_id {
 	job_mvdump = 9,
 };
 
+struct job_target_data {
+	char* bootmap_dir;
+	char* targetbase;
+	disk_type_t targettype;
+	int targetcylinders;
+	int targetheads;
+	int targetsectors;
+	int targetblocksize;
+	blocknum_t targetoffset;
+};
+
 struct job_ipl_data {
 	char* image;
 	char* parmline;
@@ -94,7 +106,7 @@ struct job_menu_data {
 
 struct job_data {
 	enum job_id id;
-	char* bootmap_dir;
+	struct job_target_data target;
 	char* name;
 	union {
 		struct job_ipl_data ipl;
@@ -115,5 +127,6 @@ struct job_data {
 
 int job_get(int argc, char* argv[], struct job_data** data);
 void job_free(struct job_data* job);
+int type_from_target(char *target, disk_type_t *type);
 
 #endif /* not JOB_H */
Index: s390-tools-sles11/zipl/include/scan.h
===================================================================
--- s390-tools-sles11.orig/zipl/include/scan.h
+++ s390-tools-sles11/zipl/include/scan.h
@@ -2,7 +2,7 @@
  * s390-tools/zipl/include/scan.h
  *   Scanner for zipl.conf configuration files
  *
- * Copyright IBM Corp. 2001, 2006.
+ * Copyright IBM Corp. 2001, 2009.
  *
  * Author(s): Carsten Otte <cotte@de.ibm.com>
  *            Peter Oberparleiter <Peter.Oberparleiter@de.ibm.com>
@@ -15,7 +15,7 @@
 
 
 #define SCAN_SECTION_NUM		7
-#define SCAN_KEYWORD_NUM		14
+#define SCAN_KEYWORD_NUM		19
 
 enum scan_id {
 	scan_id_empty = 0,
@@ -40,6 +40,11 @@ enum scan_keyword_id {
 	scan_keyword_defaultmenu = 11,
 	scan_keyword_tape	= 12,
 	scan_keyword_mvdump	= 13,
+	scan_keyword_targetbase = 14,
+	scan_keyword_targettype = 15,
+	scan_keyword_targetgeometry = 16,
+	scan_keyword_targetblocksize = 17,
+	scan_keyword_targetoffset = 18,
 };
 
 enum scan_section_type {
@@ -53,6 +58,14 @@ enum scan_section_type {
 	section_mvdump		= 6,
 };
 
+enum scan_target_type {
+	target_type_invalid	= -1,
+	target_type_scsi	= 0,
+	target_type_fba		= 1,
+	target_type_ldl		= 2,
+	target_type_cdl		= 3,
+};
+
 enum scan_key_state {
 	req, /* Keyword is required */
 	opt, /* Keyword is optional */
@@ -100,6 +113,8 @@ int scan_find_section(struct scan_token*
 		      int offset);
 int scan_check_section_data(char* keyword[], int* line, char* name,
 			    int section_line, enum scan_section_type* type);
+int scan_check_target_data(char* keyword[], int* line);
 enum scan_section_type scan_get_section_type(char* keyword[]);
+enum scan_target_type scan_get_target_type(char *type);
 
 #endif /* not SCAN_H */
Index: s390-tools-sles11/zipl/man/zipl.8
===================================================================
--- s390-tools-sles11.orig/zipl/man/zipl.8
+++ s390-tools-sles11/zipl/man/zipl.8
@@ -1,4 +1,4 @@
-.TH ZIPL 8 "Apr 2006" "s390-tools"
+.TH ZIPL 8 "Nov 2009" "s390-tools"
 .SH NAME
 zipl \- boot loader for IBM S/390 and zSeries architectures
 
@@ -58,6 +58,45 @@ See the
 .BR zipl.conf (5)
 man page for details on how to use the boot menu.
 
+.B Logical devices
+
+zipl can be used to prepare logical devices (e.g. a linear device mapper target)
+for booting when the following requirements are met by the logical device setup:
+.IP "     -"
+all boot relevant files (i.e. kernel, ramdisk and parameter files) must be
+located on a logical device which is mapped to a single physical disk of a type
+supported by zipl (i.e. DASD or SCSI disk)
+.IP "     -"
+adjacent data blocks on the logical device must correspond to adjacent blocks on
+the physical device
+.IP "     -"
+access to the first blocks (starting at block 0) of the physical device must be
+given
+.PP
+Examples for logical device setups that are supported are linear and mirror
+mapping.
+
+When working with logical devices, zipl requires that the user provides more
+information about the target device:
+.IP "     -"
+device characteristics of the underlying physical device: disk type and format
+(e.g. ECKD CDL or FCP SCSI), disk geometry in case of ECKD DASDs and block size
+.IP "     -"
+target device offset, i.e. the number of blocks between the physical device
+start and the start of the logical device containing the filesystem with all
+boot relevant files
+.IP "     -"
+a device node which provides access to the first blocks of the device
+.PP
+If the user does not provide this information explicitly by parameters
+zipl automatically runs a driver specific helper script to obtain these data,
+e.g. zipl_helper.device-mapper.
+
+Note that zipl uses /proc/devices to determine the driver name for a given
+device. If the driver name cannot be determined the preparation of a logical
+device for boot might fail.
+This can be the case in a chroot environment when /proc is not mounted
+explicitly.
 
 .SH OPTIONS
 .TP
@@ -85,6 +124,53 @@ It is not possible to specify both this 
 or configuration section on the command line at the same time.
 
 .TP
+.BR "\-\-targetbase=<BASE DEVICE>"
+Install the actual boot loader on the device node specified by BASE DEVICE.
+
+This option is required when working with logical devices (see section
+"Logical devices" above).
+
+.TP
+.BR "\-\-targettype=<TARGET TYPE>"
+Assume that the physical device is of the specified type. Valid values are:
+.IP "         -" 12
+CDL: DASD disk with ECKD/compatible disk layout
+.IP "         -" 12
+LDL: DASD disk with ECDK/linux disk layout
+.IP "         -" 12
+FBA: FBA disk DASD
+.IP "         -" 12
+SCSI: SCSI disk
+.PP
+.IP " " 8
+This option is required when working with logical devices (see section
+"Logical devices" above).
+
+.TP
+.BR "\-\-targetgeometry=<CYLINDERS,HEADS,SECTORS>"
+Assume that the physical device has the specified number of cylinders, heads and
+sectors.
+
+This option is required when working with logical devices which are located on
+DASD ECKD disks (see section "Logical devices" above).
+
+.TP
+.BR "\-\-targetblocksize=<SIZE>"
+Assume that blocks on the physical device are SIZE bytes long.
+
+This option is required when working with logical devices (see section
+"Logical devices" above).
+
+.TP
+.BR "\-\-targetoffset=<OFFSET>"
+Assume that the logical device containing the directory specified by the
+--target option is located on the physical device starting at the block
+specified by OFFSET.
+
+This option is required when working with logical devices (see section
+"Logical devices" above).
+
+.TP
 .BR "\-T <TAPE DEVICE>" " or " "\-\-tape=<TAPE DEVICE>"
 Install bootloader on the specified <TAPE DEVICE>. Use this option instead
 of the 'target' option to prepare a tape device for IPL.
Index: s390-tools-sles11/zipl/man/zipl.conf.5
===================================================================
--- s390-tools-sles11.orig/zipl/man/zipl.conf.5
+++ s390-tools-sles11/zipl/man/zipl.conf.5
@@ -1,4 +1,4 @@
-.TH ZIPL.CONF 5 "Apr 2006" "s390-tools"
+.TH ZIPL.CONF 5 "Nov 2009" "s390-tools"
 
 .SH NAME
 zipl.conf \- zipl configuration file
@@ -490,6 +490,75 @@ The device on which the target directory
 device', i.e. it will be prepared for booting (initial program load).
 .PP
 
+.B targetbase
+=
+.I base\-device
+(configuration and menu)
+.IP
+.B Configuration and menu section:
+.br
+Specify the device which will be prepared for booting.
+
+This parameter is required when working with logical devices (see zipl(8)).
+.PP
+
+.B targettype
+=
+.I type
+(configuration and menu)
+.IP
+.B Configuration and menu section:
+.br
+Specify the device type for the physical device.
+.IP "         - " 12
+CDL: DASD disk with ECKD/compatible disk layout
+.IP "         - " 12
+LDL: DASD disk with ECDK/linux disk layout
+.IP "         - " 12
+FBA: FBA disk DASD
+.IP "         - " 12
+SCSI disk
+.PP
+.IP " " 8
+This parameter is required when working with logical devices (see zipl(8)).
+.PP
+
+.B targetgeometry
+=
+.I cylinders,heads,sectors
+(configuration and menu)
+.IP
+.B Configuration and menu section:
+.br
+Specify the number of cylinders, heads and sectors for the physical device.
+
+This parameter is required when working with logical devices (see zipl(8)).
+.PP
+
+.B targetblocksize
+=
+.I size
+(configuration and menu)
+.IP
+.B Configuration and menu section:
+.br
+Specify the number of bytes per block for the physical device.
+
+This parameter is required when working with logical devices (see zipl(8)).
+.PP
+
+.B targetoffset
+=
+.I offset
+(configuration and menu)
+.IP
+.B Configuration and menu section:
+.br
+Specify the starting block number of the logical device on the physical device.
+
+This parameter is required when working with logical devices (see zipl(8)).
+.PP
+
 .B timeout
 =
 .I menu-timeout
Index: s390-tools-sles11/zipl/src/Makefile
===================================================================
--- s390-tools-sles11.orig/zipl/src/Makefile
+++ s390-tools-sles11/zipl/src/Makefile
@@ -21,6 +21,7 @@ install: all
 	@echo $(INSTALL)
 	$(INSTALL) -d -m 755 $(BINDIR)
 	$(INSTALL) -c zipl $(BINDIR)
+	$(INSTALL) -m 755 $(wildcard zipl_helper.*) $(TOOLS_LIBDIR)
 
 clean:
 	rm -f *.o zipl
Index: s390-tools-sles11/zipl/src/bootmap.c
===================================================================
--- s390-tools-sles11.orig/zipl/src/bootmap.c
+++ s390-tools-sles11/zipl/src/bootmap.c
@@ -2,7 +2,7 @@
  * s390-tools/zipl/src/bootmap.c
  *   Functions to build the bootmap file.
  *
- * Copyright IBM Corp. 2001, 2006.
+ * Copyright IBM Corp. 2001, 2009.
  *
  * Author(s): Carsten Otte <cotte@de.ibm.com>
  *            Peter Oberparleiter <Peter.Oberparleiter@de.ibm.com>
@@ -298,7 +298,8 @@ struct component_loc {
 static int
 add_component_file(int fd, const char* filename, address_t load_address,
 		   off_t offset, void* component, int add_files,
-		   struct disk_info* info, struct component_loc *location)
+		   struct disk_info* info, struct job_target_data* target,
+		   struct component_loc *location)
 {
 	struct disk_info* file_info;
 	struct component_loc loc;
@@ -336,7 +337,7 @@ add_component_file(int fd, const char* f
 		}
 	} else {
 		/* Make sure file is on correct device */
-		rc = disk_get_info_from_file(filename, &file_info);  
+		rc = disk_get_info_from_file(filename, target, &file_info);
 		if (rc)
 			return -1;
 		if (file_info->device != info->device) {
@@ -472,7 +473,7 @@ check_component_overlap(const char *name
 static int
 add_ipl_program(int fd, struct job_ipl_data* ipl, disk_blockptr_t* program,
 		int verbose, int add_files, component_header_type type,
-		struct disk_info* info)
+		struct disk_info* info, struct job_target_data* target)
 {
 	struct stat stats;
 	void* table;
@@ -500,7 +501,7 @@ add_ipl_program(int fd, struct job_ipl_d
 	}
 	rc = add_component_file(fd, ipl->image, ipl->image_addr,
 				KERNEL_HEADER_SIZE, VOID_ADD(table, offset),
-				add_files, info, &comp_loc[0]);
+				add_files, info, target, &comp_loc[0]);
 	if (rc) {
 		error_text("Could not add image file '%s'", ipl->image);
 		free(table);
@@ -542,7 +543,7 @@ add_ipl_program(int fd, struct job_ipl_d
 		rc = add_component_file(fd, ipl->ramdisk,
 					ipl->ramdisk_addr, 0,
 					VOID_ADD(table, offset),
-					add_files, info, &comp_loc[2]);
+					add_files, info, target, &comp_loc[2]);
 		if (rc) {
 			error_text("Could not add ramdisk '%s'",
 				   ipl->ramdisk);
@@ -594,7 +595,8 @@ add_ipl_program(int fd, struct job_ipl_d
 static int
 add_segment_program(int fd, struct job_segment_data* segment,
 		    disk_blockptr_t* program, int verbose, int add_files,
-		    component_header_type type, struct disk_info* info)
+		    component_header_type type, struct disk_info* info,
+		    struct job_target_data* target)
 {
 	const char *comp_name[1] = {"segment file"};
 	struct component_loc comp_loc[1];
@@ -618,7 +620,7 @@ add_segment_program(int fd, struct job_s
 	}
 	rc = add_component_file(fd, segment->segment, segment->segment_addr, 0,
 				VOID_ADD(table, offset), add_files, info,
-				&comp_loc[0]);
+				target, &comp_loc[0]);
 	if (rc) {
 		error_text("Could not add segment file '%s'",
 			   segment->segment);
@@ -661,14 +663,15 @@ create_dump_fs_parmline(const char* parm
 
 static int
 get_dump_fs_parmline(char* partition, char* parameters, uint64_t mem,
-		     char** result, struct disk_info* target_info)
+		     struct disk_info* target_info,
+		     struct job_target_data* target, char** result)
 {
 	char* buffer;
 	struct disk_info* info;
 	int rc;
 
 	/* Get information about partition */
-	rc = disk_get_info(partition, &info);
+	rc = disk_get_info(partition, target, &info);
 	if (rc) {
 		error_text("Could not get information for dump partition '%s'",
 			   partition);
@@ -700,7 +703,7 @@ static int
 add_dump_fs_program(int fd, struct job_dump_fs_data* dump_fs,
 		    disk_blockptr_t* program, int verbose,
 		    int add_files, component_header_type type,
-		    struct disk_info* info)
+		    struct disk_info* info, struct job_target_data* target)
 {
 	struct job_ipl_data ipl;
 	int rc;
@@ -725,12 +728,12 @@ add_dump_fs_program(int fd, struct job_d
 
 	/* Get file system dump parmline */
 	rc = get_dump_fs_parmline(dump_fs->partition, dump_fs->parmline,
-				  dump_fs->mem, &ipl.parmline, info);
+				  dump_fs->mem, info, target, &ipl.parmline);
 	if (rc)
 		return rc;
 	ipl.parm_addr = DEFAULT_PARMFILE_ADDRESS;
 	return add_ipl_program(fd, &ipl, program, verbose, 1,
-			       type, info);
+			       type, info, target);
 }
 
 
@@ -763,7 +766,7 @@ build_program_table(int fd, struct job_d
 		rc = add_ipl_program(fd, &job->data.ipl, &table[0],
 				     verbose || job->command_line,
 				     job->add_files, component_header_ipl,
-				     info);
+				     info, &job->target);
 		break;
 	case job_segment:
 		if (job->command_line)
@@ -774,7 +777,7 @@ build_program_table(int fd, struct job_d
 		rc = add_segment_program(fd, &job->data.segment, &table[0],
 					 verbose || job->command_line,
 					 job->add_files, component_header_ipl,
-					 info);
+					 info, &job->target);
 		break;
 	case job_dump_fs:
 		if (job->command_line)
@@ -785,7 +788,7 @@ build_program_table(int fd, struct job_d
 		rc = add_dump_fs_program(fd, &job->data.dump_fs, &table[0],
 					 verbose || job->command_line,
 					 job->add_files, component_header_dump,
-					 info);
+					 info, &job->target);
 		break;
 	case job_menu:
 		printf("Building menu '%s'\n", job->name);
@@ -804,7 +807,7 @@ build_program_table(int fd, struct job_d
 					&table[job->data.menu.entry[i].pos],
 					verbose || job->command_line,
 					job->add_files,	component_header_ipl,
-					info);
+					info, &job->target);
 				break;
 			case job_dump_fs:
 				printf("Adding #%d: fs-dump section '%s'%s\n",
@@ -818,7 +821,7 @@ build_program_table(int fd, struct job_d
 					&table[job->data.menu.entry[i].pos],
 					verbose || job->command_line,
 					job->add_files, component_header_dump,
-					info);
+					info, &job->target);
 				break;
 			case job_print_usage:
 			case job_print_version:
@@ -890,7 +893,8 @@ bootmap_create(struct job_data* job, dis
 	int rc;
 
 	/* Get full path of bootmap file */
-	filename = misc_make_path(job->bootmap_dir, BOOTMAP_TEMPLATE_FILENAME);
+	filename = misc_make_path(job->target.bootmap_dir,
+			BOOTMAP_TEMPLATE_FILENAME);
 	if (filename == NULL)
 		return -1;
 	/* Create temporary bootmap file */
@@ -904,32 +908,14 @@ bootmap_create(struct job_data* job, dis
 	/* Retrieve target device information. Note that we have to
 	 * call disk_get_info_from_file() to also get the file system
 	 * block size. */
-	rc = disk_get_info_from_file(filename, &info);
+	rc = disk_get_info_from_file(filename, &job->target, &info);
 	if (rc) {
 		close(fd);
 		free(filename);
 		return -1;
 	}
 	/* Check for supported disk and driver types */
-	switch (info->type) {
-	case disk_type_scsi:
-		break;
-	case disk_type_fba:
-	case disk_type_eckd_classic:
-	case disk_type_eckd_compatible:
-		/* Check for valid CHS geometry data. */
-		if ((info->geo.cylinders == 0) || (info->geo.heads == 0) ||
-		    (info->geo.sectors == 0)) {
-			error_reason("Invalid disk geometry (CHS=%d/%d/%d)",
-				     info->geo.cylinders, info->geo.heads,
-				     info->geo.sectors);
-			disk_free_info(info);
-			close(fd);
-			free(filename);
-			return -1;
-		}
-		break;
-	case disk_type_diag:
+	if ((info->source == source_auto) && (info->type == disk_type_diag)) {
 		error_reason("Unsupported disk type (%s)",
 			     disk_get_type_name(info->type));
 		disk_free_info(info);
@@ -959,7 +945,7 @@ bootmap_create(struct job_data* job, dis
 			return rc;
 		}
 	}
-	printf("Building bootmap in '%s'%s\n", job->bootmap_dir,
+	printf("Building bootmap in '%s'%s\n", job->target.bootmap_dir,
 	       job->add_files ? " (files will be added to bootmap file)" :
 	       "");
 	/* Write bootmap header */
@@ -1046,7 +1032,8 @@ bootmap_create(struct job_data* job, dis
 				"file %s!\n", filename);
 	} else {
 		/* Rename to final bootmap name */
-		mapname = misc_make_path(job->bootmap_dir, BOOTMAP_FILENAME);
+		mapname = misc_make_path(job->target.bootmap_dir,
+				BOOTMAP_FILENAME);
 		if (mapname == NULL) {
 			misc_free_temp_dev(device);
 			disk_free_info(info);
Index: s390-tools-sles11/zipl/src/disk.c
===================================================================
--- s390-tools-sles11.orig/zipl/src/disk.c
+++ s390-tools-sles11/zipl/src/disk.c
@@ -2,13 +2,14 @@
  * s390-tools/zipl/src/disk.c
  *   Functions to handle disk layout specific operations.
  *
- * Copyright IBM Corp. 2001, 2006.
+ * Copyright IBM Corp. 2001, 2009.
  *
  * Author(s): Carsten Otte <cotte@de.ibm.com>
  *            Peter Oberparleiter <Peter.Oberparleiter@de.ibm.com>
  */
 
 #include "disk.h"
+#include "job.h"
 
 #include <sys/types.h>
 #include <sys/stat.h>
@@ -84,16 +85,34 @@ disk_determine_dasd_type(struct disk_inf
 	return 0;
 }
 
+/* Return non-zero for ECKD type. */
 int
-disk_get_info(const char* device, struct disk_info** info)
+disk_is_eckd(disk_type_t type)
+{
+	return (type == disk_type_eckd_classic ||
+		type == disk_type_eckd_compatible);
+}
+
+int
+disk_get_info(const char* device, struct job_target_data* target,
+	      struct disk_info** info)
 {
 	struct stat stats;
+	struct stat script_stats;
 	struct proc_part_entry part_entry;
 	struct proc_dev_entry dev_entry;
 	struct dasd_information dasd_info;
 	struct disk_info *data;
 	int fd;
 	long devsize;
+	FILE *fh;
+	char *script_pre = "/lib/s390-tools/zipl_helper.";
+	char script_file[80];
+	char ppn_cmd[80];
+	char buffer[80];
+	char value[40];
+	int majnum, minnum;
+	int checkparm;
 
 	/* Get file information */
 	if (stat(device, &stats)) {
@@ -113,37 +132,140 @@ disk_get_info(const char* device, struct
 		return -1;
 	}
 	memset((void *) data, 0, sizeof(struct disk_info));
-	/* Get disk geometry. Note: geo.start contains a sector number
-	 * offset measured in physical blocks, not sectors (512 bytes) */
-	if (ioctl(fd, HDIO_GETGEO, &data->geo)) {
-		error_reason("Could not get disk geometry");
-		free(data);
-		close(fd);
-		return -1;
-	}
 	/* Try to get device driver name */
 	if (proc_dev_get_entry(stats.st_rdev, 1, &dev_entry) == 0) {
 		data->drv_name = misc_strdup(dev_entry.name);
 		proc_dev_free_entry(&dev_entry);
+	} else {
+		fprintf(stderr, "Warning: Could not determine driver name for "
+			"major %d from /proc/devices\n", major(stats.st_rdev));
+		fprintf(stderr, "Warning: Preparing a logical device for boot "
+			"might fail\n");
+	}
+	data->source = source_user;
+	/* Check if targetbase script is available */
+	strcpy(script_file, script_pre);
+	if (data->drv_name) {
+		strcat(script_file, data->drv_name);
+	}
+	if ((target->targetbase == NULL) &&
+	    (!stat(script_file, &script_stats))) {
+		data->source = source_script;
+		/* Run targetbase script */
+		strcpy(ppn_cmd, script_file);
+		strcat(ppn_cmd, " ");
+		strcat(ppn_cmd, target->bootmap_dir);
+		printf("Run %s\n", ppn_cmd);
+		fh = popen(ppn_cmd, "r");
+		if (fh == NULL) {
+			error_reason("Failed to run popen(%s,\"r\",)");
+			goto out_close;
+		}
+		checkparm = 0;
+		while (fgets(buffer, 80, fh) != NULL) {
+			if (sscanf(buffer, "targetbase=%s", value) == 1) {
+				target->targetbase = misc_strdup(value);
+				checkparm++;
+			}
+			if (sscanf(buffer, "targettype=%s", value) == 1) {
+				type_from_target(value, &target->targettype);
+				checkparm++;
+			}
+			if (sscanf(buffer, "targetgeometry=%s", value) == 1) {
+				target->targetcylinders =
+					atoi(strtok(value, ","));
+				target->targetheads = atoi(strtok(NULL, ","));
+				target->targetsectors = atoi(strtok(NULL, ","));
+				checkparm++;
+			}
+			if (sscanf(buffer, "targetblocksize=%s", value) == 1) {
+				target->targetblocksize = atoi(value);
+				checkparm++;
+			}
+			if (sscanf(buffer, "targetoffset=%s", value) == 1) {
+				target->targetoffset = atol(value);
+				checkparm++;
+			}
+		}
+		switch (pclose(fh)) {
+		case 0 :
+			/* success */
+			break;
+		case -1 :
+			error_reason("Failed to run pclose");
+			goto out_close;
+		default :
+			error_reason("Script could not determine target "
+				     "parameters");
+			goto out_close;
+		}
+		if ((!disk_is_eckd(target->targettype) && checkparm < 4) ||
+		    (disk_is_eckd(target->targettype) && checkparm != 5)) {
+			error_reason("Target parameters missing from script");
+			goto out_close;
+		}
 	}
-	/* Get DASD information */
-	if (ioctl(fd, BIODASDINFO, &dasd_info))
+
+	/* Get disk geometry. Note: geo.start contains a sector number
+	 * offset measured in physical blocks, not sectors (512 bytes) */
+	if (target->targetbase != NULL) {
+		data->geo.heads     = target->targetheads;
+		data->geo.sectors   = target->targetsectors;
+		data->geo.cylinders = target->targetcylinders;
+		data->geo.start     = target->targetoffset;
+	} else {
+		data->source = source_auto;
+		if (ioctl(fd, HDIO_GETGEO, &data->geo)) {
+			error_reason("Could not get disk geometry");
+			goto out_close;
+		}
+	}
+	if ((data->source == source_user) || (data->source == source_script)) {
 		data->devno = -1;
-	else
-		data->devno = dasd_info.devno;
-	/* Get physical block size */
-	if (ioctl(fd, BLKSSZGET, &data->phy_block_size)) {
-		error_reason("Could not get blocksize");
-		free(data);
-		close(fd);
-		return -1;
+		data->phy_block_size = target->targetblocksize;
+	} else {
+		/* Get DASD information */
+		if (ioctl(fd, BIODASDINFO, &dasd_info))
+			data->devno = -1;
+		else
+			data->devno = dasd_info.devno;
+		/* Get physical block size */
+		if (ioctl(fd, BLKSSZGET, &data->phy_block_size)) {
+			error_reason("Could not get blocksize");
+			goto out_close;
+		}
 	}
 	/* Get size of device in sectors (512 byte) */
 	if (ioctl(fd, BLKGETSIZE, &devsize)) {
 		error_reason("Could not get device size");
-		close(fd);
-		free(data);
-		return -1;
+		goto out_close;
+	}
+	if ((data->source == source_user) || (data->source == source_script)) {
+		data->type = target->targettype;
+		data->partnum = 0;
+		/* Get file information */
+		if (sscanf(target->targetbase, "%d:%d", &majnum, &minnum)
+		    == 2) {
+			data->device = makedev(majnum, minnum);
+			data->targetbase = defined_as_device;
+		}
+		else {
+			if (stat(target->targetbase, &stats)) {
+				error_reason(strerror(errno));
+				error_text("Could not get information for "
+					   "file '%s'", target->targetbase);
+				goto out_close;
+			}
+			if (!S_ISBLK(stats.st_mode)) {
+				error_reason("Target base device '%s' is not "
+					     "a block device",
+					     target->targetbase);
+				goto out_close;
+			}
+			data->device = stats.st_rdev;
+			data->targetbase = defined_as_name;
+		}
+		goto type_determined;
 	}
 	/* Determine disk type */
 	if (!data->drv_name) {
@@ -194,6 +316,15 @@ disk_get_info(const char* device, struct
 		goto out_close;
 	}
 
+type_determined:
+	/* Check for valid CHS geometry data. */
+	if (disk_is_eckd(data->type) && (data->geo.cylinders == 0 ||
+	    data->geo.heads == 0 || data->geo.sectors == 0)) {
+		error_reason("Invalid disk geometry (CHS=%d/%d/%d)",
+			     data->geo.cylinders, data->geo.heads,
+			     data->geo.sectors);
+		goto out_close;
+	}
 	/* Convert device size to size in physical blocks */
 	data->phy_blocks = devsize / (data->phy_block_size / 512);
 	if (data->partnum != 0)
@@ -221,7 +352,8 @@ out_close:
 
 
 int
-disk_get_info_from_file(const char* filename, struct disk_info** info)
+disk_get_info_from_file(const char* filename, struct job_target_data* target,
+			struct disk_info** info)
 {
 	struct stat stats;
 	char* device;
@@ -251,7 +383,7 @@ disk_get_info_from_file(const char* file
 	if (rc)
 		return -1;
 	/* Get device info */
-	rc = disk_get_info(device, info);
+	rc = disk_get_info(device, target, info);
 	if (rc == 0)
 		(*info)->fs_block_size = blocksize;
 	/* Clean up */
@@ -523,8 +655,14 @@ disk_is_large_volume(struct disk_info* i
 void
 disk_print_info(struct disk_info* info)
 {
+	char footnote[4] = "";
+	if ((info->source == source_user) || (info->source == source_script))
+		strcpy(footnote, " *)");
+
 	printf("  Device..........................: ");
 	disk_print_devt(info->device);
+	if (info->targetbase == defined_as_device)
+		printf("%s", footnote);
 	printf("\n");
 	if (info->partnum != 0) {
 		printf("  Partition.......................: ");
@@ -532,45 +670,55 @@ disk_print_info(struct disk_info* info)
 		printf("\n");
 	}
 	if (info->name) {
-		printf("  Device name.....................: %s\n",
+		printf("  Device name.....................: %s",
 		       info->name);
+		if (info->targetbase == defined_as_name)
+			printf("%s", footnote);
+		printf("\n");
 	}
 	if (info->drv_name) {
 		printf("  Device driver name..............: %s\n",
 		       info->drv_name);
 	}
-	if ((info->type == disk_type_fba) ||
-	    (info->type == disk_type_diag) ||
-	    (info->type == disk_type_eckd_classic) ||
-	    (info->type == disk_type_eckd_compatible)) {
+	if (((info->type == disk_type_fba) ||
+	     (info->type == disk_type_diag) ||
+	     (info->type == disk_type_eckd_classic) ||
+	     (info->type == disk_type_eckd_compatible)) &&
+	     (info->source == source_auto)) {
 		printf("  DASD device number..............: %04x\n",
 		       info->devno);
 	}
 	printf("  Type............................: disk %s\n",
 	       (info->partnum != 0) ? "partition" : "device");
-	printf("  Disk layout.....................: %s\n",
-	       disk_get_type_name(info->type));
-	printf("  Geometry - heads................: %d\n",
-	       info->geo.heads);
-	printf("  Geometry - sectors..............: %d\n",
-	       info->geo.sectors);
-	if (disk_is_large_volume(info)) {
-		/* ECKD large volume. There is not enough information
-		 * available in INFO to calculate disk cylinder size. */
-		printf("  Geometry - cylinders............: > 65534\n");
-	} else {
-		printf("  Geometry - cylinders............: %d\n",
-		       info->geo.cylinders);
+	printf("  Disk layout.....................: %s%s\n",
+	       disk_get_type_name(info->type), footnote);
+	if (disk_is_eckd(info->type)) {
+		printf("  Geometry - heads................: %d%s\n",
+		       info->geo.heads, footnote);
+		printf("  Geometry - sectors..............: %d%s\n",
+		       info->geo.sectors, footnote);
+		if (disk_is_large_volume(info)) {
+			/* ECKD large volume. There is not enough information
+			 * available in INFO to calculate disk cylinder size. */
+			printf("  Geometry - cylinders............: > 65534\n");
+		} else {
+			printf("  Geometry - cylinders............: %d%s\n",
+			       info->geo.cylinders, footnote);
+		}
 	}
-	printf("  Geometry - start................: %ld\n",
-	       info->geo.start);
+	printf("  Geometry - start................: %ld%s\n",
+	       info->geo.start, footnote);
 	if (info->fs_block_size >= 0)
 		printf("  File system block size..........: %d\n",
 		       info->fs_block_size);
-	printf("  Physical block size.............: %d\n",
-	       info->phy_block_size);
+	printf("  Physical block size.............: %d%s\n",
+	       info->phy_block_size, footnote);
 	printf("  Device size in physical blocks..: %ld\n",
 	       (long) info->phy_blocks);
+	if (info->source == source_user)
+		printf("  *) Data provided by user.\n");
+	if (info->source == source_script)
+		printf("  *) Data provided by script.\n");
 }
 
 
Index: s390-tools-sles11/zipl/src/install.c
===================================================================
--- s390-tools-sles11.orig/zipl/src/install.c
+++ s390-tools-sles11/zipl/src/install.c
@@ -2,7 +2,7 @@
  * s390-tools/zipl/src/install.c
  *   Functions handling the installation of the boot loader code onto disk.
  *
- * Copyright IBM Corp. 2001, 2006.
+ * Copyright IBM Corp. 2001, 2009.
  *
  * Author(s): Carsten Otte <cotte@de.ibm.com>
  *            Peter Oberparleiter <Peter.Oberparleiter@de.ibm.com>
@@ -944,7 +944,7 @@ install_dump_tape(int fd, uint64_t mem)
 
 
 int
-install_dump(const char* device, uint64_t mem)
+install_dump(const char* device, struct job_target_data* target, uint64_t mem)
 {
 	struct disk_info* info;
 	char* tempdev;
@@ -985,7 +985,7 @@ install_dump(const char* device, uint64_
 	}
 	close(fd);
 	/* This is a disk device */
-	rc = disk_get_info(device, &info);
+	rc = disk_get_info(device, target, &info);
 	if (rc) {
 		error_text("Could not get information for dump target "
 			   "'%s'", device);
@@ -1063,7 +1063,8 @@ install_dump(const char* device, uint64_
 
 
 int
-install_mvdump(char* const device[], int count, uint64_t mem, uint8_t force)
+install_mvdump(char* const device[], struct job_target_data* target, int count,
+	       uint64_t mem, uint8_t force)
 {
 	struct disk_info* info[MAX_DUMP_VOLUMES] = {0};
 	struct mvdump_parm_table parm = {0};
@@ -1094,7 +1095,7 @@ install_mvdump(char* const device[], int
 		}
 		close(fd);
 		/* This is a disk device */
-		rc = disk_get_info(device[i], &info[i]);
+		rc = disk_get_info(device[i], target, &info[i]);
 		if (rc) {
 			error_text("Could not get information for dump target "
 				   "'%s'", device[i]);
Index: s390-tools-sles11/zipl/src/job.c
===================================================================
--- s390-tools-sles11.orig/zipl/src/job.c
+++ s390-tools-sles11/zipl/src/job.c
@@ -3,7 +3,7 @@
  *   Functions and data structures representing the actual 'job' that the
  *   user wants us to execute.
  *
- * Copyright IBM Corp. 2001, 2006.
+ * Copyright IBM Corp. 2001, 2009.
  *
  * Author(s): Carsten Otte <cotte@de.ibm.com>
  *            Peter Oberparleiter <Peter.Oberparleiter@de.ibm.com>
@@ -29,6 +29,11 @@
 static struct option options[] = {
 	{ "config",		required_argument,	NULL, 'c'},
 	{ "target",		required_argument,	NULL, 't'},
+	{ "targetbase",         required_argument,      NULL, 0xaa},
+	{ "targettype",         required_argument,      NULL, 0xab},
+	{ "targetgeometry",     required_argument,      NULL, 0xac},
+	{ "targetblocksize",    required_argument,      NULL, 0xad},
+	{ "targetoffset",       required_argument,      NULL, 0xae},
 	{ "image", 		required_argument,	NULL, 'i'},
 	{ "ramdisk",		required_argument,	NULL, 'r'},
 	{ "parmfile",		required_argument,	NULL, 'p'},
@@ -148,6 +153,32 @@ get_command_line(int argc, char* argv[],
 			rc = store_option(&cmdline, scan_keyword_target,
 					  optarg);
 			break;
+		case 0xaa:
+			is_keyword = 1;
+			rc = store_option(&cmdline, scan_keyword_targetbase,
+					  optarg);
+			break;
+		case 0xab:
+			is_keyword = 1;
+			rc = store_option(&cmdline, scan_keyword_targettype,
+					  optarg);
+			break;
+		case 0xac:
+			is_keyword = 1;
+			rc = store_option(&cmdline, scan_keyword_targetgeometry,
+					  optarg);
+			break;
+		case 0xad:
+			is_keyword = 1;
+			rc = store_option(&cmdline,
+					  scan_keyword_targetblocksize,
+					  optarg);
+			break;
+		case 0xae:
+			is_keyword = 1;
+			rc = store_option(&cmdline, scan_keyword_targetoffset,
+					  optarg);
+			break;
 		case 'T':
 			is_keyword = 1;
 			rc = store_option(&cmdline, scan_keyword_tape,
@@ -256,6 +287,9 @@ get_command_line(int argc, char* argv[],
 			}
 			return rc;
 		}
+		rc = scan_check_target_data(cmdline.data, NULL);
+		if (rc)
+			return rc;
 	}
 	*line = cmdline;
 	return 0;
@@ -263,6 +297,13 @@ get_command_line(int argc, char* argv[],
 
 
 static void
+free_target_data(struct job_target_data* data)
+{
+	if (data->targetbase != NULL)
+		free(data->targetbase);
+}
+
+static void
 free_ipl_data(struct job_ipl_data* data)
 {
 	if (data->image != NULL)
@@ -356,8 +397,9 @@ free_mvdump_data(struct job_mvdump_data*
 void
 job_free(struct job_data* job)
 {
-	if (job->bootmap_dir != NULL)
-		free(job->bootmap_dir);
+	if (job->target.bootmap_dir != NULL)
+		free(job->target.bootmap_dir);
+	free_target_data(&job->target);
 	if (job->name != NULL)
 		free(job->name);
 	switch (job->id) {
@@ -635,16 +677,16 @@ check_job_data(struct job_data* job)
 	int rc;
 
 	/* Check for missing information */
-	if (job->bootmap_dir != NULL) {
-		rc = misc_check_writable_directory(job->bootmap_dir);
+	if (job->target.bootmap_dir != NULL) {
+		rc = misc_check_writable_directory(job->target.bootmap_dir);
 		if (rc) {
 			if (job->name == NULL) {
 				error_text("Target directory '%s'",
-					   job->bootmap_dir);
+					   job->target.bootmap_dir);
 			} else {
 				error_text("Target directory '%s' in section "
 					   "'%s'",
-					   job->bootmap_dir, job->name);
+					   job->target.bootmap_dir, job->name);
 			}
 			return rc;
 		}
@@ -822,6 +864,28 @@ get_parmline(char* filename, char* line,
 
 #define	MEGABYTE_MASK	(1024LL * 1024LL - 1LL)
 
+int
+type_from_target(char *target, disk_type_t *type)
+{
+	switch (scan_get_target_type(target)) {
+	case target_type_scsi:
+		*type = disk_type_scsi;
+		return 0;
+	case target_type_fba:
+		*type = disk_type_fba;
+		return 0;
+	case target_type_ldl:
+		*type = disk_type_eckd_classic;
+		return 0;
+	case target_type_cdl:
+		*type = disk_type_eckd_compatible;
+		return 0;
+	default:
+		return -1;
+	}
+}
+
+
 static int
 get_job_from_section_data(char* data[], struct job_data* job, char* section)
 {
@@ -832,10 +896,39 @@ get_job_from_section_data(char* data[], 
 		/* IPL job */
 		job->id = job_ipl;
 		/* Fill in name of bootmap directory */
-		job->bootmap_dir = misc_strdup(data[(int) scan_keyword_target]);
-		if (job->bootmap_dir == NULL)
+		job->target.bootmap_dir =
+			misc_strdup(data[(int) scan_keyword_target]);
+		if (job->target.bootmap_dir == NULL)
 			return -1;
+		/* Fill in target */
+		if (data[(int) scan_keyword_targetbase] != NULL) {
+			job->target.targetbase =
+				misc_strdup(data[(int)
+				scan_keyword_targetbase]);
+			if (job->target.targetbase == NULL)
+				return -1;
+		}
+		if (data[(int) scan_keyword_targettype] != NULL) {
+			if (type_from_target(
+				data[(int) scan_keyword_targettype],
+				&job->target.targettype))
+				return -1;
+		}
+		if (data[(int) scan_keyword_targetgeometry] != NULL) {
+			job->target.targetcylinders =
+				atoi(strtok(data[(int)
+				scan_keyword_targetgeometry], ","));
+			job->target.targetheads = atoi(strtok(NULL, ","));
+			job->target.targetsectors = atoi(strtok(NULL, ","));
+		}
+		if (data[(int) scan_keyword_targetblocksize] != NULL)
+			job->target.targetblocksize =
+				atoi(data[(int) scan_keyword_targetblocksize]);
+		if (data[(int) scan_keyword_targetoffset] != NULL)
+			job->target.targetoffset =
+				atol(data[(int) scan_keyword_targetoffset]);
 		/* Fill in name and address of image file */
+
 		job->data.ipl.image = misc_strdup(
 					data[(int) scan_keyword_image]);
 		if (job->data.ipl.image == NULL)
@@ -903,8 +996,9 @@ get_job_from_section_data(char* data[], 
 		/* SEGMENT LOAD job */
 		job->id = job_segment;
 		/* Fill in name of bootmap directory */
-		job->bootmap_dir = misc_strdup(data[(int) scan_keyword_target]);
-		if (job->bootmap_dir == NULL)
+		job->target.bootmap_dir =
+			misc_strdup(data[(int) scan_keyword_target]);
+		if (job->target.bootmap_dir == NULL)
 			return -1;
 		/* Fill in segment filename */
 		job->data.segment.segment =
@@ -940,8 +1034,9 @@ get_job_from_section_data(char* data[], 
 		/* DUMP TO FILESYSTEM job */
 		job->id = job_dump_fs;
 		/* Fill in name of bootmap directory */
-		job->bootmap_dir = misc_strdup(data[(int) scan_keyword_target]);
-		if (job->bootmap_dir == NULL)
+		job->target.bootmap_dir =
+			misc_strdup(data[(int) scan_keyword_target]);
+		if (job->target.bootmap_dir == NULL)
 			return -1;
 		/* Fill in partition name */
 		job->data.dump_fs.partition =
@@ -1046,11 +1141,43 @@ get_menu_job(struct scan_token* scan, ch
 					  atol(scan[i].content.keyword.value);
 					break;
 				case scan_keyword_target:
-					job->bootmap_dir = misc_strdup(
+					job->target.bootmap_dir = misc_strdup(
+						scan[i].content.keyword.value);
+					if (job->target.bootmap_dir == NULL)
+						return -1;
+					break;
+				case scan_keyword_targetbase:
+					job->target.targetbase = misc_strdup(
 						scan[i].content.keyword.value);
-					if (job->bootmap_dir == NULL)
+					if (job->target.targetbase == NULL)
+						return -1;
+					break;
+				case scan_keyword_targettype:
+					if (type_from_target(
+						scan[i].content.keyword.value,
+						&job->target.targettype))
 						return -1;
 					break;
+				case scan_keyword_targetgeometry:
+					job->target.targetcylinders =
+						atoi(strtok(
+						scan[i].content.keyword.value,
+						","));
+					job->target.targetheads =
+						atoi(strtok(NULL, ","));
+					job->target.targetsectors =
+						atoi(strtok(NULL, ","));
+					break;
+				case scan_keyword_targetblocksize:
+					job->target.targetblocksize =
+						atoi(
+						scan[i].content.keyword.value);
+					break;
+				case scan_keyword_targetoffset:
+					job->target.targetoffset =
+						atol(
+						scan[i].content.keyword.value);
+					break;
 				default:
 					/* Should not happen */
 					break;
Index: s390-tools-sles11/zipl/src/scan.c
===================================================================
--- s390-tools-sles11.orig/zipl/src/scan.c
+++ s390-tools-sles11/zipl/src/scan.c
@@ -2,7 +2,7 @@
  * s390-tools/zipl/src/scan.c
  *   Scanner for zipl.conf configuration files
  *
- * Copyright IBM Corp. 2001, 2006.
+ * Copyright IBM Corp. 2001, 2009.
  *
  * Author(s): Carsten Otte <cotte@de.ibm.com>
  *            Peter Oberparleiter <Peter.Oberparleiter@de.ibm.com>
@@ -31,21 +31,33 @@ enum scan_key_state scan_key_table[SCAN_
 /*	 defa dump dump imag para parm ramd segm targ prom time defa tape mv
  *	 ult  to   tofs e    mete file isk  ent  et   pt   out  ultm      dump
  *			     rs                                 enu
+ *
+ *       targ targ targ targ targ
+ *       etba etty etge etbl etof
+ *       se   pe   omet ocks fset
+ *                 ry   ize
  */
 /* defaultboot	*/
-	{opt, inv, inv, inv, inv, inv, inv, inv, inv, inv, inv, opt, inv, inv},
+	{opt, inv, inv, inv, inv, inv, inv, inv, inv, inv, inv, opt, inv, inv,
+	 inv, inv, inv, inv, inv},
 /* ipl		*/
-	{inv, inv, inv, req, opt, opt, opt, inv, req, inv, inv, inv, inv, inv},
+	{inv, inv, inv, req, opt, opt, opt, inv, req, inv, inv, inv, inv, inv,
+	 opt, opt, opt, opt, opt},
 /* segment load */
-	{inv, inv, inv, inv, inv, inv, inv, req, req, inv, inv, inv, inv, inv},
+	{inv, inv, inv, inv, inv, inv, inv, req, req, inv, inv, inv, inv, inv,
+	 inv, inv, inv, inv, inv},
 /* part dump	*/
-	{inv, req, inv, inv, inv, inv, inv, inv, opt, inv, inv, inv, inv, inv},
+	{inv, req, inv, inv, inv, inv, inv, inv, opt, inv, inv, inv, inv, inv,
+	 inv, inv, inv, inv, inv},
 /* fs dump	*/
-	{inv, inv, req, inv, opt, opt, inv, inv, req, inv, inv, inv, inv, inv},
+	{inv, inv, req, inv, opt, opt, inv, inv, req, inv, inv, inv, inv, inv,
+	 inv, inv, inv, inv, inv},
 /* ipl tape	*/
-	{inv, inv, inv, req, opt, opt, opt, inv, inv, inv, inv, inv, req, inv},
+	{inv, inv, inv, req, opt, opt, opt, inv, inv, inv, inv, inv, req, inv,
+	 inv, inv, inv, inv, inv},
 /* multi volume dump	*/
-	{inv, inv, inv, inv, inv, inv, inv, inv, inv, inv, inv, inv, inv, req}
+	{inv, inv, inv, inv, inv, inv, inv, inv, inv, inv, inv, inv, inv, req,
+	 inv, inv, inv, inv, inv}
 };
 
 /* Mapping of keyword IDs to strings */
@@ -63,13 +75,17 @@ static const struct {
 	{ "parmfile", scan_keyword_parmfile },
 	{ "ramdisk", scan_keyword_ramdisk },
 	{ "segment", scan_keyword_segment },
+	{ "targetbase", scan_keyword_targetbase},
+	{ "targettype", scan_keyword_targettype},
+	{ "targetgeometry", scan_keyword_targetgeometry},
+	{ "targetblocksize", scan_keyword_targetblocksize},
+	{ "targetoffset", scan_keyword_targetoffset},
 	{ "target", scan_keyword_target},
 	{ "prompt", scan_keyword_prompt},
 	{ "timeout", scan_keyword_timeout},
 	{ "tape", scan_keyword_tape}
 };
 
-
 /* Retrieve name of keyword identified by ID. */
 char *
 scan_keyword_name(enum scan_keyword_id id)
@@ -608,6 +624,19 @@ scan_get_section_type(char* keyword[])
 		return section_invalid;
 }
 
+enum scan_target_type
+scan_get_target_type(char *type)
+{
+	if (strcasecmp(type, "SCSI") == 0)
+		return target_type_scsi;
+	else if (strcasecmp(type, "FBA") == 0)
+		return target_type_fba;
+	else if (strcasecmp(type, "LDL") == 0)
+		return target_type_ldl;
+	else if (strcasecmp(type, "CDL") == 0)
+		return target_type_cdl;
+	return target_type_invalid;
+}
 
 #define MAX(a,b)	((a)>(b)?(a):(b))
 
@@ -643,9 +672,13 @@ scan_check_section_data(char* keyword[],
 		} else if (keyword[(int) scan_keyword_mvdump]) {
 			*type = section_mvdump;
 			main_keyword = scan_keyword_name(scan_keyword_mvdump);
-		} else
-			/* Incomplete section data */
+		} else {
+			error_reason("Line %d: section '%s' must contain "
+				     "either one of keywords 'image', "
+				     "'segment', 'dumpto', 'dumptofs', "
+				     "'mvdump' or 'tape'", section_line, name);
 			return -1;
+		}
 	}
 	/* Check keywords */
 	for (i=0; i < SCAN_KEYWORD_NUM; i++) {
@@ -734,6 +767,163 @@ scan_check_section_data(char* keyword[],
 }
 
 
+static int
+check_blocksize(int size)
+{
+	switch (size) {
+	case 512:
+	case 1024:
+	case 2048:
+	case 4096:
+		return 0;
+	}
+	return -1;
+}
+
+
+int
+scan_check_target_data(char* keyword[], int* line)
+{
+	int cylinders, heads, sectors;
+	char dummy;
+	int number;
+	enum scan_keyword_id errid;
+
+	if (keyword[(int) scan_keyword_targetbase] == 0) {
+		if (keyword[(int) scan_keyword_targettype] != 0)
+			errid = scan_keyword_targettype;
+		else if ((keyword[(int) scan_keyword_targetgeometry] != 0))
+			errid = scan_keyword_targetgeometry;
+		else if ((keyword[(int) scan_keyword_targetblocksize] != 0))
+			errid = scan_keyword_targetblocksize;
+		else if ((keyword[(int) scan_keyword_targetoffset] != 0))
+			errid = scan_keyword_targetoffset;
+		else
+			return 0;
+		if (line != NULL)
+			error_reason("Line %d: keyword 'targetbase' required "
+				"when specifying '%s'",
+				line[(int) errid], scan_keyword_name(errid));
+		else
+			error_reason("Option 'targetbase' required when "
+				"specifying '%s'",
+				scan_keyword_name(errid));
+		return -1;
+	}
+	if (keyword[(int) scan_keyword_targettype] == 0) {
+		if (line != NULL)
+			error_reason("Line %d: keyword 'targettype' "
+				"required when specifying 'targetbase'",
+				line[(int) scan_keyword_targetbase]);
+		else
+			error_reason("Option 'targettype' required "
+				     "when specifying 'targetbase'");
+		return -1;
+	}
+	switch (scan_get_target_type(keyword[(int) scan_keyword_targettype])) {
+	case target_type_cdl:
+	case target_type_ldl:
+		if ((keyword[(int) scan_keyword_targetgeometry] != 0))
+			break;
+		if (line != NULL)
+			error_reason("Line %d: keyword 'targetgeometry' "
+				"required when specifying 'targettype' %s",
+				line[(int) scan_keyword_targettype],
+				keyword[(int) scan_keyword_targettype]);
+		else
+			error_reason("Option 'targetgeometry' required when "
+				"specifying 'targettype' %s",
+				keyword[(int) scan_keyword_targettype]);
+		return -1;
+	case target_type_scsi:
+	case target_type_fba:
+		if ((keyword[(int) scan_keyword_targetgeometry] == 0))
+			break;
+		if (line != NULL)
+			error_reason("Line %d: keyword "
+				"'targetgeometry' not allowed for "
+				"'targettype' %s",
+				line[(int) scan_keyword_targetgeometry],
+				keyword[(int) scan_keyword_targettype]);
+		else
+			error_reason("Keyword 'targetgeometry' not "
+				"allowed for 'targettype' %s",
+				keyword[(int) scan_keyword_targettype]);
+		return -1;
+	case target_type_invalid:
+		if (line != NULL)
+			error_reason("Line %d: Unrecognized 'targettype' value "
+				"'%s'",
+				line[(int) scan_keyword_targettype],
+				keyword[(int) scan_keyword_targettype]);
+		else
+			error_reason("Unrecognized 'targettype' value '%s'",
+				keyword[(int) scan_keyword_targettype]);
+		return -1;
+	}
+	if (keyword[(int) scan_keyword_targetgeometry] != 0) {
+		if ((sscanf(keyword[(int) scan_keyword_targetgeometry],
+		    "%d,%d,%d %c", &cylinders, &heads, &sectors, &dummy)
+		    != 3) || (cylinders <= 0) || (heads <= 0) ||
+		    (sectors <= 0)) {
+			if (line != NULL)
+				error_reason("Line %d: Invalid target geometry "
+					"'%s'", line[
+					(int) scan_keyword_targetgeometry],
+					keyword[
+					(int) scan_keyword_targetgeometry]);
+			else
+				error_reason("Invalid target geometry '%s'",
+					keyword[
+					(int) scan_keyword_targetgeometry]);
+			return -1;
+		}
+	}
+	if (keyword[(int) scan_keyword_targetblocksize] == 0) {
+		if (line != NULL)
+			error_reason("Line %d: Keyword 'targetblocksize' "
+				"required when specifying 'targetbase'",
+				line[(int) scan_keyword_targetbase]);
+		else
+			error_reason("Option 'targetblocksize' required when "
+				"specifying 'targetbase'");
+		return -1;
+	}
+	if ((sscanf(keyword[(int) scan_keyword_targetblocksize], "%d %c",
+	    &number, &dummy) != 1) || check_blocksize(number)) {
+		if (line != NULL)
+			error_reason("Line %d: Invalid target blocksize '%s'",
+				line[(int) scan_keyword_targetblocksize],
+				keyword[(int) scan_keyword_targetblocksize]);
+		else
+			error_reason("Invalid target blocksize '%s'",
+				keyword[(int) scan_keyword_targetblocksize]);
+		return -1;
+	}
+	if (keyword[(int) scan_keyword_targetoffset] == 0) {
+		if (line != NULL)
+			error_reason("Line %d: Keyword 'targetoffset' "
+				"required when specifying 'targetbase'",
+				line[(int) scan_keyword_targetbase]);
+		else
+			error_reason("Option 'targetoffset' required when "
+				"specifying 'targetbase'");
+		return -1;
+	}
+	if (sscanf(keyword[(int) scan_keyword_targetoffset], "%d %c",
+	    &number, &dummy) != 1) {
+		if (line != NULL)
+			error_reason("Line %d: Invalid target offset '%s'",
+				line[(int) scan_keyword_targetoffset],
+				keyword[(int) scan_keyword_targetoffset]);
+		else
+			error_reason("Invalid target offset '%s'",
+				keyword[(int) scan_keyword_targetoffset]);
+		return -1;
+	}
+	return 0;
+}
+
 /* Check section at INDEX for compliance with config file rules. Upon success,
  * return zero and advance INDEX to point to the end of the section. Return
  * non-zero otherwise. */
@@ -764,6 +954,7 @@ check_section(struct scan_token* scan, i
 	else
 		type = section_invalid;
 	memset(keyword, 0, sizeof(keyword));
+	memset(keyword_line, 0, sizeof(keyword_line));
 	line = scan[i].line;
 	/* Account for keywords */
 	for (i++; (int) scan[i].id != 0; i++) {
@@ -802,13 +993,10 @@ check_section(struct scan_token* scan, i
 		}
 	}
 	rc = scan_check_section_data(keyword, keyword_line, name, line, &type);
-	/* Check for missing keyword */
-	if (type == section_invalid) {
-		error_reason("Line %d: section '%s' must contain either one "
-			     "of keywords 'image', 'segment', 'dumpto', "
-			     "'dumptofs', 'mvdump' or 'tape'", line, name);
-		return -1;
-	}
+	if (rc)
+		return rc;
+	/* Check target data */
+	rc = scan_check_target_data(keyword, keyword_line);
 	if (rc)
 		return rc;
 	/* Advance index to end of section */
@@ -840,6 +1028,8 @@ find_num_assignment(struct scan_token* s
 static int
 check_menu(struct scan_token* scan, int* index)
 {
+	char* keyword[SCAN_KEYWORD_NUM];
+	int keyword_line[SCAN_KEYWORD_NUM];
 	enum scan_keyword_id key_id;
 	char* name;
 	char* str;
@@ -852,6 +1042,7 @@ check_menu(struct scan_token* scan, int*
 	int is_default;
 	int is_prompt;
 	int is_timeout;
+	int rc;
 
 	i = *index;
 	name = scan[i].content.menu.name;
@@ -862,6 +1053,8 @@ check_menu(struct scan_token* scan, int*
 			     scan[line].line, name);
 		return -1;
 	}
+	memset(keyword, 0, sizeof(keyword));
+	memset(keyword_line, 0, sizeof(keyword_line));
 	line = scan[i].line;
 	is_num = 0;
 	is_target = 0;
@@ -886,6 +1079,24 @@ check_menu(struct scan_token* scan, int*
 				}
 				is_target = 1;
 				break;
+			case scan_keyword_targetbase:
+			case scan_keyword_targettype:
+			case scan_keyword_targetgeometry:
+			case scan_keyword_targetblocksize:
+			case scan_keyword_targetoffset:
+				key_id = scan[i].content.keyword.keyword;
+				keyword_line[key_id] = scan[i].line;
+				/* Rule 5 */
+				if (keyword[(int) key_id] != NULL) {
+					error_reason("Line %d: keyword '%s' "
+						"already specified",
+						scan[i].line,
+						scan_keyword_name(key_id));
+					return -1;
+				}
+				keyword[(int) key_id] =
+					scan[i].content.keyword.value;
+				break;
 			case scan_keyword_default:
 				if (is_default) {
 					error_reason("Line %d: keyword '%s' "
@@ -1044,6 +1255,10 @@ check_menu(struct scan_token* scan, int*
 			     name);
 		return -1;
 	}
+	/* Check target data */
+	rc = scan_check_target_data(keyword, keyword_line);
+	if (rc)
+		return rc;
 	/* Advance index to end of menu section */
 	*index = i - 1;
 	return 0;
Index: s390-tools-sles11/zipl/src/zipl.c
===================================================================
--- s390-tools-sles11.orig/zipl/src/zipl.c
+++ s390-tools-sles11/zipl/src/zipl.c
@@ -2,7 +2,7 @@
  * s390-tools/zipl/src/zipl.c
  *   zSeries Initial Program Loader tool.
  *
- * Copyright IBM Corp. 2001, 2006.
+ * Copyright IBM Corp. 2001, 2009.
  *
  * Author(s): Carsten Otte <cotte@de.ibm.com>
  *            Peter Oberparleiter <Peter.Oberparleiter@de.ibm.com>
@@ -41,7 +41,7 @@ int dry_run = 1;
 static const char tool_name[] = "zipl: zSeries Initial Program Loader";
 
 /* Copyright notice */
-static const char copyright_notice[] = "Copyright IBM Corp. 2001, 2008";
+static const char copyright_notice[] = "Copyright IBM Corp. 2001, 2009";
 
 /* Usage information */
 static const char* usage_text[] = {
@@ -55,6 +55,11 @@ static const char* usage_text[] = {
 "-c, --config CONFIGFILE         Read configuration from CONFIGFILE",
 "-t, --target TARGETDIR          Write bootmap file to TARGETDIR and install",
 "                                bootloader on device containing TARGETDIR",
+"    --targetbase BASEDEVICE     Install bootloader on BASEDEVICE",
+"    --targettype TYPE           Use device type: CDL, LDL, FBA, SCSI",
+"    --targetgeometry C,H,S      Use disk geometry: cylinders,heads,sectors",
+"    --targetblocksize SIZE      Use number of bytes per block",
+"    --targetoffset OFFSET       Use offset between logical and physical disk",
 "-i, --image IMAGEFILE[,ADDR]    Install Linux kernel image from IMAGEFILE",
 "-r, --ramdisk RAMDISK[,ADDR]    Install initial ramdisk from file RAMDISK",
 "-p, --parmfile PARMFILE[,ADDR]  Use kernel parmline stored in PARMFILE",
@@ -187,10 +192,12 @@ main(int argc, char* argv[])
 		break;
 	case job_dump_partition:
 		/* Retrieve target device information */
-		rc = install_dump(job->data.dump.device, job->data.dump.mem);
+		rc = install_dump(job->data.dump.device, &job->target,
+				  job->data.dump.mem);
 		break;
 	case job_mvdump:
 		rc = install_mvdump(job->data.mvdump.device,
+				    &job->target,
 				    job->data.mvdump.device_count,
 				    job->data.mvdump.mem,
 				    job->data.mvdump.force);
Index: s390-tools-sles11/zipl/src/zipl_helper.device-mapper
===================================================================
--- /dev/null
+++ s390-tools-sles11/zipl/src/zipl_helper.device-mapper
@@ -0,0 +1,716 @@
+#!/usr/bin/perl -w
+#
+# zipl_helper.device-mapper: print zipl parameters for a device-mapper device
+#
+# Copyright IBM Corp. 2009
+#
+# Author(s): Peter Oberparleiter <peter.oberparleiter@de.ibm.com>
+#
+# Usage: zipl_helper.device-mapper <target directory>
+#
+# This tool attempts to obtain zipl parameters for a target directory located
+# on a device-mapper device. It assumes that the device-mapper table for this
+# device conforms to the following rules:
+# - directory is located on a device consisting of a single device-mapper
+#   target
+# - only linear, mirror and multipath targets are supported
+# - supported physical device types are DASD and SCSI devices
+# - all of the device which contains the directory must be located on a single
+#   physical device (which may be mirrorred or accessed through a multipath
+#   target)
+# - any mirror in the device-mapper setup must include block 0 of the
+#   physical device
+#
+
+use strict;
+use File::Basename;
+
+# Required tools
+our $dmsetup = "dmsetup";
+our $mknod = "mknod";
+our $dasdview = "dasdview";
+our $blockdev = "blockdev";
+
+# Constants
+our $SECTOR_SIZE = 512;
+our $DASD_PARTN_MASK = 0x03;
+our $SCSI_PARTN_MASK = 0x0f;
+
+# Internal constants
+our $DEV_TYPE_CDL = 0;
+our $DEV_TYPE_LDL = 1;
+our $DEV_TYPE_FBA = 2;
+our $DEV_TYPE_SCSI = 3;
+
+our $TARGET_START = 0;
+our $TARGET_LENGTH = 1;
+our $TARGET_TYPE = 2;
+our $TARGET_DATA = 3;
+
+our $TARGET_TYPE_LINEAR = 0;
+our $TARGET_TYPE_MIRROR = 1;
+our $TARGET_TYPE_MULTIPATH = 2;
+
+our $LINEAR_MAJOR = 0;
+our $LINEAR_MINOR = 1;
+our $LINEAR_START_SECTOR = 2;
+
+our $MIRROR_MAJOR = 0;
+our $MIRROR_MINOR = 1;
+our $MIRROR_START_SECTOR = 2;
+
+our $MULTIPATH_MAJOR = 0;
+our $MULTIPATH_MINOR = 1;
+
+sub get_physical_device($);
+sub get_major_minor($);
+sub get_table($$);
+sub get_linear_data($$);
+sub get_mirror_data($$);
+sub get_multipath_data($$);
+sub filter_table($$$);
+sub get_target_start($);
+sub get_target_major_minor($);
+sub create_temp_device_node($$$);
+sub get_blocksize($);
+sub get_dasd_info($);
+sub get_partition_start($);
+sub is_dasd($);
+sub get_partition_base($$$);
+sub get_device_characteristics($$);
+sub get_type_name($);
+sub check_for_mirror($@);
+sub get_target_base($$$$@);
+sub get_device_name($$);
+
+my $phy_geometry;	# Disk geometry of physical device
+my $phy_blocksize;	# Blocksize of physical device
+my $phy_offset;		# Offset in 512-byte sectors between start of physical
+			# device and start of filesystem
+my $phy_type;		# Type of physical device
+my $phy_bootsectors;	# Size of boot record in 512-byte sectors
+my $phy_partstart;	# Partition offset of physical device
+my $phy_major;		# Major device number of physical device
+my $phy_minor;		# Minor device number of physical device
+my @target_list;	# List of dm-targets between filesystem and physical
+			# device.
+my $base_major;		# Major device number of base device.
+my $base_minor;		# Minor device number of base device
+my $directory;		# Command line parameter
+my $toolname;		# Name of tool
+
+# Start
+$toolname = basename($0);
+$directory = $ARGV[0];
+if (!defined($directory)) {
+	die("Usage: $toolname <target directory>\n");
+}
+
+# Determine physical (non-dm) device on which directory is located
+($phy_major, $phy_minor, $phy_offset, @target_list) =
+	get_physical_device($directory);
+# Determine type and characteristics of physical device
+($phy_type, $phy_blocksize, $phy_geometry, $phy_bootsectors, $phy_partstart) =
+	get_device_characteristics($phy_major, $phy_minor);
+
+# Handle partitions
+if ($phy_partstart > 0) {
+	# Only the partition of the physical device is mapped so only the
+	# physical device can provide access to the boot record.
+	($base_major, $base_minor) =
+		get_partition_base($phy_type, $phy_major, $phy_minor);
+	# Check for mirror
+	check_for_mirror(scalar(@target_list) - 1, @target_list);
+	# Adjust filesystem offset
+	$phy_offset += $phy_partstart * ($phy_blocksize / $SECTOR_SIZE);
+	$phy_partstart = 0;
+	# Update device geometry
+	(undef, undef, $phy_geometry, undef, undef) =
+		get_device_characteristics($base_major, $base_minor);
+} else {
+	# All of the device is mapped, so the base device is the top most
+	# dm device which provides access to boot sectors
+	($base_major, $base_minor) =
+		get_target_base($phy_major, $phy_minor, 0, $phy_bootsectors,
+				@target_list);
+}
+
+# Check for valid offset of file system
+if (($phy_offset % ($phy_blocksize / $SECTOR_SIZE)) != 0) {
+	die("Error: File system not aligned on physical block size\n");
+}
+
+# Print resulting information
+print("targetbase=$base_major:$base_minor\n");
+print("targettype=".get_type_name($phy_type)."\n");
+if (defined($phy_geometry)) {
+	print("targetgeometry=$phy_geometry\n");
+}
+print("targetblocksize=$phy_blocksize\n");
+print("targetoffset=".($phy_offset / ($phy_blocksize / $SECTOR_SIZE))."\n");
+
+exit(0);
+
+# get_physical_device(directory)
+# Returns (phy_major, phy_minor, phy_offset, @target_list).
+# target_list: [target_data1, target_data2, ..., target_datan]
+# target_data: [major, minor, target]
+sub get_physical_device($)
+{
+	my ($directory) = @_;
+	my $major;
+	my $minor;
+	my $table;
+	my $target;
+	my $start;
+	my $length;
+	my @target_list;
+
+	# Get information about device containing filesystem
+	($major, $minor) = get_major_minor($directory);
+	$table = get_table($major, $minor);
+	if (scalar(@$table) == 0) {
+		die("Error: Could not retrieve device-mapper information for ".
+		    "device '".get_device_name($major, $minor)."'\n");
+	}
+	# Filesystem must be on a single dm target
+	if (scalar(@$table) != 1) {
+		die("Error: Unsupported setup: Directory '$directory' is ".
+		    "located on a multi-target device-mapper device\n");
+	}
+
+	$target = $table->[0];
+	push(@target_list, [$major, $minor, $target]);
+	$start = $target->[$TARGET_START];
+	$length = $target->[$TARGET_LENGTH];
+	while (1) {
+		# Convert fs_start to offset on parent dm device
+		$start += get_target_start($target);
+		($major, $minor) = get_target_major_minor($target);
+		$table = get_table($major, $minor);
+		if (scalar(@$table) == 0) {
+			# Found non-dm device
+			return ($major, $minor, $start, @target_list);
+		}
+		# Get target in parent table which contains filesystem
+		$table = filter_table($table, $start, $length);
+		if (scalar(@$table) != 1) {
+			die("Error: Unsupported setup: Could not map ".
+			    "directory '$directory' to a single physical ".
+			    "device\n");
+		}
+		$target = $table->[0];
+		push(@target_list, [$major, $minor, $target]);
+		# Convert fs_start to offset on parent target
+		$start -= $target->[$TARGET_START];
+	}
+}
+
+# get_major_minor(filename)
+# Returns: (device major, device minor) of the device containing the
+# specified file.
+sub get_major_minor($)
+{
+	my ($filename) = @_;
+	my @stat;
+	my $dev;
+	my $major;
+	my $minor;
+
+	@stat = stat($filename);
+	if (!@stat) {
+		die("Error: Could not stat '$filename'\n");
+	}
+	$dev = $stat[0];
+	$major = ($dev & 0xfff00) >> 8;
+	$minor = ($dev & 0xff) | (($dev >> 12) & 0xfff00);
+
+	return ($major, $minor);
+}
+
+# get_table(major, minor)
+# Returns: [target1, target2, ..., targetn]
+# target: [start, length, type, data]
+# data: linear_data|mirror_data|multipath_data
+sub get_table($$)
+{
+	my ($major, $minor) = @_;
+	my @table;
+	my $dev_name = get_device_name($major, $minor);
+	local *HANDLE;
+
+	open(HANDLE, "$dmsetup table -j $major -m $minor 2>/dev/null|") or
+		return undef;
+	while (<HANDLE>) {
+		if (!(/^(\d+)\s+(\d+)\s+(\S+)\s+(\S.*)$/)) {
+			die("Error: Unrecognized device-mapper table format ".
+			    "for device '$dev_name'\n");
+		}
+		my ($start, $length, $target_type, $args) = ($1, $2, $3, $4);
+		my $data;
+		my $type;
+
+		if ($target_type eq "linear") {
+			$type = $TARGET_TYPE_LINEAR;
+			$data = get_linear_data($dev_name, $args);
+		} elsif ($target_type eq "mirror") {
+			$type = $TARGET_TYPE_MIRROR;
+			$data = get_mirror_data($dev_name, $args);
+		} elsif ($target_type eq "multipath") {
+			$type = $TARGET_TYPE_MULTIPATH;
+			$data = get_multipath_data($dev_name, $args);
+		} else {
+			die("Error: Unsupported setup: Unsupported ".
+			    "device-mapper target type '$target_type' for ".
+			    "device '$dev_name'\n");
+		}
+		push(@table, [$start, $length, $type, $data]);
+	}
+	close(HANDLE);
+	return \@table;
+}
+
+# get_linear_data(dev_name, args)
+# Returns: [major, minor, start_sector]
+sub get_linear_data($$)
+{
+	my ($dev_name, $args) = @_;
+
+	if (!($args =~ /^(\d+):(\d+)\s+(\d+)$/)) {
+		die("Error: Unrecognized device-mapper table format for ".
+		    "device '$dev_name'\n");
+	}
+	return [$1, $2, $3];
+}
+
+# get_mirror_data(dev_name, args)
+# Returns [[major1, minor1, start_sector1], [major2, minor2, start_sector2], ..]
+sub get_mirror_data($$)
+{
+	my ($dev_name, $args) = @_;
+	my @argv = split(/\s+/, $args);
+	my @data;
+	my $offset;
+
+	# Remove log_type + #logargs + logargs + #devs
+	splice(@argv, 0, $argv[1] + 3);
+	if (!@argv) {
+		goto out_error;
+	}
+	while (@argv) {
+		if (!($argv[0] =~ /^(\d+):(\d+)$/)) {
+			goto out_error;
+		}
+		push(@data, [$1, $2, $argv[1]]);
+		if (!defined($offset)) {
+			$offset = $argv[1];
+		} elsif ($argv[1] != $offset) {
+			die("Error: Unsupported setup: Mirror target on ".
+			    "device '$dev_name' contains entries with varying ".
+			    "sector offsets\n");
+		}
+		splice(@argv, 0, 2);
+	}
+	if (!scalar(@data)) {
+		goto out_error;
+	}
+	return \@data;
+
+out_error:
+	die("Error: Unrecognized device-mapper table format for device ".
+	    "'$dev_name'\n");
+}
+
+# get_multipath_data(dev_name, args)
+# Returns [[major1, minor1], [major2, minor2], ..]
+sub get_multipath_data($$)
+{
+	my ($dev_name, $args) = @_;
+	my @argv = split(/\s+/, $args);
+	my @data;
+
+	# Remove #features + features
+	splice(@argv, 0, $argv[0] + 1);
+	if (!@argv) {
+		goto out_error;
+	}
+	# Remove #handlerargs + handlerargs
+	splice(@argv, 0, $argv[0] + 1);
+	if (!@argv) {
+		goto out_error;
+	}
+	# Remove #pathgroups + pathgroup
+	splice(@argv, 0, 2);
+	while (@argv) {
+		# Remove pathselector + #selectorargs + selectorargs
+		splice(@argv, 0, 2 + $argv[1]);
+		if (!@argv) {
+			goto out_error;
+		}
+		my $num_paths = $argv[0];
+		my $num_path_args = $argv[1];
+		# Remove #paths + #pathargs
+		splice(@argv, 0, 2);
+		while ($num_paths-- > 0) {
+			if (!@argv) {
+				goto out_error;
+			}
+			if (!($argv[0] =~ /(\d+):(\d+)/)) {
+				goto out_error;
+			}
+			push(@data, [$1, $2]);
+			# Remove device + deviceargs
+			splice(@argv, 0, 1 + $num_path_args);
+		}
+	}
+	if (!@data) {
+		goto out_error;
+	}
+	return \@data;
+
+out_error:
+	die("Error: Unrecognized device-mapper table format for device ".
+	    "'$dev_name'\n");
+}
+
+# filter_table(table, start, length)
+# Returns table containing only targets between start and start + length - 1.
+sub filter_table($$$)
+{
+	my ($table, $start, $length) = @_;
+	my $end = $start + $length - 1;
+	my @result;
+	my $target;
+
+	foreach $target (@$table) {
+		my $target_start = $target->[$TARGET_START];
+		my $target_end = $target_start + $target->[$TARGET_LENGTH] - 1;
+
+		if (!(($target_end < $start) || ($target_start > $end))) {
+			push(@result, $target);
+		}
+	}
+	return \@result;
+}
+
+# get_target_start(target)
+# Returns the start sector of target.
+sub get_target_start($)
+{
+	my ($target) = @_;
+	my $type = $target->[$TARGET_TYPE];
+	my $data = $target->[$TARGET_DATA];
+
+	if ($type == $TARGET_TYPE_LINEAR) {
+		return $data->[$LINEAR_START_SECTOR];
+	} elsif ($type == $TARGET_TYPE_MIRROR) {
+		my $mirror_data = $data->[0];
+		return $mirror_data->[$MIRROR_START_SECTOR];
+	} else {
+		return 0;
+	}
+}
+
+# get_target_major_minor(target)
+# Returns (major, minor) of target of target.
+sub get_target_major_minor($)
+{
+	my ($target) = @_;
+	my $type = $target->[$TARGET_TYPE];
+	my $data = $target->[$TARGET_DATA];
+	my $major;
+	my $minor;
+
+	if ($type == $TARGET_TYPE_LINEAR) {
+		$major = $data->[$LINEAR_MAJOR];
+		$minor = $data->[$LINEAR_MINOR];
+	} elsif ($type == $TARGET_TYPE_MIRROR) {
+		# Use data of first device in list
+		my $mirror_data = $data->[0];
+		$major = $mirror_data->[$MIRROR_MAJOR];
+		$minor = $mirror_data->[$MIRROR_MINOR];
+	} elsif ($type == $TARGET_TYPE_MULTIPATH) {
+		# Use data of first device in list
+		my $multipath_data = $data->[0];
+		$major = $multipath_data->[$MULTIPATH_MAJOR];
+		$minor = $multipath_data->[$MULTIPATH_MINOR];
+	}
+	return ($major, $minor);
+}
+
+# create_temp_device_node(type, major, minor)
+# Returns the name of a temporary device node.
+sub create_temp_device_node($$$)
+{
+	my ($type, $major, $minor) = @_;
+	my $path = "/dev";
+	my $name;
+	my $num;
+
+	for ($num = 0; $num < 100; $num++) {
+		$name = sprintf("$path/zipl-dm-temp-%02d", $num);
+		if (-e $name) {
+			next;
+		}
+		if (system("$mknod $name $type $major $minor --mode 0600 ".
+			   "2>/dev/null")) {
+			next;
+		}
+		return $name;
+	}
+	die("Error: Could not create temporary device node in '$path'\n");
+}
+
+# get_blocksize(device)
+# # Return blocksize in bytes for device.
+sub get_blocksize($)
+{
+	my ($dev) = @_;
+	my $blocksize;
+	local *HANDLE;
+
+	open(HANDLE, "$blockdev --getss $dev 2>/dev/null|") or
+		return undef;
+	$blocksize = <HANDLE>;
+	chomp($blocksize);
+	close(HANDLE);
+
+	return $blocksize;
+}
+
+# get_dasd_info(device)
+# Returns (type, cylinders, heads, sectors)
+sub get_dasd_info($)
+{
+	my ($dev) = @_;
+	my $disk_type;
+	my $format;
+	my $cyl;
+	my $heads;
+	my $sectors;
+	my $type;
+	local *HANDLE;
+
+	open(HANDLE, "$dasdview -x -f  $dev 2>/dev/null|") or
+		# dasdview returned with an error
+		return undef;
+	while (<HANDLE>) {
+		if (/^number of cylinders.*\s(\d+)\s*$/) {
+			$cyl = $1;
+		} elsif (/^tracks per cylinder.*\s(\d+)\s*$/) {
+			$heads = $1;
+		} elsif (/^blocks per track.*\s(\d+)\s*$/) {
+			$sectors = $1;
+		} elsif (/^type\s+:\s+(\S+)\s*$/) {
+			$disk_type = $1;
+		} elsif (/^format.*\s+dec\s(\d+)\s/) {
+			$format = $1;
+		}
+	}
+	close(HANDLE);
+	if (!defined($cyl) || !defined($heads) || !defined($sectors) ||
+	    !defined($disk_type) || !defined($format)) {
+		# Unrecognized dadsview output format
+		return undef;
+	}
+	if ($disk_type eq "FBA") {
+		$type = $DEV_TYPE_FBA;
+	} elsif ($disk_type eq "ECKD") {
+		if ($format == 1) {
+			$type = $DEV_TYPE_LDL;
+		} elsif ($format == 2) {
+			$type = $DEV_TYPE_CDL;
+		}
+	}
+
+	return ($type, $cyl, $heads, $sectors);
+}
+
+# get_partition_start(device)
+# Return the partition offset of device.
+sub get_partition_start($)
+{
+	my ($dev) = @_;
+	my $line;
+	my $offset;
+	local *HANDLE;
+
+	open(HANDLE, "$blockdev --report $dev 2>/dev/null|") or
+		return undef;
+	$line = <HANDLE>;
+	if ($line =~ /RO\s+RA\s+SSZ\s+BSZ\s+StartSec\s+Size\s+Device/) {
+		$line = <HANDLE>;
+		if ($line =~ /^\S+\s+\d+\s+\d+\s+\d+\s+(\d+)/) {
+			$offset = $1;
+		}
+	}
+	close(HANDLE);
+	return $offset;
+}
+
+# is_dasd(type)
+# Return whether disk with type is a DASD.
+sub is_dasd($)
+{
+	my ($type) = @_;
+
+	return ($type == $DEV_TYPE_CDL) || ($type == $DEV_TYPE_LDL) ||
+	       ($type == $DEV_TYPE_FBA);
+}
+
+# get_partition_base(type, major, minor)
+# Return (major, minor) of the base device on which the partition is located.
+sub get_partition_base($$$)
+{
+	my ($type, $major, $minor) = @_;
+
+	if (is_dasd($type)) {
+		return ($major, $minor & ~$DASD_PARTN_MASK);
+	} else {
+		return ($major, $minor & ~$SCSI_PARTN_MASK);
+	}
+}
+
+# get_device_characteristics(major, minor)
+# Returns (type, blocksize, geometry, bootsectors, partstart) for device.
+sub get_device_characteristics($$)
+{
+	my ($major, $minor) = @_;
+	my $dev;
+	my $blocksize;
+	my $type;
+	my $cyl;
+	my $heads;
+	my $sectors;
+	my $geometry;
+	my $bootsectors;
+	my $partstart;
+
+	$dev = create_temp_device_node("b", $major, $minor);
+	$blocksize = get_blocksize($dev);
+	if (!defined($blocksize)) {
+		unlink($dev);
+		die("Error: Could not get block size for ".
+		    get_device_name($major, $minor)."\n");
+	}
+	($type, $cyl, $heads, $sectors) = get_dasd_info($dev);
+	if (defined($type)) {
+		$geometry = "$cyl,$heads,$sectors";
+		if ($type == $DEV_TYPE_CDL) {
+			# First track contains IPL records
+			$bootsectors = $blocksize * $sectors / $SECTOR_SIZE;
+		} elsif ($type == $DEV_TYPE_LDL) {
+			# First two blocks contain IPL records
+			$bootsectors = $blocksize * 2 / $SECTOR_SIZE;
+		} elsif ($type == $DEV_TYPE_FBA) {
+			# First block contains IPL records
+			$bootsectors = $blocksize / $SECTOR_SIZE;
+		}
+	} else {
+		# Assume SCSI if get_dasd_info failed
+		$type = $DEV_TYPE_SCSI;
+		# First block contains IPL records
+		$bootsectors = $blocksize / $SECTOR_SIZE;
+	}
+	$partstart = get_partition_start($dev);
+	unlink($dev);
+	if (!defined($partstart)) {
+		die("Error: Could not determine partition start for ".
+		    get_device_name($major, $minor)."\n");
+	}
+	return ($type, $blocksize, $geometry, $bootsectors, $partstart);
+}
+
+# get_type_name(type)
+# Return textual representation of device type.
+sub get_type_name($)
+{
+	my ($type) = @_;
+
+	if ($type == $DEV_TYPE_CDL) {
+		return "CDL";
+	} elsif ($type == $DEV_TYPE_LDL) {
+		return "LDL";
+	} elsif ($type == $DEV_TYPE_FBA) {
+		return "FBA";
+	} elsif ($type == $DEV_TYPE_SCSI) {
+		return "SCSI";
+	}
+	return undef;
+}
+
+
+# check_for_mirror(index, target_list)
+# Die if there is a mirror target between index and 0.
+sub check_for_mirror($@)
+{
+	my ($i, @target_list) = @_;
+
+	for (;$i >= 0; $i--) {
+		my $entry = $target_list[$i];
+		my ($major, $minor, $target) = @$entry;
+
+		if ($target->[$TARGET_TYPE] == $TARGET_TYPE_MIRROR) {
+			# IPL records are not mirrored.
+			die("Error: Unsupported setup: Block 0 is not ".
+			    "mirrored in device '".
+			    get_device_name($major, $minor)."'\n");
+		}
+	}
+}
+
+# get_target_base(bottom_major, bottom_minor, start, length, target_list)
+# Return (major, minor) for the top most target in the target list that maps
+# the region on (bottom_major, bottom_minor) defined by start and length at
+# offset 0.
+sub get_target_base($$$$@)
+{
+	my ($bot_major, $bot_minor, $start, $length, @target_list) = @_;
+	my $entry;
+	my $top_major;
+	my $top_minor;
+	my $i;
+
+	# Pre-initialize with bottom major-minor
+	$top_major = $bot_major;
+	$top_minor = $bot_minor;
+	# Process all entries starting with the last one
+	for ($i = scalar(@target_list) - 1; $i >= 0; $i--) {
+		my $entry = $target_list[$i];
+		my ($major, $minor, $target) = @$entry;
+
+		if (($target->[$TARGET_START] != 0) ||
+		    (get_target_start($target) != 0) ||
+		    ($target->[$TARGET_LENGTH] < $length)) {
+			last;
+		}
+		$top_major = $major;
+		$top_minor = $minor;
+	}
+	# Check for mirrorring between base device and fs device.
+	check_for_mirror($i, @target_list);
+	return ($top_major, $top_minor);
+}
+
+# get_device_name(major, minor)
+# Return the name of the device specified by major and minor.
+sub get_device_name($$)
+{
+	my ($major, $minor) = @_;
+	my $name;
+	local *HANDLE;
+
+	$name = "$major:$minor";
+	open(HANDLE, "</proc/partitions") or goto out;
+	while (<HANDLE>) {
+		if (/^\s*(\d+)\s+(\d+)\s+\d+\s+(\S+)\s*$/) {
+			if (($major == $1) && ($minor == $2)) {
+				$name = $3;
+				last;
+			}
+		}
+	}
+	close(HANDLE);
+out:
+	return $name;
+}
