mirror of
https://github.com/ibm-s390-linux/s390-tools.git
synced 2026-08-05 02:14:52 +00:00
Make use of the pre-defined __packed macro throughout zipl. This requires adding the global include dir to ALL_CFLAGS. Signed-off-by: Philipp Rudo <prudo@linux.ibm.com> Reviewed-by: Stefan Haberland <sth@linux.ibm.com> Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
1204 lines
31 KiB
C
1204 lines
31 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 "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;
|
|
}
|
|
|
|
|
|
#define DISK_LAYOUT_ID 0x00000001
|
|
|
|
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 {
|
|
uint8_t magic[4];
|
|
uint32_t version_id;
|
|
uint8_t reserved[8];
|
|
uint8_t program_table_pointer[16];
|
|
uint8_t reserved2[0x50];
|
|
struct boot_info boot_info;
|
|
} __packed* 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);
|
|
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,
|
|
¶m);
|
|
}
|
|
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 initial program load from an FBA type disk. */
|
|
static int
|
|
install_fba(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(&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 initial program load from an ECKD type disk with
|
|
* Linux Disk Layout. */
|
|
static int
|
|
install_eckd_ldl(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(&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 initial program load from an ECKD type disk with
|
|
* OS/390 compatible disk layout. */
|
|
static int
|
|
install_eckd_cdl(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(&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;
|
|
}
|
|
|
|
int
|
|
install_bootloader(const char *device, disk_blockptr_t *program_table,
|
|
disk_blockptr_t *scsi_dump_sb_blockptr,
|
|
disk_blockptr_t *stage1b_list, blocknum_t stage1b_count,
|
|
struct disk_info *info, struct job_data *job)
|
|
{
|
|
int fd, 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);
|
|
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");
|
|
}
|
|
}
|
|
/* Call disk specific install functions */
|
|
rc = -1;
|
|
switch (info->type) {
|
|
case disk_type_scsi:
|
|
rc = install_scsi(fd, program_table, info,
|
|
scsi_dump_sb_blockptr);
|
|
if (rc == 0 && job->id == job_dump_partition)
|
|
rc = overwrite_partition_start(fd, info, 0);
|
|
break;
|
|
case disk_type_fba:
|
|
rc = install_fba(fd, program_table, stage1b_list,
|
|
stage1b_count, info);
|
|
break;
|
|
case disk_type_eckd_ldl:
|
|
rc = install_eckd_ldl(fd, program_table, stage1b_list,
|
|
stage1b_count, info);
|
|
break;
|
|
case disk_type_eckd_cdl:
|
|
rc = install_eckd_cdl(fd, program_table, stage1b_list,
|
|
stage1b_count, 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->start_blk = info->geo.start;
|
|
param->end_blk = info->geo.start + info->phy_blocks - 1 - count;
|
|
param->num_heads = info->geo.heads;
|
|
param->blocksize = 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(¶m, info, 0);
|
|
boot_get_dump_info(&stage1.boot_info, BOOT_INFO_DEV_TYPE_ECKD, ¶m);
|
|
|
|
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(¶m, info, 0);
|
|
boot_get_dump_info(&stage1.boot_info, BOOT_INFO_DEV_TYPE_ECKD, ¶m);
|
|
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.start_blk = (uint64_t) info->geo.start;
|
|
param.blockct = (uint64_t) blk - 1;
|
|
boot_get_dump_info(&stage0.boot_info, BOOT_INFO_DEV_TYPE_FBA, ¶m);
|
|
|
|
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;
|
|
}
|