/*
 *
 * 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);
			goto out;
		}
		tops = find_target_ops(type);
		if (!tops) {
			ERR("Unsupported setup: Unsupported device-mapper "
			    "target type '%s' for device '%s'\n",
			    type, devname);
		}
		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);
}
