mirror of
https://github.com/ibm-s390-linux/s390-tools.git
synced 2026-08-05 02:14:52 +00:00
Add new command-line option --ipldev. When specified and IPL was done from a supported device type, the IPL device is selected for the requested tool operation. Example to list IPL device information: $ lszdev --ipldev Example to create a persistent configuration for the IPL device: $ chzdev --enable --persistent --ipldev Reviewed-by: Vineeth Vijayan <vneethv@linux.ibm.com> Signed-off-by: Peter Oberparleiter <oberpar@linux.ibm.com> Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
1320 lines
33 KiB
C
1320 lines
33 KiB
C
/*
|
|
* zdev - Modify and display the persistent configuration of 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 <stdlib.h>
|
|
#include <string.h>
|
|
|
|
#include "lib/util_path.h"
|
|
|
|
#include "blkinfo.h"
|
|
#include "ccw.h"
|
|
#include "device.h"
|
|
#include "devnode.h"
|
|
#include "findmnt.h"
|
|
#include "iscsi.h"
|
|
#include "misc.h"
|
|
#include "namespace.h"
|
|
#include "net.h"
|
|
#include "select.h"
|
|
#include "udev.h"
|
|
|
|
/* Create and initialize selection options. */
|
|
struct select_opts *select_opts_new(void)
|
|
{
|
|
struct select_opts *select;
|
|
|
|
select = misc_malloc(sizeof(struct select_opts));
|
|
util_list_init(&select->devids, struct strlist_node, node);
|
|
util_list_init(&select->by_path, struct strlist_node, node);
|
|
util_list_init(&select->by_node, struct strlist_node, node);
|
|
util_list_init(&select->by_if, struct strlist_node, node);
|
|
util_list_init(&select->by_attr, struct strlist_node, node);
|
|
|
|
return select;
|
|
}
|
|
|
|
/* Free selection options. */
|
|
void select_opts_free(struct select_opts *select)
|
|
{
|
|
void *next, *this;
|
|
|
|
if (!select)
|
|
return;
|
|
util_list_iterate_safe(&select->devids, this, next)
|
|
free(this);
|
|
util_list_iterate_safe(&select->by_path, this, next)
|
|
free(this);
|
|
util_list_iterate_safe(&select->by_node, this, next)
|
|
free(this);
|
|
util_list_iterate_safe(&select->by_if, this, next)
|
|
free(this);
|
|
util_list_iterate_safe(&select->by_attr, this, next)
|
|
free(this);
|
|
|
|
free(select);
|
|
}
|
|
|
|
/* Check if devices are specified in the options structure. */
|
|
bool select_opts_dev_specified(struct select_opts *select)
|
|
{
|
|
if (!util_list_is_empty(&select->devids) ||
|
|
!util_list_is_empty(&select->by_path) ||
|
|
!util_list_is_empty(&select->by_node) ||
|
|
!util_list_is_empty(&select->by_if) ||
|
|
!util_list_is_empty(&select->by_attr) ||
|
|
select->all || select->configured || select->existing ||
|
|
select->online || select->offline || select->failed ||
|
|
select->ipldev)
|
|
return true;
|
|
|
|
return false;
|
|
}
|
|
|
|
/* Allocated and initialize a new list of selected_dev objects. */
|
|
struct util_list *selected_dev_list_new(void)
|
|
{
|
|
struct util_list *list;
|
|
|
|
list = misc_malloc(sizeof(struct util_list));
|
|
util_list_init(list, struct selected_dev_node, node);
|
|
|
|
return list;
|
|
}
|
|
|
|
/* Free resources associated with specified selected_dev_node. */
|
|
void selected_dev_free(struct selected_dev_node *dev)
|
|
{
|
|
free(dev->id);
|
|
free(dev->param);
|
|
free(dev);
|
|
}
|
|
|
|
/* Release all resources associated with the specified list. */
|
|
void selected_dev_list_free(struct util_list *list)
|
|
{
|
|
struct selected_dev_node *p, *n;
|
|
|
|
if (!list)
|
|
return;
|
|
util_list_iterate_safe(list, p, n) {
|
|
util_list_remove(list, p);
|
|
selected_dev_free(p);
|
|
}
|
|
free(list);
|
|
}
|
|
|
|
/* Used for debugging purpose. */
|
|
void selected_dev_list_print(struct util_list *list, int i)
|
|
{
|
|
struct selected_dev_node *sel;
|
|
|
|
indent(i, "selected_dev_list at %p:\n", (void *) list);
|
|
if (!list)
|
|
return;
|
|
util_list_iterate(list, sel)
|
|
selected_dev_print(sel, i + 2);
|
|
}
|
|
|
|
static struct selected_dev_node *selected_dev_node_new(struct devtype *dt,
|
|
struct subtype *st,
|
|
const char *id,
|
|
const char *param,
|
|
exit_code_t rc)
|
|
{
|
|
struct selected_dev_node *sel;
|
|
|
|
sel = misc_malloc(sizeof(struct selected_dev_node));
|
|
sel->dt = dt;
|
|
sel->st = st;
|
|
if (st)
|
|
sel->id = st->namespace->normalize_id(id);
|
|
if (!sel->id)
|
|
sel->id = misc_strdup(id);
|
|
sel->param = param ? misc_strdup(param) : NULL;
|
|
sel->rc = rc;
|
|
|
|
return sel;
|
|
}
|
|
|
|
/* Add a new selected_dev object to the list. */
|
|
struct selected_dev_node *selected_dev_list_add(struct util_list *list,
|
|
struct devtype *dt,
|
|
struct subtype *st,
|
|
const char *id,
|
|
const char *param,
|
|
exit_code_t rc)
|
|
{
|
|
struct selected_dev_node *sel;
|
|
|
|
sel = selected_dev_node_new(dt, st, id, param, rc);
|
|
util_list_add_tail(list, sel);
|
|
|
|
return sel;
|
|
}
|
|
|
|
/* Used for debugging. */
|
|
void selected_dev_print(struct selected_dev_node *sel, int indent)
|
|
{
|
|
printf("%*sselected_dev_node at %p\n", indent, "", (void *) sel);
|
|
indent += 2;
|
|
printf("%*sdt=%p (%s)\n", indent, "", (void *) sel->dt,
|
|
sel->dt ? sel->dt->name : "<none>");
|
|
printf("%*sst=%p (%s)\n", indent, "", (void *) sel->st,
|
|
sel->st ? sel->st->name : "<none>");
|
|
printf("%*sid=%s\n", indent, "", sel->id);
|
|
printf("%*sparam=%s\n", indent, "", sel->param);
|
|
printf("%*src=%d\n", indent, "", sel->rc);
|
|
}
|
|
|
|
#define is_range(x) (strchr((x), '-'))
|
|
|
|
/**
|
|
* devid_spec - Device ID specification
|
|
* @spec: Device ID or range as specified on the command line
|
|
* @match: The resulting selected_dev_node
|
|
* @exists: A hint that this spec matched an existing device
|
|
*/
|
|
struct devid_spec {
|
|
const char *spec;
|
|
struct selected_dev_node *match;
|
|
int exists;
|
|
};
|
|
|
|
/* Make sure that all devices specified by ID are available. */
|
|
static void unblacklist_devices(struct select_opts *select)
|
|
{
|
|
struct devtype *dt, *only_dt = select->devtype;
|
|
struct subtype *st, *only_st = select->subtype;
|
|
struct namespace *ns;
|
|
struct strlist_node *s;
|
|
int ns_done[NUM_NAMESPACES];
|
|
int i, j, nsi, need_wait;
|
|
|
|
/* Reset done array. */
|
|
for (i = 0; i < NUM_NAMESPACES; i++)
|
|
ns_done[i] = 0;
|
|
|
|
/* Process all selected namespaces. */
|
|
need_wait = 0;
|
|
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;
|
|
ns = st->namespace;
|
|
|
|
/* Ensure that each namespace is handled only once. */
|
|
nsi = namespaces_index(ns);
|
|
if (nsi < 0)
|
|
continue;
|
|
if (ns_done[nsi])
|
|
continue;
|
|
ns_done[nsi] = 1;
|
|
|
|
/* Check if blacklist is supported. */
|
|
if (!ns->unblacklist_id && !ns->unblacklist_id_range)
|
|
continue;
|
|
|
|
/* Unblacklist all specified IDs. */
|
|
util_list_iterate(&select->devids, s) {
|
|
if (ns_is_id_valid(ns, s->str) &&
|
|
ns->is_id_blacklisted(s->str) &&
|
|
ns->unblacklist_id) {
|
|
ns->unblacklist_id(s->str);
|
|
need_wait = 1;
|
|
} else if (ns_is_id_range_valid(ns, s->str) &&
|
|
ns->is_id_range_blacklisted(s->str) &&
|
|
ns->unblacklist_id_range) {
|
|
ns->unblacklist_id_range(s->str);
|
|
need_wait = 1;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Devices may have become available which trigger udev rules.
|
|
* Wait for udev to settle before reading device state. */
|
|
if (need_wait)
|
|
udev_settle();
|
|
}
|
|
|
|
/* Check if @dev matches state selection parameters in @select. */
|
|
bool select_match_state(struct device *dev, struct select_opts *select)
|
|
{
|
|
struct subtype *st = dev->subtype;
|
|
int exists, configured, autoconf, online;
|
|
struct util_list *errors;
|
|
|
|
exists = dev->active.exists || dev->active.definable;
|
|
configured = dev->persistent.exists;
|
|
autoconf = dev->autoconf.exists;
|
|
if (select->all && !(exists || configured || autoconf))
|
|
return false;
|
|
if (select->existing && !exists)
|
|
return false;
|
|
if (select->configured && !configured)
|
|
return false;
|
|
|
|
online = subtype_online_get(st, dev, config_active);
|
|
if (select->online && online != 1)
|
|
return false;
|
|
if (select->offline && online == 1)
|
|
return false;
|
|
|
|
errors = subtype_get_errors(st, dev->id);
|
|
if (select->failed && !errors)
|
|
return false;
|
|
strlist_free(errors);
|
|
|
|
return true;
|
|
}
|
|
|
|
/* If device @ID of subtype @st matches state selection specified in @select,
|
|
* add it to @selected. */
|
|
static exit_code_t match_state_and_add(struct select_opts *select,
|
|
struct util_list *selected,
|
|
config_t config,
|
|
read_scope_t scope,
|
|
struct subtype *st,
|
|
const char *id, const char *param,
|
|
struct selected_dev_node **match_ptr)
|
|
{
|
|
struct device *dev;
|
|
struct selected_dev_node *sel;
|
|
struct devtype *dt = st->devtype;
|
|
exit_code_t rc;
|
|
config_t c;
|
|
|
|
/* Online information must be obtained from active config. */
|
|
c = SCOPE_ACTIVE(config) ? config : config_all;
|
|
|
|
/* Heavy-weight, but we may need device online information. */
|
|
rc = subtype_read_device(st, id, c, scope, &dev);
|
|
if (rc)
|
|
return rc;
|
|
|
|
if (!select_match_state(dev, select))
|
|
return EXIT_OK;
|
|
|
|
sel = selected_dev_list_add(selected, dt, st, dev->id, param, 0);
|
|
|
|
if (match_ptr && !*match_ptr)
|
|
*match_ptr = sel;
|
|
|
|
return EXIT_OK;
|
|
}
|
|
|
|
/* Ask for confirmation when the number of devices addressed by range exceeds
|
|
* RANGE_LIMIT. */
|
|
static bool check_range_size(struct namespace *ns, const char *range)
|
|
{
|
|
static int warned;
|
|
unsigned long num;
|
|
|
|
if (force || warned)
|
|
return true;
|
|
|
|
num = ns->num_ids_in_range(range);
|
|
if (num <= RANGE_LIMIT)
|
|
return true;
|
|
|
|
/* Warn only once. */
|
|
warned = 1;
|
|
|
|
return confirm("Note: This will attempt to select more than %d "
|
|
"devices (at least %u)\nContinue?", RANGE_LIMIT, num);
|
|
}
|
|
|
|
/* Check if device is matched by list of device IDs and ranges in @specs. */
|
|
static struct devid_spec *match_id(struct namespace *ns, const char *id,
|
|
struct util_list *specs)
|
|
{
|
|
struct ptrlist_node *p;
|
|
struct devid_spec *spec;
|
|
|
|
util_list_iterate(specs, p) {
|
|
spec = p->ptr;
|
|
if (ns_is_id_range_valid(ns, spec->spec)) {
|
|
if (ns->is_id_in_range(id, spec->spec))
|
|
return spec;
|
|
} else if (ns->cmp_ids(id, spec->spec) == 0)
|
|
return spec;
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
/* Set spec->match of all matching spec entries in @specs to @match. */
|
|
static void set_match(struct namespace *ns, const char *id,
|
|
struct util_list *specs, struct selected_dev_node *match)
|
|
{
|
|
struct ptrlist_node *p;
|
|
struct devid_spec *spec;
|
|
|
|
util_list_iterate(specs, p) {
|
|
spec = p->ptr;
|
|
if (spec->match)
|
|
continue;
|
|
if (ns_is_id_range_valid(ns, spec->spec)) {
|
|
if (ns->is_id_in_range(id, spec->spec))
|
|
spec->match = match;
|
|
} else if (ns->cmp_ids(id, spec->spec) == 0)
|
|
spec->match = match;
|
|
}
|
|
}
|
|
|
|
struct select_cb_data {
|
|
struct select_opts *select;
|
|
struct util_list *selected;
|
|
config_t config;
|
|
read_scope_t scope;
|
|
struct util_list *specs;
|
|
};
|
|
|
|
/* Check if this existing device matches any of the devices specified.
|
|
* If so, add a selected_dev_node to data->selected. */
|
|
static exit_code_t select_cb(struct subtype *st, const char *id,
|
|
config_t config, void *data)
|
|
{
|
|
struct select_cb_data *cb_data = data;
|
|
struct devid_spec *spec;
|
|
const char *param;
|
|
struct selected_dev_node **match, *old_match = NULL;
|
|
exit_code_t rc;
|
|
|
|
longrun_current++;
|
|
if (!cb_data->specs) {
|
|
/* No devids provided on command line. */
|
|
param = NULL;
|
|
match = NULL;
|
|
goto add;
|
|
}
|
|
spec = match_id(st->namespace, id, cb_data->specs);
|
|
if (!spec)
|
|
return EXIT_OK;
|
|
param = spec->spec;
|
|
match = &spec->match;
|
|
old_match = spec->match;
|
|
|
|
/* Note that ID matched existing device to generate a more fitting
|
|
* exit code. */
|
|
spec->exists = 1;
|
|
|
|
add:
|
|
rc = match_state_and_add(cb_data->select, cb_data->selected,
|
|
cb_data->config, cb_data->scope,
|
|
st, id, param, match);
|
|
|
|
if (rc || !match || !*match || old_match)
|
|
return rc;
|
|
|
|
/* At this point the following is true:
|
|
* - device IDs were specified
|
|
* - the current device ID spec matched
|
|
* - this is the first match.
|
|
* To prevent "Device not found" error messages on multiple
|
|
* specification of the same IDs, set spec->match for all subsequent
|
|
* matching device ID specs. */
|
|
set_match(st->namespace, id, cb_data->specs, *match);
|
|
|
|
return rc;
|
|
}
|
|
|
|
/* Select devices according to @select in subtype @st. Select only devices
|
|
* which exist/are configured. */
|
|
static exit_code_t select_by_st_nocreate(struct select_opts *select,
|
|
struct util_list *selected,
|
|
config_t config,
|
|
read_scope_t scope,
|
|
struct subtype *st,
|
|
struct util_list *specs)
|
|
{
|
|
struct select_cb_data data;
|
|
|
|
data.select = select;
|
|
data.selected = selected;
|
|
data.config = config;
|
|
data.scope = scope;
|
|
if (util_list_is_empty(specs))
|
|
data.specs = NULL;
|
|
else
|
|
data.specs = specs;
|
|
|
|
return subtype_for_each_id(st, config, select_cb, &data);
|
|
}
|
|
|
|
/* Add a node for a devid specification that did not match any device. */
|
|
static struct selected_dev_node *add_failed_devid_spec(
|
|
struct select_opts *select,
|
|
struct util_list *selected,
|
|
struct devid_spec *spec,
|
|
struct selected_dev_node *before)
|
|
{
|
|
struct selected_dev_node *sel;
|
|
exit_code_t rc;
|
|
|
|
if (select->subtype || spec->exists)
|
|
rc = EXIT_DEVICE_NOT_FOUND;
|
|
else
|
|
rc = EXIT_INCOMPLETE_TYPE;
|
|
|
|
sel = selected_dev_node_new(select->devtype, select->subtype,
|
|
spec->spec, spec->spec, rc);
|
|
|
|
if (before)
|
|
util_list_add_prev(selected, sel, before);
|
|
else
|
|
util_list_add_tail(selected, sel);
|
|
|
|
return sel;
|
|
}
|
|
|
|
/* Add a devnode for any device that is a parent device of @devnode to
|
|
* @result. */
|
|
static bool add_parent_devnodes(struct util_list *result,
|
|
struct devnode *devnode)
|
|
{
|
|
struct util_list *devnodes;
|
|
struct ptrlist_node *p;
|
|
struct devnode *d;
|
|
bool rc = false;
|
|
|
|
switch (devnode->type) {
|
|
case BLOCKDEV:
|
|
devnodes = blkinfo_get_ancestor_devnodes(devnode);
|
|
if (devnodes) {
|
|
rc = true;
|
|
|
|
util_list_iterate(devnodes, p)
|
|
ptrlist_add(result, p->ptr);
|
|
|
|
ptrlist_free(devnodes, 0);
|
|
break;
|
|
}
|
|
d = iscsi_get_net_devnode(devnode);
|
|
if (d) {
|
|
ptrlist_add(result, d);
|
|
rc = true;
|
|
break;
|
|
}
|
|
break;
|
|
case NETDEV:
|
|
if (net_add_linked_devnodes(result, devnode)) {
|
|
rc = true;
|
|
break;
|
|
}
|
|
if (net_add_vlan_base(result, devnode)) {
|
|
rc = true;
|
|
break;
|
|
}
|
|
if (net_add_bonding_base(result, devnode)) {
|
|
rc = true;
|
|
break;
|
|
}
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
return rc;
|
|
}
|
|
|
|
/* Select device that provides device node found at specified path. */
|
|
exit_code_t select_by_devnode(struct select_opts *select,
|
|
struct util_list *selected,
|
|
config_t config, read_scope_t scope,
|
|
struct devtype *only_dt, struct subtype *only_st,
|
|
struct devnode *devnode, const char *path,
|
|
err_t err)
|
|
{
|
|
struct select_opts *dummy_opts = NULL;
|
|
struct subtype *st;
|
|
char *id = NULL, *str;
|
|
const char *name = path ? path : devnode->name;
|
|
struct util_list *todos, *unresolved;
|
|
exit_code_t rc = EXIT_OK;
|
|
struct ptrlist_node *p;
|
|
int num_resolved = 0;
|
|
|
|
/* Allocate dummy selection options in case none were supplied. */
|
|
if (!select) {
|
|
dummy_opts = select_opts_new();
|
|
select = dummy_opts;
|
|
}
|
|
|
|
todos = ptrlist_new();
|
|
ptrlist_add(todos, devnode_copy(devnode));
|
|
unresolved = strlist_new();
|
|
|
|
util_list_iterate(todos, p) {
|
|
devnode = p->ptr;
|
|
|
|
if (subtypes_find_by_devnode(devnode, &st, &id)) {
|
|
/* In case devtype or subtype was specified on command
|
|
* line, filter out devices that do not match type. */
|
|
if ((only_st && st != only_st) ||
|
|
(only_dt && st->devtype != only_dt))
|
|
goto next;
|
|
|
|
match_state_and_add(select, selected, config, scope, st,
|
|
id, NULL, NULL);
|
|
num_resolved++;
|
|
next:
|
|
free(id);
|
|
} else if (!add_parent_devnodes(todos, devnode))
|
|
strlist_add(unresolved, devnode->name);
|
|
}
|
|
|
|
str = strlist_flatten(unresolved, " ");
|
|
if (num_resolved == 0) {
|
|
/* Not a single device could be resolved. */
|
|
err_t_print(err, "Could not determine device that provides "
|
|
"%s (%s)\n", name, str);
|
|
rc = EXIT_DEVICE_NOT_FOUND;
|
|
} else if (!util_list_is_empty(unresolved)) {
|
|
/* Some devices in a compound device could not be selected. */
|
|
err_t_print(err, "Could not determine all devices that provide "
|
|
"%s (%s)\n", name, str);
|
|
}
|
|
free(str);
|
|
|
|
ptrlist_free(todos, 1);
|
|
strlist_free(unresolved);
|
|
select_opts_free(dummy_opts);
|
|
|
|
return rc;
|
|
}
|
|
|
|
/* Select device that provides device node found at specified path. */
|
|
exit_code_t select_by_node(struct select_opts *select,
|
|
struct util_list *selected, config_t config,
|
|
read_scope_t scope, struct devtype *only_dt,
|
|
struct subtype *only_st, const char *path,
|
|
err_t err)
|
|
{
|
|
struct devnode *devnode;
|
|
exit_code_t rc;
|
|
|
|
devnode = devnode_from_node(path, err);
|
|
if (!devnode)
|
|
return EXIT_DEVICE_NOT_FOUND;
|
|
|
|
rc = select_by_devnode(select, selected, config, scope, only_dt,
|
|
only_st, devnode, path, err);
|
|
|
|
free(devnode);
|
|
|
|
return rc;
|
|
}
|
|
|
|
/* Select devices that provide the mountpoint in which path lies. */
|
|
exit_code_t select_by_path(struct select_opts *select,
|
|
struct util_list *selected, config_t config,
|
|
read_scope_t scope, struct devtype *only_dt,
|
|
struct subtype *only_st, const char *path,
|
|
err_t err)
|
|
{
|
|
struct devnode *devnode;
|
|
struct util_list *devnodes;
|
|
struct ptrlist_node *p;
|
|
exit_code_t rc;
|
|
|
|
if (!util_path_exists(path)) {
|
|
err_t_print(err, "Path not found: %s\n", path);
|
|
return EXIT_DEVICE_NOT_FOUND;
|
|
}
|
|
|
|
/* Try to get devnode by using stat. */
|
|
devnode = devnode_from_path(path);
|
|
if (devnode)
|
|
goto found;
|
|
|
|
/* Try to get devnode via blkinfo. */
|
|
devnode = blkinfo_get_devnode_by_path(path);
|
|
if (devnode)
|
|
goto found;
|
|
|
|
/* Try to get devnodes via findmnt (e.g. for btrfs subvolume mounts) */
|
|
devnodes = findmnt_get_devnodes_by_path(path);
|
|
if (devnodes)
|
|
goto found_multi;
|
|
|
|
goto notfound;
|
|
|
|
found:
|
|
/* Select "main" devnode. */
|
|
rc = select_by_devnode(select, selected, config, scope, only_dt,
|
|
only_st, devnode, NULL, err);
|
|
if (rc)
|
|
goto out;
|
|
|
|
/* Check if additional devnodes are involved (same file system UUID,
|
|
* e.g. for a btrfs file system with multiple devices). */
|
|
devnodes = blkinfo_get_same_uuid_devnodes(devnode);
|
|
if (!devnodes)
|
|
goto out;
|
|
|
|
found_multi:
|
|
util_list_iterate(devnodes, p) {
|
|
rc = select_by_devnode(select, selected, config, scope, only_dt,
|
|
only_st, p->ptr, NULL, err);
|
|
if (rc)
|
|
break;
|
|
}
|
|
|
|
ptrlist_free(devnodes, 1);
|
|
|
|
out:
|
|
free(devnode);
|
|
|
|
return rc;
|
|
|
|
notfound:
|
|
err_t_print(err, "Could not determine device that provides %s%s\n",
|
|
path, file_is_devnode(path) ? " (did you mean --by-node?)" :
|
|
"");
|
|
|
|
return EXIT_DEVICE_NOT_FOUND;
|
|
}
|
|
|
|
static exit_code_t select_create_one(struct select_opts *select,
|
|
struct util_list *selected,
|
|
struct devtype *only_dt,
|
|
struct subtype *only_st,
|
|
config_t config, read_scope_t scope,
|
|
struct namespace *ns, const char *id,
|
|
const char *spec,
|
|
struct selected_dev_node **match_ptr)
|
|
{
|
|
int i, j;
|
|
struct devtype *dt;
|
|
struct subtype *st;
|
|
exit_code_t rc = EXIT_OK;
|
|
|
|
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 && st->namespace != ns)
|
|
continue;
|
|
|
|
rc = match_state_and_add(select, selected, config,
|
|
scope, st, id,
|
|
spec, match_ptr);
|
|
if (rc)
|
|
return rc;
|
|
}
|
|
}
|
|
|
|
return rc;
|
|
}
|
|
|
|
static exit_code_t select_create_range(struct select_opts *select,
|
|
struct util_list *selected,
|
|
struct devtype *only_dt,
|
|
struct subtype *only_st,
|
|
config_t config, read_scope_t scope,
|
|
const char *range,
|
|
struct selected_dev_node **match_ptr)
|
|
{
|
|
int i;
|
|
struct namespace *ns;
|
|
struct ns_range_iterator *it;
|
|
exit_code_t rc = EXIT_OK;
|
|
|
|
for (i = 0; (ns = namespaces[i]); i++) {
|
|
if (!ns_is_id_range_valid(ns, range))
|
|
continue;
|
|
|
|
if (!check_range_size(ns, range))
|
|
return EXIT_ABORTED;
|
|
|
|
/* Select device for each ID in range. */
|
|
it = ns_range_iterator_new();
|
|
ns_range_for_each(ns, range, it) {
|
|
rc = select_create_one(select, selected, only_dt,
|
|
only_st, config, scope,
|
|
ns, it->id, range, match_ptr);
|
|
if (rc)
|
|
break;
|
|
}
|
|
ns_range_iterator_free(it);
|
|
if (rc)
|
|
return rc;
|
|
}
|
|
|
|
return EXIT_OK;
|
|
}
|
|
|
|
static exit_code_t select_create_by_spec(struct select_opts *select,
|
|
struct util_list *selected,
|
|
config_t config, read_scope_t scope,
|
|
struct devid_spec *spec)
|
|
{
|
|
struct util_list *cand;
|
|
exit_code_t rc;
|
|
struct selected_dev_node *match, *sel, *n;
|
|
struct subtype *st;
|
|
|
|
/* Special handling in case type is not fully specified: add
|
|
* candidates to special list that is checked afterwards. */
|
|
if (select->subtype)
|
|
cand = selected;
|
|
else
|
|
cand = selected_dev_list_new();
|
|
|
|
match = NULL;
|
|
if (is_range(spec->spec)) {
|
|
/* Select range of devices. */
|
|
rc = select_create_range(select, cand, select->devtype,
|
|
select->subtype, config, scope,
|
|
spec->spec, &match);
|
|
} else {
|
|
/* Select device by ID. */
|
|
rc = select_create_one(select, cand, select->devtype,
|
|
select->subtype, config, scope, NULL,
|
|
spec->spec, spec->spec, &match);
|
|
}
|
|
if (rc)
|
|
goto out;
|
|
|
|
if (select->subtype) {
|
|
/* Type was fully specified, we're done. */
|
|
goto out;
|
|
}
|
|
|
|
/* Check for type ambiguities. */
|
|
st = NULL;
|
|
util_list_iterate(cand, sel) {
|
|
if (!st) {
|
|
st = sel->st;
|
|
continue;
|
|
}
|
|
if (sel->st == st)
|
|
continue;
|
|
|
|
/* There are too many candidates. Create error entry. */
|
|
match = selected_dev_list_add(selected, select->devtype,
|
|
select->subtype,
|
|
spec->spec, spec->spec,
|
|
EXIT_INCOMPLETE_TYPE);
|
|
goto out;
|
|
}
|
|
|
|
/* No ambiguity found - copy candidates to selected list. */
|
|
util_list_iterate_safe(cand, sel, n) {
|
|
util_list_remove(cand, sel);
|
|
util_list_add_tail(selected, sel);
|
|
if (!match)
|
|
match = sel;
|
|
}
|
|
|
|
out:
|
|
if (!spec->match)
|
|
spec->match = match;
|
|
if (cand != selected)
|
|
selected_dev_list_free(cand);
|
|
|
|
return rc;
|
|
}
|
|
|
|
static exit_code_t select_create(struct select_opts *select,
|
|
struct util_list *selected,
|
|
config_t config, read_scope_t scope,
|
|
struct util_list *specs)
|
|
{
|
|
struct ptrlist_node *p;
|
|
exit_code_t rc = EXIT_OK;
|
|
|
|
/* Iterate over device IDs because there's a chance we don't know
|
|
* the subtype. */
|
|
util_list_iterate(specs, p) {
|
|
rc = select_create_by_spec(select, selected, config, scope,
|
|
p->ptr);
|
|
if (rc)
|
|
break;
|
|
}
|
|
|
|
return rc;
|
|
}
|
|
|
|
static exit_code_t select_nocreate(struct select_opts *select,
|
|
struct util_list *selected, int pairs,
|
|
config_t config, read_scope_t scope,
|
|
struct util_list *specs)
|
|
{
|
|
int i, j;
|
|
struct devtype *dt;
|
|
struct subtype *st;
|
|
exit_code_t rc = EXIT_OK;
|
|
config_t id_config;
|
|
unsigned long count;
|
|
|
|
/* Determine from which configuration sets to check for devices. */
|
|
id_config = get_config(select->existing || select->all ||
|
|
select->offline || select->online ||
|
|
SCOPE_ACTIVE(config),
|
|
select->configured || select->all ||
|
|
SCOPE_PERSISTENT(config),
|
|
select->all || SCOPE_AUTOCONF(config));
|
|
|
|
longrun_total = 0;
|
|
if (quiet)
|
|
goto skip;
|
|
|
|
/* Count devices. */
|
|
verb("Scanning for devices in %s configuration%s:\n",
|
|
config_to_str(id_config), !SCOPE_SINGLE(id_config) ? "s" : "");
|
|
for (i = 0; (dt = devtypes[i]); i++) {
|
|
if (select->devtype && dt != select->devtype)
|
|
continue;
|
|
for (j = 0; (st = dt->subtypes[j]); j++) {
|
|
if (select->subtype && st != select->subtype)
|
|
continue;
|
|
count = subtype_count_ids(st, id_config);
|
|
longrun_total += count;
|
|
verb(" %-19s: %lu\n", st->devname, count);
|
|
}
|
|
}
|
|
|
|
if (longrun_total == 0) {
|
|
verb(" No device found\n");
|
|
return EXIT_OK;
|
|
}
|
|
verb(" %-19s: %lu\n", "Total", longrun_total);
|
|
|
|
skip:
|
|
/* Select devices. */
|
|
longrun_start("Reading device information", pairs);
|
|
for (i = 0; (dt = devtypes[i]); i++) {
|
|
if (select->devtype && dt != select->devtype)
|
|
continue;
|
|
for (j = 0; (st = dt->subtypes[j]); j++) {
|
|
if (select->subtype && st != select->subtype)
|
|
continue;
|
|
rc = select_by_st_nocreate(select, selected,
|
|
id_config, scope, st, specs);
|
|
if (rc)
|
|
goto out;
|
|
}
|
|
}
|
|
|
|
out:
|
|
longrun_stop();
|
|
|
|
return rc;
|
|
}
|
|
|
|
static struct subtype *derive_st(const char *spec)
|
|
{
|
|
int i, j;
|
|
struct devtype *dt;
|
|
struct subtype *st, *result = NULL;
|
|
|
|
for (i = 0; (dt = devtypes[i]); i++) {
|
|
for (j = 0; (st = dt->subtypes[j]); j++) {
|
|
if (!ns_is_id_valid(st->namespace, spec) &&
|
|
!ns_is_id_range_valid(st->namespace, spec))
|
|
continue;
|
|
/* Abort on ambiguous match. */
|
|
if (result)
|
|
return NULL;
|
|
result = st;
|
|
}
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
static exit_code_t select_remaining(struct select_opts *select,
|
|
struct util_list *selected, int define,
|
|
config_t config, read_scope_t scope,
|
|
struct util_list *specs)
|
|
{
|
|
struct subtype *st;
|
|
struct ptrlist_node *p;
|
|
struct devid_spec *spec;
|
|
exit_code_t rc = EXIT_OK;
|
|
|
|
util_list_iterate(specs, p) {
|
|
spec = p->ptr;
|
|
if (spec->match)
|
|
continue;
|
|
if (select->subtype)
|
|
st = select->subtype;
|
|
else
|
|
st = derive_st(spec->spec);
|
|
if (!st)
|
|
continue;
|
|
if (define && !st->support_definable)
|
|
continue;
|
|
if (is_range(spec->spec)) {
|
|
/* Select range of devices. */
|
|
rc = select_create_range(select, selected, st->devtype,
|
|
st, config,
|
|
scope, spec->spec,
|
|
&spec->match);
|
|
} else {
|
|
/* Select device by ID. */
|
|
rc = select_create_one(select, selected, st->devtype,
|
|
st, config, scope,
|
|
NULL, spec->spec, spec->spec,
|
|
&spec->match);
|
|
}
|
|
if (rc)
|
|
break;
|
|
}
|
|
|
|
return rc;
|
|
}
|
|
|
|
static exit_code_t select_by_devid(struct select_opts *select,
|
|
struct util_list *selected, int existing,
|
|
int pairs, config_t config,
|
|
read_scope_t scope, struct util_list *specs)
|
|
{
|
|
static exit_code_t rc;
|
|
|
|
if (existing) {
|
|
rc = select_nocreate(select, selected, pairs, config, scope,
|
|
specs);
|
|
if (rc)
|
|
return rc;
|
|
return select_remaining(select, selected, 1, config, scope,
|
|
specs);
|
|
}
|
|
|
|
if (select->subtype) {
|
|
/* Subtype specified - no guesswork involved. */
|
|
if (!util_list_is_empty(specs)) {
|
|
return select_create(select, selected, config, scope,
|
|
specs);
|
|
}
|
|
return select_nocreate(select, selected, pairs, config,
|
|
scope, specs);
|
|
}
|
|
|
|
/* Subtype not specified - check for existing devices first. Use
|
|
* config_all to enable using active devices as template for persistent
|
|
* configurations. */
|
|
rc = select_nocreate(select, selected, pairs, config_all, scope, specs);
|
|
if (rc)
|
|
return rc;
|
|
|
|
/* No information about the device type available. There are two
|
|
* possibilities:
|
|
* 1. Subtype can be inferred from namespace
|
|
* 2. A devid specification cannot be resolved */
|
|
return select_remaining(select, selected, 0, config, scope, specs);
|
|
}
|
|
|
|
/* Add selected_dev_node entries for all device ID specifications that did not
|
|
* match anything. */
|
|
static void handle_unmatched_specs(struct select_opts *select,
|
|
struct util_list *selected,
|
|
struct util_list *specs)
|
|
{
|
|
struct ptrlist_node *p;
|
|
struct selected_dev_node *before;
|
|
struct devid_spec *spec;
|
|
|
|
/* Since unmatched entries should be added before the first match of
|
|
* the following devid specification, we need to traverse the
|
|
* devid_spec list in reverse. */
|
|
before = NULL;
|
|
for (p = util_list_end(specs); p; p = util_list_prev(specs, p)) {
|
|
spec = p->ptr;
|
|
if (spec->match) {
|
|
/* This devid spec matched something. */
|
|
before = spec->match;
|
|
continue;
|
|
}
|
|
|
|
/* Add "not found" result for this devid spec. */
|
|
before = add_failed_devid_spec(select, selected, spec, before);
|
|
}
|
|
}
|
|
|
|
/* Remove duplicate entries in selected list. */
|
|
static void remove_duplicates(struct util_list *selected,
|
|
struct selected_dev_node *first)
|
|
{
|
|
struct selected_dev_node *sel, *s, *n;
|
|
|
|
sel = first ? first : util_list_start(selected);
|
|
|
|
for (; sel; sel = util_list_next(selected, sel)) {
|
|
for (s = util_list_next(selected, sel); s; s = n) {
|
|
n = util_list_next(selected, s);
|
|
if (sel->dt != s->dt || sel->st != s->st)
|
|
continue;
|
|
if (sel->id && s->id) {
|
|
if (strcmp(sel->id, s->id) == 0)
|
|
goto remove;
|
|
continue;
|
|
}
|
|
if (sel->param && s->param &&
|
|
strcmp(sel->param, s->param) == 0)
|
|
goto remove;
|
|
continue;
|
|
|
|
remove:
|
|
util_list_remove(selected, s);
|
|
selected_dev_free(s);
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Select devices that provide networking interface @name. */
|
|
exit_code_t select_by_interface(struct select_opts *select,
|
|
struct util_list *selected, config_t config,
|
|
read_scope_t scope, struct devtype *only_dt,
|
|
struct subtype *only_st, const char *name,
|
|
err_t err)
|
|
{
|
|
struct devnode *d;
|
|
exit_code_t rc;
|
|
|
|
d = devnode_new(NETDEV, 0, 0, name);
|
|
rc = select_by_devnode(select, selected, config, scope, only_dt,
|
|
only_st, d, NULL, err);
|
|
free(d);
|
|
|
|
return rc;
|
|
}
|
|
|
|
struct by_attr_cb_data {
|
|
struct select_opts *select;
|
|
struct util_list *selected;
|
|
read_scope_t scope;
|
|
const char *key;
|
|
const char *value;
|
|
bool invert;
|
|
};
|
|
|
|
static exit_code_t by_attr_cb(struct subtype *st, const char *id,
|
|
config_t config, void *data)
|
|
{
|
|
struct by_attr_cb_data *cb_data = data;
|
|
struct device *dev;
|
|
static exit_code_t rc;
|
|
struct setting *s;
|
|
char *value;
|
|
bool match = false;
|
|
|
|
rc = subtype_read_device(st, id, config_all, cb_data->scope, &dev);
|
|
if (rc)
|
|
return rc;
|
|
|
|
/* Check for attribute value in active configuration. */
|
|
s = setting_list_find(dev->active.settings, cb_data->key);
|
|
if (s)
|
|
match = setting_match_value(s, cb_data->value);
|
|
else {
|
|
/* Try reading setting directly. */
|
|
value = device_read_active_attrib(dev, cb_data->key);
|
|
if (value && strcmp(value, cb_data->value) == 0)
|
|
match = true;
|
|
free(value);
|
|
}
|
|
if (match)
|
|
goto out;
|
|
|
|
/* Check for attribute value in persistent configuration. */
|
|
s = setting_list_find(dev->persistent.settings, cb_data->key);
|
|
if (s) {
|
|
match = setting_match_value(s, cb_data->value);
|
|
if (match)
|
|
goto out;
|
|
}
|
|
/* Check for attribute value in autoconf configuration. */
|
|
s = setting_list_find(dev->autoconf.settings, cb_data->key);
|
|
if (s)
|
|
match = setting_match_value(s, cb_data->value);
|
|
|
|
out:
|
|
if ((match && !cb_data->invert) || (!match && cb_data->invert)) {
|
|
/* Apply state selection and add. */
|
|
rc = match_state_and_add(cb_data->select, cb_data->selected,
|
|
config, cb_data->scope, st, id, NULL,
|
|
NULL);
|
|
}
|
|
|
|
return rc;
|
|
}
|
|
|
|
/* Select devices with specified attribute settings. */
|
|
static exit_code_t select_by_attr(struct select_opts *select,
|
|
struct util_list *selected, config_t config,
|
|
read_scope_t scope, struct devtype *only_dt,
|
|
struct subtype *only_st, const char *attr,
|
|
err_t err)
|
|
{
|
|
char *key, *value;
|
|
exit_code_t rc = EXIT_OK;
|
|
int i, j;
|
|
struct devtype *dt;
|
|
struct subtype *st;
|
|
struct by_attr_cb_data cb_data;
|
|
bool invert = false;
|
|
|
|
key = misc_strdup(attr);
|
|
value = strchr(key, '=');
|
|
if (!value) {
|
|
rc = EXIT_USAGE_ERROR;
|
|
goto out;
|
|
}
|
|
/* Check for KEY!=VALUE. */
|
|
if (value > key && value[-1] == '!') {
|
|
value[-1] = 0;
|
|
invert = true;
|
|
}
|
|
*value = 0;
|
|
value++;
|
|
|
|
/* Iterate over all devices. */
|
|
cb_data.select = select;
|
|
cb_data.selected = selected;
|
|
cb_data.scope = scope;
|
|
cb_data.key = key;
|
|
cb_data.value = value;
|
|
cb_data.invert = invert;
|
|
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;
|
|
rc = subtype_for_each_id(st, config_all, by_attr_cb,
|
|
&cb_data);
|
|
if (rc)
|
|
goto out;
|
|
}
|
|
}
|
|
|
|
|
|
out:
|
|
free(key);
|
|
|
|
return rc;
|
|
}
|
|
|
|
/* Select device that was used for IPL. */
|
|
static void select_ipldev(struct util_list *selected)
|
|
{
|
|
struct subtype *st;
|
|
char *id;
|
|
|
|
if (!subtypes_find_ipldev(&st, &id))
|
|
return;
|
|
|
|
selected_dev_list_add(selected, st->devtype, st, id, NULL, 0);
|
|
free(id);
|
|
}
|
|
|
|
/* Determine list of IDs of selected devices based on selection options.
|
|
* Results are stored as list of struct selected_dev_node in SELECTED.
|
|
* If @existing is set, only return nodes for existing devices.
|
|
* If @unblacklist is set, remove all devices specified by ID or ID range from
|
|
* blacklists. If @pairs is set, output progress output in "pairs" format.
|
|
* @config specifies the configuration from which to read devices. */
|
|
exit_code_t select_devices(struct select_opts *select,
|
|
struct util_list *selected, int existing,
|
|
int unblacklist, int pairs, config_t config,
|
|
read_scope_t scope, err_t err)
|
|
{
|
|
struct util_list *specs = NULL;
|
|
struct strlist_node *s;
|
|
struct devid_spec *spec;
|
|
exit_code_t rc = EXIT_OK;
|
|
struct selected_dev_node *first;
|
|
|
|
/* Note for later. */
|
|
first = util_list_start(selected);
|
|
|
|
if (unblacklist)
|
|
unblacklist_devices(select);
|
|
|
|
/* Skip non-function selection if only by- function was specified. */
|
|
if (util_list_is_empty(&select->devids) &&
|
|
!(util_list_is_empty(&select->by_path) &&
|
|
util_list_is_empty(&select->by_node) &&
|
|
util_list_is_empty(&select->by_if) &&
|
|
util_list_is_empty(&select->by_attr) && !select->ipldev))
|
|
goto by_function;
|
|
|
|
/* Convert device ID specifications to enable tracking of specifications
|
|
* that did not match any device. */
|
|
specs = ptrlist_new();
|
|
util_list_iterate(&select->devids, s) {
|
|
spec = misc_malloc(sizeof(struct devid_spec));
|
|
spec->spec = s->str;
|
|
ptrlist_add(specs, spec);
|
|
}
|
|
|
|
/* Select in all specified devtypes and subtypes. */
|
|
rc = select_by_devid(select, selected, existing, pairs, config, scope,
|
|
specs);
|
|
if (rc)
|
|
goto out;
|
|
|
|
handle_unmatched_specs(select, selected, specs);
|
|
|
|
by_function:
|
|
/* Select devices specified via --by-node */
|
|
util_list_iterate(&select->by_node, s) {
|
|
rc = select_by_node(select, selected, config, scope,
|
|
select->devtype, select->subtype, s->str,
|
|
err);
|
|
if (rc)
|
|
goto out;
|
|
}
|
|
/* Select device specified via --by-path */
|
|
util_list_iterate(&select->by_path, s) {
|
|
rc = select_by_path(select, selected, config, scope,
|
|
select->devtype, select->subtype, s->str,
|
|
err);
|
|
if (rc)
|
|
goto out;
|
|
}
|
|
|
|
/* Select devices specified via --by-interface */
|
|
util_list_iterate(&select->by_if, s) {
|
|
rc = select_by_interface(select, selected, config, scope,
|
|
select->devtype, select->subtype,
|
|
s->str, err);
|
|
if (rc)
|
|
goto out;
|
|
}
|
|
|
|
/* Select devices specified via --by-attr */
|
|
util_list_iterate(&select->by_attr, s) {
|
|
rc = select_by_attr(select, selected, config, scope,
|
|
select->devtype, select->subtype,
|
|
s->str, err);
|
|
if (rc)
|
|
goto out;
|
|
}
|
|
|
|
if (select->ipldev)
|
|
select_ipldev(selected);
|
|
|
|
out:
|
|
/* Release device ID specifications tracking list. */
|
|
ptrlist_free(specs, 1);
|
|
|
|
if (!rc)
|
|
remove_duplicates(selected, first);
|
|
|
|
return rc;
|
|
}
|