Files
s390-tools/zdev/src/select.c
Peter Oberparleiter d9a9bd8dcf zdev: Add option to select IPL device
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>
2024-08-22 10:32:36 +02:00

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;
}