zipl/src: add support of md-mirrors

Add a script to process mirrored zipl targets managed by Linux "md"
driver (AKA "software mirrors")

. In the file zipl_helper.device-mapper.c implement two handlers
  identified by pairs (ZIPL_UTIL_ID, MD_DRIVER_ID) and
  (CHREIPL_UTIL_ID, MD_DRIVER_ID) for the helpers zipl_helper.md
  and chreipl_helper.md respectively

  . zipl_helper.md: prints sets of target parameters (one such set
    per mirror) for a specified logical zipl target managed by linux
    "md" driver.

  . chreipl_helper.md: for a specified logical zipl target managed
    by linux "md" driver prints a random disk from the set of disks
    participating in the mirrored setup.

. Add zipl_helper.md and chreipl_helper.md as symbolic links to
  zipl_helper.device-mapper

Signed-off-by: Eduard Shishkin <edward6@linux.ibm.com>
Reviewed-by: Stefan Haberland <sth@linux.ibm.com>
Signed-off-by: Steffen Eiden <seiden@linux.ibm.com>
This commit is contained in:
Eduard Shishkin
2024-05-23 16:13:23 +02:00
committed by Steffen Eiden
parent 0eeab96f0d
commit 3296d85e35
2 changed files with 486 additions and 4 deletions

View File

@@ -13,21 +13,25 @@ objects = misc.o error.o scan.o job.o boot.o bootmap.o fs-map.o disk.o \
bootmap_header.o envblk.o install.o zipl.o
zipl_helpers = $(basename $(wildcard zipl_helper.*.c))
zipl_helpers += zipl_helper.md
chreipl_helpers = $(subst zipl_,chreipl_, $(zipl_helpers))
all: zipl zipl-editenv $(chreipl_helpers)
all: zipl zipl-editenv zipl_helper.md $(chreipl_helpers)
boot.o: ../boot/.loaders
zipl: $(objects) $(libs)
zipl_helper.device-mapper: $(rootdir)/libdasd/libdasd.a \
$(rootdir)/libvtoc/libvtoc.a \
$(libs) zipl_helper.device-mapper.o
$(libs) misc.o error.o zipl_helper.device-mapper.o
zipl-editenv: $(libs) misc.o error.o fs-map.o bootmap_header.o \
envblk.o zipl-editenv.o
chreipl_helper.%: zipl_helper.%
zipl_helper.md: zipl_helper.device-mapper
ln -f -s $< $@
chreipl_helper.%: zipl_helper.device-mapper
ln -f -s $< $@
install: all

View File

