Files
s390-tools/zdev/src/lszdev.c
Peter Oberparleiter fe68ec513d zdev: Add support for handling auto-configuration data
Auto-configuration is the name of a new configuration target that is
supported by chzdev and lszdev besides the existing active and
persistent configuration targets. Directives created in this new
configuration are stored as udev rules in the /run/udev/rules.d
directory.

Auto-configuration directives are only in effect if there are no
directives for the same device in the user-provided persistent
configuration. This allows users to override auto-configuration
directives if necessary.

Due to the volatile nature of the /run directory, auto-configuration
directives are cleared on reboot. Therefore mechanisms that generate
auto-configuration directives must recreate them on every boot.

The lszdev tool displays auto-configuration data both in list view
as well as in detail view. Users can specify the new option --auto-conf
to only show data from this configuration target.

Mechanisms that generate automated configuration directives can use
chzdev together with the --auto-conf option to create the corresponding
udev rules.

Note: This change does not include a mechanism that generates
      auto-configuration directives.

Signed-off-by: Peter Oberparleiter <oberpar@linux.vnet.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2018-06-08 15:03:30 +02:00

1623 lines
38 KiB
C

/*
* 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 <getopt.h>
#include <string.h>
#include <unistd.h>
#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;
}