/*
 * s390-tools/zipl/src/install.c
 *   Functions handling the installation of the boot loader code onto disk.
 *
 * Copyright (C) 2001-2003 IBM Deutschland Entwicklung GmbH, IBM Corporation
 *
 * Author(s): Carsten Otte <cotte@de.ibm.com>
 *            Peter Oberparleiter <Peter.Oberparleiter@de.ibm.com>
 */

#include "install.h"

#include <sys/types.h>
#include <sys/stat.h>
#include <sys/ioctl.h>
#include <fcntl.h>
#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
#include <stdlib.h>
#include <stdarg.h>
#include <linux/mtio.h>
#include <syslog.h>

#include "boot.h"
#include "bootmap.h"
#include "disk.h"
#include "error.h"
#include "misc.h"


/* Types of SCSI disk layouts */
enum scsi_layout {
	scsi_layout_pcbios,
	scsi_layout_sun,
	scsi_layout_sgi,
	scsi_layout_unknown
};


/* Determine SCSI disk layout from the specified BOOTBLOCK. */
static enum scsi_layout
get_scsi_layout(unsigned char* bootblock)
{
	if ((bootblock[510] == 0x55) && (bootblock[511] == 0xaa))
		return scsi_layout_pcbios;
	else if ((bootblock[508] == 0xda) && (bootblock[509] == 0xbe))
		return scsi_layout_sun;
	else if ((bootblock[0] == 0x0b) && (bootblock[1] == 0xe5) &&
		 (bootblock[2] == 0xa9) && (bootblock[3] == 0x41))
		return scsi_layout_sgi;
	return scsi_layout_unknown;
}


#define DISK_LAYOUT_ID 0x00000001

/* Create an IPL master boot record data structure for SCSI MBRs in memory
 * at location BUFFER. TABLE contains a pointer to the program table. INFO
 * provides information about the disk. */
static int
update_scsi_mbr(void* bootblock, disk_blockptr_t* table,
		struct disk_info* info)
{
	struct scsi_mbr {
		uint8_t		magic[4];
		uint32_t	version_id;
		uint8_t		reserved[8];
		uint8_t		program_table_pointer[16];
	}* mbr;
	void* buffer;

	switch (get_scsi_layout(bootblock)) {
	case scsi_layout_pcbios:
		if (verbose)
			printf("Detected SCSI PCBIOS disk layout.\n");
		buffer = bootblock;
		break;
	case scsi_layout_sun:
	case scsi_layout_sgi:
	default:
		error_reason("Unsupported SCSI disk layout");
		return -1;
	}

	mbr = (struct scsi_mbr *) buffer;
	memset(buffer, 0, sizeof(struct scsi_mbr));
	memcpy(&mbr->magic, ZIPL_MAGIC, ZIPL_MAGIC_SIZE);
	mbr->version_id = DISK_LAYOUT_ID;
	bootmap_store_blockptr(&mbr->program_table_pointer, table, info);
	return 0;
}


/* Install bootloader for initial program load from a SCSI type disk. FD
 * specifies the file descriptor of the device file. PROGRAM_TABLE points
 * to the disk block containing the program table. INFO provides
 * information about the disk type. Return 0 on success, non-zero otherwise. */
static int
install_scsi(int fd, disk_blockptr_t* program_table, struct disk_info* info)
{
	unsigned char* bootblock;
	int rc;

	bootblock = (unsigned char*) misc_malloc(info->phy_block_size);
	if (bootblock == NULL)
		return -1;
	/* Read bootblock */
	if (lseek(fd, 0, SEEK_SET) != 0) {
		error_reason(strerror(errno));
		error_text("Could not seek on device");
		free(bootblock);
		return -1;
	}
	rc = misc_read(fd, bootblock, info->phy_block_size);
	if (rc) {
		error_text("Could not read master boot record");
		free(bootblock);
		return rc;
	}
	/* Put zIPL data into MBR */
	rc = update_scsi_mbr(bootblock, program_table, info);
	if (rc) {
		free(bootblock);
		return -1;
	}
	/* Write MBR back to disk */
	if (verbose)
		printf("Writing SCSI master boot record.\n");
	if (lseek(fd, 0, SEEK_SET) != 0) {
		error_reason(strerror(errno));
		error_text("Could not seek on device");
		free(bootblock);
		return -1;
	}
	rc = misc_write(fd, bootblock, info->phy_block_size);
	if (rc)
		error_text("Could not write master boot record");
	free(bootblock);
	return rc;
}


