/* * zdev - Modify and display the persistent configuration of devices * * lszdev: Display configuration of z Systems specific devices * * Copyright IBM Corp. 2016, 2017 * * s390-tools is free software; you can redistribute it and/or modify * it under the terms of the MIT license. See LICENSE for details. */ #include #include #include #include "lib/zt_common.h" #include "blkinfo.h" #include "ccw.h" #include "ctc.h" #include "device.h" #include "devtype.h" #include "inuse.h" #include "misc.h" #include "module.h" #include "namespace.h" #include "opts.h" #include "path.h" #include "scsi.h" #include "select.h" #include "subtype.h" #include "table.h" #include "table_types.h" /* Main program action. */ typedef enum { ACT_LIST, ACT_INFO, ACT_LIST_COLUMNS, ACT_LIST_TYPES, ACT_HELP, ACT_VERSION, } action_t; /* Representation of command line options. */ struct options { /* Selection. */ struct select_opts *select; unsigned int type:1; /* Actions */ unsigned int info; unsigned int list_columns:1; unsigned int list_types:1; unsigned int help:1; unsigned int version:1; /* Options */ config_t config; unsigned int active:1; unsigned int persistent:1; unsigned int auto_conf:1; struct util_list *columns; /* List of struct strlist_node */ unsigned int no_headings:1; struct util_list *base; /* List of struct strlist_node */ unsigned int pairs:1; unsigned int verbose:1; unsigned int quiet:1; }; /* Makefile converts lszdev_usage.txt into C file which we include here. */ static const char *usage_text = #include "lszdev_usage.c" ; #define OPT_ANONYMOUS_BASE 0x80 enum { OPT_ALL = (OPT_ANONYMOUS_BASE+__COUNTER__), OPT_CONFIGURED = (OPT_ANONYMOUS_BASE+__COUNTER__), OPT_EXISTING = (OPT_ANONYMOUS_BASE+__COUNTER__), OPT_ONLINE = (OPT_ANONYMOUS_BASE+__COUNTER__), OPT_OFFLINE = (OPT_ANONYMOUS_BASE+__COUNTER__), OPT_FAILED = (OPT_ANONYMOUS_BASE+__COUNTER__), OPT_BY_PATH = (OPT_ANONYMOUS_BASE+__COUNTER__), OPT_BY_NODE = (OPT_ANONYMOUS_BASE+__COUNTER__), OPT_BY_INTERFACE = (OPT_ANONYMOUS_BASE+__COUNTER__), OPT_BY_ATTRIB = (OPT_ANONYMOUS_BASE+__COUNTER__), OPT_TYPE = 't', OPT_INFO = 'i', OPT_LIST_COLUMNS = 'l', OPT_LIST_TYPES = 'L', OPT_HELP = 'h', OPT_VERSION = 'v', OPT_ACTIVE = 'a', OPT_PERSISTENT = 'p', OPT_COLUMNS = 'c', OPT_NO_HEADINGS = 'n', OPT_BASE = (OPT_ANONYMOUS_BASE+__COUNTER__), OPT_VERBOSE = 'V', OPT_QUIET = 'q', OPT_PAIRS = 'P', OPT_AUTO_CONF = (OPT_ANONYMOUS_BASE+__COUNTER__), }; static struct opts_conflict conflict_list[] = { OPTS_CONFLICT(OPT_INFO, OPT_LIST_COLUMNS, OPT_LIST_TYPES, OPT_HELP, OPT_VERSION, OPT_COLUMNS, OPT_NO_HEADINGS, 0), OPTS_CONFLICT(OPT_LIST_COLUMNS, OPT_LIST_TYPES, OPT_HELP, OPT_VERSION, 0), OPTS_CONFLICT(OPT_LIST_TYPES, OPT_HELP, OPT_VERSION, 0), OPTS_CONFLICT(OPT_HELP, OPT_VERSION, 0), OPTS_CONFLICT(OPT_TYPE, OPT_CONFIGURED, OPT_EXISTING, OPT_ONLINE, OPT_OFFLINE, OPT_BY_PATH, OPT_BY_NODE, OPT_BY_INTERFACE, OPT_FAILED, 0), OPTS_CONFLICT(OPT_ONLINE, OPT_OFFLINE), OPTS_CONFLICT(OPT_QUIET, OPT_VERBOSE), OPTS_CONFLICT(0, 0), }; /* Command line options. */ static const struct option opt_list[] = { /* Selection. */ { "all", no_argument, NULL, OPT_ALL }, { "configured", no_argument, NULL, OPT_CONFIGURED }, { "existing", no_argument, NULL, OPT_EXISTING }, { "online", no_argument, NULL, OPT_ONLINE }, { "offline", no_argument, NULL, OPT_OFFLINE }, { "failed", no_argument, NULL, OPT_FAILED }, { "by-path", required_argument, NULL, OPT_BY_PATH }, { "by-node", required_argument, NULL, OPT_BY_NODE }, { "by-interface", required_argument, NULL, OPT_BY_INTERFACE }, { "by-attrib", required_argument, NULL, OPT_BY_ATTRIB }, { "type", no_argument, NULL, OPT_TYPE }, /* Actions. */ { "info", no_argument, NULL, OPT_INFO }, { "list-columns", no_argument, NULL, OPT_LIST_COLUMNS }, { "list-types", no_argument, NULL, OPT_LIST_TYPES }, { "help", no_argument, NULL, OPT_HELP }, { "version", no_argument, NULL, OPT_VERSION }, /* Options. */ { "active", no_argument, NULL, OPT_ACTIVE }, { "persistent", no_argument, NULL, OPT_PERSISTENT }, { "auto-conf", no_argument, NULL, OPT_AUTO_CONF }, { "columns", required_argument, NULL, OPT_COLUMNS }, { "no-headings", no_argument, NULL, OPT_NO_HEADINGS }, { "base", required_argument, NULL, OPT_BASE }, { "pairs", no_argument, NULL, OPT_PAIRS }, { "verbose", no_argument, NULL, OPT_VERBOSE }, { "quiet", no_argument, NULL, OPT_QUIET }, { NULL, no_argument, NULL, 0 }, }; /* Command line abbreviations. */ static const char opt_str[] = ":tilLhvapPc:nVq"; /* Initialize options data structure. */ static void init_options(struct options *opts) { memset(opts, 0, sizeof(struct options)); opts->select = select_opts_new(); opts->columns = strlist_new(); opts->base = strlist_new(); } /* Release memory used in options data structure. */ static void free_options(struct options *opts) { if (!opts) return; select_opts_free(opts->select); strlist_free(opts->columns); strlist_free(opts->base); } /* Print usage information. */ static void print_usage(void) { printf("%s", usage_text); } /* Print version information. */ static void print_version(void) { printf("%s version %s\n", toolname, RELEASE_STRING); } /* Determine main program action from parse command line options. */ static action_t get_action(struct options *opts) { if (opts->help) return ACT_HELP; if (opts->version) return ACT_VERSION; if (opts->info) return ACT_INFO; if (opts->list_columns) return ACT_LIST_COLUMNS; if (opts->list_types) return ACT_LIST_TYPES; return ACT_LIST; } /* Check options data structure for syntax errors. */ static exit_code_t check_options(struct options *opts, int specified[OPTS_MAX + 1], int op) { if (opts_check_conflict(op, specified, conflict_list, opt_list)) return EXIT_USAGE_ERROR; return EXIT_OK; } /* Determine if STR is a valid device specification. */ static bool is_devspec(struct devtype *dt, struct subtype *st, char *str) { char *copy, *curr, *next; bool rc; /* ID,ID-ID,... */ copy = misc_strdup(str); next = copy; /* Separate by comma. */ rc = true; if (st) { /* Is this a valid ID or range for this subtype? */ while ((curr = strsep(&next, ","))) { if (!ns_is_id_valid(st->namespace, curr) && !ns_is_id_range_valid(st->namespace, curr)) { rc = false; break; } } } else if (dt) { /* Is this a valid ID or range for any subtype of this * devtype? */ while ((curr = strsep(&next, ","))) { if (!devtype_is_id_valid(dt, curr) && !devtype_is_id_range_valid(dt, curr)) { rc = false; break; } } } else { /* Is this a valid ID or range for any subtype? */ while ((curr = strsep(&next, ","))) { if (!namespaces_is_id_valid(curr) && !namespaces_is_id_range_valid(curr)) { rc = false; break; } } } free(copy); return rc; } static exit_code_t check_devid(struct devtype *only_dt, struct subtype *only_st, const char *id) { int i, j; struct devtype *dt; struct subtype *st; struct namespace *ns; for (i = 0; (dt = devtypes[i]); i++) { if (only_dt && dt != only_dt) continue; for (j = 0; (st = dt->subtypes[j]); j++) { if (only_st && st != only_st) continue; if (ns_is_id_valid(st->namespace, id)) return EXIT_OK; } } /* ID not valid - generate corresponding error message and exit code. */ ns = NULL; if (only_st) ns = only_st->namespace; else if (only_dt && devtype_count_namespaces(only_dt) == 1) ns = only_dt->subtypes[0]->namespace; else ns = devtype_most_similar_namespace(only_dt, NULL, id); if (ns) return ns->is_id_valid(id, err_print); if (only_dt) { error("Unrecognized %s ID format: %s\n", only_dt->devname, id); return EXIT_INVALID_ID; } syntax("Unknown device type or device ID format: %s\n", id); return EXIT_USAGE_ERROR; } static exit_code_t check_range(struct devtype *only_dt, struct subtype *only_st, const char *range) { int i, j; struct devtype *dt; struct subtype *st; struct namespace *ns; for (i = 0; (dt = devtypes[i]); i++) { if (only_dt && dt != only_dt) continue; for (j = 0; (st = dt->subtypes[j]); j++) { if (only_st && st != only_st) continue; if (ns_is_id_range_valid(st->namespace, range)) return EXIT_OK; } } /* Range not valid - generate corresponding error message and exit * code. */ ns = NULL; if (only_st) ns = only_st->namespace; else if (only_dt && devtype_count_namespaces(only_dt) == 1) ns = only_dt->subtypes[0]->namespace; if (ns) return ns->is_id_range_valid(range, err_print); if (only_dt) { error("Unrecognized %s device ID range format: %s\n", only_dt->devname, range); return EXIT_INVALID_ID; } syntax("Unknown device type or device ID range format: %s\n", range); return EXIT_USAGE_ERROR; } /* Get corresponding exit code and error message for invalid devspec. */ static exit_code_t check_devspec(struct devtype *dt, struct subtype *st, const char *devspec) { char *copy, *curr, *next; exit_code_t rc = EXIT_OK; /* ID,ID-ID,... */ copy = misc_strdup(devspec); next = copy; /* Separate by comma. */ while ((curr = strsep(&next, ","))) { if (strchr(curr, '-')) rc = check_range(dt, st, curr); else rc = check_devid(dt, st, curr); if (rc) break; } free(copy); return rc; } static exit_code_t check_devspecs(struct options *opts) { struct strlist_node *str; exit_code_t rc = EXIT_OK; util_list_iterate(&opts->select->devids, str) { rc = check_devspec(opts->select->devtype, opts->select->subtype, str->str); if (rc) break; } return rc; } /* Parse positional parameters. */ static exit_code_t parse_positional(struct options *opts, int argc, char *argv[], int start) { action_t action = get_action(opts); struct subtype *st; struct devtype *dt; int i; bool got_devspec; enum { invalid, required, optional, } ex_dev, ex_dt; /* Determine valid parameter combinations for each action. */ switch (action) { case ACT_LIST: case ACT_INFO: if (opts->type) { ex_dev = invalid; ex_dt = required; } else { ex_dev = optional; ex_dt = optional; } break; case ACT_LIST_TYPES: ex_dev = invalid; ex_dt = optional; break; default: ex_dev = invalid; ex_dt = invalid; break; } /* Collect parameters. */ for (i = start; i < argc; i++) { dt = devtype_find(argv[i]); st = subtype_find(argv[i]); if (ex_dt != invalid && st && !opts->select->devtype) { /* Got expected subtype. */ opts->select->subtype = st; opts->select->devtype = st->devtype; continue; } if (ex_dt != invalid && dt && !opts->select->devtype) { /* Got expected devtype. */ opts->select->devtype = dt; continue; } if (ex_dev != invalid && is_devspec(opts->select->devtype, opts->select->subtype, argv[i])) { /* Got expected device ID. */ strlist_add_multi(&opts->select->devids, argv[i], ",", 0); continue; } /* Handle error cases. */ if (ex_dt != invalid && ex_dev == invalid) { if (opts->select->devtype) goto err_extra; else goto err_unknown_devtype; } if (ex_dt == invalid && ex_dev != invalid) goto err_unknown_dev; if (ex_dt != invalid && ex_dev != invalid) { if (opts->select->devtype) goto err_unknown_dev; goto err_unknown_dev_or_devtype; } goto err_extra; } /* Check for missing parameters. */ got_devspec = select_opts_dev_specified(opts->select); if (ex_dt == required && !opts->select->devtype) goto err_no_devtype; if (ex_dev == required && !got_devspec) goto err_no_dev; if (ex_dev != invalid) goto check_devspec; return EXIT_OK; check_devspec: return check_devspecs(opts); err_unknown_dev_or_devtype: syntax("Unknown device type or device ID specification: %s\n", argv[i]); return EXIT_USAGE_ERROR; err_unknown_dev: return check_devspec(opts->select->devtype, opts->select->subtype, argv[i]); err_unknown_devtype: error("Unrecognized device type: %s\n", argv[i]); return EXIT_UNKNOWN_DEVTYPE; err_extra: syntax("Unexpected parameter found: %s\n", argv[i]); return EXIT_USAGE_ERROR; err_no_devtype: error("Please specify a device type\n" "Use '%s --list-types' to get a list of device types\n", toolname); return EXIT_USAGE_ERROR; err_no_dev: syntax("Please specify a device\n"); return EXIT_USAGE_ERROR; } /* Parse command line options. Filter out invalid combinations. */ static exit_code_t parse_options(struct options *opts, int argc, char *argv[]) { exit_code_t rc; int opt; int specified[OPTS_MAX + 1]; /* Suppress getopt error messages. */ memset(specified, 0, sizeof(specified)); rc = EXIT_OK; opterr = 0; while ((opt = getopt_long(argc, argv, opt_str, opt_list, NULL)) != -1) { switch (opt) { case OPT_ALL: /* --all */ opts->select->all = 1; break; case OPT_CONFIGURED: /* --configured */ opts->select->configured = 1; break; case OPT_EXISTING: /* --existing */ opts->select->existing = 1; break; case OPT_ONLINE: /* --online */ opts->select->online = 1; break; case OPT_OFFLINE: /* --offline */ opts->select->offline = 1; break; case OPT_FAILED: /* --failed */ opts->select->failed = 1; break; case OPT_BY_PATH: /* --by-path MOUNTPOINT */ strlist_add(&opts->select->by_path, "%s", optarg); break; case OPT_BY_NODE: /* --by-node NODE */ strlist_add(&opts->select->by_node, "%s", optarg); break; case OPT_BY_INTERFACE: /* --by-interface NAME */ strlist_add(&opts->select->by_if, "%s", optarg); break; case OPT_BY_ATTRIB: /* --by-attrib NAME */ if (!strchr(optarg, '=')) { syntax("--by-attrib requires argument in " "ATTRIB=VALUE or ATTRIB!=VALUE " "format\n"); return EXIT_USAGE_ERROR; } strlist_add(&opts->select->by_attr, "%s", optarg); break; case OPT_TYPE: /* --type */ opts->type = 1; break; case OPT_INFO: /* --info */ opts->info++; break; case OPT_LIST_COLUMNS: /* --list-columns */ opts->list_columns = 1; break; case OPT_LIST_TYPES: /* --list-types */ opts->list_types = 1; break; case OPT_HELP: /* --help */ opts->help = 1; /* --help has precedence - exit early */ return EXIT_OK; case OPT_VERSION: /* --version */ opts->version = 1; /* --version has precedence - exit early */ return EXIT_OK; case OPT_ACTIVE: /* --active */ opts->active = 1; break; case OPT_PERSISTENT: /* --persistent */ opts->persistent = 1; break; case OPT_AUTO_CONF: /* --auto-conf */ opts->auto_conf = 1; break; case OPT_COLUMNS: /* --columns COLUMN */ strlist_add_multi(opts->columns, optarg, ",", 0); break; case OPT_NO_HEADINGS: /* --no-headings */ opts->no_headings = 1; break; case OPT_BASE: /* --base PATH */ strlist_add(opts->base, "%s", optarg); break; case OPT_PAIRS: /* --pairs */ opts->pairs = 1; break; case OPT_VERBOSE: /* --verbose */ opts->verbose = 1; break; case OPT_QUIET: /* --quiet */ opts->quiet = 1; break; case ':': /* Missing option argument. */ syntax("Option '%s' requires an argument\n", argv[optind - 1]); return EXIT_USAGE_ERROR; case '?': /* Unknown option character. */ if (optopt) syntax("Unrecognized option '-%c'\n", optopt); else { syntax("Unrecognized option '%s'\n", argv[optind - 1]); } return EXIT_USAGE_ERROR; default: break; } if (opt >= 0 && opt <= OPTS_MAX) specified[opt] = 1; /* Check after each option to report errors in order of * specification. */ rc = check_options(opts, specified, opt); if (rc) break; } if (rc) goto out; /* Determine configuration set. */ if (!opts->active && !opts->persistent && !opts->auto_conf) { /* Default display targets are active + persistent - note that * autoconf data is still shown when available to make users * aware of this type of data. */ opts->config = config_active | config_persistent; } else { opts->config = get_config(opts->active, opts->persistent, opts->auto_conf); } /* Handle positional parameters. */ rc = parse_positional(opts, argc, argv, optind); /* Set implicit settings. */ if (!select_opts_dev_specified(opts->select)) { switch (get_action(opts)) { case ACT_LIST: case ACT_INFO: /* List(--info without --type or device spec selects all * devices. */ if (!opts->type) { if (opts->config == config_all) opts->select->all = 1; else if (opts->config == config_active) opts->select->existing = 1; else if (opts->config == config_persistent) opts->select->configured = 1; } break; default: break; } } out: return rc; } /* Column IDs for the devices table. */ enum dev_table_id { dev_type, dev_devid, dev_names, dev_blockdevs, dev_chardevs, dev_netdevs, dev_exists, dev_pers, dev_auto, dev_online, dev_failed, dev_modules, dev_attr, dev_attrpath, }; /* Definition of output table for device list. */ static struct column *dev_table = COLUMN_ARRAY( COLUMN("TYPE", align_left, dev_type, 1, "Device type"), COLUMN("ID", align_left, dev_devid, 1, "Device identifier"), COLUMN("ON", align_left, dev_online, 1, "Device is online in the active configuration"), COLUMN("EXISTS", align_left, dev_exists, 0, "Device exists in the active configuration"), COLUMN("PERS", align_left, dev_pers, 1, "Device is configured persistently"), COLUMN("AUTO", align_left, dev_auto, 0, "Auto-configuration exists for device"), COLUMN("FAILED", align_left, dev_failed, 0, "Device is in error"), COLUMN("NAMES", align_left, dev_names, 1, "Associated Linux device names"), COLUMN("BLOCKDEVS", align_left, dev_blockdevs, 0, "Associated block devices including partitions"), COLUMN("CHARDEVS", align_left, dev_chardevs, 0, "Associated character devices"), COLUMN("NETDEVS", align_left, dev_netdevs, 0, "Associated network interfaces"), COLUMN("MODULES", align_left, dev_modules, 0, "Required kernel modules"), COLUMN("ATTR:", align_left, dev_attr, 0, "Value of specific attribute, e.g. ATTR:online"), COLUMN("ATTRPATH:", align_left, dev_attrpath, 0, "Path to specific attribute in active configuration") ); static char *merge_str(const char *act, const char *pers, const char *ac, config_t config) { char *str; size_t len; act = act ? act : "-"; pers = pers ? pers : "-"; ac = ac ? ac : "-"; if (strcmp(act, pers) == 0 && strcmp(act, ac) == 0) return misc_strdup(act); len = strlen(act) + strlen(pers) + strlen(ac) + /* 3 * / + NUL */ 4; str = misc_malloc(len); if (SCOPE_ACTIVE(config)) strcat(str, act); if (SCOPE_PERSISTENT(config)) { if (*str) strcat(str, "/"); strcat(str, pers); } if (SCOPE_AUTOCONF(config)) { if (*str) strcat(str, "/"); strcat(str, ac); } return str; } /* Return string representing online status of device @dev. */ static char *dev_table_get_online(struct device *dev, config_t config) { int online; online = subtype_online_get(dev->subtype, dev, config_active); return misc_strdup(YESNO(online == 1)); } /* Return string representing failed status of device @dev. */ static char *dev_table_get_failed(struct device *dev) { struct util_list *errors; errors = subtype_get_errors(dev->subtype, dev->id); strlist_free(errors); return misc_strdup(YESNO(errors != NULL)); } /* Return string for list of modules required by device @dev. */ static char *dev_table_get_modules(struct device *dev) { struct util_list *names; char *str; names = strlist_new(); device_add_modules(names, dev); str = strlist_flatten(names, " "); strlist_free(names); return str; } static char *get_attr(struct device *dev, const char *name, config_t config) { struct setting_list *list; struct setting *s; list = device_get_setting_list(dev, config); if (!list) return NULL; s = setting_list_find(list, name); if (!s) { if (config == config_active) { /* Try reading active attribute directly. */ return device_read_active_attrib(dev, name); } return NULL; } return misc_strdup(s->value); } /* Return string for attribute with specified @name for device @dev. */ static char *dev_table_get_attr(struct device *dev, const char *attr, config_t config) { char *act = NULL, *pers = NULL, *ac = NULL, *str; const char *name; name = strchr(attr, ':'); if (!name) return NULL; name++; if (SCOPE_ACTIVE(config)) act = get_attr(dev, name, config_active); if (SCOPE_PERSISTENT(config)) pers = get_attr(dev, name, config_persistent); if (SCOPE_AUTOCONF(config)) ac = get_attr(dev, name, config_autoconf); str = merge_str(act, pers, ac, config); free(act); free(pers); free(ac); return str; } /* Return string for path to attribute with specified @name for device @dev * in the active configuration. */ static char *dev_table_get_attrpath(struct device *dev, const char *attr) { const char *name; name = strchr(attr, ':'); if (!name) return NULL; name++; return subtype_get_active_attrib_path(dev->subtype, dev, name); } /* Retrieve value of a cell for struct device @item in column @id in the * devices table. */ static char *dev_table_get_value(void *item, int id, const char *heading, void *data) { struct device *dev = item; struct options *opts = data; switch (id) { case dev_type: return misc_strdup(dev->subtype->name); case dev_devid: return misc_strdup(dev->id); case dev_names: return subtype_get_devnodes_str(dev->subtype, dev->id, 1, 0, 1, 1); case dev_blockdevs: return subtype_get_devnodes_str(dev->subtype, dev->id, 1, 1, 0, 0); case dev_chardevs: return subtype_get_devnodes_str(dev->subtype, dev->id, 0, 0, 1, 0); case dev_netdevs: return subtype_get_devnodes_str(dev->subtype, dev->id, 0, 0, 0, 1); case dev_exists: return misc_strdup(YESNO(dev->active.exists || dev->active.definable)); case dev_pers: if (!dev->persistent.exists && dev->autoconf.exists) return misc_strdup("auto"); return misc_strdup(YESNO(dev->persistent.exists)); case dev_auto: return misc_strdup(YESNO(dev->autoconf.exists)); case dev_online: return dev_table_get_online(dev, config_active); case dev_failed: return dev_table_get_failed(dev); case dev_modules: return dev_table_get_modules(dev); case dev_attr: return dev_table_get_attr(dev, heading, opts->config); case dev_attrpath: return dev_table_get_attrpath(dev, heading); default: break; } return NULL; } /* Determine if we need to read all device information based on command line * parameters. */ static read_scope_t get_scope(struct options *opts) { struct strlist_node *s; struct column *col; util_list_iterate(opts->columns, s) { col = table_get_column(dev_table, s->str); if (!col) return scope_known; switch (col->id) { case dev_type: case dev_devid: case dev_names: case dev_blockdevs: case dev_chardevs: case dev_netdevs: case dev_exists: case dev_pers: case dev_auto: case dev_online: case dev_modules: continue; default: return scope_known; } } return scope_mandatory; } /* Build list of items in table from list of selected struct devices. */ static struct util_list *dev_table_build(struct options *opts, exit_code_t *rc_ptr) { struct util_list *selected, *devices = NULL; struct selected_dev_node *sel; struct device *dev; int active, persistent, autoconf; read_scope_t scope; exit_code_t rc; config_t config = opts->config; scope = get_scope(opts); selected = selected_dev_list_new(); /* Read auto-config data when no configuration set was specified to * make user aware of auto-config data. */ if (!opts->active && !opts->persistent && !opts->auto_conf) config |= config_autoconf; rc = select_devices(opts->select, selected, 1, 0, opts->pairs, config, scope, err_print); if (rc) goto out; devices = ptrlist_new(); /* Process selected devices. */ util_list_iterate(selected, sel) { if (sel->rc) continue; if (subtype_read_device(sel->st, sel->id, opts->config, scope, &dev)) continue; if (dev->processed) continue; dev->processed = 1; /* Only process existing devices. */ active = dev->active.exists || dev->active.definable; persistent = dev->persistent.exists; autoconf = dev->autoconf.exists; if (!active && !persistent && !autoconf) continue; ptrlist_add(devices, dev); } if (util_list_is_empty(devices)) { error("No device was selected!\n"); ptrlist_free(devices, 0); devices = NULL; rc = EXIT_EMPTY_SELECTION; } out: selected_dev_list_free(selected); if (rc_ptr) *rc_ptr = rc; return devices; } /* Perform device list. */ static exit_code_t do_list_devices(struct options *opts) { struct util_list *items; exit_code_t rc; rc = table_check_columns(dev_table, opts->columns); if (rc) return rc; /* Create table from selection options. */ items = dev_table_build(opts, &rc); if (rc) return rc; if (!items) return EXIT_EMPTY_SELECTION; /* Adjust columns visible depending on selected config set. */ table_set_default(dev_table, dev_online, SCOPE_ACTIVE(opts->config)); table_set_default(dev_table, dev_pers, SCOPE_PERSISTENT(opts->config)); table_set_default(dev_table, dev_auto, SCOPE_AUTOCONF(opts->config) && !SCOPE_PERSISTENT(opts->config)); table_set_default(dev_table, dev_names, SCOPE_ACTIVE(opts->config)); /* Display table. */ rc = table_print(dev_table, dev_table_get_value, opts, items, opts->columns, !opts->no_headings, opts->pairs, 0, util_list_is_empty(opts->columns)); ptrlist_free(items, 0); return rc; } static char *devtype_get_modules_str(struct devtype *dt) { struct util_list *modules; char *str; modules = strlist_new(); devtype_add_modules(modules, dt, 1); str = strlist_flatten(modules, " "); strlist_free(modules); return str; } enum settings_table_id { settings_attribute, settings_readonly, settings_active, settings_persistent, settings_autoconf, }; static struct column *settings_table = COLUMN_ARRAY( COLUMN("ATTRIBUTE", align_left, settings_attribute, 1, ""), COLUMN("READONLY", align_left, settings_readonly, 1, ""), COLUMN("ACTIVE", align_left, settings_active, 1, ""), COLUMN("PERSISTENT", align_left, settings_persistent, 1, ""), COLUMN("AUTOCONF", align_left, settings_autoconf, 0, "") ); static struct util_list *settings_table_build(struct setting_list *active, struct setting_list *persistent, struct setting_list *autoconf, bool readonly) { struct util_list *names, *items; struct setting *s; struct strlist_node *str; /* Get a list of all configured attributes. */ names = strlist_new(); if (active) { util_list_iterate(&active->list, s) { if ((readonly && !s->readonly) || (!readonly && s->readonly)) continue; strlist_add(names, s->name); } } if (persistent && !readonly) { util_list_iterate(&persistent->list, s) strlist_add(names, s->name); } if (autoconf && !readonly) { util_list_iterate(&autoconf->list, s) strlist_add(names, s->name); } strlist_sort_unique(names, str_cmp); /* Convert strlist to ptrlist. */ items = ptrlist_new(); util_list_iterate(names, str) ptrlist_add(items, misc_strdup(str->str)); strlist_free(names); return items; } struct settings_table_data { struct setting_list *active; struct setting_list *persistent; struct setting_list *autoconf; int pairs; bool readonly; }; static char *settings_table_get_value(void *item, int id, const char *heading, void *data) { char *name = item; struct settings_table_data *stdata = data; struct setting_list *list = NULL; struct setting *s; switch (id) { case settings_attribute: case settings_readonly: return misc_strdup(name); case settings_active: list = stdata->active; break; case settings_persistent: list = stdata->persistent; break; case settings_autoconf: list = stdata->autoconf; break; default: break; } if (list) { s = setting_list_find(list, name); if (s && !((id == settings_persistent || id == settings_autoconf) && s->derived)) { if (stdata->pairs) return misc_strdup(s->value); else return quote_str(s->value, 1); } } return misc_strdup(stdata->pairs ? "" : "-"); } #define INFO_INDENT 2 #define INFO_WIDTH 19 static void settings_table_print(struct setting_list *active, struct setting_list *persistent, struct setting_list *autoconf, struct options *opts, int ind, bool readonly, bool neednl) { struct util_list *items; struct settings_table_data data; items = settings_table_build( SCOPE_ACTIVE(opts->config) ? active : NULL, SCOPE_PERSISTENT(opts->config) ? persistent : NULL, autoconf, readonly); if (util_list_is_empty(items)) { if (!opts->pairs && !readonly) indent(ind, "%sNo settings found\n", neednl ? "\n" : ""); goto out; } if (neednl) printf("\n"); table_set_default(settings_table, settings_attribute, readonly ? 0 : 1); table_set_default(settings_table, settings_readonly, readonly ? 1 : 0); table_set_default(settings_table, settings_active, SCOPE_ACTIVE(opts->config)); table_set_default(settings_table, settings_persistent, SCOPE_PERSISTENT(opts->config) && !readonly ? 1 : 0); table_set_default(settings_table, settings_autoconf, (SCOPE_AUTOCONF(opts->config) || autoconf) && !readonly ? 1 : 0); data.active = active; data.persistent = persistent; data.autoconf = autoconf; data.pairs = opts->pairs; table_print(settings_table, settings_table_get_value, &data, items, NULL, 1, opts->pairs, ind, 0); out: ptrlist_free(items, 1); } static void print_info(const char *key, const char *value) { if (!*value) value = "-"; printf("%*s%*s%c %s\n", INFO_INDENT, "", -INFO_WIDTH, key, *key ? ':' : ' ', value); } static void print_pair(const char *key, const char *value) { char *quoted; quoted = quote_str(value, 1); printf("%s=%s\n", key, quoted); free(quoted); } static void print_pair_nonl(const char *key, const char *value) { char *quoted; quoted = quote_str(value, 1); printf("%s=%s", key, quoted); free(quoted); } /* Perform device type list/info action. */ static exit_code_t do_info_type(struct options *opts) { struct devtype *dt = opts->select->devtype; exit_code_t rc; char *modules; const char *name, *desc, *active, *persistent; /* Get devtype settings. */ rc = dt->read_settings(dt, opts->config); if (rc) return rc; /* Ensure that settings which are not set are shown as such. */ if (dt->persistent_settings) setting_list_remove_derived(dt->persistent_settings); /* Determine values for printing. */ name = dt->name; desc = *dt->title ? dt->title : dt->subtypes[0]->title; modules = devtype_get_modules_str(dt); active = YESNO(dt->active_exists); persistent = YESNO(dt->persistent_exists); /* Print information. */ if (opts->pairs) { print_pair("TYPE", name); print_pair("DESCRIPTION", desc); print_pair("MODULES", modules); print_pair("ACTV", active); print_pair("PERS", persistent); } else { printf("DEVICE TYPE %s\n", name); print_info("Description", desc); print_info("Modules", modules); print_info("Active", active); print_info("Persistent", persistent); printf("\n"); } if (!dt->type_attribs[0]) { if (!opts->pairs) { indent(INFO_INDENT, "Device type does not provide " "type attributes\n"); } } else { settings_table_print(dt->active_settings, dt->persistent_settings, NULL, opts, INFO_INDENT, false, false); } free(modules); return rc; } /* Perform --list-types. */ static exit_code_t do_list_type(struct options *opts) { return table_types_show(opts->columns, !opts->no_headings, opts->pairs); } /* Perform --list-columns. */ static void do_list_columns(struct options *opts) { table_print_columns(dev_table, opts->columns, !opts->no_headings, opts->pairs); } /* Perform --info on one device. */ static void do_info_one_device(struct device *dev, struct options *opts) { struct subtype *st = dev->subtype; char *names, *bdevs, *cdevs, *ndevs, *modules, *online, *path, *key; const char *id, *type, *exists, *persist, *ac, *prefix; struct util_list *resources, *errors; struct strlist_node *s; bool first; int i; /* Determine values for printing. */ id = dev->id; type = st->name; names = subtype_get_devnodes_str(st, dev->id, 1, 0, 1, 1); bdevs = subtype_get_devnodes_str(st, dev->id, 1, 1, 0, 0); cdevs = subtype_get_devnodes_str(st, dev->id, 0, 0, 1, 0); ndevs = subtype_get_devnodes_str(st, dev->id, 0, 0, 0, 1); resources = inuse_get_resources(dev); modules = dev_table_get_modules(dev); online = dev_table_get_online(dev, config_active); errors = subtype_get_errors(dev->subtype, dev->id); exists = YESNO(dev->active.exists || dev->active.definable); persist = YESNO(dev->persistent.exists); ac = YESNO(dev->autoconf.exists); /* Print information. */ if (opts->pairs) { print_pair("ID", id); print_pair("TYPE", type); print_pair("NAMES", names); print_pair("BLOCKDEVS", bdevs); print_pair("CHARDEVS", cdevs); print_pair("NETDEVS", ndevs); if (resources) { util_list_iterate(resources, s) print_pair("RESOURCE", s->str); } print_pair("MODULES", modules); print_pair("ONLINE", online); print_pair("EXISTS", exists); print_pair("PERSISTENT", persist); if (SCOPE_AUTOCONF(opts->config) || dev->autoconf.exists) print_pair("AUTOCONF", ac); if (errors) { util_list_iterate(errors, s) print_pair("ERROR", s->str); } } else { printf("DEVICE %s %s\n", type, id); print_info("Names", names); if (bdevs && *bdevs) print_info("Block devices", bdevs); if (cdevs && *cdevs) print_info("Character devices", cdevs); if (ndevs && *ndevs) print_info("Network interfaces", ndevs); if (resources) { first = true; util_list_iterate(resources, s) { print_info(first ? "Resources provided" : "", s->str); first = false; } } print_info("Modules", modules); print_info("Online", online); print_info("Exists", exists); print_info("Persistent", persist); if (SCOPE_AUTOCONF(opts->config) || dev->autoconf.exists) print_info("Auto-configured", ac); if (errors) { first = true; util_list_iterate(errors, s) { print_info(first ? "Errors" : "", s->str); first = false; } } } if (opts->info > 1) { /* Print device path. */ path = subtype_get_active_attrib_path(st, dev, ""); if (path) { if (opts->pairs) print_pair("DEVPATH", path); else print_info("Device path", path); free(path); } /* Print prefix paths. */ for (i = 0; st->prefixes && (prefix = st->prefixes[i]); i++) { path = subtype_get_active_attrib_path(st, dev, prefix); if (!path) continue; if (opts->pairs) { print_pair_nonl("PREFIX", prefix); printf(" "); print_pair("PATH", path); } else { key = misc_asprintf("%s path", prefix); print_info(key, path); free(key); } free(path); } } settings_table_print(dev->active.exists ? dev->active.settings : NULL, dev->persistent.exists ? dev->persistent.settings : NULL, dev->autoconf.exists ? dev->autoconf.settings : NULL, opts, INFO_INDENT, false, !opts->pairs); settings_table_print(dev->active.exists ? dev->active.settings : NULL, dev->persistent.exists ? dev->persistent.settings : NULL, dev->autoconf.exists ? dev->autoconf.settings : NULL, opts, INFO_INDENT, true, !opts->pairs); strlist_free(errors); free(online); free(modules); strlist_free(resources); free(ndevs); free(cdevs); free(bdevs); free(names); } /* Perform --info on devices. */ static exit_code_t do_info_devices(struct options *opts) { struct util_list *selected; struct selected_dev_node *sel; struct device *dev; int found, active, persistent, autoconf; exit_code_t rc, drc = EXIT_OK; read_scope_t scope; config_t config = opts->config; if (opts->info > 1) scope = scope_all; else scope = scope_known; /* Get list of selected devices. */ selected = selected_dev_list_new(); /* Read auto-config data when no configuration set was specified to * make user aware of auto-config data. */ if (!opts->active && !opts->persistent && !opts->auto_conf) config |= config_autoconf; select_devices(opts->select, selected, 1, 0, opts->pairs, config, scope, err_print); /* Process selected devices. */ found = 0; util_list_iterate(selected, sel) { if (sel->rc) continue; rc = subtype_read_device(sel->st, sel->id, opts->config, scope, &dev); if (rc) { if (!drc) drc = rc; continue; } /* Only process existing devices. */ active = dev->active.exists || dev->active.definable; persistent = dev->persistent.exists; autoconf = dev->autoconf.exists; if (!active && !persistent && !autoconf) continue; if (found > 0) printf("\n"); do_info_one_device(dev, opts); found++; } selected_dev_list_free(selected); if (found == 0) { error("No device was selected!\n"); drc = EXIT_EMPTY_SELECTION; } return drc; } int main(int argc, char *argv[]) { exit_code_t rc, drc = EXIT_OK; struct options opts; debug_init(argc, argv); /* Handle command line. */ toolname = argv[0]; init_options(&opts); devtypes_init(); rc = parse_options(&opts, argc, argv); if (rc) { if (!drc) drc = rc; goto out; } /* Set globals. */ verbose = opts.verbose; quiet = opts.quiet; path_set_base(opts.base); if (opts.pairs) set_stdout_data(); /* Do not load modules when listing devices. Modules for existing * devices should have been already loaded via udev modalias matching. * Also users don't expect changes to their system when running a * list tool. */ module_load_suppress(1); /* Perform main action. */ switch (get_action(&opts)) { case ACT_LIST: if (opts.type) rc = do_info_type(&opts); else rc = do_list_devices(&opts); break; case ACT_INFO: if (opts.type) rc = do_info_type(&opts); else rc = do_info_devices(&opts); break; case ACT_LIST_COLUMNS: do_list_columns(&opts); break; case ACT_LIST_TYPES: rc = do_list_type(&opts); break; case ACT_HELP: print_usage(); break; case ACT_VERSION: print_version(); break; } if (rc && !drc) drc = rc; out: /* Write out any remaining messages. */ delayed_print(0); /* Clean-up. */ free_options(&opts); blkinfo_exit(); ccw_exit(); ctc_exit(); devtypes_exit(); inuse_exit(); misc_exit(); module_exit(); rc = namespace_exit(); if (rc && !drc) drc = rc; path_exit(); scsi_exit(); return drc ? drc : rc; }