Files
s390-tools/zipl/src/install.c
Eduard Shishkin f7d2339c6a zipl: List-Directed IPL from ECKD DASD
Make zipl tool prepare ECKD DASD for booting by different IPL
programs (with the same boot record installed).

Besides standard CCW-type IPL, user gets an ability to trigger
(with the same boot record installed!) List-Directed IPL. This
allows to use the feature of secure boot from ECKD DASD (which
is not available for CCW-type IPL).

When using the old boot interfaces, the usual CCW-type IPL is
triggered for DASD. Also for compatibility reasons zipl(8) tool
is modified to create and install one, or two "similar" program
tables per boot partition, depending on job and disk type. The
"similar" program tables differ only in block pointers format.
The old IPL programs (CCW-type IPL) use program table based on the
old format.

All program tables are packed to the same bootmap file. Their
order and logical offsets in the file are not significant (not
used by anyone).

The picture below shows which program table is used for IPL of
specified type from disk of specified type. Here "0" and "1" are
identifiers of program tables based on the old and new block
pointers format respectively. E.g. program table "0" is used for
CCW-type IPL from ECKD DASD. LD-IPL from DASD FBA is unsupported
(respectively, only one program table "0" is used), etc.

                            CCW-IPL   LD-IPL

 SCSI                          X        0
 DASD FBA                      0        X
 ECKD DASD LDL                 0        X
 ECKD DASD CDL                 0        1

Signed-off-by: Eduard Shishkin <edward6@linux.ibm.com>
Reviewed-by: Stefan Haberland <sth@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2022-11-29 17:03:57 +01:00

1272 lines
33 KiB
C