@@ -32,11 +32,27 @@
* major:minor parameters are printed. Otherwise, the script exits with an
* error message and a non-zero return code.
*
* 3. Usage: zipl_helper.md <target directory> or
* <major:minor of target md device>
*
* This tool identifies the set of all physical devices that the specified
* logical target md-device is composed of and for each such component
* prints target parameters needed to install a boot record on respective
* disk. Only RAID1 md-setups are supported. All physical devices in the md
* RAID1 setup must have identical type and geometry and equal file system
* offsets from the beginning of the disk.
*
* 4. Usage: chreipl_helper.md <major:minor of target md device>
*
* This tool identifies single (random) physical device from the set of
* raid devices that the specified target md-device is composed of and
* prints the pair major:minor of respective physical disk.
*/
#include <errno.h>
#include <limits.h>
#include <linux/limits.h>
#include <linux/raid/md_u.h>
#include <locale.h>
#include <stdarg.h>
#include <stdbool.h>
@@ -46,9 +62,11 @@
#include <sys/stat.h>
#include <sys/sysmacros.h>
#include <sys/types.h>
#include <fcntl.h>
#include <unistd.h>
#include <assert.h>
#include "misc.h"
#include "lib/dasd_base.h"
#include "lib/util_base.h"
#include "lib/util_file.h"
@@ -112,8 +130,21 @@ struct physical_device {
struct dmpath_entry *mirror;
};
struct mdstat {
char *version;
char *raid_level;
char *state;
unsigned long raid_devices;
unsigned long total_devices;
unsigned long active_devices;
unsigned long working_devices;
unsigned long failed_devices;
unsigned long spare_devices;
};
enum driver_id {
DM_DRIVER_ID,
MD_DRIVER_ID,
LAST_DRIVER_ID
};
@@ -139,6 +170,9 @@ struct helper {
#define DASD_PARTN_MASK 0x03
#define SCSI_PARTN_MASK 0x0f
#define MD_MAJOR 9
#define DEVICE_MAPPER_MAJOR 253
/* Internal constants */
enum dev_type {
DEV_TYPE_CDL = 0,
@@ -161,6 +195,11 @@ enum lookup_result {
DM_MULTIPLE_TARGETS
};
static int is_device_mapper(unsigned int major)
{
return major == DEVICE_MAPPER_MAJOR;
}
static void get_type_name(char *name, unsigned short type)
{
switch (type) {
@@ -1234,6 +1273,399 @@ static int handle_device_mapper(dev_t dev, int util_id, char *name)
}
}
/**
* In the file stream FP find the first line formatted as
* 'W : STRVAL' and coincided in W with the specified keyword KW.
* Store the string value in STR
*/
static int kw_to_str(char **line, size_t *n, FILE *fp,
const char *kw, char **str)
{
char *w = NULL;
int ret = -1;
while (getline(line, n, fp) != -1) {
if (sscanf(*line, "%ms : %m[a-zA-Z,0-9. ]",
&w, str) >= 2 &&
strcmp(w, kw) == 0) {
ret = 0;
break;
}
}
free(w);
if (ret)
ERR("Unrecognized %s\n", kw);
return ret;
}
/**
* In the file stream FP find the first line formatted as
* 'W1 W2 : STRVAL' and coincided in W1 and W2 with the specified
* keywords KW1 and KW2. Store the string value in STR.
*/
static int kw_pair_to_str(char **line, size_t *n, FILE *fp,
const char *kw1, const char *kw2,
char **str)
{
char *w1 = NULL;
char *w2 = NULL;
int ret = -1;
while (getline(line, n, fp) != -1) {
if (sscanf(*line, "%ms %ms : %m[a-zA-Z,0-9. ]",
&w1, &w2, str) >= 3 &&
strcmp(w1, kw1) == 0 &&
strcmp(w2, kw2) == 0) {
ret = 0;
break;
}
}
free(w1);
free(w2);
if (ret)
ERR("Unrecognized %s %s\n", kw1, kw1);
return ret;
}
/**
* In the file stream FP find the first line formatted as
* 'W1 W2 : NUMVAL' and coincided in W1 and W2 with the specified
* keywords KW1 and KW2. Store the numerical value in VAL.
*/
static int kw_pair_to_ulong_silent(char **line, size_t *n, FILE *fp,
const char *kw1, const char *kw2,
unsigned long *val)
{
char *w1 = NULL;
char *w2 = NULL;
int ret = -1;
while (getline(line, n, fp) != -1) {
if (sscanf(*line, "%ms %ms : %lu", &w1, &w2, val) >= 3 &&
strcmp(w1, kw1) == 0 &&
strcmp(w2, kw2) == 0) {
ret = 0;
break;
}
}
free(w1);
free(w2);
return ret;
}
static int kw_pair_to_ulong(char **line, size_t *n, FILE *fp,
const char *kw1, const char *kw2,
unsigned long *value)
{
if (kw_pair_to_ulong_silent(line, n, fp, kw1, kw2, value)) {
ERR("Unrecognized %s %s\n", kw1, kw2);
return -1;
}
return 0;
}
static int get_components_header(char **line, size_t *n, FILE *fp)
{
while (getline(line, n, fp) != -1) {
if (strstr(*line, "Number") &&
strstr(*line, "Major") &&
strstr(*line, "Minor") &&
strstr(*line, "RaidDevice") &&
strstr(*line, "State")) {
return 0;
}
}
return -1;
}
static int md_get_component(char **line, size_t *n, FILE *fp, int nr_expected,
unsigned int *major, unsigned int *minor)
{
int nr_found, raid_dev;
while (getline(line, n, fp) != -1) {
if (sscanf(*line, "%d %u %u %d",
&nr_found, major, minor, &raid_dev) >= 4 &&
nr_expected == raid_dev) {
return 0;
}
}
ERR("Unrecognized %d component\n", nr_expected);
return -1;
}
/**
* Parse the output of "mdadm --examine MAJOR:MINOR" command, where
* MAJOR and MINOR specify a physical component of some logical md device.
* Find out how much space (in sectors) is reserved for md-metadata
* (typically superblock and bitmap) at the beginning of that component.
*/
static int md_get_data_offset(unsigned long major, unsigned long minor,
unsigned long *data_offset)
{
char *line = NULL;
size_t n = 0;
FILE *fp;
fp = exec_cmd_and_get_out_stream("mdadm --examine %lu:%lu",
major, minor);
if (fp == NULL)
return -1;
if (kw_pair_to_ulong_silent(&line, &n, fp,
"Data", "Offset", data_offset))
*data_offset = 0;
free(line);
pclose(fp);
return 0;
}
static void md_print_bad_status(struct mdstat *md, const char *devname)
{
fprintf(stderr, "Raid Devices: %lu\n", md->raid_devices);
fprintf(stderr, "Active Devices: %lu\n", md->active_devices);
fprintf(stderr, "Working Devices: %lu\n", md->working_devices);
fprintf(stderr, "%s: RAID inconsistent (some its components are not active or working).\n",
devname);
}
static int devname_by_device(dev_t device, char **devname)
{
struct util_proc_part_entry entry;
if (util_proc_part_get_entry(device, &entry))
return -1;
*devname = misc_make_path("/dev", entry.name);
util_proc_part_free_entry(&entry);
return *devname ? 0 : -1;
}
static int check_md(dev_t dev, int *is_md_device)
{
mdu_array_info_t array;
char *devname = NULL;
int fd;
if (major(dev) == MD_MAJOR) {
*is_md_device = 1;
return 0;
}
if (devname_by_device(dev, &devname))
return -1;
fd = open(devname, O_RDONLY);
free(devname);
if (fd == -1)
return -1;
if (ioctl(fd, GET_ARRAY_INFO, &array) >= 0)
*is_md_device = 1;
else
*is_md_device = 0;
close(fd);
return 0;
}
/**
* Print one set of zipl parameters for a raid component specified
* by MAJOR and MINOR.
*
* FS_START_MD: file system start on the md-device in sector units
*/
static int md_mirror_to_params(unsigned int major, unsigned int minor,
unsigned long partstart_md, int util_id)
{
struct device_characteristics dc;
unsigned long data_start_md;
unsigned long partstart_phy;
unsigned long fs_start_base;
int is_md_device;
dev_t base;
dev_t dev;
int ret;
dev = makedev(major, minor);
if (check_md(dev, &is_md_device)) {
ERR("Failed to get md-status for (%d:%d)\n",
major, minor);
return -1;
}
if (is_md_device) {
ERR("Unsupported configuration: nested md devices\n");
return -1;
}
if (is_device_mapper(major)) {
char *name;
misc_asprintf(&name, "%d:%d", major, minor);
fail_on_dm_mirrors = 1;
ret = handle_device_mapper(dev, util_id, name);
free(name);
return ret;
}
/*
* find space reserved for md-metadata at the beginning
* of the physical device
*/
if (md_get_data_offset(major, minor, &data_start_md))
return -1;
/*
* find parameters of the physical device
*/
if (get_dev_characteristics(&dc, dev))
return -1;
partstart_phy = dc.partstart * (dc.blocksize / SECTOR_SIZE);
if (data_start_md && partstart_phy < dc.bootsectors) {
ERR("Unsupported configuration: md metadata locate at boot area\n");
return -1;
}
if (partstart_phy > 0) {
base = get_partition_base(dc.type, dev);
/* Update device geometry */
get_dev_characteristics(&dc, base);
} else {
base = dev;
}
if (util_id == CHREIPL_UTIL_ID) {
printf("%u:%u\n", major(base), minor(base));
return 0;
}
fs_start_base = partstart_md + data_start_md + partstart_phy;
if (fs_start_base % (dc.blocksize / SECTOR_SIZE)) {
ERR("File system is not aligned on physical block size\n");
return -1;
}
base_dev_to_params(base, &dc, fs_start_base);
return 0;
}
/**
* If STATE contains THIS property, then issue an error message
* with the proposed FIXUP
*/
static int error_on(const char *state, const char *this, const char *fixup,
const char *devname)
{
if (strstr(state, this)) {
fprintf(stderr, "%s: Inconsistent RAID state ('%s'). %s.\n",
devname, this, fixup);
return -1;
}
return 0;
}
static int check_md_state(struct mdstat *md, const char *devname)
{
if (error_on(md->state, "recovering", "Complete its recovery", devname))
return -1;
if (error_on(md->state, "degraded", "Restore its redundancy", devname))
return -1;
if (error_on(md->state, "dirty", "Bring it into clean state\n", devname))
return -1;
if (md->raid_devices != md->active_devices ||
md->raid_devices > md->working_devices) {
md_print_bad_status(md, devname);
return -1;
}
return 0;
}
/**
* Parse the output of "mdadm --detail" command issued for logical
* device DEV.
*
* Identify the set of physical devices that DEVNAME is composed of.
*
* If util_id is ZIPL_UTIL_ID:
* Calculate and print (to the standerd output) target parameters
* for each identified component.
*
* If util_id is CHREIPL_UTIL_ID:
* Print the pair major:minor of one (random) identified component.
*/
static int md_dev_to_params(dev_t dev, const char *devname, int util_id)
{
struct mdstat md = {0};
char *line = NULL;
long partstart_md;
unsigned long i;
int ret = -1;
size_t n = 0;
FILE *fp;
fp = exec_cmd_and_get_out_stream("mdadm --detail %s", devname);
if (fp == NULL)
goto out;
if (kw_to_str(&line, &n, fp, "Version", &md.version) ||
kw_pair_to_str(&line, &n, fp, "Raid", "Level", &md.raid_level) ||
kw_pair_to_ulong(&line, &n, fp, "Raid", "Devices",
&md.raid_devices) ||
kw_pair_to_ulong(&line, &n, fp, "Total", "Devices",
&md.total_devices) ||
kw_to_str(&line, &n, fp, "State", &md.state) ||
kw_pair_to_ulong(&line, &n, fp, "Active", "Devices",
&md.active_devices) ||
kw_pair_to_ulong(&line, &n, fp, "Working", "Devices",
&md.working_devices) ||
kw_pair_to_ulong(&line, &n, fp, "Failed", "Devices",
&md.failed_devices) ||
kw_pair_to_ulong(&line, &n, fp, "Spare", "Devices",
&md.spare_devices))
goto out;
/* check raid level */
if (strcmp(md.raid_level, "raid1") != 0) {
ERR("%s: Unsupported raid level %s\n", devname, md.raid_level);
goto out;
}
/* check that raid is healthy */
if (check_md_state(&md, devname))
goto out;
/* Handle the case when DEV is a partition */
partstart_md = get_partition_start(major(dev), minor(dev));
if (partstart_md < 0) {
fprintf(stderr, "Could not determine partition start for '%s'\n",
devname);
goto out;
}
/* get information about raid components */
if (get_components_header(&line, &n, fp)) {
fprintf(stderr, "Could not get raid components info for %s\n",
devname);
goto out;
}
for (i = 0; i < md.raid_devices; i++) {
unsigned int major, minor;
if (md_get_component(&line, &n, fp, i, &major, &minor))
goto out;
if (md_mirror_to_params(major, minor, partstart_md, util_id))
goto out;
if (util_id == CHREIPL_UTIL_ID)
/* chreipl accepts only one base disk */
break;
}
ret = 0;
out:
free(md.raid_level);
free(md.version);
free(md.state);
free(line);
pclose(fp);
return ret;
}
static int devname_by_major_minor(unsigned int major, unsigned int minor,
char **devname)
{
return devname_by_device(makedev(major, minor), devname);
}
static int print_params_device_mapper(char *argv[], struct helper *h)
{
unsigned int major, minor;
@@ -1247,6 +1679,38 @@ static int print_params_device_mapper(char *argv[], struct helper *h)
return handle_device_mapper(dev, h->util_id, name);
}
int print_params_md(char *argv[], struct helper *h)
{
unsigned int major, minor;
char *devname;
dev_t dev;
int ret;
if (h->util_id != CHREIPL_UTIL_ID && h->util_id != ZIPL_UTIL_ID) {
fprintf(stderr, "Unsupported utility %d\n", h->util_id);
return -1;
}
if (extract_major_minor_from_cmdline(argv, &major, &minor) == 0) {
if (devname_by_major_minor(major, minor, &devname)) {
fprintf(stderr, "Could not resolve %u:%u to device name\n",
major, minor);
return -1;
}
dev = makedev(major, minor);
} else {
if (device_by_filename(&dev, argv[1]))
return -1;
if (devname_by_device(dev, &devname)) {
fprintf(stderr, "Directory %s is not over any block device\n",
argv[1]);
return -1;
}
}
ret = md_dev_to_params(dev, devname, h->util_id);
free(devname);
return ret;
}
static int check_usage_zipl_helper(int argc, char *argv[])
{
if (argc <= 1) {
@@ -1276,6 +1740,13 @@ static struct helper helpers[LAST_DRIVER_ID][LAST_UTIL_ID] = {
. check_usage = check_usage_zipl_helper,
. print_params = print_params_device_mapper
},
[MD_DRIVER_ID][ZIPL_UTIL_ID] = {
. util_id = ZIPL_UTIL_ID,
. driver_id = MD_DRIVER_ID,
. name = "zipl_helper.md",
. check_usage = check_usage_zipl_helper,
. print_params = print_params_md
},
[DM_DRIVER_ID][CHREIPL_UTIL_ID] = {
. util_id = CHREIPL_UTIL_ID,
. driver_id = DM_DRIVER_ID,
@@ -1283,6 +1754,13 @@ static struct helper helpers[LAST_DRIVER_ID][LAST_UTIL_ID] = {
. check_usage = check_usage_chreipl_helper,
. print_params = print_params_device_mapper
},
[MD_DRIVER_ID][CHREIPL_UTIL_ID] = {
. util_id = CHREIPL_UTIL_ID,
. driver_id = MD_DRIVER_ID,
. name = "chreipl_helper.md",
. check_usage = check_usage_chreipl_helper,
. print_params = print_params_md
}
};
static struct helper *helper_by_toolname(const char *toolname)
@@ -1307,7 +1785,7 @@ int main(int argc, char *argv[])
exit(EXIT_FAILURE);
if (setlocale(LC_ALL, "C") == NULL) {
ERR("Could not use standard locale\n");
fprintf(stderr, "Could not use standard locale\n");
exit(EXIT_FAILURE);
}
if (h->print_params(argv, h))