/* Install bootloader for initial program load from an FBA type disk. */
static int
install_fba(int fd, disk_blockptr_t* program_table,
	    disk_blockptr_t* stage2_list, blocknum_t stage2_count,
	    struct disk_info* info)
{
	struct boot_fba_stage0 stage0;
	int rc;

	/* Initialize stage 0 data */
	rc = boot_init_fba_stage0(&stage0, program_table, stage2_list,
				  stage2_count, info);
	if (rc == 0) {
		/* Install stage 0 */
		rc = misc_write(fd, &stage0, sizeof(struct boot_fba_stage0));
	}
	if (rc)
		error_text("Could not write boot loader");
	return rc;
}


/* Install bootloader for initial program load from an ECKD type disk with
 * Linux/390 classic disk layout. */
static int
install_eckd_classic(int fd, disk_blockptr_t* program_table,
		     disk_blockptr_t* stage2_list, blocknum_t stage2_count,
		     struct disk_info* info, struct job_data* job)
{
	struct boot_eckd_stage0 stage0;
	struct boot_eckd_classic_stage1 stage1;
	int rc;

	/* Prepare stage 0 */
	boot_init_eckd_classic_stage0(&stage0);
	/* Install stage 0 */
	rc = misc_write(fd, &stage0, sizeof(struct boot_eckd_stage0));
	if (rc) {
		error_text("Could not write boot loader");
		return rc;
	}
	/* Prepare stage 1 */
	rc = boot_init_eckd_classic_stage1(&stage1, stage2_list, stage2_count,
					   info);
	if (rc)
		return rc;
	/* Store program table pointer in stage 1 loader */
	bootmap_store_blockptr(&stage1.param1, program_table, info);
	/* Install stage 1 */
	if (lseek(fd, info->phy_block_size, SEEK_SET) !=
	    info->phy_block_size) {
		error_reason(strerror(errno));
		error_text("Could not seek on device");
		return -1;
	}
	rc = misc_write(fd, &stage1, sizeof(struct boot_eckd_classic_stage1));
	if (rc) {
		error_text("Could not write boot loader");
		return rc;
	}
	return rc;
}



/* Install bootloader for initial program load from an ECKD type disk with
 * OS/390 compatible disk layout. */
static int
install_eckd_compatible(int fd, disk_blockptr_t* program_table,
			struct disk_info* info, struct job_data* job)
{
	struct boot_eckd_stage0 stage0;
	struct boot_eckd_compatible_stage1 stage1;
	void* buffer;
	size_t size;
	int rc;

	/* Prepare stage 0 */
	boot_init_eckd_compatible_stage0(&stage0);
	/* Install stage 0 */
	if (lseek(fd, 4, SEEK_SET) != 4) {
		error_reason(strerror(errno));
		error_text("Could not seek on device");
		return -1;
	}
	rc = misc_write(fd, &stage0, sizeof(struct boot_eckd_stage0));
	if (rc) {
		error_text("Could not write boot loader");
		return -1;
	}
	/* Prepare stage 1 */
	rc = boot_init_eckd_compatible_stage1(&stage1, info);
	if (rc)
		return rc;
	/* Store program table pointer in stage 1 loader */
	bootmap_store_blockptr(&stage1.param1, program_table, info);
	/* Install stage 1 */
	if (lseek(fd, 4 + info->phy_block_size, SEEK_SET) !=
	    4 + info->phy_block_size) {
		error_reason(strerror(errno));
		error_text("Could not seek on device");
		return -1;
	}
	rc = misc_write(fd, &stage1,
			sizeof(struct boot_eckd_compatible_stage1));
	if (rc) {
		error_text("Could not write boot loader");
		return -1;
	}
	/* Install stage 2 */
	if (lseek(fd, 3 * info->phy_block_size, SEEK_SET) !=
	    3 * info->phy_block_size) {
		error_reason(strerror(errno));
		error_text("Could not seek on device");
		return -1;
	}
	rc = boot_get_eckd_stage2(&buffer, &size, job);
	if (rc)
		return -1;
	rc = misc_write(fd, buffer, size);
	if (rc)
		error_text("Could not write boot loader");
	free(buffer);
	return rc;
}


