Files
s390-tools/zipl/src/zipl_helper.device-mapper.c
Eduard Shishkin d6f7eff5b5 zipl_helper.device-mapper: Fix segmentation fault in an error path
Fix segmentation fault when trying to process not supported dm-targets
Release allocated memory in error paths

Reviewed-by: Alexander Egorenkov <egorenar@linux.ibm.com>
Signed-off-by: Eduard Shishkin <edward6@linux.ibm.com>
Signed-off-by: Steffen Eiden <seiden@linux.ibm.com>
2025-09-15 11:12:15 +02:00

2119 lines
51 KiB
C

/*
*
* zipl_helper.device-mapper: print zipl parameters for a device-mapper device
*
* Copyright IBM Corp. 2009, 2017
* Copyright Red Hat Inc. 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.
*
* Depending on the name by which the script is called, it serves one of two
* purposes:
*
* 1. Usage: zipl_helper.device-mapper <target directory> or
* <major:minor of target device>
*
* This tool attempts to obtain zipl parameters for a target directory or
* partition located on a device-mapper device. It assumes that the
* device-mapper table for this device conforms to the following rules:
* - directory is located on a device consisting of a single device-mapper
* target
* - only linear, multipath, mirror and raid targets are supported
* - supported physical device types are DASD and SCSI devices
* - all of the device which contains the directory must be located on a single
* physical device (which may be mirrored or accessed through a multipath
* target)
*
* 2. Usage: chreipl_helper.device-mapper <major:minor of target device>
*
* This tool identifies the physical device which contains the specified
* device-mapper target devices. If the physical device was found, its
* 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 <linux/nvme_ioctl.h>
#include <locale.h>
#include <stdarg.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#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"
#include "lib/util_libc.h"
#include "lib/util_list.h"
#include "lib/util_path.h"
#include "lib/util_proc.h"
#include "lib/util_sys.h"
#define WARN(...) \
fprintf(stderr, "Warning: " __VA_ARGS__)
#define ERR(...) \
fprintf(stderr, "Error: " __VA_ARGS__)
static bool fail_on_dm_mirrors;
static struct target_ops *target_ops_by_type(int target_type);
static struct target_ops *find_target_ops(char *id);
struct target_status {
char *path;
char status;
struct util_list_node list;
};
struct target_data {
dev_t device;
unsigned long start;
struct util_list_node list;
};
struct target {
unsigned long start;
unsigned long length;
unsigned short type;
struct util_list *data;
struct util_list_node list;
};
struct target_ops {
int type;
const char *id;
int (*check_target_status)(const char *devname);
struct util_list *(*get_target_data)(const char *devname, char *args);
};
/* "extended" device */
struct ext_dev {
dev_t dev;
unsigned long fs_off; /* file system start in sector units */
};
struct dmpath_entry {
struct ext_dev dev;
struct target *target;
struct util_list_node list;
};
struct device_characteristics {
unsigned short type;
unsigned int is_nvme:1;
unsigned int blocksize;
unsigned long bootsectors;
unsigned long partstart;
struct hd_geometry geo;
};
struct physical_device {
unsigned long offset;
struct util_list *dmpath;
struct device_characteristics dc;
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
};
enum util_id {
ZIPL_UTIL_ID,
CHREIPL_UTIL_ID,
LAST_UTIL_ID
};
struct helper {
int util_id;
int driver_id;
const char *name;
int (*check_usage)(int argc, char *argv[]);
int (*print_params)(char *argv[], struct helper *h);
};
/* From include/linux/fs.h */
#define BDEVNAME_SIZE 32
/* Constants */
#define SECTOR_SIZE 512
#define DASD_PARTN_MASK 0x03
#define SCSI_PARTN_MASK 0x0f
#define MD_MAJOR 9
/* Internal constants */
enum dev_type {
DEV_TYPE_CDL = 0,
DEV_TYPE_LDL,
DEV_TYPE_FBA,
DEV_TYPE_SCSI
};
enum target_type {
TARGET_TYPE_LINEAR = 0,
TARGET_TYPE_MIRROR,
TARGET_TYPE_MULTIPATH,
TARGET_TYPE_RAID,
LAST_TARGET_TYPE
};
enum lookup_result {
DM_LOOKUP_ERROR,
DM_EMPTY_TABLE,
DM_NO_TARGET,
DM_SINGLE_TARGET,
DM_MULTIPLE_TARGETS
};
/**
* Issue an error message about THIS bad property followed by the FIXUP
*/
static void print_bad_raid_state(const char *this, const char *fixup,
const char *devname)
{
fprintf(stderr, "%s: Inconsistent RAID state (%s)\n",
devname, this);
fprintf(stderr, "%s\n",
fixup ? fixup : "Bring it into consistent state first");
}
/**
* If STATE contains THIS property, then fail with the proposed FIXUP
*/
static int error_on(const char *state, const char *this, const char *fixup,
const char *devname)
{
if (strstr(state, this)) {
print_bad_raid_state(this, fixup, devname);
return -1;
}
return 0;
}
static int is_device_mapper(dev_t device)
{
struct util_proc_dev_entry pde;
int result = 0;
if (util_proc_dev_get_entry(device, 1, &pde) == 0) {
if (strcmp(pde.name, UTIL_PROC_DEV_ENTRY_DM) == 0)
result = 1;
util_proc_dev_free_entry(&pde);
} else {
misc_warn_on_failed_pdge(device);
}
return result;
}
static void get_type_name(char *name, unsigned short type)
{
switch (type) {
case DEV_TYPE_SCSI:
strcpy(name, "SCSI");
break;
case DEV_TYPE_CDL:
strcpy(name, "CDL");
break;
case DEV_TYPE_FBA:
strcpy(name, "FBA");
break;
case DEV_TYPE_LDL:
strcpy(name, "LDL");
break;
default:
name[0] = '\0';
WARN("Unrecognized dev type %d\n", type);
}
}
static FILE *exec_cmd_and_get_out_stream(const char *fmt, ...)
{
FILE *stream;
va_list ap;
char *cmd;
va_start(ap, fmt);
util_vasprintf(&cmd, fmt, ap);
va_end(ap);
stream = popen(cmd, "r");
if (stream == NULL)
WARN("'%s' failed\n", cmd);
free(cmd);
return stream;
}
static struct target_data *target_data_new(unsigned int maj, unsigned int min,
unsigned int start)
{
struct target_data *td = util_malloc(sizeof(struct target_data));
td->device = makedev(maj, min);
td->start = start;
return td;
}
static void target_data_free(struct target_data *td)
{
free(td);
}
static void target_data_list_free(struct util_list *data)
{
struct target_data *td, *n;
util_list_iterate_safe(data, td, n) {
util_list_remove(data, td);
target_data_free(td);
}
util_list_free(data);
}
static struct target *target_new(unsigned long start, unsigned long length,
unsigned short type, struct util_list *data)
{
struct target *entry = util_malloc(sizeof(struct target));
entry->start = start;
entry->length = length;
entry->type = type;
entry->data = data;
return entry;
}
static void target_free(struct target *target)
{
target_data_list_free(target->data);
free(target);
}
static unsigned long target_get_start(struct target *target)
{
struct target_data *td = util_list_start(target->data);
return target->start + td->start;
}
/*
* Return the first device from those that constitute the logical TARGET
*/
static dev_t first_device_by_target_data(struct target *target)
{
struct target_data *td = util_list_start(target->data);
return td->device;
}
static struct dmpath_entry *dmpath_entry_new(struct ext_dev *dev,
struct target *target)
{
struct dmpath_entry *de = util_malloc(sizeof(struct dmpath_entry));
de->dev = *dev;
de->target = target;
return de;
}
static void dmpath_entry_free(struct dmpath_entry *entry)
{
target_free(entry->target);
free(entry);
}
static void dmpath_free(struct util_list *dmpath)
{
struct dmpath_entry *de, *n;
util_list_iterate_safe(dmpath, de, n) {
util_list_remove(dmpath, de);
dmpath_entry_free(de);
}
util_list_free(dmpath);
}
static void get_device_name(char *devname, dev_t dev)
{
struct util_proc_part_entry entry;
if (util_proc_part_get_entry(dev, &entry) == 0) {
strcpy(devname, entry.name);
util_proc_part_free_entry(&entry);
} else {
sprintf(devname, "%u:%u", major(dev), minor(dev));
}
}
static int create_temp_device_node(char *name, unsigned int major,
unsigned int minor)
{
const char path_base[] = "/dev";
char buf[PATH_MAX];
int n;
for (n = 0; n < 100; n++) {
snprintf(buf, sizeof(buf), "%s/zipl-dm-temp-%02d", path_base, n);
if (util_path_exists(buf))
continue;
if (mknod(buf, S_IFBLK, makedev(major, minor)) != 0)
continue;
strcpy(name, buf);
return 0;
}
ERR("Could not create temporary device node in '%s'\n", path_base);
return -1;
}
static long get_partition_start(unsigned int major, unsigned int minor)
{
unsigned long val;
if (!util_path_is_dir("/sys/dev/block/%u:%u", major, minor))
return -1;
if (util_file_read_ul(&val, 10, "/sys/dev/block/%u:%u/start",
major, minor) != 0) {
return 0;
}
return val;
}
static int check_nvme(struct device_characteristics *dc, dev_t dev,
const char *devname)
{
struct util_proc_dev_entry dev_entry;
int fd;
if (util_proc_dev_get_entry(dev, 1, &dev_entry) == 0) {
if (strcmp(dev_entry.name, UTIL_PROC_DEV_ENTRY_BLKEXT) == 0) {
fd = open(devname, O_RDONLY);
if (fd == -1) {
ERR("Could not open %s\n", devname);
util_proc_dev_free_entry(&dev_entry);
return -1;
}
dc->is_nvme = (ioctl(fd, NVME_IOCTL_ID) >= 0);
close(fd);
}
util_proc_dev_free_entry(&dev_entry);
} else {
misc_warn_on_failed_pdge(dev);
}
return 0;
}
static int get_dev_characteristics(struct device_characteristics *dc, dev_t dev)
{
char devname[PATH_MAX];
dasd_information2_t info;
long pstart;
if (create_temp_device_node(devname, major(dev), minor(dev)) != 0)
return -1;
if (dasd_get_blocksize(devname, &dc->blocksize) != 0) {
ERR("Could not get block size for '%s'\n", devname);
goto err;
}
if (dasd_get_info(devname, &info) != 0) {
/* Assume SCSI if dasdinfo failed */
dc->type = DEV_TYPE_SCSI;
if (check_nvme(dc, dev, devname))
goto err;
/* First block contains IPL records */
dc->bootsectors = dc->blocksize / SECTOR_SIZE;
} else {
if (dasd_get_geo(devname, &dc->geo) != 0) {
ERR("Could not get geo info for '%s'\n", devname);
goto err;
}
if (strncmp(info.type, "FBA", 3) == 0) {
dc->type = DEV_TYPE_FBA;
dc->bootsectors = dc->blocksize / SECTOR_SIZE;
} else if (strncmp(info.type, "ECKD", 4) == 0) {
if (info.format == 1) {
dc->type = DEV_TYPE_LDL;
dc->bootsectors = dc->blocksize * 2 /
SECTOR_SIZE;
} else if (info.format == 2) {
dc->type = DEV_TYPE_CDL;
dc->bootsectors = dc->blocksize *
dc->geo.sectors / SECTOR_SIZE;
}
}
}
pstart = get_partition_start(major(dev), minor(dev));
if (pstart < 0) {
ERR("Could not determine partition start for '%s'\n",
devname);
goto err;
}
dc->partstart = pstart / (dc->blocksize / SECTOR_SIZE);
unlink(devname);
return 0;
err:
unlink(devname);
return -1;
}
static struct util_list *get_linear_data(const char *devname, char *args)
{
unsigned int major, minor, start;
struct util_list *data;
if (sscanf(args, "%u:%u %u", &major, &minor, &start) < 3) {
ERR("Unrecognized device-mapper table format for device '%s'\n",
devname);
return NULL;
}
data = util_list_new(struct target_data, list);
util_list_add_tail(data, target_data_new(major, minor, start));
return data;
}
#define STR_TOKEN_OR_GOTO(string, tok, label) \
do { \
tok = strtok(string, " "); \
if (tok == NULL) { \
goto label; \
} \
} while (0)
#define NEXT_STR_TOKEN_OR_GOTO(tok, label) \
STR_TOKEN_OR_GOTO(NULL, tok, label)
#define INT_TOKEN_OR_GOTO(string, tok, label) \
do { \
char *tp = strtok(string, " "); \
if (tp == NULL) { \
goto label; \
} \
errno = 0; \
tok = strtol(tp, NULL, 10); \
if (errno != 0) { \
goto label; \
} \
} while (0)
#define NEXT_INT_TOKEN_OR_GOTO(tok, label) \
INT_TOKEN_OR_GOTO(NULL, tok, label)
#define SKIP_TOKEN_OR_GOTO(string, label) \
do { \
if (strtok(string, " ") == NULL) { \
goto label; \
} \
} while (0)
#define SKIP_NEXT_TOKEN_OR_GOTO(label) \
SKIP_TOKEN_OR_GOTO(NULL, label)
#define SKIP_NEXT_TOKENS_OR_GOTO(count, label) \
do { \
int i; \
for (i = 0; i < count; i++) { \
SKIP_NEXT_TOKEN_OR_GOTO(label); \
} \
} while (0)
static int check_mirror_status(const char *devname)
{
char *line = NULL;
size_t n = 0;
int ret = -1;
FILE *fp;
fp = exec_cmd_and_get_out_stream("dmsetup status /dev/%s 2>/dev/null",
devname);
if (fp == NULL) {
ERR("Failed to get mirror status\n");
return -1;
}
while (getline(&line, &n, fp) != -1) {
char *status = NULL;
char *token = NULL;
long cnt;
int i;
/* Sample output (single line):
* 0 8192000 mirror \
* 2 253:3 253:7 \
* 8000/8000 1 \
* AA 1 \
* core
*/
STR_TOKEN_OR_GOTO(line, token, out);
SKIP_NEXT_TOKEN_OR_GOTO(out); /* length */
NEXT_STR_TOKEN_OR_GOTO(token, out); /* type */
if (strcmp(token, "mirror") != 0) {
ERR("Unrecognized mirror status\n");
goto out;
}
NEXT_INT_TOKEN_OR_GOTO(cnt, out); /* #nr_mirrors */
SKIP_NEXT_TOKENS_OR_GOTO(cnt, out); /* mirrors */
SKIP_NEXT_TOKENS_OR_GOTO(2, out); /* sync_count, nr_regions */
NEXT_STR_TOKEN_OR_GOTO(status, out); /* status buffer */
for (i = 0; i < cnt; i++) {
if (status[i] != 'A') {
print_bad_raid_state("some mirrors are not 'alive and in-sync'",
NULL, devname);
goto out;
}
}
}
ret = 0;
out:
free(line);
pclose(fp);
return ret;
}
/**
* The 'mirror' dm target is deprecated. Users are recommended to use 'raid'
* target of 'raid1' type instead.
*
* There is no kernel documentation for the mirror target. Parameters obtained
* from Linux sources: drivers/md/dm-log.c and drivers/md/dm-raid1.c
*
* <starting_sector> <length> mirror \
* <log_type> <#log_args> <log_arg1>...<log_argN> \
* <#devs> <device_name_1> <offset_1>...<device name N> <offset N> \
* <#features> <feature_1>...<feature_N>
*/
static struct util_list *get_mirror_data(const char *devname, char *args)
{
struct util_list *data = util_list_new(struct target_data, list);
long nlogs, ndevs, nfeats, base_offset = -1;
SKIP_TOKEN_OR_GOTO(args, out); /* log_type */
NEXT_INT_TOKEN_OR_GOTO(nlogs, out); /* #log_args */
SKIP_NEXT_TOKENS_OR_GOTO(nlogs, out); /* log_args* */
NEXT_INT_TOKEN_OR_GOTO(ndevs, out);
for (; ndevs > 0; ndevs--) {
unsigned int major, minor;
long offset;
char *name;
NEXT_STR_TOKEN_OR_GOTO(name, out);
if (sscanf(name, "%u:%u", &major, &minor) < 2)
goto out;
NEXT_INT_TOKEN_OR_GOTO(offset, out);
if ((base_offset >= 0) && (offset != base_offset)) {
ERR("Unsupported setup: Mirror target on device '%s' "
"contains entries with varying sector offsets\n",
devname);
goto out2;
} else {
base_offset = offset;
}
util_list_add_tail(data, target_data_new(major, minor, offset));
}
NEXT_INT_TOKEN_OR_GOTO(nfeats, out);
SKIP_NEXT_TOKENS_OR_GOTO(nfeats, out);
return data;
out:
ERR("Unrecognized device-mapper table format for device '%s'\n", devname);
out2:
target_data_list_free(data);
return NULL;
}
/**
* Kernel documentation for 'raid' target:
* https://www.kernel.org/doc/Documentation/device-mapper/dm-raid.txt
*
* The target is named "raid" and it accepts the following parameters:
*
* <raid_type> <#raid_params> <raid_params> \
* <#raid_devs> <metadata_dev0> <dev0> [.. <metadata_devN> <devN>]
*/
struct util_list *get_raid_data(const char *devname, char *args)
{
struct util_list *data = util_list_new(struct target_data, list);
long nparm, ndevs;
char *type;
STR_TOKEN_OR_GOTO(args, type, out);
if (strcmp(type, "raid1") != 0) {
ERR("Unsupported raid type %s (only 'raid1' is supported)\n",
type);
goto out;
}
NEXT_INT_TOKEN_OR_GOTO(nparm, out); /* #raid_params */
SKIP_NEXT_TOKENS_OR_GOTO(nparm, out); /* raid_params */
NEXT_INT_TOKEN_OR_GOTO(ndevs, out);
for (; ndevs > 0; ndevs--) {
unsigned int major, minor;
char *name;
SKIP_NEXT_TOKEN_OR_GOTO(out); /* metadata device */
NEXT_STR_TOKEN_OR_GOTO(name, out); /* data device */
if (sscanf(name, "%u:%u", &major, &minor) < 2)
goto out;
util_list_add_tail(data,
target_data_new(major, minor,
0 /* offset */));
}
return data;
out:
ERR("Unrecognized device-mapper table format for device '%s'\n",
devname);
target_data_list_free(data);
return NULL;
}
/**
* From https://www.kernel.org/doc/Documentation/device-mapper/dm-raid.txt
* Status output:
*
* <s> <l> raid \
* <raid_type> <#devices> <health_chars> \
* <sync_ratio> <sync_action> <mismatch_cnt>
*/
static int check_raid_status(const char *devname)
{
char *line = NULL;
size_t n = 0;
int ret = -1;
FILE *fp;
fp = exec_cmd_and_get_out_stream("dmsetup status /dev/%s 2>/dev/null",
devname);
if (fp == NULL) {
ERR("Failed to get raid status\n");
return -1;
}
while (getline(&line, &n, fp) != -1) {
char *status = NULL;
char *token = NULL;
long cnt;
int i;
/* Sample output (single line):
*
* 0 43261952 raid \
* raid1 2 AA \
* 43261952/43261952 \
* idle 0 0 -
*/
STR_TOKEN_OR_GOTO(line, token, out);
SKIP_NEXT_TOKEN_OR_GOTO(out); /* length */
NEXT_STR_TOKEN_OR_GOTO(token, out); /* type */
if (strcmp(token, "raid") != 0) {
ERR("Unrecognized status for 'raid' target\n");
goto out;
}
NEXT_STR_TOKEN_OR_GOTO(token, out); /* type */
if (strcmp(token, "raid1") != 0) {
ERR("Unrecognized type (%s) of 'raid' target. Only 'raid1' is supported\n",
token);
goto out;
}
NEXT_INT_TOKEN_OR_GOTO(cnt, out); /* #nr_mirrors */
NEXT_STR_TOKEN_OR_GOTO(status, out); /* status buffer */
for (i = 0; i < cnt; i++) {
if (status[i] != 'A') {
print_bad_raid_state("some mirrors are not 'alive and in-sync'",
NULL, devname);
goto out;
}
}
}
ret = 0;
out:
free(line);
pclose(fp);
return ret;
}
static struct target_status *target_status_new(const char *path, char status)
{
struct target_status *ts = util_malloc(sizeof(struct target_status));
ts->path = util_strdup(path);
ts->status = status;
return ts;
}
static void target_status_free(struct target_status *ts)
{
free(ts->path);
free(ts);
}
static void status_list_free(struct util_list *status)
{
struct target_status *ts, *n;
util_list_iterate_safe(status, ts, n) {
util_list_remove(status, ts);
target_status_free(ts);
}
util_list_free(status);
}
static char status_list_get_status(struct util_list *status, const char *node)
{
struct target_status *ts;
util_list_iterate(status, ts) {
if (strcmp(ts->path, node) == 0)
return ts->status;
}
return 'F';
}
static struct util_list *get_multipath_status(const char *devname)
{
struct util_list *status;
int len, failed = 0;
char *line = NULL;
size_t n = 0;
FILE *fp;
fp = exec_cmd_and_get_out_stream("dmsetup status /dev/%s 2>/dev/null",
devname);
if (fp == NULL) {
ERR("No paths found for '%s'\n", devname);
return NULL;
}
status = util_list_new(struct target_status, list);
while (getline(&line, &n, fp) != -1) {
char *token = NULL;
long cnt, ngr;
/* Sample output (single line):
* 0 67108864 multipath \
* 2 0 0 \
* 0 \
* 2 2 \
* E 0 \
* 2 2 \
* 8:16 F 1 \
* 0 1 \
* 8:0 F 1 \
* 0 1 \
* A 0 \
* 2 2 \
* 8:32 A 0 \
* 0 1 \
* 8:48 A 0 \
* 0 1
*/
STR_TOKEN_OR_GOTO(line, token, out);
SKIP_NEXT_TOKEN_OR_GOTO(out); /* length */
NEXT_STR_TOKEN_OR_GOTO(token, out); /* dtype */
if (strcmp(token, "multipath") != 0)
continue;
NEXT_INT_TOKEN_OR_GOTO(cnt, out); /* #mp_feature_args */
SKIP_NEXT_TOKENS_OR_GOTO(cnt, out); /* mp_feature_args* */
NEXT_INT_TOKEN_OR_GOTO(cnt, out); /* #handler_status_args */
SKIP_NEXT_TOKENS_OR_GOTO(cnt, out); /* handler_status_args* */
NEXT_INT_TOKEN_OR_GOTO(ngr, out);
SKIP_NEXT_TOKEN_OR_GOTO(out); /* ign */
for (; ngr > 0; ngr--) {
long npaths, nsa;
NEXT_STR_TOKEN_OR_GOTO(token, out); /* group_state */
NEXT_INT_TOKEN_OR_GOTO(cnt, out); /* #ps_status_args */
SKIP_NEXT_TOKENS_OR_GOTO(cnt, out); /* ps_status_args* */
NEXT_INT_TOKEN_OR_GOTO(npaths, out);
NEXT_INT_TOKEN_OR_GOTO(nsa, out);
for (; npaths > 0; npaths--) {
struct target_status *ts;
char *path, *active;
/* Fetch single path description */
NEXT_STR_TOKEN_OR_GOTO(path, out);
NEXT_STR_TOKEN_OR_GOTO(active, out);
ts = target_status_new(path, active[0]);
util_list_add_tail(status, ts);
NEXT_INT_TOKEN_OR_GOTO(cnt, out); /* fail_cnt */
failed += (active[0] != 'A');
SKIP_NEXT_TOKENS_OR_GOTO(nsa, out); /* selector_args* */
}
}
}
len = util_list_len(status);
if (len == 0) {
ERR("No paths found for '%s'\n", devname);
goto out;
} else if (failed == len) {
ERR("All paths for '%s' failed\n", devname);
goto out;
} else if (failed > 0) {
WARN("There are one or more failed paths for device '%s'\n",
devname);
}
goto success;
out:
status_list_free(status);
status = NULL;
success:
free(line);
pclose(fp);
return status;
}
static struct util_list *get_multipath_data(const char *devname, char *args)
{
struct util_list *data = util_list_new(struct target_data, list);
struct util_list *status = get_multipath_status(devname);
long cnt, pgroups;
if (status == NULL)
goto out_status;
INT_TOKEN_OR_GOTO(args, cnt, out); /* #feat */
SKIP_NEXT_TOKENS_OR_GOTO(cnt, out); /* feats* */
NEXT_INT_TOKEN_OR_GOTO(cnt, out); /* #handlers */
SKIP_NEXT_TOKENS_OR_GOTO(cnt, out); /* handlers* */
NEXT_INT_TOKEN_OR_GOTO(pgroups, out);
SKIP_NEXT_TOKEN_OR_GOTO(out); /* pathgroup */
for (; pgroups > 0; pgroups--) {
long npaths;
SKIP_NEXT_TOKEN_OR_GOTO(out); /* path_selector */
NEXT_INT_TOKEN_OR_GOTO(cnt, out); /* #selector_args */
SKIP_NEXT_TOKENS_OR_GOTO(cnt, out); /* selector_args* */
NEXT_INT_TOKEN_OR_GOTO(npaths, out);
NEXT_INT_TOKEN_OR_GOTO(cnt, out); /* #np_args */
for (; npaths > 0; npaths--) {
unsigned int major, minor;
char *path;
NEXT_STR_TOKEN_OR_GOTO(path, out);
if (sscanf(path, "%u:%u", &major, &minor) < 2)
goto out;
if (status_list_get_status(status, path) == 'A') {
struct target_data *td;
td = target_data_new(major, minor, 0);
util_list_add_tail(data, td);
}
SKIP_NEXT_TOKENS_OR_GOTO(cnt, out);
}
}
status_list_free(status);
return data;
out:
status_list_free(status);
out_status:
target_data_list_free(data);
ERR("Unrecognized device-mapper table format for device '%s'\n", devname);
return NULL;
}
static void table_free(struct util_list *table)
{
struct target *target, *n;
if (!table)
return;
util_list_iterate_safe(table, target, n) {
util_list_remove(table, target);
target_free(target);
}
util_list_free(table);
}
/**
* Remove all targets which don't maintain bytes in the interval
* [start,start+length-1] from the TABLE
*/
static void filter_table(struct util_list *table, unsigned int start,
unsigned int length)
{
struct target *target, *n;
util_list_iterate_safe(table, target, n) {
if (!(((target->start + target->length - 1) >= start) &&
(target->start <= (start + length - 1)))) {
util_list_remove(table, target);
target_free(target);
}
}
}
/**
* Return list of target devices
*/
static int get_table(dev_t dev, struct util_list **table)
{
char devname[BDEVNAME_SIZE];
char *line = NULL;
size_t n = 0;
FILE *fp;
*table = util_list_new(struct target, list);
fp = exec_cmd_and_get_out_stream("dmsetup table -j %u -m %u 2>/dev/null",
major(dev), minor(dev));
if (fp == NULL)
return 0;
get_device_name(devname, dev);
while (getline(&line, &n, fp) != -1) {
char *type = NULL, *args = NULL;
struct util_list *data = NULL;
unsigned long start, length;
struct target_ops *tops;
if (sscanf(line, "%lu %lu %ms %m[a-zA-Z0-9_: -]",
&start, &length, &type, &args) < 4) {
ERR("Unrecognized device-mapper table format for device '%s'\n",
devname);
free(type);
free(args);
goto out;
}
tops = find_target_ops(type);
if (!tops) {
ERR("Unsupported setup: Unsupported device-mapper "
"target type '%s' for device '%s'\n",
type, devname);
free(type);
free(args);
goto out;
}
data = tops->get_target_data(devname, args);
free(type);
free(args);
if (data == NULL)
goto out;
util_list_add_tail(*table,
target_new(start, length, tops->type, data));
}
free(line);
pclose(fp);
return 0;
out:
free(line);
pclose(fp);
table_free(*table);
*table = NULL;
return -1;
}
static bool is_dasd(unsigned short type)
{
return (type == DEV_TYPE_CDL) || (type == DEV_TYPE_LDL) ||
(type == DEV_TYPE_FBA);
}
/**
* Remove TARGET from TABLE and add it to DMPATH
*/
static void target_move(struct target *target, struct ext_dev *dev,
struct util_list **table,
struct util_list *dmpath)
{
util_list_remove(*table, target);
table_free(*table);
*table = NULL;
util_list_add_head(dmpath, dmpath_entry_new(dev, target));
}
/**
* Look for a TARGET in a device-mapper's TABLE on the parent level by DEVICE
*/
static int lookup_parent(dev_t device, struct util_list **table,
struct target **target,
void (*filter_table_fn)(struct util_list *table,
unsigned int start,
unsigned int len),
unsigned int start, unsigned int len)
{
if (get_table(device, table))
return DM_LOOKUP_ERROR;
if (*table == NULL || util_list_is_empty(*table))
return DM_EMPTY_TABLE;
/* optionally apply filter to the table */
if (filter_table_fn)
filter_table_fn(*table, start, len);
*target = util_list_start(*table);
if (*target == NULL)
return DM_NO_TARGET;
if (util_list_next(*table, *target) != NULL)
return DM_MULTIPLE_TARGETS;
return DM_SINGLE_TARGET;
}
static int target_is_mirrored(struct target *t)
{
return t->type == TARGET_TYPE_MIRROR || t->type == TARGET_TYPE_RAID;
}
/**
* Starting from DEVICE go upward the device tree and find the topmost
* device, which is not a logical device managed by device-mapper driver.
*
* On success: return the whole path traveled. Data of the topmost target
* in that path consists of non-dm devices.
* FS_START contains file system offset on the topmost dm-device.
*
* BOTTOM: the logical device at the lowest level from which the ascent
* begins.
*/
static struct util_list *dmpath_walk(struct ext_dev *bottom, const char *dir,
unsigned long *fs_start,
struct dmpath_entry **mirror)
{
struct util_list *dmpath = util_list_new(struct dmpath_entry, list);
struct util_list *table = NULL;
struct ext_dev top = *bottom;
char devname[BDEVNAME_SIZE];
struct target *target;
unsigned int length;
int ret;
ret = lookup_parent(top.dev, &table, &target, NULL, 0, 0);
switch (ret) {
case DM_LOOKUP_ERROR:
goto error;
case DM_EMPTY_TABLE:
get_device_name(devname, top.dev);
ERR("Could not retrieve device-mapper information for device "
"'%s'\n", devname);
goto error;
case DM_NO_TARGET:
/* impossible: table is not empty and no filter was applied */
assert(0);
goto error;
case DM_MULTIPLE_TARGETS:
ERR("Unsupported setup: Directory '%s' is located on a "
"multi-target device-mapper device\n", dir);
goto error;
case DM_SINGLE_TARGET:
break;
}
length = target->length;
while (true) {
struct dmpath_entry *de;
target_move(target, &top, &table, dmpath);
de = util_list_start(dmpath);
if (target_is_mirrored(de->target)) {
if (*mirror || fail_on_dm_mirrors) {
ERR("Unsupported setup: Nested mirrors");
goto error;
}
/* save the encountered mirror */
*mirror = de;
}
/*
* Go to the upper level.
* First, select the first device from those that constitute
* the logical target (which is the "point of branching" in
* the device tree).
*/
top.dev = first_device_by_target_data(target);
top.fs_off += target_get_start(target);
/*
* look for a target maintaining bytes in the interval
* [fs_off, fs_off+length - 1] on the parent level
*/
ret = lookup_parent(top.dev, &table, &target,
filter_table, top.fs_off, length);
switch (ret) {
case DM_LOOKUP_ERROR:
goto error;
case DM_EMPTY_TABLE:
/* Found non-dm device */
table_free(table);
*fs_start = top.fs_off;
return dmpath;
case DM_NO_TARGET:
/* break through */
case DM_MULTIPLE_TARGETS:
ERR("Unsupported setup: Could not map directory '%s' "
"to a single physical device\n", dir);
goto error;
case DM_SINGLE_TARGET:
break;
}
}
error:
table_free(table);
dmpath_free(dmpath);
return NULL;
}
/*
* In case of success PD contains a dmpath. The topmost target of that
* dmpath is a dm-device. So, any callers who don't expect it, should
* complete the resolution process by themselves
*/
static int get_physical_device(struct physical_device *pd, struct ext_dev *dev,
const char *dir)
{
pd->dmpath = dmpath_walk(dev, dir, &pd->offset, &pd->mirror);
return pd->dmpath == NULL ? -1 : 0;
}
static int device_by_filename(dev_t *dev, const char *filename)
{
struct stat buf;
if (stat(filename, &buf) != 0) {
ERR("Could not stat '%s'", filename);
return -1;
}
*dev = buf.st_dev;
return 0;
}
static struct dmpath_entry *get_top_entry(const struct physical_device *pd)
{
return util_list_start(pd->dmpath);
}
/**
* Get topmost non-dm device
*/
static void get_topmost_device(const struct physical_device *pd,
struct ext_dev *top_dev)
{
top_dev->dev = first_device_by_target_data(get_top_entry(pd)->target);
top_dev->fs_off = pd->offset;
}
/**
* Find the topmost entry in the DMPATH, which provides access to
* the boot sectors
*/
static struct dmpath_entry *find_base_entry(struct util_list *dmpath,
unsigned int nr_boot_sectors)
{
struct dmpath_entry *te, *top;
top = util_list_start(dmpath);
util_list_iterate(dmpath, te) {
if (target_get_start(te->target) != 0 ||
te->target->length < nr_boot_sectors)
break;
top = te;
}
return top;
}
/**
* Resolve a partition represented by DEV to a base disk.
* Store the result in BASE.
*/
static int get_partition_base(struct device_characteristics *dc,
dev_t dev, dev_t *base)
{
if (dc->is_nvme)
return util_sys_get_base_dev(dev, base);
*base = makedev(major(dev), minor(dev) &
(is_dasd(dc->type) ? ~DASD_PARTN_MASK : ~SCSI_PARTN_MASK));
return 0;
}
static int extract_major_minor_from_cmdline(char *argv[], unsigned int *major,
unsigned int *minor)
{
if (sscanf(argv[1], "%u:%u", major, minor) != 2)
return -1;
return 0;
}
static bool toolname_is(const char *toolname, const char *what)
{
int wlen = strlen(what);
int tlen = strlen(toolname);
if (tlen < wlen)
return false;
return strcmp(toolname + tlen - wlen, what) == 0;
}
static void print_usage_zipl_helper(const char *toolname)
{
fprintf(stderr, "%s <major:minor of target device>", toolname);
fprintf(stderr, " or <target directory>\n");
}
static void print_usage_chreipl_helper(const char *toolname)
{
fprintf(stderr, "%s <major:minor of target device>\n", toolname);
}
/**
* Complete the PD structure and assign the base device
*/
static int complete_physical_device(struct physical_device *pd, dev_t *base_dev)
{
struct device_characteristics *dc = &pd->dc;
struct dmpath_entry *top_entry, *base_entry;
dev_t top_dev;
top_entry = get_top_entry(pd);
top_dev = first_device_by_target_data(top_entry->target);
/* Retrieve parameters of the topmost device */
if (get_dev_characteristics(dc, top_dev) != 0)
return -1;
if (dc->partstart > 0) {
/*
* The topmost found device is a partition.
* Since just a part of the physical device is mapped, only
* the physical device can provide access to the boot record
*/
struct device_characteristics ndc = {0};
base_entry = top_entry;
if (get_partition_base(dc, top_dev, base_dev)) {
ERR("Failed to get base device for %lu", top_dev);
return -1;
}
/* Complete the filesystem offset */
pd->offset += (dc->partstart * (dc->blocksize / SECTOR_SIZE));
dc->partstart = 0;
/* Update device geometry */
get_dev_characteristics(&ndc, *base_dev);
dc->geo = ndc.geo;
} else if (pd->mirror) {
/*
* For proper handling heterogeneous mirrors it is
* required for base device to be the topmost found device
*/
base_entry = util_list_start(pd->dmpath);
*base_dev = base_entry->dev.dev;
} else {
/*
* In this case base device is the uppermost physical
* device which provides access to boot sectors
*/
base_entry = find_base_entry(pd->dmpath, dc->bootsectors);
if (!base_entry)
return -1;
if (target_get_start(base_entry->target) == 0) {
/* base device is a dm device */
*base_dev = base_entry->dev.dev;
} else {
/* base device is a non-dm device */
*base_dev =
first_device_by_target_data(base_entry->target);
}
}
/* Check for valid offset of filesystem */
if ((pd->offset % (dc->blocksize / SECTOR_SIZE)) != 0) {
ERR("File system not aligned on physical block size\n");
return -1;
}
return 0;
}
/**
* Print a set of zipl parameters for a base device.
*
* BASE: physical or logical device, which provides access to boot sectors
* FS_START: offset (in sectors) of the first block managed by the file system
*/
static void base_dev_to_params(dev_t base, struct device_characteristics *dc,
unsigned long fs_start)
{
char type_name[8];
printf("targetbase=%u:%u\n", major(base), minor(base));
get_type_name(type_name, dc->type);
printf("targettype=%s\n", type_name);
if (dc->geo.cylinders != 0 &&
dc->geo.heads != 0 &&
dc->geo.sectors != 0) {
printf("targetgeometry=%d,%d,%d\n",
dc->geo.cylinders,
dc->geo.heads,
dc->geo.sectors);
}
printf("targetblocksize=%d\n", dc->blocksize);
printf("targetoffset=%lu\n",
fs_start / (dc->blocksize / SECTOR_SIZE));
}
static int check_handle_md(struct ext_dev *dev, int *is_md_dev, int util_id);
/**
* Print parameters for logical device DEV required by zipl
* tool to install IPL records on its's physical components.
*
* DEV: a logical device managed by device-mapper driver
*/
static int dm_dev_to_zipl_params(struct ext_dev *dev, char *dir)
{
struct physical_device pd = {0};
struct ext_dev top_dev;
int top_dev_is_md;
dev_t base_dev;
if (get_physical_device(&pd, dev, dir))
return -1;
get_topmost_device(&pd, &top_dev);
if (check_handle_md(&top_dev, &top_dev_is_md, ZIPL_UTIL_ID))
return -1;
if (top_dev_is_md) {
if (pd.mirror) {
ERR("Unsupported setup: dm-mirror over md-devices\n");
return -1;
}
return 0;
}
if (complete_physical_device(&pd, &base_dev))
goto error;
if (pd.offset < pd.dc.bootsectors) {
ERR("Unsupported setup: data starts at boot area on (%u:%u)\n",
major(base_dev), minor(base_dev));
return -1;
}
base_dev_to_params(base_dev, &pd.dc, pd.offset);
if (pd.mirror) {
char devname[BDEVNAME_SIZE];
struct target_data *mtd;
struct target_ops *tops;
struct target *mt;
mt = pd.mirror->target;
mtd = util_list_start(mt->data);
tops = target_ops_by_type(mt->type);
get_device_name(devname, pd.mirror->dev.dev);
if (tops->check_target_status(devname))
goto error;
/* Print also parameters for other mirrors */
fail_on_dm_mirrors = 1;
for (mtd = util_list_next(mt->data, mtd);
mtd != NULL;
mtd = util_list_next(mt->data, mtd)) {
struct ext_dev mirror = {mtd->device,
pd.mirror->dev.fs_off};
if (dm_dev_to_zipl_params(&mirror, dir))
goto error;
}
}
dmpath_free(pd.dmpath);
return 0;
error:
dmpath_free(pd.dmpath);
return -1;
}
static int dm_dev_to_chreipl_params(struct ext_dev *dev, char *dir)
{
struct physical_device pd = {0};
struct ext_dev top_dev;
int top_dev_is_md;
if (get_physical_device(&pd, dev, dir))
return -1;
/*
* chreipl(8) utility doesn't expect dm-device at the
* chreipl_helper output. So, complete the resolution
* process (see the comment to get_physical_device)
*/
get_topmost_device(&pd, &top_dev);
dmpath_free(pd.dmpath);
if (check_handle_md(&top_dev, &top_dev_is_md, CHREIPL_UTIL_ID))
return -1;
if (!top_dev_is_md)
printf("%u:%u\n", major(top_dev.dev), minor(top_dev.dev));
return 0;
}
static int handle_device_mapper(struct ext_dev *dev, int util_id, char *name)
{
switch (util_id) {
case CHREIPL_UTIL_ID:
return dm_dev_to_chreipl_params(dev, name);
case ZIPL_UTIL_ID:
return dm_dev_to_zipl_params(dev, name);
default:
ERR("Unsupported utility %d\n", util_id);
return -1;
}
}
/**
* 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_status(struct mdstat *md, const char *devname)
{
fprintf(stderr, "%s: Raid status:\n", 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);
}
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)
{
struct util_proc_dev_entry pde;
mdu_array_info_t array;
char *devname = NULL;
int fd;
if (major(dev) == MD_MAJOR) {
*is_md_device = 1;
return 0;
}
/*
* check if @dev is an md-partition
*/
if (util_proc_dev_get_entry(dev, 1, &pde) != 0)
return -1;
if (strcmp(pde.name, UTIL_PROC_DEV_ENTRY_BLKEXT)) {
util_proc_dev_free_entry(&pde);
*is_md_device = 0;
return 0;
}
util_proc_dev_free_entry(&pde);
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 fs_start_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);
/*
* 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;
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(dev)) {
struct ext_dev xdev = {dev, data_start_md + fs_start_md};
char *name;
misc_asprintf(&name, "%d:%d", major, minor);
fail_on_dm_mirrors = 1;
ret = handle_device_mapper(&xdev, util_id, name);
free(name);
return ret;
}
/*
* 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) {
if (get_partition_base(&dc, dev, &base)) {
ERR("Failed to get base device for %lu", dev);
return -1;
}
/* 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 = fs_start_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;
}
static int check_md_state(struct mdstat *md, const char *devname)
{
if (error_on(md->state, "recovering",
"Complete its recovery first", devname))
return -1;
if (error_on(md->state, "degraded",
"Restore its redundancy first", devname))
return -1;
if (error_on(md->state, "dirty",
"Bring it into clean state first", devname))
return -1;
if (md->raid_devices != md->active_devices ||
md->raid_devices > md->working_devices) {
md_print_status(md, devname);
print_bad_raid_state("some mirrors are not active or working",
NULL, 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(struct ext_dev *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->dev), minor(dev->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 + dev->fs_off, 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;
struct ext_dev dev = {0};
char *name = argv[1];
if (extract_major_minor_from_cmdline(argv, &major, &minor) == 0)
dev.dev = makedev(major, minor);
else if (device_by_filename(&dev.dev, name))
return -1;
return handle_device_mapper(&dev, h->util_id, name);
}
int print_params_md(char *argv[], struct helper *h)
{
unsigned int major, minor;
struct ext_dev dev = {0};
char *devname;
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.dev = makedev(major, minor);
} else {
if (device_by_filename(&dev.dev, argv[1]))
return -1;
if (devname_by_device(dev.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;
}
/*
* Check if DEV is an MD-device.
* If yes, then resolve DEV to the set of underlying base disks
* and print parameters for each of them.
*/
static int check_handle_md(struct ext_dev *dev, int *is_md_dev, int util_id)
{
char *dev_name = NULL;
int ret;
ret = check_md(dev->dev, is_md_dev);
if (ret) {
ERR("Failed to get md-status for device %lu", dev->dev);
return -1;
}
if (!*is_md_dev)
return 0;
ret = devname_by_device(dev->dev, &dev_name);
if (ret) {
ERR("Could not get device name of device %lu\n", dev->dev);
return -1;
}
ret = md_dev_to_params(dev, dev_name, util_id);
free(dev_name);
return ret;
}
static int check_usage_zipl_helper(int argc, char *argv[])
{
if (argc <= 1) {
print_usage_zipl_helper(argv[0]);
return -1;
}
return 0;
}
static int check_usage_chreipl_helper(int argc, char *argv[])
{
unsigned int major, minor;
if (argc <= 1 ||
extract_major_minor_from_cmdline(argv, &major, &minor)) {
print_usage_chreipl_helper(argv[0]);
return -1;
}
return 0;
}
static struct target_ops target_ops[LAST_TARGET_TYPE] = {
[TARGET_TYPE_LINEAR] = {
. type = TARGET_TYPE_LINEAR,
. id = "linear",
. get_target_data = get_linear_data
},
[TARGET_TYPE_MIRROR] = {
. type = TARGET_TYPE_MIRROR,
. id = "mirror",
. get_target_data = get_mirror_data,
. check_target_status = check_mirror_status
},
[TARGET_TYPE_MULTIPATH] = {
. type = TARGET_TYPE_MULTIPATH,
. id = "multipath",
. get_target_data = get_multipath_data
},
[TARGET_TYPE_RAID] = {
. type = TARGET_TYPE_RAID,
. id = "raid",
. get_target_data = get_raid_data,
. check_target_status = check_raid_status
}
};
static struct target_ops *target_ops_by_type(int target_type)
{
return &target_ops[target_type];
}
static struct target_ops *find_target_ops(char *id)
{
int i;
for (i = 0; i < LAST_TARGET_TYPE; i++) {
if (!strcmp(id, target_ops_by_type(i)->id))
return target_ops_by_type(i);
}
return NULL;
}
static struct helper helpers[LAST_DRIVER_ID][LAST_UTIL_ID] = {
[DM_DRIVER_ID][ZIPL_UTIL_ID] = {
. util_id = ZIPL_UTIL_ID,
. driver_id = DM_DRIVER_ID,
. name = "zipl_helper.device-mapper",
. 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,
. name = "chreipl_helper.device-mapper",
. 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)
{
int i, j;
for (i = 0; i < LAST_DRIVER_ID; i++)
for (j = 0; j < LAST_UTIL_ID; j++)
if (toolname_is(toolname, helpers[i][j].name))
return &helpers[i][j];
return NULL;
}
int main(int argc, char *argv[])
{
struct helper *h;
h = helper_by_toolname(argv[0]);
assert(h != NULL);
if (h->check_usage(argc, argv))
exit(EXIT_FAILURE);
if (setlocale(LC_ALL, "C") == NULL) {
fprintf(stderr, "Could not use standard locale\n");
exit(EXIT_FAILURE);
}
if (h->print_params(argv, h))
exit(EXIT_FAILURE);
exit(EXIT_SUCCESS);
}