mirror of
https://github.com/ibm-s390-linux/s390-tools.git
synced 2026-08-05 02:14:52 +00:00
This commit is based on the s390-tools-1.39.0 version. Changes on top of s390-tools-1.39.0: - Add MIT license to all source files - Add LICENSE file - Transform REAMDE to README.md (markdown) - Add AUTHORS.md file - Add CONTRIBUTING.md file - Move changelog from README to CHANGELOG.md file Reviewed-by: Stefan Haberland <sth@linux.vnet.ibm.com> Signed-off-by: Michael Holzheu <holzheu@linux.vnet.ibm.com>
1113 lines
30 KiB
C
1113 lines
30 KiB
C
/*
|
|
* zipl - zSeries Initial Program Loader tool
|
|
*
|
|
* Functions to handle disk layout specific operations
|
|
*
|
|
* Copyright IBM Corp. 2001, 2017
|
|
*
|
|
* 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 <dirent.h>
|
|
#include <errno.h>
|
|
#include <fcntl.h>
|
|
#include <limits.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <sys/ioctl.h>
|
|
#include <sys/stat.h>
|
|
#include <sys/sysmacros.h>
|
|
#include <sys/vfs.h>
|
|
#include <unistd.h>
|
|
|
|
#include "lib/util_proc.h"
|
|
|
|
#include "disk.h"
|
|
#include "error.h"
|
|
#include "install.h"
|
|
#include "job.h"
|
|
#include "misc.h"
|
|
|
|
/* from linux/fs.h */
|
|
#define FIBMAP _IO(0x00,1)
|
|
#define FIGETBSZ _IO(0x00,2)
|
|
#define BLKGETSIZE _IO(0x12,96)
|
|
#define BLKSSZGET _IO(0x12,104)
|
|
|
|
/* from linux/hdregs.h */
|
|
#define HDIO_GETGEO 0x0301
|
|
|
|
#define DASD_IOCTL_LETTER 'D'
|
|
#define BIODASDINFO _IOR(DASD_IOCTL_LETTER, 1, \
|
|
struct dasd_information)
|
|
#define DASD_PARTN_MASK 0x03
|
|
#define SCSI_PARTN_MASK 0x0f
|
|
|
|
/* Definitions for dasd device driver, taken from linux/include/asm/dasd.h */
|
|
struct dasd_information {
|
|
unsigned int devno; /* S/390 devno */
|
|
unsigned int real_devno; /* for aliases */
|
|
unsigned int schid; /* S/390 subchannel identifier */
|
|
unsigned int cu_type : 16; /* from SenseID */
|
|
unsigned int cu_model : 8; /* from SenseID */
|
|
unsigned int dev_type : 16; /* from SenseID */
|
|
unsigned int dev_model : 8; /* from SenseID */
|
|
unsigned int open_count;
|
|
unsigned int req_queue_len;
|
|
unsigned int chanq_len; /* length of chanq */
|
|
char type[4]; /* from discipline.name */
|
|
unsigned int status; /* current device level */
|
|
unsigned int label_block; /* where to find the VOLSER */
|
|
unsigned int FBA_layout; /* fixed block size (like AIXVOL) */
|
|
unsigned int characteristics_size;
|
|
unsigned int confdata_size;
|
|
char characteristics[64]; /* from read_device_characteristics */
|
|
char configuration_data[256]; /* from read_configuration_data */
|
|
};
|
|
|
|
static int
|
|
disk_determine_dasd_type(struct disk_info *data,
|
|
struct dasd_information dasd_info)
|
|
{
|
|
if (strncmp(dasd_info.type, "FBA ",4) == 0)
|
|
data->type = disk_type_fba;
|
|
else if (strncmp(dasd_info.type, "DIAG",4) == 0)
|
|
data->type = disk_type_diag;
|
|
else if (strncmp(dasd_info.type, "ECKD",4) == 0) {
|
|
if (dasd_info.FBA_layout)
|
|
data->type = disk_type_eckd_ldl;
|
|
else
|
|
data->type = disk_type_eckd_cdl;
|
|
} else {
|
|
error_reason("Unknown DASD type");
|
|
return -1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/* Return non-zero for ECKD type. */
|
|
int
|
|
disk_is_eckd(disk_type_t type)
|
|
{
|
|
return (type == disk_type_eckd_ldl ||
|
|
type == disk_type_eckd_cdl);
|
|
}
|
|
|
|
static int
|
|
read_block_by_offset(int fd, int blksize, uint64_t offset, char *buffer)
|
|
{
|
|
|
|
if (lseek(fd, offset, SEEK_SET) == -1) {
|
|
/* Seek error. */
|
|
error_text("Error: Could not seek to %llu: %s!\n",
|
|
(unsigned long long) offset);
|
|
return -1;
|
|
}
|
|
|
|
misc_read(fd, buffer, blksize);
|
|
|
|
return 0;
|
|
}
|
|
|
|
int
|
|
determine_virtblk_type(struct disk_info *data, struct stat *stats)
|
|
{
|
|
char *device;
|
|
char *buffer;
|
|
int fd, rc, shift, sb;
|
|
|
|
rc = 0;
|
|
buffer = (char *) malloc(data->phy_block_size);
|
|
if (!buffer)
|
|
return -1;
|
|
|
|
/*
|
|
* The geo.start value reported for virtblk devices is based on a
|
|
* 512 byte blocksize.
|
|
* For DASD devices it is based on the real (most likely 4k) blocksize
|
|
* and the DASD device driver reports a shifted value.
|
|
* For virtblk devices we need to shift the value manually according to
|
|
* the physical blocksize of the device.
|
|
*/
|
|
shift = 0;
|
|
for (sb = 512; sb < data->phy_block_size; sb = sb << 1)
|
|
shift++;
|
|
|
|
if (data->geo.heads == 15) {
|
|
/* assume DASD */
|
|
data->partnum = stats->st_rdev & DASD_PARTN_MASK;
|
|
data->device = stats->st_rdev & ~DASD_PARTN_MASK;
|
|
|
|
rc = misc_temp_dev(data->device, 1, &device);
|
|
if (rc)
|
|
goto out_err;
|
|
|
|
fd = open(device, O_RDONLY);
|
|
|
|
/* read 3rd record, containing the volume label */
|
|
read_block_by_offset(fd, data->phy_block_size,
|
|
2 * data->phy_block_size, buffer);
|
|
misc_ebcdic_to_ascii((unsigned char *) buffer,
|
|
(unsigned char *) buffer + 4);
|
|
/* determine dasd type by label */
|
|
if (!strncmp(buffer, "VOL1", 4)) {
|
|
data->type = disk_type_eckd_cdl;
|
|
data->geo.start >>= shift;
|
|
} else if (!strncmp(buffer, "LNX1", 4)) {
|
|
data->type = disk_type_eckd_ldl;
|
|
data->geo.start >>= shift;
|
|
} else if (!strncmp(buffer, "CMS1", 4)) {
|
|
data->type = disk_type_eckd_ldl;
|
|
data->geo.start >>= shift;
|
|
} else {
|
|
/* DASD label was not found,
|
|
* type has to be specified by hand
|
|
*/
|
|
error_text("Failed to read DASD label, "
|
|
"please specify type manually");
|
|
rc = -1;
|
|
}
|
|
close(fd);
|
|
misc_free_temp_dev(device);
|
|
} else {
|
|
data->type = disk_type_scsi;
|
|
data->partnum = stats->st_rdev & SCSI_PARTN_MASK;
|
|
data->device = stats->st_rdev & ~SCSI_PARTN_MASK;
|
|
}
|
|
|
|
out_err:
|
|
free(buffer);
|
|
return rc;
|
|
}
|
|
|
|
int
|
|
disk_get_info(const char* device, struct job_target_data* target,
|
|
struct disk_info** info)
|
|
{
|
|
struct stat stats;
|
|
struct stat script_stats;
|
|
struct util_proc_part_entry part_entry;
|
|
struct util_proc_dev_entry dev_entry;
|
|
struct dasd_information dasd_info;
|
|
struct disk_info *data;
|
|
int fd;
|
|
long devsize;
|
|
FILE *fh;
|
|
char *script_pre = TOOLS_LIBDIR "/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)) {
|
|
error_reason(strerror(errno));
|
|
return -1;
|
|
}
|
|
/* Open device file */
|
|
fd = open(device, O_RDONLY);
|
|
if (fd == -1) {
|
|
error_reason(strerror(errno));
|
|
return -1;
|
|
}
|
|
/* Get memory for result */
|
|
data = (struct disk_info *) misc_malloc(sizeof(struct disk_info));
|
|
if (data == NULL) {
|
|
close(fd);
|
|
return -1;
|
|
}
|
|
memset((void *) data, 0, sizeof(struct disk_info));
|
|
/* Try to get device driver name */
|
|
if (util_proc_dev_get_entry(stats.st_rdev, 1, &dev_entry) == 0) {
|
|
data->drv_name = misc_strdup(dev_entry.name);
|
|
util_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);
|
|
if (target->bootmap_dir == NULL) {
|
|
/* happens in case of partition dump */
|
|
snprintf(ppn_cmd, sizeof(ppn_cmd), "%s %d:%d",
|
|
script_file, major(stats.st_rdev),
|
|
minor(stats.st_rdev));
|
|
} else {
|
|
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 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;
|
|
data->phy_block_size = target->targetblocksize;
|
|
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;
|
|
data->partnum = minor(stats.st_rdev) - minnum;
|
|
}
|
|
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;
|
|
}
|
|
if (ioctl(fd, BLKSSZGET, &data->phy_block_size)) {
|
|
error_reason("Could not get blocksize");
|
|
goto out_close;
|
|
}
|
|
/* Determine disk type */
|
|
if (!data->drv_name) {
|
|
/* Driver name cannot be read */
|
|
if (ioctl(fd, BIODASDINFO, &dasd_info)) {
|
|
data->devno = -1;
|
|
if (data->geo.start) {
|
|
/* SCSI partition */
|
|
data->type = disk_type_scsi;
|
|
data->partnum = stats.st_rdev & SCSI_PARTN_MASK;
|
|
data->device = stats.st_rdev & ~SCSI_PARTN_MASK;
|
|
} else {
|
|
/* SCSI disk */
|
|
data->type = disk_type_scsi;
|
|
data->partnum = 0;
|
|
data->device = stats.st_rdev;
|
|
}
|
|
} else {
|
|
/* DASD */
|
|
data->devno = dasd_info.devno;
|
|
if (disk_determine_dasd_type(data, dasd_info))
|
|
goto out_close;
|
|
data->partnum = stats.st_rdev & DASD_PARTN_MASK;
|
|
data->device = stats.st_rdev & ~DASD_PARTN_MASK;
|
|
}
|
|
} else if (strcmp(data->drv_name, "dasd") == 0) {
|
|
/* Driver name is 'dasd' */
|
|
if (ioctl(fd, BIODASDINFO, &dasd_info)) {
|
|
error_reason("Could not determine DASD type");
|
|
goto out_close;
|
|
}
|
|
data->devno = dasd_info.devno;
|
|
if (disk_determine_dasd_type(data, dasd_info))
|
|
goto out_close;
|
|
data->partnum = stats.st_rdev & DASD_PARTN_MASK;
|
|
data->device = stats.st_rdev & ~DASD_PARTN_MASK;
|
|
} else if (strcmp(data->drv_name, "sd") == 0) {
|
|
/* Driver name is 'sd' */
|
|
data->devno = -1;
|
|
data->type = disk_type_scsi;
|
|
data->partnum = stats.st_rdev & SCSI_PARTN_MASK;
|
|
data->device = stats.st_rdev & ~SCSI_PARTN_MASK;
|
|
|
|
} else if (strcmp(data->drv_name, "virtblk") == 0) {
|
|
|
|
if (ioctl(fd, HDIO_GETGEO, &data->geo) != 0)
|
|
perror("Could not retrieve disk geometry information.");
|
|
if (ioctl(fd, BLKSSZGET, &data->phy_block_size) != 0)
|
|
perror("Could not retrieve blocksize information.");
|
|
|
|
if (determine_virtblk_type(data, &stats)) {
|
|
error_reason("Virtblk device type not clearly "
|
|
"determined.");
|
|
goto out_close;
|
|
}
|
|
} else {
|
|
/* Driver name is unknown */
|
|
error_reason("Unsupported device driver '%s'", data->drv_name);
|
|
goto out_close;
|
|
}
|
|
|
|
type_determined:
|
|
/* Get size of device in sectors (512 byte) */
|
|
if (ioctl(fd, BLKGETSIZE, &devsize)) {
|
|
error_reason("Could not get device size");
|
|
goto out_close;
|
|
}
|
|
|
|
/* 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)
|
|
data->partition = stats.st_rdev;
|
|
/* Try to get device name */
|
|
if (util_proc_part_get_entry(data->device, &part_entry) == 0) {
|
|
data->name = misc_strdup(part_entry.name);
|
|
util_proc_part_free_entry(&part_entry);
|
|
if (data->name == NULL)
|
|
goto out_close;
|
|
}
|
|
/* There is no easy way to find out whether there is a file system
|
|
* on this device, so we set the respective block size to an invalid
|
|
* value. */
|
|
data->fs_block_size = -1;
|
|
close(fd);
|
|
*info = data;
|
|
return 0;
|
|
out_close:
|
|
close(fd);
|
|
free(data);
|
|
return -1;
|
|
|
|
}
|
|
|
|
int
|
|
disk_is_tape(const char* device)
|
|
{
|
|
int fd, rc = 0;
|
|
|
|
/* Check for tape */
|
|
fd = open(device, O_RDWR);
|
|
if (fd == -1)
|
|
return 0;
|
|
if (rewind_tape(fd) == 0)
|
|
rc = 1;
|
|
close(fd);
|
|
return rc;
|
|
}
|
|
|
|
int
|
|
disk_is_scsi(const char* device, struct job_target_data* target)
|
|
{
|
|
struct disk_info* info;
|
|
int rc = 0;
|
|
|
|
if (disk_get_info(device, target, &info) == -1)
|
|
return 0;
|
|
if (info->type == disk_type_scsi)
|
|
rc = 1;
|
|
disk_free_info(info);
|
|
return rc;
|
|
}
|
|
|
|
int
|
|
disk_get_info_from_file(const char* filename, struct job_target_data* target,
|
|
struct disk_info** info)
|
|
{
|
|
struct stat stats;
|
|
char* device;
|
|
int blocksize;
|
|
int fd;
|
|
int rc;
|
|
|
|
if (stat(filename, &stats)) {
|
|
error_reason(strerror(errno));
|
|
return -1;
|
|
}
|
|
/* Retrieve file system block size */
|
|
fd = open(filename, O_RDONLY);
|
|
if (fd == -1) {
|
|
error_reason(strerror(errno));
|
|
return -1;
|
|
}
|
|
rc = ioctl(fd, FIGETBSZ, &blocksize);
|
|
close(fd);
|
|
if (rc == -1) {
|
|
error_reason("Could not get file system block size for '%s'",
|
|
filename);
|
|
return -1;
|
|
}
|
|
/* Create temporary device file */
|
|
rc = misc_temp_dev(stats.st_dev, 1, &device);
|
|
if (rc)
|
|
return -1;
|
|
/* Get device info */
|
|
rc = disk_get_info(device, target, info);
|
|
if (rc == 0)
|
|
(*info)->fs_block_size = blocksize;
|
|
/* Clean up */
|
|
misc_free_temp_dev(device);
|
|
return rc;
|
|
}
|
|
|
|
|
|
void
|
|
disk_free_info(struct disk_info* info)
|
|
{
|
|
if (info->name)
|
|
free(info->name);
|
|
if (info->drv_name)
|
|
free(info->drv_name);
|
|
free(info);
|
|
}
|
|
|
|
|
|
#ifndef REISERFS_SUPER_MAGIC
|
|
#define REISERFS_SUPER_MAGIC 0x52654973
|
|
#endif /* not REISERFS_SUPER_MAGIC */
|
|
|
|
#ifndef REISERFS_IOC_UNPACK
|
|
#define REISERFS_IOC_UNPACK _IOW(0xCD,1,long)
|
|
#endif /* not REISERFS_IOC_UNPACK */
|
|
|
|
/* Retrieve the physical blocknumber (block on disk) of the specified logical
|
|
* block (block in file). FD provides the file descriptor, LOGICAL is the
|
|
* logical block number. Upon success, return 0 and store the physical
|
|
* blocknumber in the variable pointed to by PHYSICAL. Return non-zero
|
|
* otherwise. */
|
|
int
|
|
disk_get_blocknum(int fd, int fd_is_basedisk, blocknum_t logical,
|
|
blocknum_t* physical, struct disk_info* info)
|
|
{
|
|
struct statfs buf;
|
|
blocknum_t phy_per_fs;
|
|
int mapped;
|
|
int subblock;
|
|
|
|
/* No file system: partition or raw disk */
|
|
if (info->fs_block_size == -1) {
|
|
if (fd_is_basedisk)
|
|
*physical = logical;
|
|
else
|
|
*physical = logical + info->geo.start;
|
|
return 0;
|
|
}
|
|
|
|
/* Get file system type */
|
|
if (fstatfs(fd, &buf)) {
|
|
error_reason(strerror(errno));
|
|
return -1;
|
|
}
|
|
/* Files on ReiserFS need unpacking */
|
|
if (buf.f_type == REISERFS_SUPER_MAGIC) {
|
|
if (ioctl(fd, REISERFS_IOC_UNPACK, 1)) {
|
|
error_reason("Could not unpack ReiserFS file");
|
|
return -1;
|
|
}
|
|
}
|
|
/* Get mapping in file system blocks */
|
|
phy_per_fs = info->fs_block_size / info->phy_block_size;
|
|
mapped = logical / phy_per_fs;
|
|
subblock = logical % phy_per_fs;
|
|
if (ioctl(fd, FIBMAP, &mapped)) {
|
|
error_reason("Could not get file mapping");
|
|
return -1;
|
|
}
|
|
if (mapped == 0) {
|
|
/* This is a hole in the file */
|
|
*physical = 0;
|
|
} else {
|
|
/* Convert file system block to physical */
|
|
*physical = mapped * phy_per_fs + subblock;
|
|
/* Add partition start */
|
|
*physical += info->geo.start;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
|
|
/* Return the cylinder on which the block number BLOCKNUM is stored on the
|
|
* CHS device identified by INFO. */
|
|
int
|
|
disk_cyl_from_blocknum(blocknum_t blocknum, struct disk_info* info)
|
|
{
|
|
return blocknum / (info->geo.heads * info->geo.sectors);
|
|
}
|
|
|
|
|
|
/* Return the head on which the block number BLOCKNUM is stored on the
|
|
* CHS device identified by INFO. */
|
|
int
|
|
disk_head_from_blocknum(blocknum_t blocknum, struct disk_info* info)
|
|
{
|
|
return (blocknum / info->geo.sectors) % info->geo.heads;
|
|
}
|
|
|
|
|
|
/* Return the sector on which the block number BLOCKNUM is stored on the
|
|
* CHS device identified by INFO. */
|
|
int
|
|
disk_sec_from_blocknum(blocknum_t blocknum, struct disk_info* info)
|
|
{
|
|
return blocknum % info->geo.sectors + 1;
|
|
}
|
|
|
|
|
|
/* Create a block pointer in memory at location PTR which represents the
|
|
* given blocknumber BLOCKNUM. INFO provides information about the disk
|
|
* layout. */
|
|
void
|
|
disk_blockptr_from_blocknum(disk_blockptr_t* ptr, blocknum_t blocknum,
|
|
struct disk_info* info)
|
|
{
|
|
switch (info->type) {
|
|
case disk_type_scsi:
|
|
case disk_type_fba:
|
|
case disk_type_diag:
|
|
ptr->linear.block = blocknum;
|
|
ptr->linear.size = info->phy_block_size;
|
|
ptr->linear.blockct = 0;
|
|
break;
|
|
case disk_type_eckd_ldl:
|
|
case disk_type_eckd_cdl:
|
|
if (blocknum == 0) {
|
|
/* Special case: zero blocknum will be expanded to
|
|
* size * (blockct+1) bytes of zeroes. */
|
|
ptr->chs.cyl = 0;
|
|
ptr->chs.head = 0;
|
|
ptr->chs.sec = 0;
|
|
} else {
|
|
|
|
ptr->chs.cyl = disk_cyl_from_blocknum(blocknum, info);
|
|
ptr->chs.head = disk_head_from_blocknum(blocknum,
|
|
info);
|
|
ptr->chs.sec = disk_sec_from_blocknum(blocknum, info);
|
|
}
|
|
ptr->chs.size = info->phy_block_size;
|
|
ptr->chs.blockct = 0;
|
|
break;
|
|
}
|
|
}
|
|
|
|
|
|
/* Write BYTECOUNT bytes of data from memory at location DATA as a block to
|
|
* the file identified by file descriptor FD. Make sure that the data is
|
|
* aligned on a block size boundary and that at most INFO->PHY_BLOCK_SIZE
|
|
* bytes are written. INFO provides information about the disk layout. Upon
|
|
* success, return 0 and store the pointer to the resulting disk block to BLOCK
|
|
* (if BLOCK is not NULL). Return non-zero otherwise. */
|
|
static int
|
|
disk_write_block_aligned_base(int fd, int is_base_disk, const void* data,
|
|
size_t bytecount, disk_blockptr_t* block,
|
|
struct disk_info* info)
|
|
{
|
|
blocknum_t current_block;
|
|
blocknum_t blocknum;
|
|
off_t current_pos;
|
|
int align;
|
|
|
|
current_pos = lseek(fd, 0, SEEK_CUR);
|
|
if (current_pos == -1) {
|
|
error_text(strerror(errno));
|
|
return -1;
|
|
}
|
|
/* Ensure block alignment of current file pos */
|
|
align = info->phy_block_size;
|
|
if (current_pos % align != 0) {
|
|
current_pos = lseek(fd, align - current_pos % align, SEEK_CUR);
|
|
if (current_pos == -1) {
|
|
error_text(strerror(errno));
|
|
return -1;
|
|
}
|
|
}
|
|
current_block = current_pos / align;
|
|
/* Ensure maximum size */
|
|
if (bytecount > (size_t) align)
|
|
bytecount = align;
|
|
/* Write data block */
|
|
if (misc_write(fd, data, bytecount))
|
|
return -1;
|
|
if (block != NULL) {
|
|
/* Store block pointer */
|
|
if (disk_get_blocknum(fd, is_base_disk, current_block,
|
|
&blocknum, info))
|
|
return -1;
|
|
disk_blockptr_from_blocknum(block, blocknum, info);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
int
|
|
disk_write_block_aligned(int fd, const void* data, size_t bytecount,
|
|
disk_blockptr_t* block, struct disk_info* info)
|
|
{
|
|
return disk_write_block_aligned_base(fd, 0, data, bytecount, block,
|
|
info);
|
|
}
|
|
|
|
/* Write BYTECOUNT bytes from memory at location BUFFER to the file identified
|
|
* by file descriptor FD and return the list of pointers to the disk blocks
|
|
* that make up the respective part of the file. Upon success return the number
|
|
* of blocks and set BLOCKLIST to point to the uncompressed list. Return zero
|
|
* otherwise. Note that the data is written to a file position which is aligned
|
|
* on a block size boundary. */
|
|
blocknum_t
|
|
disk_write_block_buffer(int fd, int fd_is_basedisk, const void* buffer,
|
|
size_t bytecount, disk_blockptr_t** blocklist,
|
|
struct disk_info* info)
|
|
{
|
|
disk_blockptr_t* list;
|
|
blocknum_t count;
|
|
blocknum_t i;
|
|
size_t written;
|
|
size_t chunk_size;
|
|
int rc;
|
|
|
|
count = (bytecount + info->phy_block_size - 1) / info->phy_block_size;
|
|
list = (disk_blockptr_t *) misc_malloc(sizeof(disk_blockptr_t) *
|
|
count);
|
|
if (list == NULL) {
|
|
close(fd);
|
|
return 0;
|
|
}
|
|
memset((void *) list, 0, sizeof(disk_blockptr_t) * count);
|
|
/* Build list */
|
|
for (i=0, written=0; i < count; i++, written += chunk_size) {
|
|
chunk_size = bytecount - written;
|
|
if (chunk_size > (size_t) info->phy_block_size)
|
|
chunk_size = info->phy_block_size;
|
|
rc = disk_write_block_aligned_base(fd, fd_is_basedisk,
|
|
VOID_ADD(buffer, written),
|
|
chunk_size,
|
|
&list[i], info);
|
|
if (rc) {
|
|
free(list);
|
|
return 0;
|
|
}
|
|
}
|
|
*blocklist = list;
|
|
return count;
|
|
}
|
|
|
|
|
|
/* Print device node. */
|
|
void
|
|
disk_print_devt(dev_t d)
|
|
{
|
|
printf("%02x:%02x", major(d), minor(d));
|
|
}
|
|
|
|
|
|
/* Return a name for a given disk TYPE. */
|
|
char *
|
|
disk_get_type_name(disk_type_t type)
|
|
{
|
|
switch (type) {
|
|
case disk_type_scsi:
|
|
return "SCSI disk layout";
|
|
case disk_type_fba:
|
|
return "FBA disk layout";
|
|
case disk_type_diag:
|
|
return "DIAG disk layout";
|
|
case disk_type_eckd_ldl:
|
|
return "ECKD/linux disk layout";
|
|
case disk_type_eckd_cdl:
|
|
return "ECKD/compatible disk layout";
|
|
default:
|
|
return "Unknown disk type";
|
|
}
|
|
}
|
|
|
|
|
|
/* Return non-zero for ECKD large volumes. */
|
|
int
|
|
disk_is_large_volume(struct disk_info* info)
|
|
{
|
|
return (info->type == disk_type_eckd_ldl ||
|
|
info->type == disk_type_eckd_cdl) &&
|
|
info->geo.cylinders == 0xfffe;
|
|
}
|
|
|
|
|
|
/* Print textual representation of INFO contents. */
|
|
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.......................: ");
|
|
disk_print_devt(info->partition);
|
|
printf("\n");
|
|
}
|
|
if (info->name) {
|
|
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_ldl) ||
|
|
(info->type == disk_type_eckd_cdl)) &&
|
|
(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%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%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%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");
|
|
}
|
|
|
|
/* Print textual representation of geo structure. */
|
|
void
|
|
disk_print_geo(struct disk_info *data)
|
|
{
|
|
printf(" geo.heads.........:%u\n", data->geo.heads);
|
|
printf(" geo.sectors.......:%u\n", data->geo.sectors);
|
|
printf(" geo.cylinders.....:%u\n", data->geo.cylinders);
|
|
printf(" geo.start.........:%lu\n", data->geo.start);
|
|
printf(" blksize...........:%u\n", data->phy_block_size);
|
|
}
|
|
|
|
/* Check whether a block is a zero block which identifies a hole in a file.
|
|
* Return non-zero if BLOCK is a zero block, 0 otherwise. */
|
|
int
|
|
disk_is_zero_block(disk_blockptr_t* block, struct disk_info* info)
|
|
{
|
|
switch (info->type) {
|
|
case disk_type_scsi:
|
|
case disk_type_fba:
|
|
return block->linear.block == 0;
|
|
case disk_type_eckd_ldl:
|
|
case disk_type_eckd_cdl:
|
|
return (block->chs.cyl == 0) && (block->chs.head == 0) &&
|
|
(block->chs.sec == 0);
|
|
default:
|
|
break;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
|
|
#define DASD_MAX_LINK_COUNT 255
|
|
#define SCSI_MAX_LINK_COUNT 65535
|
|
|
|
/* Check whether two block pointers FIRST and SECOND can be merged into
|
|
* one block pointer by increasing the block count field of the first
|
|
* pointer. INFO provides information about the disk type. Return non-zero if
|
|
* blocks can be merged, 0 otherwise. */
|
|
static int
|
|
can_merge_blocks(disk_blockptr_t* first, disk_blockptr_t* second,
|
|
struct disk_info* info)
|
|
{
|
|
int max_count;
|
|
|
|
/* Zero blocks can never be merged */
|
|
if (disk_is_zero_block(first, info) || disk_is_zero_block(second, info))
|
|
return 0;
|
|
if (info->type == disk_type_scsi)
|
|
max_count = SCSI_MAX_LINK_COUNT;
|
|
else
|
|
max_count = DASD_MAX_LINK_COUNT;
|
|
switch (info->type) {
|
|
case disk_type_scsi:
|
|
case disk_type_fba:
|
|
/* Check link count limits */
|
|
if (((int) first->linear.blockct) +
|
|
((int) second->linear.blockct) + 1 > max_count)
|
|
return 0;
|
|
if (first->linear.block + first->linear.blockct + 1 ==
|
|
second->linear.block)
|
|
return 1;
|
|
break;
|
|
case disk_type_eckd_ldl:
|
|
case disk_type_eckd_cdl:
|
|
/* Check link count limits */
|
|
if (((int) first->chs.blockct) +
|
|
((int) second->chs.blockct) + 1 > max_count)
|
|
return 0;
|
|
if ((first->chs.cyl == second->chs.cyl) &&
|
|
(first->chs.head == second->chs.head) &&
|
|
(first->chs.sec + first->chs.blockct + 1 ==
|
|
second->chs.sec))
|
|
return 1;
|
|
break;
|
|
case disk_type_diag:
|
|
break;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
|
|
/* Merge two block pointers FIRST and SECOND into one pointer. The resulting
|
|
* pointer is stored in FIRST. INFO provides information about the disk
|
|
* type. */
|
|
static void
|
|
merge_blocks(disk_blockptr_t* first, disk_blockptr_t* second,
|
|
struct disk_info* info)
|
|
{
|
|
switch (info->type) {
|
|
case disk_type_scsi:
|
|
case disk_type_fba:
|
|
first->linear.blockct += second->linear.blockct + 1;
|
|
break;
|
|
case disk_type_eckd_ldl:
|
|
case disk_type_eckd_cdl:
|
|
first->chs.blockct += second->chs.blockct + 1;
|
|
break;
|
|
case disk_type_diag:
|
|
/* Should not happen */
|
|
break;
|
|
}
|
|
}
|
|
|
|
|
|
/* Analyze COUNT elements in LIST and try to merge pointers to adjacent
|
|
* blocks. INFO provides information about the disk type. Return the new
|
|
* number of elements in the list. */
|
|
blocknum_t
|
|
disk_compact_blocklist(disk_blockptr_t* list, blocknum_t count,
|
|
struct disk_info* info)
|
|
{
|
|
blocknum_t i;
|
|
blocknum_t last;
|
|
|
|
if (count < 2)
|
|
return count;
|
|
for (i=1, last=0; i < count; i++) {
|
|
if (can_merge_blocks(&list[last], &list[i], info)) {
|
|
merge_blocks(&list[last], &list[i], info);
|
|
} else {
|
|
list[++last] = list[i];
|
|
}
|
|
}
|
|
return last + 1;
|
|
}
|
|
|
|
|
|
/* Retrieve a list of pointers to the disk blocks that make up the file
|
|
* specified by FILENAME. Upon success, return the number of blocks and set
|
|
* BLOCKLIST to point to the uncompacted list. INFO provides information
|
|
* about the device which contains the file. Return zero otherwise. */
|
|
blocknum_t
|
|
disk_get_blocklist_from_file(const char* filename, disk_blockptr_t** blocklist,
|
|
struct disk_info* info)
|
|
{
|
|
disk_blockptr_t* list;
|
|
struct stat stats;
|
|
int fd;
|
|
blocknum_t count;
|
|
blocknum_t i;
|
|
blocknum_t blocknum;
|
|
|
|
fd = open(filename, O_RDONLY);
|
|
if (fd == -1) {
|
|
error_reason(strerror(errno));
|
|
error_text("Could not open file '%s'", filename);
|
|
return 0;
|
|
}
|
|
if (fstat(fd, &stats)) {
|
|
error_reason(strerror(errno));
|
|
error_text("Could not get information for file '%s'",
|
|
filename);
|
|
close (fd);
|
|
return 0;
|
|
}
|
|
/* Get number of blocks */
|
|
count = ((blocknum_t) stats.st_size +
|
|
info->phy_block_size - 1) / info->phy_block_size;
|
|
if (count == 0) {
|
|
error_reason("Could not read empty file '%s'", filename);
|
|
close(fd);
|
|
return 0;
|
|
}
|
|
list = (disk_blockptr_t *) misc_malloc(sizeof(disk_blockptr_t) *
|
|
count);
|
|
if (list == NULL) {
|
|
close(fd);
|
|
return 0;
|
|
}
|
|
memset((void *) list, 0, sizeof(disk_blockptr_t) * count);
|
|
/* Build list */
|
|
for (i=0; i < count; i++) {
|
|
if (disk_get_blocknum(fd, 0, i, &blocknum, info)) {
|
|
free(list);
|
|
close(fd);
|
|
return 0;
|
|
}
|
|
disk_blockptr_from_blocknum(&list[i], blocknum, info);
|
|
}
|
|
close(fd);
|
|
*blocklist = list;
|
|
return count;
|
|
}
|
|
|
|
/* Check whether input device is in subchannel set 0.
|
|
* Path to "dev" attribute containing the major/minor number depends on
|
|
* whether option CONFIG_SYSFS_DEPRECATED is set or not */
|
|
|
|
int disk_check_subchannel_set(int devno, dev_t device, char* dev_name)
|
|
{
|
|
struct dirent *direntp;
|
|
DIR* fdd;
|
|
static const char sys_bus_ccw_dev_filename[] = "/sys/bus/ccw/devices";
|
|
char dev_file[PATH_MAX];
|
|
char *buffer;
|
|
int minor, major;
|
|
|
|
snprintf(dev_file, PATH_MAX, "%s/0.0.%04x", sys_bus_ccw_dev_filename,
|
|
devno);
|
|
fdd = opendir(dev_file);
|
|
if (!fdd)
|
|
goto out_with_warning;
|
|
while ((direntp = readdir(fdd)))
|
|
if (strncmp(direntp->d_name, "block:", 6) == 0)
|
|
break;
|
|
if (direntp != NULL)
|
|
snprintf(dev_file, PATH_MAX, "%s/0.0.%04x/%s/dev",
|
|
sys_bus_ccw_dev_filename, devno, direntp->d_name);
|
|
else {
|
|
closedir(fdd);
|
|
snprintf(dev_file, PATH_MAX, "%s/0.0.%04x/block",
|
|
sys_bus_ccw_dev_filename, devno);
|
|
fdd = opendir(dev_file);
|
|
if (!fdd)
|
|
goto out_with_warning;
|
|
while ((direntp = readdir(fdd)))
|
|
if (strncmp(direntp->d_name, "dasd", 4) == 0)
|
|
break;
|
|
if (direntp == NULL)
|
|
goto out_with_warning;
|
|
snprintf(dev_file, PATH_MAX, "%s/0.0.%04x/block/%s/dev",
|
|
sys_bus_ccw_dev_filename, devno, direntp->d_name);
|
|
}
|
|
closedir(fdd);
|
|
if (misc_read_special_file(dev_file, &buffer, NULL, 1))
|
|
goto out_with_warning;
|
|
if (sscanf(buffer, "%i:%i", &major, &minor) != 2) {
|
|
free(buffer);
|
|
goto out_with_warning;
|
|
}
|
|
free(buffer);
|
|
if (makedev(major, minor) != device) {
|
|
error_reason("Dump target '%s' must belong to "
|
|
"subchannel set 0.", dev_name);
|
|
return -1;
|
|
}
|
|
return 0;
|
|
out_with_warning:
|
|
fprintf(stderr, "Warning: Could not determine whether dump target %s "
|
|
"belongs to subchannel set 0.\n", dev_name);
|
|
return 0;
|
|
}
|