int
install_bootloader(const char* device, disk_blockptr_t *program_table,
		   disk_blockptr_t* stage2_list, blocknum_t stage2_count,
		   struct disk_info* info, struct job_data* job)
{
	int fd;
	int rc;

	/* Inform user about what we're up to */
	printf("Preparing boot device: ");
	if (info->name) {
		printf("%s", info->name);
		if (info->devno >= 0)
			printf(" (%04x)", info->devno);
		printf(".\n");
	} else if (info->devno >= 0) {
		printf("%04x.\n", info->devno);
	} else
		disk_print_devt(info->device);
	/* Open device file */
	fd = open(device, O_RDWR);
	if (fd == -1) {
		error_reason(strerror(errno));
		error_text("Could not open temporary device file '%s'",
			   device);
		return -1;
	}
	/* Call disk specific install functions */
	rc = -1;
	switch (info->type) {
	case disk_type_scsi:
		rc = install_scsi(fd, program_table, info);
		break;
	case disk_type_fba:
		rc = install_fba(fd, program_table, stage2_list, stage2_count,
				 info);
		break;
	case disk_type_eckd_classic:
		rc = install_eckd_classic(fd, program_table, stage2_list,
					  stage2_count, info, job);
		break;
	case disk_type_eckd_compatible:
		rc = install_eckd_compatible(fd, program_table, info, job);
		break;
	case disk_type_diag:
	case disk_type_unknown:
		/* Should not happen */
		break;
	}
	close(fd);
	if (rc == 0) {
		if (info->devno >= 0)
			syslog(LOG_INFO, "Boot loader written to %s (%04x) - "
			       "%02x:%02x",
			       (info->name ? info->name : "-"), info->devno,
			       major(info->device), minor(info->device));
		else
			syslog(LOG_INFO, "Boot loader written to %s - "
			       "%02x:%02x",
			       (info->name ? info->name : "-"),
			       major(info->device), minor(info->device));
	}
	return rc;
}


/* Rewind the tape device identified by FD. Return 0 on success, non-zero
 * otherwise. */
static int
rewind_tape(int fd)
{
	struct mtop op;

	/* Magnetic tape rewind operation */
	op.mt_count = 1;
	op.mt_op = MTREW;
	if (ioctl(fd, MTIOCTOP, &op) == -1)
		return -1;
	else
		return 0;
}


static int
ask_for_confirmation(const char* fmt, ...)
{
	va_list args;
	char answer;

	/* Always assume positive answer in non-interactive mode */
	if (!interactive)
		return 0;
	/* Print question */
	va_start(args, fmt);
	vprintf(fmt, args);
	va_end(args);
	/* Process user reply */
	scanf("%c", &answer);
	if ((answer == 'y') || (answer == 'Y'))
		return 0;
	error_text("Operation canceled by user");
	return -1;
}


/* Write data from file FILENAME to file descriptor FD. Data will be written
 * in blocks of BLOCKSIZE bytes. Return 0 on success, non-zero otherwise. */
static int
write_tapefile(int fd, const char* filename, size_t blocksize)
{
	struct stat stats;
	ssize_t written;
	size_t offset;
	size_t chunk;
	void* buffer;
	int read_fd;

	if (stat(filename, &stats)) {
		error_reason(strerror(errno));
		return -1;
	}
	if (!S_ISREG(stats.st_mode)) {
		error_reason("Not a regular file");
		return -1;
	}
	buffer = misc_malloc(blocksize);
	if (buffer == NULL)
		return -1;
	read_fd = open(filename, O_RDONLY);
	if (fd == -1) {
		error_reason(strerror(errno));
		free(buffer);
		return -1;
	}
	for (offset = 0; offset < stats.st_size; offset += chunk) {
		chunk = stats.st_size - offset;
		if (chunk > blocksize)
			chunk = blocksize;
		else
			memset(buffer, 0, blocksize);
		if (misc_read(read_fd, buffer, chunk)) {
			close(read_fd);
			free(buffer);
			return -1;
		}
		written = write(fd, buffer, chunk);
		if (written != chunk) {
			if (written == -1)
				error_reason(strerror(errno));
			else
				error_reason("Write error");
			close(read_fd);
			free(buffer);
			return -1;
		}
	}
	close(read_fd);
	free(buffer);
	return 0;
}


/* Write SIZE bytes of data from memory location DATA to file descriptor FD.
 * Data will be written in blocks of BLOCKSIZE bytes. Return 0 on success,
 * non-zero otherwise. */
