/* * * 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 or * * * 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 * * 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 or * * * 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 * * 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #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 * * mirror \ * <#log_args> ... \ * <#devs> ... \ * <#features> ... */ 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_params> \ * <#raid_devs> [.. ] */ 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: * * raid \ * <#devices> \ * */ 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 ", toolname); fprintf(stderr, " or \n"); } static void print_usage_chreipl_helper(const char *toolname) { fprintf(stderr, "%s \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); }