/*
* zipl - zSeries Initial Program Loader tool
*
* Functions handling the installation of the boot loader code onto disk
*
* Copyright IBM Corp. 2001, 2018
*
* s390-tools is free software; you can redistribute it and/or modify
* it under the terms of the MIT license. See LICENSE for details.
*/
#include <errno.h>
#include <fcntl.h>
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/ioctl.h>
#include <sys/mtio.h>
#include <sys/stat.h>
#include <sys/sysmacros.h>
#include <sys/time.h>
#include <syslog.h>
#include <unistd.h>
#include "lib/zt_common.h"
#include "lib/util_sys.h"
#include "lib/vtoc.h"
#include "boot.h"
#include "bootmap.h"
#include "disk.h"
#include "error.h"
#include "install.h"
#include "misc.h"
#define ECKD_CDL_DUMP_REC 3 /* Start record (0 based) for stage 2 and 1b */
/* CDL record 2 = 148 byte, independent from bs, bootinfo is last structure */
#define CDL_BOOTINFO_ADDR 112
/* ADDRESS for bootinfo structure to be BIOS compatible in MBR */
#define DEFAULT_BOOTINFO_ADDR 408
static inline unsigned long blk_cnt(int size, struct disk_info *info)
{
return (size + info->phy_block_size - 1) / info->phy_block_size;
}
/* Types of SCSI disk layouts */
enum scsi_layout {
scsi_layout_pcbios,
scsi_layout_sun,
scsi_layout_sgi,
scsi_layout_unknown
};
/* From linux/fs.h */
#define BLKFLSBUF _IO(0x12, 97)
/* 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;
}
static int
overwrite_partition_start(int fd, struct disk_info* info, int mv_dump_magic);
/* 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, disk_blockptr_t* scsi_dump_sb_blockptr)
{
struct scsi_mbr *mbr;
struct scsi_dump_param param;
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:
error_reason("Unsupported SCSI disk layout");
return -1;
default:
if (info->partnum) {
error_reason("Unsupported SCSI disk layout");
return -1;
} else {
if (verbose)
printf ("Detected plain SCSI partition.\n");
buffer=bootblock;
}
}
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,
0 /* this argument is ignored for scsi */);
if (scsi_dump_sb_blockptr->linear.block != 0) {
/* Write dump boot_info */
param.block = scsi_dump_sb_blockptr->linear.block *
scsi_dump_sb_blockptr->linear.size;
boot_get_dump_info(&mbr->boot_info, BOOT_INFO_DEV_TYPE_SCSI,
&param);
}
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,
disk_blockptr_t* scsi_dump_sb_blockptr)
{
unsigned char* bootblock;
int rc;
bootblock = (unsigned char*) misc_malloc(info->phy_block_size);
if (bootblock == NULL)
return -1;
/* Read bootblock */
if (misc_seek(fd, 0)) {
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,
scsi_dump_sb_blockptr);
if (rc) {
free(bootblock);
return -1;
}
/* Write MBR back to disk */
if (verbose)
printf("Writing SCSI master boot record.\n");
if (misc_seek(fd, 0)) {
free(bootblock);
return -1;
}
rc = DRY_RUN_FUNC(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 CCW-type IPL from a FBA type disk */
static int
install_fba_ccw(int fd, disk_blockptr_t *program_table,
disk_blockptr_t *stage1b_list, blocknum_t stage1b_count,
struct disk_info *info)
{
struct boot_fba_stage0 stage0;
/* Install stage 0 and store program table pointer */
if (boot_init_fba_stage0(&stage0, stage1b_list, stage1b_count))
return -1;
boot_get_ipl_info_ccw(&stage0.boot_info, BOOT_INFO_DEV_TYPE_FBA,
program_table, info);
if (DRY_RUN_FUNC(misc_pwrite(fd, &stage0, sizeof(stage0), 0)))
return -1;
return 0;
}
/* Install stage1b bootloader for ECKD type disk */
int
install_eckd_stage1b(int fd, disk_blockptr_t **stage1b_list,
blocknum_t *stage1b_count, disk_blockptr_t *stage2_list,
blocknum_t stage2_count, struct disk_info *info)
{
struct boot_eckd_stage1b *stage1b;
int stage1b_size, rc = -1;
*stage1b_list = NULL;
*stage1b_count = 0;
stage1b_size = ROUNDUP(sizeof(*stage1b), info->phy_block_size);
stage1b = misc_malloc(stage1b_size);
if (stage1b == NULL)
goto out;
memset(stage1b, 0, stage1b_size);
if (boot_init_eckd_stage1b(stage1b, stage2_list, stage2_count))
goto out_free_stage1b;
*stage1b_count = disk_write_block_buffer(fd, 1, stage1b, stage1b_size,
stage1b_list, info);
if (*stage1b_count == 0)
goto out_free_stage1b;
rc = 0;
out_free_stage1b:
free(stage1b);
out:
return rc;
}
/*
* Install bootloader for CCW-type IPL from ECKD type disk with
* Linux Disk Layout
*/
static int
install_eckd_ldl_ccw(int fd, disk_blockptr_t *program_table,
disk_blockptr_t *stage1b_list, blocknum_t stage1b_count,
struct disk_info *info)
{
struct boot_eckd_ldl_stage0 stage0;
struct boot_eckd_stage1 stage1;
/* Install stage 0 */
boot_init_eckd_ldl_stage0(&stage0);
if (DRY_RUN_FUNC(misc_pwrite(fd, &stage0, sizeof(stage0), 0)))
return -1;
/* Install stage 1 and store program table pointer */
if (boot_init_eckd_stage1(&stage1, stage1b_list, stage1b_count))
return -1;
boot_get_ipl_info_ccw(&stage1.boot_info, BOOT_INFO_DEV_TYPE_ECKD,
program_table, info);
if (DRY_RUN_FUNC(misc_pwrite(fd, &stage1, sizeof(stage1),
sizeof(stage0))))
return -1;
return 0;
}
/**
* Install bootloader for CCW-type IPL from ECKD type disk with
* OS/390 compatible disk layout
*/
static int install_eckd_cdl_ccw(int fd, disk_blockptr_t *program_table,
disk_blockptr_t *stage1b_list,
blocknum_t stage1b_count,
struct disk_info *info)
{
struct boot_eckd_cdl_stage0 stage0;
struct boot_eckd_stage1 stage1;
/* Install stage 0 */
boot_init_eckd_cdl_stage0(&stage0);
if (DRY_RUN_FUNC(misc_pwrite(fd, &stage0, sizeof(stage0), 4)))
return -1;
/* Install stage 1 and store program table pointer */
if (boot_init_eckd_stage1(&stage1, stage1b_list, stage1b_count))
return -1;
boot_get_ipl_info_ccw(&stage1.boot_info, BOOT_INFO_DEV_TYPE_ECKD,
program_table, info);
if (DRY_RUN_FUNC(misc_pwrite(fd, &stage1, sizeof(stage1),
4 + info->phy_block_size)))
return -1;
return 0;
}
/**
* Install a program table for List-Directed IPL on a CDL-formatted DASD.
*
* The installation means storing an actual boot record address in the
* volume label.
*
* BR: represents an actual boot record address.
*/
static int install_eckd_cdl_ld(int fd, disk_blockptr_t *br,
struct disk_info *info)
{
struct vol_label_cdl vl;
int rc;
/* Read a volume label from CDL-formatted DASD */
if (misc_seek(fd, 2 * info->phy_block_size))
return -1;
rc = misc_read(fd, &vl, sizeof(vl));
if (rc) {
error_text("Could not read volume label");
return rc;
}
/* Verify that we have a VOL1 label */
if (!is_vol1(vl.vollbl)) {
error_text("Volume label 'vol1' not initialized");
return -1;
}
/* Pack the actual boot record address */
vtoc_set_cchhb(&vl.br, br->chs.cyl, br->chs.head, br->chs.sec);
/* Write out the updated volume label */
if (misc_seek(fd, 2 * info->phy_block_size))
return -1;
rc = DRY_RUN_FUNC(misc_write(fd, &vl, sizeof(vl)));
if (rc)
error_text("Could not update volume lablel 'vol1'");
return 0;
}
/**
* Install a "compatible" boot record referring one, or two "similar"
* program tables.
*
* The picture below shows which program table is used for IPL
* of specified type from disk of specified type.
* E.g. program table "0" is used for CCW-type IPL from ECKD DASD,
* LD-IPL from DASD FBA is unsupported (respectively, only one program
* table "0" is used), etc.
*
* CCW-IPL LD-IPL
*
* SCSI X 0
* DASD FBA 0 X
* ECKD DASD LDL 0 X
* ECKD DASD CDL 0 1
*/
int install_bootloader(struct job_data *job, struct install_set *bis)
{
disk_blockptr_t *scsi_dump_sb_blockptr = &bis->scsi_dump_sb_blockptr;
struct program_table *pt0 = &bis->tables[PROGRAM_TABLE_0];
struct program_table *pt1 = &bis->tables[PROGRAM_TABLE_1];
struct disk_info *info = bis->info;
char *device = bis->device;
int fd, rc;
/* Inform user about what we're up to */
printf("Preparing boot device for %sIPL: ",
disk_get_ipl_type(info->type));
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);
printf(".\n");
}
/* 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;
}
/* Ensure that potential cache inconsistencies between disk and
* partition are resolved by flushing the corresponding buffers. */
if (!dry_run) {
if (ioctl(fd, BLKFLSBUF)) {
fprintf(stderr, "Warning: Could not flush disk "
"caches.\n");
}
}
/*
* Depending on disk type, install one or two program tables
* for CCW-type IPL and (or) for List-Directed IPL (see the
* picture in comments above)
*/
rc = -1;
switch (info->type) {
case disk_type_scsi:
rc = install_scsi(fd, &pt0->table, info,
scsi_dump_sb_blockptr);
if (rc == 0 && job->id == job_dump_partition &&
!is_ngdump_enabled(device, &job->target))
rc = overwrite_partition_start(fd, info, 0);
break;
case disk_type_fba:
rc = install_fba_ccw(fd,
&pt0->table,
pt0->stage1b_list,
pt0->stage1b_count, info);
break;
case disk_type_eckd_ldl:
rc = install_eckd_ldl_ccw(fd,
&pt0->table,
pt0->stage1b_list,
pt0->stage1b_count, info);
break;
case disk_type_eckd_cdl:
rc = install_eckd_cdl_ccw(fd,
&pt0->table,
pt0->stage1b_list,
pt0->stage1b_count, info);
if (rc)
break;
rc = install_eckd_cdl_ld(fd, pt1->stage1b_list, info);
break;
case disk_type_diag:
/* Should not happen */
break;
}
if (fsync(fd))
error_text("Could not sync device file '%s'", device);
if (close(fd))
error_text("Could not close device file '%s'", device);
if (!dry_run && 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. */
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 */
while (scanf("%c", &answer) != 1);
if ((answer == 'y') || (answer == 'Y'))
return 0;
fprintf(stderr, "Operation canceled by user.\n");
return -2;
}
/* 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;
off_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 != (ssize_t) 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 != (ssize_t) 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 = DRY_RUN_FUNC(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 = DRY_RUN_FUNC(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 = DRY_RUN_FUNC(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 = DRY_RUN_FUNC(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 = DRY_RUN_FUNC(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 = DRY_RUN_FUNC(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 = DRY_RUN_FUNC(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 = DRY_RUN_FUNC(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;
}
/* Write 64k null bytes with dump signature at offset 512 to
* start of dump partition */
static int
overwrite_partition_start(int fd, struct disk_info* info, int mv_dump_magic)
{
int rc;
unsigned int bytes = 65536;
char* buffer;
const char dump_magic[] = {0xa8, 0x19, 0x01, 0x73,
0x61, 0x8f, 0x23, 0xfe};
if (misc_seek(fd, info->geo.start * info->phy_block_size))
return -1;
if (info->phy_block_size * info->phy_blocks < bytes)
bytes = info->phy_block_size * info->phy_blocks;
buffer = calloc(1, bytes);
if (buffer == NULL) {
error_text("Could not allocate buffer");
return -1;
}
if (mv_dump_magic)
memcpy(VOID_ADD(buffer, 512), dump_magic, sizeof(dump_magic));
rc = DRY_RUN_FUNC(misc_write(fd, buffer, bytes));
free(buffer);
if (rc) {
error_text("Could not write dump signature");
return rc;
}
return 0;
}
/*
* Ensure that end block is within bounds.
* Force block size of 4KiB because otherwise there is not enough space
* to write the dump tool.
*/
static int check_eckd_dump_partition(struct disk_info* info)
{
unsigned long long end_blk = info->geo.start + info->phy_blocks - 1;
if (end_blk > UINT32_MAX) {
error_reason("partition end exceeds bounds (offset "
"%lld MB, max %lld MB)",
(end_blk * info->phy_block_size) >> 20,
(((unsigned long long) UINT32_MAX) *
info->phy_block_size) >> 20);
return -1;
}
if (info->phy_block_size != 4096) {
error_reason("unsupported DASD block size %d (should be 4096)",
info->phy_block_size);
return -1;
}
return 0;
}
static void eckd_dump_store_param(struct eckd_dump_param *param,
struct disk_info *info, blocknum_t count)
{
param->blk_start = info->geo.start;
param->blk_end = info->geo.start + info->phy_blocks - 1 - count;
param->num_heads = info->geo.heads;
param->blk_size = info->phy_block_size;
param->bpt = info->geo.sectors;
}
static int install_svdump_eckd_ldl(int fd, struct disk_info *info, uint64_t mem)
{
disk_blockptr_t *stage2_list, *stage1b_list;
blocknum_t stage2_count, stage1b_count;
struct boot_eckd_ldl_stage0 stage0;
struct boot_eckd_stage1 stage1;
struct eckd_dump_param param;
size_t stage2_size;
void *stage2;
int rc = -1;
if (boot_get_eckd_dump_stage2(&stage2, &stage2_size, mem))
goto out;
if (blk_cnt(stage2_size, info) > STAGE2_BLK_CNT_MAX) {
error_reason("ECKD dump record is too large");
goto out_free_stage2;
}
if (overwrite_partition_start(fd, info, 0))
goto out_free_stage2;
/* Install stage 2 and stage 1b to beginning of partition */
if (misc_seek(fd, info->geo.start * info->phy_block_size))
goto out_free_stage2;
stage2_count = disk_write_block_buffer(fd, 1, stage2, stage2_size,
&stage2_list, info);
if (stage2_count == 0)
goto out_free_stage2_list;
if (install_eckd_stage1b(fd, &stage1b_list, &stage1b_count,
stage2_list, stage2_count, info))
goto out_free_stage2_list;
/* Install stage 0 - afterwards we are at stage 1 position*/
boot_init_eckd_ldl_stage0(&stage0);
if (DRY_RUN_FUNC(misc_pwrite(fd, &stage0, sizeof(stage0), 0)))
goto out_free_stage1b_list;
/* Install stage 1 and fill in dump partition parameter */
if (boot_init_eckd_stage1(&stage1, stage1b_list, stage1b_count))
goto out_free_stage1b_list;
eckd_dump_store_param(&param, info, 0);
boot_get_dump_info(&stage1.boot_info, BOOT_INFO_DEV_TYPE_ECKD, &param);
if (DRY_RUN_FUNC(misc_pwrite(fd, &stage1, sizeof(stage1),
sizeof(stage0))))
goto out_free_stage1b_list;
rc = 0;
out_free_stage1b_list:
free(stage1b_list);
out_free_stage2_list:
free(stage2_list);
out_free_stage2:
free(stage2);
out:
return rc;
}
static int install_dump_eckd_cdl(int fd, struct disk_info *info, void *stage2,
size_t stage2_size, int mvdump, int force)
{
blocknum_t count, stage2_count, stage1b_count;
disk_blockptr_t *stage2_list, *stage1b_list;
struct boot_eckd_cdl_stage0 stage0_cdl;
struct boot_eckd_stage1 stage1;
struct eckd_dump_param param;
int rc = -1;
count = blk_cnt(stage2_size, info);
if (count > STAGE2_BLK_CNT_MAX) {
error_reason("ECKD dump record is too large");
goto out;
}
count += blk_cnt(sizeof(struct boot_eckd_stage1b), info);
if (count > (blocknum_t) info->geo.sectors - ECKD_CDL_DUMP_REC) {
error_reason("ECKD dump record is too large");
goto out;
}
/* Install stage 2 */
if (misc_seek(fd, ECKD_CDL_DUMP_REC * info->phy_block_size))
goto out;
stage2_count = disk_write_block_buffer(fd, 1, stage2, stage2_size,
&stage2_list, info);
if (stage2_count == 0)
goto out;
/* Install stage 1b behind stage 2*/
if (install_eckd_stage1b(fd, &stage1b_list, &stage1b_count,
stage2_list, stage2_count, info))
goto out_free_stage2_list;
/* Install stage 0 */
boot_init_eckd_cdl_stage0(&stage0_cdl);
if (DRY_RUN_FUNC(misc_pwrite(fd, &stage0_cdl, sizeof(stage0_cdl), 4)))
goto out_free_stage1b_list;
/* Install stage 1 and fill in dump partition parameter */
if (boot_init_eckd_stage1(&stage1, stage1b_list, stage1b_count))
goto out_free_stage1b_list;
eckd_dump_store_param(&param, info, 0);
boot_get_dump_info(&stage1.boot_info, BOOT_INFO_DEV_TYPE_ECKD, &param);
if (DRY_RUN_FUNC(misc_pwrite(fd, &stage1, sizeof(stage1),
info->phy_block_size + 4)))
goto out_free_stage1b_list;
if (!force && overwrite_partition_start(fd, info, mvdump))
goto out_free_stage1b_list;
rc = 0;
out_free_stage1b_list:
free(stage1b_list);
out_free_stage2_list:
free(stage2_list);
out:
return rc;
}
static int
install_svdump_eckd_cdl(int fd, struct disk_info *info, uint64_t mem)
{
size_t stage2_size;
void *stage2;
int rc;
if (boot_get_eckd_dump_stage2(&stage2, &stage2_size, mem))
return -1;
rc = install_dump_eckd_cdl(fd, info, stage2, stage2_size, 0, 0);
free(stage2);
return rc;
}
static int
install_mvdump_eckd_cdl(int fd, struct disk_info *info, uint64_t mem,
uint8_t force, struct mvdump_parm_table parm)
{
size_t stage2_size;
void *stage2;
int rc;
/* Write stage 2 + parameter block */
if (boot_get_eckd_mvdump_stage2(&stage2, &stage2_size, mem,
force, parm))
return -1;
rc = install_dump_eckd_cdl(fd, info, stage2, stage2_size, 1, force);
free(stage2);
return rc;
}
int
install_fba_stage1b(int fd, disk_blockptr_t **stage1b_list,
blocknum_t *stage1b_count, disk_blockptr_t *stage2_list,
blocknum_t stage2_count, struct disk_info *info)
{
struct boot_fba_stage1b *stage1b;
int stage1b_size, rc = -1;
*stage1b_list = NULL;
*stage1b_count = 0;
stage1b_size = ROUNDUP(sizeof(*stage1b), info->phy_block_size);
stage1b = misc_malloc(stage1b_size);
if (stage1b == NULL)
goto out;
memset(stage1b, 0, stage1b_size);
if (boot_init_fba_stage1b(stage1b, stage2_list, stage2_count))
goto out_free_stage1b;
*stage1b_count = disk_write_block_buffer(fd, 1, stage1b, stage1b_size,
stage1b_list, info);
if (*stage1b_count == 0)
goto out_free_stage1b;
rc = 0;
out_free_stage1b:
free(stage1b);
out:
return rc;
}
static int
install_svdump_fba(int fd, struct disk_info *info, uint64_t mem)
{
blocknum_t stage1b_count, stage2_count, blk;
disk_blockptr_t *stage1b_list, *stage2_list;
struct boot_fba_stage0 stage0;
struct fba_dump_param param;
size_t stage2_size;
void *stage2;
int rc = -1;
/* Overwrite first 64k of partition */
if (overwrite_partition_start(fd, info, 0))
goto out;
/* Install stage 2 at end of partition */
if (boot_get_fba_dump_stage2(&stage2, &stage2_size, mem))
goto out;
if (blk_cnt(stage2_size, info) > STAGE2_BLK_CNT_MAX) {
error_reason("FBA dump record is too large");
goto out_free_stage2;
}
blk = (info->geo.start + info->phy_blocks - blk_cnt(stage2_size, info));
if (misc_seek(fd, blk * info->phy_block_size))
goto out_free_stage2;
stage2_count = disk_write_block_buffer(fd, 1, stage2, stage2_size,
&stage2_list, info);
if (stage2_count == 0)
goto out_free_stage2;
/* Install stage 1b in front of stage 2 */
blk -= blk_cnt(sizeof(struct boot_fba_stage1b), info);
if (misc_seek(fd, blk * info->phy_block_size))
goto out_free_stage2_list;
if (install_fba_stage1b(fd, &stage1b_list, &stage1b_count,
stage2_list, stage2_count, info))
goto out_free_stage2_list;
/* Install stage 0/1 fill in dump partition parameter */
if (boot_init_fba_stage0(&stage0, stage1b_list, stage1b_count))
goto out_free_stage1b_list;
param.blk_start = info->geo.start;
param.blk_end = blk - 1;
boot_get_dump_info(&stage0.boot_info, BOOT_INFO_DEV_TYPE_FBA, &param);
if (DRY_RUN_FUNC(misc_pwrite(fd, &stage0, sizeof(stage0), 0)))
goto out_free_stage1b_list;
rc = 0;
out_free_stage1b_list:
free(stage1b_list);
out_free_stage2_list:
free(stage2_list);
out_free_stage2:
free(stage2);
out:
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 = DRY_RUN_FUNC(misc_write(fd, buffer, size));
if (rc)
error_text("Could not write to tape device");
free(buffer);
return rc;
}
int
install_dump(const char* device, struct job_target_data* target, uint64_t mem)
{
struct disk_info* info;
char* tempdev;
uint64_t part_size;
int fd;
int rc;
fd = misc_open_exclusive(device);
if (fd == -1) {
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 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, target, &info);
if (rc) {
error_text("Could not get information for dump target "
"'%s'", device);
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_ldl:
case disk_type_eckd_cdl:
if (check_eckd_dump_partition(info)) {
error_text("Dump target '%s'", device);
rc = -1;
break;
}
/* Fall through. */
case disk_type_fba:
part_size = info->phy_block_size * info->phy_blocks;
printf("Dump target: partition '%s' with a size of %llu MB.\n",
device, (unsigned long long) part_size >> 20);
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_ldl)
rc = install_svdump_eckd_ldl(fd, info, mem);
else if (info->type == disk_type_eckd_cdl)
rc = install_svdump_eckd_cdl(fd, info, mem);
else
rc = install_svdump_fba(fd, info, mem);
break;
case disk_type_scsi:
error_reason("%s: Unsupported disk type '%s' (try --dumptofs)",
device, disk_get_type_name(info->type));
rc = -1;
break;
case disk_type_diag:
error_reason("%s: Unsupported disk type '%s'",
device, disk_get_type_name(info->type));
rc = -1;
break;
}
misc_free_temp_dev(tempdev);
disk_free_info(info);
if (fsync(fd))
error_text("Could not sync device file '%s'", device);
if (close(fd))
error_text("Could not close device file '%s'", device);
return rc;
}
int
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;
char* tempdev;
uint64_t total_size = 0;
int rc = 0, i, j, fd;
struct timeval time;
memset(&parm, 0, sizeof(struct mvdump_parm_table));
gettimeofday(&time, NULL);
parm.num_param = count;
parm.timestamp = (time.tv_sec << 20) + time.tv_usec;
for (i = 0; i < count; i++) {
int dummy, ssid;
char busid[16];
fd = misc_open_exclusive(device[i]);
if (fd == -1) {
error_text("Could not open dump target '%s'",
device[i]);
rc = -1;
goto out;
}
if (rewind_tape(fd) == 0) {
/* Rewind worked - this is a tape */
error_text("Dump target '%s' is a tape device",
device[i]);
close(fd);
rc = -1;
goto out;
}
close(fd);
/* This is a disk device */
rc = disk_get_info(device[i], target, &info[i]);
if (rc) {
error_text("Could not get information for dump target "
"'%s'", device[i]);
goto out;
}
if (info[i]->partnum == 0) {
error_reason("Dump target '%s' is not a disk partition",
device[i]);
rc = -1;
goto out;
}
if (info[i]->type != disk_type_eckd_cdl) {
error_reason("Dump target '%s' has to be ECKD DASD "
"with cdl format.", device[i]);
rc = -1;
goto out;
}
for (j = 0; j < i; j++) {
if (info[j]->partition == info[i]->partition) {
error_text("Dump targets '%s' and '%s' are "
"identical devices.",
device[i], device[j]);
rc = -1;
goto out;
}
}
if (check_eckd_dump_partition(info[i])) {
error_text("Dump target '%s'", device[i]);
rc = -1;
goto out;
}
parm.param[i].start_blk = info[i]->geo.start;
parm.param[i].end_blk = info[i]->geo.start +
info[i]->phy_blocks - 1;
parm.param[i].bpt = info[i]->geo.sectors;
parm.param[i].num_heads = info[i]->geo.heads;
parm.param[i].blocksize = info[i]->phy_block_size >> 8;
parm.param[i].devno = info[i]->devno;
if (util_sys_get_dev_addr(device[i], busid) != 0) {
error_text("Could not find bus-ID for '%s'", device[i]);
rc = -1;
goto out;
}
if (sscanf(busid, "%x.%x.%x", &dummy, &ssid, &dummy) != 3) {
error_text("Could not find bus-ID for '%s'", device[i]);
rc = -1;
goto out;
}
parm.ssid[i] = ssid;
}
if (verbose) {
for (i = 0; i < count; i++) {
printf("Multi-volume dump target %d:\n", i + 1);
disk_print_info(info[i]);
printf("-------------------------------------------\n");
}
}
for (i = 0; i < count; i++)
total_size += info[i]->phy_block_size * info[i]->phy_blocks;
printf("Dump target: %d partitions with a total size of %ld MB.\n",
count, (long) total_size >> 20);
if (interactive) {
printf("Warning: All information on the following "
"partitions will be lost!\n");
for (i = 0; i < count; i++)
printf(" %s\n", device[i]);
rc = ask_for_confirmation("Do you want to continue creating "
"multi-volume dump partitions "
"(y/n)?");
if (rc)
goto out;
}
for (i = 0; i < count; i++) {
rc = misc_temp_dev(info[i]->device, 1, &tempdev);
if (rc) {
rc = -1;
goto out;
}
fd = misc_open_exclusive(tempdev);
if (fd == -1) {
error_text("Could not open temporary device node '%s'",
tempdev);
misc_free_temp_dev(tempdev);
rc = -1;
goto out;
}
if (verbose)
printf("Installing dump record on target partition "
"'%s'\n", device[i]);
rc = install_mvdump_eckd_cdl(fd, info[i], mem, force, parm);
misc_free_temp_dev(tempdev);
if (fsync(fd))
error_text("Could not sync device file '%s'", device);
if (close(fd))
error_text("Could not close device file '%s'", device);
if (rc)
goto out;
}
out:
for (i = 0; i < count; i++)
if (info[i] != NULL)
disk_free_info(info[i]);
return rc;
}