static int
write_tapebuffer(int fd, const char* data, size_t size, size_t blocksize)
{
	ssize_t written;
	size_t offset;
	size_t chunk;
	void* buffer;

	buffer = misc_malloc(blocksize);
	if (buffer == NULL)
		return -1;
	for (offset = 0; offset < size; offset += chunk) {
		chunk = size - offset;
		if (chunk > blocksize)
			chunk = blocksize;
		else
			memset(buffer, 0, blocksize);
		memcpy(buffer, VOID_ADD(data, offset), chunk);
		written = write(fd, buffer, chunk);
		if (written != chunk) {
			if (written == -1)
				error_reason(strerror(errno));
			else
				error_reason("Write error");
			free(buffer);
			return -1;
		}
	}
	free(buffer);
	return 0;
}


/* Write COUNT tapemarks to file handle FD. */
static int
write_tapemark(int fd, int count)
{
	struct mtop op;

	op.mt_count = count;
	op.mt_op = MTWEOF;
	if (ioctl(fd, MTIOCTOP, &op) == -1) {
		error_reason("Could not write tapemark");
		return -1;
	}
	return 0;
}


#define IPL_TAPE_BLOCKSIZE	1024

/* Install IPL record on tape device. */
int
install_tapeloader(const char* device, const char* image, const char* parmline,
		   const char* ramdisk, address_t image_addr,
		   address_t parm_addr, address_t initrd_addr)
{
	void* buffer;
	size_t size;
	int rc;
	int fd;

	printf("Preparing boot tape: %s\n", device);
	/* Prepare boot loader */
	rc = boot_get_tape_ipl(&buffer, &size, parm_addr, initrd_addr,
			       image_addr);
	if (rc)
		return rc;
	/* Open device */
	fd = open(device, O_RDWR);
	if (fd == -1) {
		error_reason(strerror(errno));
		error_text("Could not open tape device '%s'", device);
		free(buffer);
		return -1;
	}
	if (rewind_tape(fd) != 0) {
		error_text("Could not rewind tape device '%s'", device);
		free(buffer);
		close(fd);
		return -1;
	}
	/* Write boot loader */
	rc = write_tapebuffer(fd, buffer, size, IPL_TAPE_BLOCKSIZE);
	free(buffer);
	if (rc) {
		error_text("Could not write boot loader to tape");
		close(fd);
		return rc;
	}
	rc = write_tapemark(fd, 1);
	if (rc) {
		error_text("Could not write boot loader to tape");
		close(fd);
		return rc;
	}
	/* Write image file */
	if (verbose) {
		printf("  kernel image......: %s at 0x%llx\n", image,
		       (unsigned long long) image_addr);
	}
	rc = write_tapefile(fd, image, IPL_TAPE_BLOCKSIZE);
	if (rc) {
		error_text("Could not write image file '%s' to tape", image);
		close(fd);
		return rc;
	}
	rc = write_tapemark(fd, 1);
	if (rc) {
		error_text("Could not write boot loader to tape");
		close(fd);
		return rc;
	}
	if (parmline != NULL) {
		if (verbose) {
			printf("  kernel parmline...: '%s' at 0x%llx\n",
			       parmline, (unsigned long long) parm_addr);
		}
		/* Write parameter line */
		rc = write_tapebuffer(fd, parmline, strlen(parmline),
				      IPL_TAPE_BLOCKSIZE);
		if (rc) {
			error_text("Could not write parameter string to tape");
			close(fd);
			return rc;
		}
	}
	rc = write_tapemark(fd, 1);
	if (rc) {
		error_text("Could not write boot loader to tape");
		close(fd);
		return rc;
	}
	if (ramdisk != NULL) {
		/* Write ramdisk */
		if (verbose) {
			printf("  initial ramdisk...: %s at 0x%llx\n",
			       ramdisk, (unsigned long long) initrd_addr);
		}
		rc = write_tapefile(fd, ramdisk, IPL_TAPE_BLOCKSIZE);
		if (rc) {
			error_text("Could not write ramdisk file '%s' to tape",
				   ramdisk);
			close(fd);
			return rc;
		}
	}
	rc = write_tapemark(fd, 1);
	if (rc) {
		error_text("Could not write boot loader to tape");
		close(fd);
		return rc;
	}
	if (rewind_tape(fd) != 0) {
		error_text("Could not rewind tape device '%s' to tape", device);
		rc = -1;
	}
	close(fd);
	return rc;
}


struct eckd_dump_param {
	uint16_t	cyl;
	uint16_t	head;
	uint8_t		sec;
	uint16_t	blocksize;
	uint8_t		num_heads;
} __attribute((packed));

static int
install_dump_eckd_classic(int fd, struct disk_info* info, uint64_t mem)
{
	struct boot_eckd_stage0 stage0;
	struct boot_eckd_classic_stage1 stage1;
	struct eckd_dump_param* param;
	void* buffer;
	size_t size;
	int rc;

	/* Prepare stage 0 */
	boot_init_eckd_classic_stage0(&stage0);
	/* Install stage 0 */
	rc = misc_write(fd, &stage0, sizeof(struct boot_eckd_stage0));
	if (rc) {
		error_text("Could not write boot loader");
		return rc;
	}
	/* Prepare stage 1 */
	rc = boot_init_eckd_classic_dump_stage1(&stage1, info);
	if (rc)
		return rc;
	/* Fill in start of dump partition */
	param = (struct eckd_dump_param *) &stage1.param1;
	param->cyl = disk_cyl_from_blocknum(info->geo.start, info);
	param->head = disk_head_from_blocknum(info->geo.start, info);
	param->sec = disk_sec_from_blocknum(info->geo.start, info);
	param->blocksize = info->phy_block_size;
	param->num_heads = info->geo.heads;
	/* Fill in end of dump partition. Note that on ECKD devices with
	 * classic disk layout, there is exactly one partition so that the
	 * end of the device equals the end of the partition. */
	param = (struct eckd_dump_param *) &stage1.param2;
	param->cyl = info->geo.cylinders - 1;
	param->head = info->geo.heads - 1;
	param->sec = info->geo.sectors;
	param->blocksize = info->phy_block_size;
	param->num_heads = 0; /* Unused */
	/* Install stage 1 */
	if (lseek(fd, info->phy_block_size, SEEK_SET) !=
	    info->phy_block_size) {
		error_reason(strerror(errno));
		error_text("Could not seek on device");
		return -1;
	}
	rc = misc_write(fd, &stage1, sizeof(struct boot_eckd_classic_stage1));
	if (rc) {
		error_text("Could not write dump record");
		return rc;
	}
	/* Install stage 2 dump record at beginning of partition */
	if (lseek(fd, info->geo.start * info->phy_block_size, SEEK_SET) !=
	    (off_t) info->geo.start * info->phy_block_size) {
		error_text("Could not seek on device");
		return -1;
	}
	rc = boot_get_eckd_dump_stage2(&buffer, &size, mem);
	if (rc)
		return rc;
	rc = misc_write(fd, buffer, size);
	if (rc)
		error_text("Could not write dump record");
	free(buffer);
	return rc;
}


static int
install_dump_eckd_compatible(int fd, struct disk_info* info, uint64_t mem)
{
	struct boot_eckd_stage0 stage0;
	struct boot_eckd_compatible_stage1 stage1;
	struct eckd_dump_param* param;
	void* buffer;
	size_t size;
	int rc;

	/* Prepare stage 0 */
	boot_init_eckd_compatible_stage0(&stage0);
	/* Install stage 0 */
	if (lseek(fd, 4, SEEK_SET) != 4) {
		error_reason(strerror(errno));
		error_text("Could not seek on device");
		return -1;
	}
	rc = misc_write(fd, &stage0, sizeof(struct boot_eckd_stage0));
	if (rc) {
		error_text("Could not write boot loader");
		return rc;
	}
	/* Prepare stage 1 */
	rc = boot_init_eckd_compatible_dump_stage1(&stage1, info);
	if (rc)
		return rc;
	/* Fill in start of dump partition */
	param = (struct eckd_dump_param *) &stage1.param1;
	param->cyl = disk_cyl_from_blocknum(info->geo.start, info);
	param->head = disk_head_from_blocknum(info->geo.start, info);
	param->sec = disk_sec_from_blocknum(info->geo.start, info);
	param->blocksize = info->phy_block_size;
	param->num_heads = info->geo.heads;
	/* Fill in end of dump partition. */
	param = (struct eckd_dump_param *) &stage1.param2;
	param->cyl = disk_cyl_from_blocknum(info->geo.start +
					    info->phy_blocks - 1, info);
	param->head = disk_head_from_blocknum(info->geo.start +
					      info->phy_blocks - 1, info);
	param->sec = disk_sec_from_blocknum(info->geo.start +
					    info->phy_blocks - 1, info);
	param->blocksize	= info->phy_block_size;
	param->num_heads	= 0; /* Unused */
	/* Install stage 1 */
	if (lseek(fd, info->phy_block_size + 4, SEEK_SET) !=
	    info->phy_block_size + 4) {
		error_reason(strerror(errno));
		error_text("Could not seek on device");
		return -1;
	}
	rc = misc_write(fd, &stage1,
			sizeof(struct boot_eckd_compatible_stage1));
	if (rc) {
		error_text("Could not write dump record");
		return rc;
	}
	/* Install stage 2 dump record */
	if (lseek(fd, 3 * info->phy_block_size, SEEK_SET) !=
	    3 * info->phy_block_size) {
		error_text("Could not seek on device");
		return -1;
	}
	rc = boot_get_eckd_dump_stage2(&buffer, &size, mem);
	if (rc)
		return rc;
	rc = misc_write(fd, buffer, size);
	if (rc)
		error_text("Could not write dump record");
	free(buffer);
	return rc;
}


static int
install_dump_tape(int fd, uint64_t mem)
{
	void* buffer;
	size_t size;
	int rc;

	rc = boot_get_tape_dump(&buffer, &size, mem);
	if (rc)
		return rc;
	rc = misc_write(fd, buffer, size);
	if (rc)
		error_text("Could not write to tape device");
	free(buffer);
	return 0;
}


int
install_dump(const char* device, uint64_t mem)
{
	struct disk_info* info;
	char* tempdev;
	int fd;
	int rc;

	fd = open(device, O_RDWR);
	if (fd == -1) {
		error_reason(strerror(errno));
		error_text("Could not open dump device '%s'", device);
		return -1;
	}
	if (rewind_tape(fd) == 0) {
		/* Rewind worked - this is a tape */
		rc = ask_for_confirmation("Warning: All information on device "
					  "'%s' will be lost!\nDo you want to "
					  "continue creating a one-shot dump "
 					  "tape (y/n) ?", device);
		if (rc) {
			close(fd);
			return rc;
		}
		if (verbose)
			printf("Installing tape dump record\n");
		rc = install_dump_tape(fd, mem);
		if (rc) {
			error_text("Could not install dump record on tape "
				   "device '%s'", device);
		} else {
			if (verbose) {
				printf("Dump record successfully installed on "
				       "tape device '%s'.\n", device);
			}
		}
		close(fd);
		return rc;
	}
	close(fd);
	/* This is a disk device */
	rc = disk_get_info(device, &info);
	if (rc) {
		error_text("Could not get information for dump target "
			   "'%s'", device);
		close(fd);
		return rc;
	}
	if (info->partnum == 0) {
		error_reason("Dump target '%s' is not a disk partition",
			     device);
		disk_free_info(info);
		return -1;
	}
	if (verbose) {
		printf("Target device information\n");
		disk_print_info(info);
	}
	rc = misc_temp_dev(info->device, 1, &tempdev);
	if (rc) {
		disk_free_info(info);
		return -1;
	}
	fd = open(tempdev, O_RDWR);
	if (fd == -1) {
		error_text("Could not open temporary device node '%s'",
			   tempdev);
		misc_free_temp_dev(tempdev);
		disk_free_info(info);
		return -1;
	}
	switch (info->type) {
	case disk_type_eckd_classic:
	case disk_type_eckd_compatible:
		rc = ask_for_confirmation("Warning: All information on "
					  "partition '%s' will be lost!\n"
					  "Do you want to continue creating "
					  "a dump partition (y/n)?", device);
		if (rc)
			break;
		if (verbose) {
			printf("Installing dump record on partition with %s\n",
			       disk_get_type_name(info->type));
		}
		if (info->type == disk_type_eckd_classic)
			rc = install_dump_eckd_classic(fd, info, mem);
		else
			rc = install_dump_eckd_compatible(fd, info, mem);
		break;
	case disk_type_scsi:
	case disk_type_fba:
	case disk_type_diag:
	case disk_type_unknown:
		error_reason("Unsupported disk type '%s' of dump target '%s'",
			     disk_get_type_name(info->type), device);
		rc = -1;
		break;
	}
	misc_free_temp_dev(tempdev);
	disk_free_info(info);
	close(fd);
	return rc;
}
