Files
s390-tools/zdev/src/ccwgroup.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

1272 lines
29 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 <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "lib/util_base.h"
#include "lib/util_path.h"
#include "attrib.h"
#include "ccw.h"
#include "ccwgroup.h"
#include "device.h"
#include "devnode.h"
#include "devtype.h"
#include "iscsi.h"
#include "misc.h"
#include "module.h"
#include "namespace.h"
#include "path.h"
#include "select.h"
#include "setting.h"
#include "subtype.h"
#include "udev.h"
#include "udev_ccwgroup.h"
#define DEVNAME "CCWGROUP device"
/*
* Common CCWGROUP device attributes.
*/
struct attrib ccwgroup_attr_online = {
.name = "online",
.title = "Activate a device",
.desc = "Control the activation of a device:\n"
" 0: Device is disabled and cannot be used\n"
" 1: Device is enabled\n",
.mandatory = 1,
.defval = "0",
.accept = ACCEPT_ARRAY(ACCEPT_RANGE(0, 1)),
};
/*
* CCWGROUP device methods.
*/
/**
* ccwgroup_parse_devid - Parse a string into a ccwgroup_devid
* @devid_ptr: Target ccwgroup_devid or NULL
* @id: String to parse
* @err: Specify what to do with errors (ignore, print, delayed_print)
*
* Return %EXIT_OK on success, a corresponding error exit code otherwise.
*/
exit_code_t ccwgroup_parse_devid(struct ccwgroup_devid *devid_ptr,
const char *id, err_t err)
{
struct ccwgroup_devid devid;
char *copy, *curr, *next;
unsigned int i, j;
exit_code_t rc;
char *reason = NULL;
memset(&devid, 0, sizeof(struct ccwgroup_devid));
copy = misc_strdup(id);
next = copy;
for (i = 0; i < CCWGROUP_MAX_DEVIDS; i++) {
curr = strsep(&next, ":");
if (!curr)
break;
rc = ccw_parse_devid(&devid.devid[i], curr, err);
if (rc)
goto out;
}
devid.num = i;
rc = EXIT_INVALID_ID;
if (i == 0) {
reason = "No CCW device ID specified\n";
goto out;
}
if (next && *next) {
reason = "Too many CCW device IDs specified\n";
goto out;
}
for (i = 1; i < devid.num; i++) {
for (j = 0; j < i; j++) {
if (ccw_cmp_devids(&devid.devid[j],
&devid.devid[i]) == 0) {
reason = "Duplicate CCW device ID specified";
goto out;
}
}
}
rc = EXIT_OK;
if (devid_ptr)
*devid_ptr = devid;
out:
free(copy);
if (reason) {
err_t_print(err, "Error in %s ID format: %s: %s\n", DEVNAME,
reason, id);
}
return rc;
}
bool ccwgroup_parse_devid_simple(struct ccwgroup_devid *devid, const char *id)
{
if (ccwgroup_parse_devid(devid, id, err_ignore) == EXIT_OK)
return true;
return false;
}
static exit_code_t ccwgroup_is_id_valid(const char *id, err_t err)
{
return ccwgroup_parse_devid(NULL, id, err);
}
bool ccwgroup_is_id_similar(const char *id)
{
char *copy, *start, *end;
bool result = false;
copy = misc_strdup(id);
start = copy;
/* ccwid:ccwid:ccwid */
end = strchr(start, ':');
if (!end)
goto out;
*end = 0;
if (!ccw_is_id_similar(start))
goto out;
start = end + 1;
end = strchr(start, ':');
if (!end)
goto out;
*end = 0;
if (!ccw_is_id_similar(start))
goto out;
start = end + 1;
if (ccw_is_id_similar(start))
result = true;
out:
free(copy);
return result;
}
int ccwgroup_cmp_parsed_ids(const void *a, const void *b)
{
return memcmp(a, b, sizeof(struct ccwgroup_devid));
}
int ccwgroup_cmp_ids(const char *a_str, const char *b_str)
{
struct ccwgroup_devid a, b;
if (!ccwgroup_parse_devid_simple(&a, a_str) ||
!ccwgroup_parse_devid_simple(&b, b_str))
return -1;
if (a.num == b.num)
return ccwgroup_cmp_parsed_ids(&a, &b);
/* ccwid vs ccwid */
if (a.num == 1 || b.num == 1)
return ccw_cmp_devids(&a.devid[0], &b.devid[0]);
return -1;
}
char *ccwgroup_devid_to_str(struct ccwgroup_devid *devid)
{
char *last = NULL, *result = NULL;
unsigned int i;
for (i = 0; i < devid->num; i++) {
if (last) {
result = misc_asprintf("%s:%x.%x.%04x", last,
devid->devid[i].cssid,
devid->devid[i].ssid,
devid->devid[i].devno);
} else {
result = misc_asprintf("%x.%x.%04x",
devid->devid[i].cssid,
devid->devid[i].ssid,
devid->devid[i].devno);
}
free(last);
last = result;
}
return result;
}
static char *ccwgroup_normalize_id(const char *id)
{
struct ccwgroup_devid devid;
if (!ccwgroup_parse_devid_simple(&devid, id))
return NULL;
return ccwgroup_devid_to_str(&devid);
}
static exit_code_t ccwgroup_parse_devid_range(struct ccwgroup_devid *from_ptr,
struct ccwgroup_devid *to_ptr,
const char *range, err_t err)
{
char *from_str, *to_str;
struct ccwgroup_devid from, to;
exit_code_t rc;
const char *reason = NULL;
/* Split range. */
from_str = misc_strdup(range);
to_str = strchr(from_str, '-');
if (!to_str) {
rc = EXIT_INVALID_ID;
reason = "Missing hyphen";
goto out;
}
*to_str = 0;
to_str++;
/* Parse range start end end ID. */
rc = ccwgroup_parse_devid(&from, from_str, err);
if (rc)
goto out;
rc = ccwgroup_parse_devid(&to, to_str, err);
if (rc)
goto out;
/* Only allow ranges on CCWGROUP devices specified as single ID. */
if (from.num != 1 || to.num != 1) {
rc = EXIT_INVALID_ID;
reason = "Ranges only supported on single CCW device IDs";
goto out;
}
rc = EXIT_OK;
if (from_ptr)
*from_ptr = from;
if (to_ptr)
*to_ptr = to;
out:
free(from_str);
if (reason) {
err_t_print(err, "Error in %s ID range format: %s: %s\n",
DEVNAME, reason, range);
}
return rc;
}
static bool ccwgroup_parse_devid_range_simple(struct ccwgroup_devid *from,
struct ccwgroup_devid *to,
const char *range)
{
if (ccwgroup_parse_devid_range(from, to, range, err_ignore) == EXIT_OK)
return true;
return false;
}
static exit_code_t ccwgroup_is_id_range_valid(const char *range, err_t err)
{
return ccwgroup_parse_devid_range(NULL, NULL, range, err);
}
static unsigned long ccwgroup_num_ids_in_range(const char *range)
{
struct ccwgroup_devid f, t;
if (!ccwgroup_parse_devid_range_simple(&f, &t, range))
return 0;
if (f.devid[0].cssid != t.devid[0].cssid ||
f.devid[0].ssid != t.devid[0].ssid)
return 0;
if (f.devid[0].devno > t.devid[0].devno)
return 0;
return t.devid[0].devno - f.devid[0].devno + 1;
}
bool ccwgroup_is_id_in_range(const char *id, const char *range)
{
struct ccwgroup_devid devid, from, to;
if (!ccwgroup_parse_devid_simple(&devid, id))
return false;
if (!ccwgroup_parse_devid_range_simple(&from, &to, range))
return false;
return ccw_devid_in_range(&devid.devid[0], &from.devid[0],
&to.devid[0]);
}
struct ccwgroup_devid *ccwgroup_copy_devid(struct ccwgroup_devid *devid)
{
struct ccwgroup_devid *result;
result = misc_malloc(sizeof(struct ccwgroup_devid));
*result = *devid;
return result;
}
/* Initialize the range iterator object IT with the specified range and
* set it->id to the first ID in the range or NULL if the range is not valid.
* All used fields in IT must be objects allocated using alloc(). */
static void ccwgroup_range_start(struct ns_range_iterator *it,
const char *range)
{
struct ccwgroup_devid from, to;
if (!ccwgroup_parse_devid_range_simple(&from, &to, range)) {
memset(it, 0, sizeof(struct ns_range_iterator));
return;
}
it->devid = ccwgroup_copy_devid(&from);
it->devid_last = ccwgroup_copy_devid(&to);
it->id = ccwgroup_devid_to_str(it->devid);
}
/* Set it->id to the next ID in the range or NULL if the end of the range
* was reached. */
void ccwgroup_range_next(struct ns_range_iterator *it)
{
struct ccwgroup_devid *curr, *last;
if (!it->id)
return;
free(it->id);
curr = it->devid;
last = it->devid_last;
if (curr->devid[0].devno < last->devid[0].devno) {
curr->devid[0].devno++;
it->id = ccwgroup_devid_to_str(curr);
} else
it->id = NULL;
}
static void *ccwgroup_parse_id(const char *id, err_t err)
{
struct ccwgroup_devid *devid;
devid = misc_malloc(sizeof(struct ccwgroup_devid));
if (ccwgroup_parse_devid(devid, id, err) != EXIT_OK) {
free(devid);
return NULL;
}
return devid;
}
static int ccwgroup_cmp_devids(struct ccwgroup_devid *a,
struct ccwgroup_devid *b)
{
return ccwgroup_cmp_parsed_ids(a, b);
}
static void ccwgroup_get_online_attrib(struct attrib **attribs, int online,
config_t config, struct attrib **a_ptr,
const char **name_ptr,
const char **value_ptr)
{
const char *name;
const char *value = online ? "1" : "0";
struct attrib *a;
a = attrib_find(attribs, "online");
if (a)
name = a->name;
else
name = "online";
*a_ptr = a;
*name_ptr = name;
*value_ptr = value;
}
/* Modify the online attribute of the specified device. */
static void ccwgroup_st_online_set(struct subtype *st, struct device *dev,
int online, config_t config)
{
struct attrib *a;
const char *name, *value;
ccwgroup_get_online_attrib(st->dev_attribs, online, config, &a, &name,
&value);
if (SCOPE_ACTIVE(config)) {
if (online)
setting_list_apply(dev->active.settings, a, name,
value);
else {
/* CCWGROUP devices should be deconfigured in
* offline state. */
dev->active.deconfigured = 1;
dev->active.modified = 1;
}
}
if (SCOPE_PERSISTENT(config))
setting_list_apply(dev->persistent.settings, a, name, value);
if (SCOPE_AUTOCONF(config))
setting_list_apply(dev->autoconf.settings, a, name, value);
}
static int get_online(struct setting_list *list)
{
struct setting *s;
if (!list)
return -1;
s = setting_list_find(list, "online");
if (!s)
return -1;
if (atoi(s->value) != 0)
return 1;
return 0;
}
/* Return -1 if online state is not configured, 0 for offline and 1 for online.
* If multiple configurations are specified, return the minimum of all. */
static int ccwgroup_st_online_get(struct subtype *st, struct device *dev,
config_t config)
{
int act_online = 1, pers_online = 1, auto_online = 1;
if (SCOPE_ACTIVE(config))
act_online = get_online(dev->active.settings);
if (SCOPE_PERSISTENT(config))
pers_online = get_online(dev->persistent.settings);
if (SCOPE_AUTOCONF(config))
auto_online = get_online(dev->autoconf.settings);
return MIN(MIN(act_online, pers_online), auto_online);
}
/* Determine if the online state of the specified device was modified. */
static bool ccwgroup_st_online_specified(struct subtype *st,
struct device *dev, config_t config)
{
struct setting *s;
if (SCOPE_ACTIVE(config) && dev->active.settings) {
s = setting_list_find(dev->active.settings, "online");
if (s && s->specified)
return true;
}
if (SCOPE_PERSISTENT(config) && dev->persistent.settings) {
s = setting_list_find(dev->persistent.settings, "online");
if (s && s->specified)
return true;
}
if (SCOPE_AUTOCONF(config) && dev->autoconf.settings) {
s = setting_list_find(dev->autoconf.settings, "online");
if (s && s->specified)
return true;
}
return false;
}
/* Add summary descriptions of operational problems with CCWGROUP device @id to
* strlist @errors. */
static void ccwgroup_st_add_errors(struct subtype *st, const char *id,
struct util_list *errors)
{
struct ccwgroup_devid devid;
char *ccwid;
unsigned int i;
if (!ccwgroup_parse_devid_simple(&devid, id))
return;
/* Add errors for each associated CCW device. */
for (i = 0; i < devid.num; i++) {
ccwid = ccw_devid_to_str(&devid.devid[i]);
subtype_add_errors(&ccw_subtype, ccwid, errors);
free(ccwid);
}
}
char *ccwgroup_get_partial_id(const char *id)
{
struct ccwgroup_devid devid;
if (!ccwgroup_parse_devid_simple(&devid, id))
return NULL;
return ccw_devid_to_str(&devid.devid[0]);
}
static char *ccwgroup_get_dev_path(const char *drv, const char *id)
{
char *partial_id, *path;
partial_id = ccwgroup_get_partial_id(id);
if (!partial_id)
return NULL;
path = path_get_ccwgroup_device(drv, partial_id);
free(partial_id);
return path;
}
static char *ccwgroup_get_dev_path_by_devid(const char *drv,
struct ccwgroup_devid *devid)
{
char *partial_id, *path;
partial_id = ccw_devid_to_str(&devid->devid[0]);
path = path_get_ccwgroup_device(drv, partial_id);
free(partial_id);
return path;
}
static void ccwgroup_st_add_devnodes(struct subtype *st, const char *id,
struct util_list *devnodes)
{
struct ccwgroup_subtype_data *data = st->data;
char *path;
path = ccwgroup_get_dev_path(data->ccwgroupdrv, id);
if (path)
devnode_add_net_from_sysfs(devnodes, path);
free(path);
}
/* Read the full CCWGROUP device ID from the main CCW device ID. */
static bool read_full_id(struct ccwgroup_devid *devid_ptr, const char *drv,
const char *id)
{
char *path, *link_path, *link, *base;
unsigned int i;
struct ccwgroup_devid devid;
bool result = false;
path = path_get_ccwgroup_device(drv, id);
if (!util_path_is_dir(path))
goto out;
memset(&devid, 0, sizeof(struct ccwgroup_devid));
for (i = 0; i < CCWGROUP_MAX_DEVIDS; i++) {
/* Read cdev<n> link target. */
link_path = misc_asprintf("%s/cdev%u", path, i);
link = misc_readlink(link_path);
free(link_path);
if (!link) {
result = false;
break;
}
/* Parse as CCW device ID. */
base = basename(link);
result = ccw_parse_devid_simple(&devid.devid[i], base);
free(link);
if (!result)
goto out;
devid.num++;
}
if (devid.num > 0) {
result = true;
*devid_ptr = devid;
}
out:
free(path);
return result;
}
struct get_ids_cb_data {
const char *drv;
struct util_list *ids;
};
static exit_code_t get_ids_cb(const char *path, const char *file, void *data)
{
struct get_ids_cb_data *cb_data = data;
struct ccwgroup_devid devid;
char *id;
if (strchr(file, '.') && read_full_id(&devid, cb_data->drv, file)) {
id = ccwgroup_devid_to_str(&devid);
strlist_add(cb_data->ids, id);
free(id);
}
return EXIT_OK;
}
/* Add all existing CCWGROUP devices attached to driver DRV to strlist LIST.
* If DRV is NULL add all CCWGROUP device IDs. */
static void ccwgroup_add_ids(const char *drv, const char *mod,
struct util_list *list)
{
struct get_ids_cb_data cb_data;
char *path;
cb_data.drv = drv;
cb_data.ids = list;
cio_settle(0);
path = path_get_ccwgroup_devices(drv);
if (mod)
module_try_load_once(mod, path);
if (util_path_is_dir(path))
path_for_each(path, get_ids_cb, &cb_data);
free(path);
}
static void expand_id(const char *ccwgroupdrv, struct device *dev)
{
struct ccwgroup_devid *devid = dev->devid;
if (devid->num != 1 || !ccwgroupdrv)
return;
if (read_full_id(devid, ccwgroupdrv, dev->id)) {
free(dev->id);
dev->id = ccwgroup_devid_to_str(devid);
}
}
static exit_code_t ccwgroup_st_read_active(struct subtype *st,
struct device *dev,
read_scope_t scope)
{
struct ccwgroup_subtype_data *data = st->data;
const char *drv = data->ccwgroupdrv;
struct ccwgroup_devid *devid;
struct device_state *state = &dev->active;
char *path;
cio_settle(0);
expand_id(drv, dev);
devid = dev->devid;
state->modified = 0;
state->deconfigured = 0;
state->definable = 0;
path = ccwgroup_get_dev_path_by_devid(drv, devid);
if (util_path_exists(path)) {
state->exists = 1;
device_read_active_settings(dev, scope);
} else
state->exists = 0;
free(path);
return EXIT_OK;
}
static exit_code_t ccwgroup_st_read_persistent(struct subtype *st,
struct device *dev,
read_scope_t scope)
{
struct ccwgroup_subtype_data *data = st->data;
expand_id(data->ccwgroupdrv, dev);
return udev_ccwgroup_read_device(dev, false);
}
static exit_code_t ccwgroup_st_read_autoconf(struct subtype *st,
struct device *dev,
read_scope_t scope)
{
struct ccwgroup_subtype_data *data = st->data;
expand_id(data->ccwgroupdrv, dev);
return udev_ccwgroup_read_device(dev, true);
}
static exit_code_t ccwgroup_st_configure_active(struct subtype *st,
struct device *dev)
{
cio_settle(0);
return device_write_active_settings(dev);
}
static exit_code_t _ccwgroup_st_configure_persistent(struct subtype *st,
struct device *dev,
bool autoconf)
{
struct ccwgroup_subtype_data *data = st->data;
struct ccwgroup_devid *devid = dev->devid;
if (devid->num != data->num_devs) {
delayed_err("Incomplete device ID specified\n");
return EXIT_INCOMPLETE_ID;
}
return udev_ccwgroup_write_device(dev, autoconf);
}
static exit_code_t ccwgroup_st_configure_persistent(struct subtype *st,
struct device *dev)
{
return _ccwgroup_st_configure_persistent(st, dev, false);
}
static exit_code_t ccwgroup_st_configure_autoconf(struct subtype *st,
struct device *dev)
{
return _ccwgroup_st_configure_persistent(st, dev, true);
}
static char *ccwgroup_st_get_active_attrib_path(struct subtype *,
struct device *, const char *);
/* Set the specified CCWGROUP device offline. */
static exit_code_t ccwgroup_st_deconfigure_active(struct subtype *st,
struct device *dev)
{
struct setting *s;
char *path;
exit_code_t rc;
if (get_online(dev->active.settings) != 1)
return EXIT_OK;
/* Set the online attribute to 0. */
s = setting_list_apply(dev->active.settings, &ccwgroup_attr_online,
ccwgroup_attr_online.name, "0");
path = ccwgroup_st_get_active_attrib_path(st, dev, s->name);
rc = setting_write(path, s);
free(path);
return rc;
}
static exit_code_t ccwgroup_st_deconfigure_persistent(struct subtype *st,
struct device *dev)
{
return udev_ccwgroup_remove_rule(st->name, dev->id, false);
}
static exit_code_t ccwgroup_st_deconfigure_autoconf(struct subtype *st,
struct device *dev)
{
return udev_ccwgroup_remove_rule(st->name, dev->id, true);
}
/* Check if a CCWGROUP device with the specified ID exists. */
static bool ccwgroup_is_grouped(const char *drv, const char *id)
{
struct ccwgroup_devid devid, full_devid;
char *ccw_id;
bool result;
cio_settle(0);
if (!ccwgroup_parse_devid_simple(&devid, id))
return false;
ccw_id = ccw_devid_to_str(&devid.devid[0]);
result = read_full_id(&full_devid, drv, ccw_id);
free(ccw_id);
if (!result)
return false;
if (devid.num == 1) {
/* CCWGROUP device ID specified as single CCW device ID. */
return result;
}
if (ccwgroup_cmp_devids(&devid, &full_devid) == 0)
return true;
return false;
}
static bool ccwgroup_st_exists_active(struct subtype *st, const char *id)
{
struct ccwgroup_subtype_data *data = st->data;
return ccwgroup_is_grouped(data->ccwgroupdrv, id);
}
static bool ccwgroup_st_exists_persistent(struct subtype *st, const char *id)
{
return udev_ccwgroup_exists(st->name, id, false);
}
static bool ccwgroup_st_exists_autoconf(struct subtype *st, const char *id)
{
return udev_ccwgroup_exists(st->name, id, true);
}
int ccwgroup_qsort_cmp(const void *a_ptr, const void *b_ptr)
{
const char *a = *((const char **) a_ptr);
const char *b = *((const char **) b_ptr);
return ccwgroup_cmp_ids(a, b);
}
static void ccwgroup_st_add_active_ids(struct subtype *st,
struct util_list *ids)
{
struct ccwgroup_subtype_data *data = st->data;
ccwgroup_add_ids(data->ccwgroupdrv, data->mod, ids);
}
static void ccwgroup_st_add_persistent_ids(struct subtype *st,
struct util_list *ids)
{
udev_ccwgroup_add_device_ids(st->name, ids, false);
}
static void ccwgroup_st_add_autoconf_ids(struct subtype *st,
struct util_list *ids)
{
udev_ccwgroup_add_device_ids(st->name, ids, true);
}
/* Check if CCW device ID @b is part of CCW group device ID @a. */
static bool id_contains(struct ccwgroup_devid *a, struct ccw_devid *b)
{
unsigned int i;
for (i = 0; i < a->num; i++) {
if (ccw_cmp_devids(&a->devid[i], b) == 0)
return true;
}
return false;
}
/* Check if there are common CCW device IDs in @a and @b. */
static bool id_overlap(struct ccwgroup_devid *a, struct ccwgroup_devid *b)
{
unsigned int i;
for (i = 0; i < a->num; i++) {
if (id_contains(b, &a->devid[i]))
return true;
}
return false;
}
/* Remove all CCW device IDs which are part of CCW group device %dev from
* %list. */
static void ccwgroup_st_rem_combined(struct subtype *st, struct device *dev,
struct selected_dev_node *curr,
struct util_list *list)
{
struct selected_dev_node *p, *n;
struct ccw_devid devid;
struct ccwgroup_devid groupid, *gdevid;
gdevid = dev->devid;
util_list_iterate_safe(list, p, n) {
if (curr) {
/* Skip all entries before and including %curr. */
if (p == curr)
curr = NULL;
continue;
}
if (p->st != st)
continue;
if (ccw_parse_devid_simple(&devid, p->id)) {
if (id_contains(gdevid, &devid))
goto remove;
} else if (ccwgroup_parse_devid_simple(&groupid, p->id)) {
if (id_overlap(gdevid, &groupid))
goto remove;
}
continue;
remove:
util_list_remove(list, p);
selected_dev_free(p);
}
}
/* Return a newly allocated character string containing the path to the file
* that implements a device attribute of the specified @name or %NULL if
* such an attribute is not allowed. */
static char *ccwgroup_st_get_active_attrib_path(struct subtype *st,
struct device *dev,
const char *name)
{
struct ccwgroup_subtype_data *data = st->data;
char *devpath, *path;
devpath = ccwgroup_get_dev_path_by_devid(data->ccwgroupdrv, dev->devid);
path = misc_asprintf("%s/%s", devpath, name);
free(devpath);
return path;
}
/* Return a newly allocated character string containing the value of the
* specified device attribute. */
static char *ccwgroup_st_get_active_attrib(struct subtype *st,
struct device *dev,
const char *name)
{
struct ccwgroup_subtype_data *data = st->data;
/* Special handling for "online": if device is not grouped, device
* is offline. This is needed to prevent incorrect conflict messages
* in setting_list_check_conflict(). */
if (strcmp(name, ccwgroup_attr_online.name) == 0 &&
!ccwgroup_is_grouped(data->ccwgroupdrv, dev->id))
return misc_strdup("0");
/* Rest handled via default procedure. */
return NULL;
}
/* Check if a CCWGROUP device with the specified ID can be grouped. */
static exit_code_t ccwgroup_can_group(struct ccwgroup_devid *devid)
{
struct ccw_devinfo *devinfo;
unsigned int i;
const char *err = NULL;
char *ccwid;
exit_code_t rc = EXIT_OK;
cio_settle(0);
for (i = 0; i < devid->num && rc == EXIT_OK; i++) {
devinfo = ccw_devinfo_get(&devid->devid[i], 1);
if (!devinfo->exists) {
err = "CCW device %s not found\n";
rc = EXIT_GROUP_NOT_FOUND;
} else if (devinfo->grouped) {
err = "CCW device %s already grouped\n";
rc = EXIT_GROUP_ALREADY;
}
free(devinfo);
if (!err)
continue;
ccwid = ccw_devid_to_str(&devid->devid[i]);
delayed_err(err, ccwid);
free(ccwid);
}
return rc;
}
static exit_code_t rebind_devices(struct ccwgroup_devid *devid, const char *drv)
{
unsigned int i;
char *curr_drv, *ccwid;
exit_code_t rc = EXIT_OK;
for (i = 0; i < devid->num && rc == EXIT_OK; i++) {
/* Determine CCW device driver to which device is bound. */
curr_drv = ccw_get_driver(&devid->devid[i]);
if (curr_drv) {
if (strcmp(curr_drv, drv) == 0)
goto next;
rc = ccw_unbind_device(&devid->devid[i]);
if (rc)
goto next;
} else {
ccwid = ccw_devid_to_str(&devid->devid[i]);
delayed_err("Could not determine driver of CCW "
"device %s\n", ccwid);
free(ccwid);
}
rc = ccw_bind_device(&devid->devid[i], drv);
next:
free(curr_drv);
}
return rc;
}
/* Create a CCWGROUP device with the specified driver @drv and device ID
* @ID. Only fully specified IDs are valid. Return %EXIT_OK on success,
* another exit code on error. */
static exit_code_t ccwgroup_group(const char *drv, const char *ccwdrv,
const char *id)
{
struct ccwgroup_devid devid;
char *path, *group_path, *value, *delim;
exit_code_t rc = EXIT_OK;
cio_settle(0);
if (!ccwgroup_parse_devid_simple(&devid, id)) {
delayed_err("Unrecognized device ID format: %s\n", id);
return EXIT_INVALID_ID;
}
if (devid.num == 1) {
delayed_err("Incomplete CCWGROUP device ID specified\n");
return EXIT_INCOMPLETE_ID;
}
rc = ccwgroup_can_group(&devid);
if (rc)
return rc;
rc = rebind_devices(&devid, ccwdrv);
if (rc)
return rc;
path = path_get_sys_bus_drv(CCWGROUP_BUS, drv);
group_path = misc_asprintf("%s/group", path);
value = ccwgroup_devid_to_str(&devid);
delim = value;
while ((delim = strchr(delim, ':')))
*delim = ',';
rc = misc_write_text_file(group_path, value, err_delayed_print);
if (rc)
rc = EXIT_GROUP_FAILED;
/* Wait until any asynchronous ccwgroup processing has finished. */
cio_settle(1);
free(value);
free(path);
free(group_path);
return rc;
}
static exit_code_t ccwgroup_st_device_define(struct subtype *st,
struct device *dev)
{
struct ccwgroup_subtype_data *data = st->data;
return ccwgroup_group(data->ccwgroupdrv, data->ccwdrv, dev->id);
}
/* Ungroup a CCWGROUP device. */
static exit_code_t ccwgroup_ungroup(const char *drv, const char *id)
{
char *path, *file;
exit_code_t rc = EXIT_OK;
cio_settle(0);
path = ccwgroup_get_dev_path(drv, id);
if (!path)
return EXIT_INVALID_ID;
file = misc_asprintf("%s/ungroup", path);
rc = misc_write_text_file(file, "\n", err_delayed_print);
if (rc)
rc = EXIT_UNGROUP_FAILED;
free(path);
free(file);
return rc;
}
static exit_code_t ccwgroup_st_device_undefine(struct subtype *st,
struct device *dev)
{
struct ccwgroup_subtype_data *data = st->data;
return ccwgroup_ungroup(data->ccwgroupdrv, dev->id);
}
static char *ccwgroup_st_resolve_devnode(struct subtype *st,
struct devnode *devnode)
{
struct ccwgroup_subtype_data *data = st->data;
char *link = NULL, *pattern = NULL, *id = NULL, *curr, dummy;
unsigned int cssid, ssid, devno;
struct ccwgroup_devid devid;
struct devnode *netdev = NULL;
if (devnode->type == BLOCKDEV) {
netdev = iscsi_get_net_devnode(devnode);
if (!netdev)
return NULL;
devnode = netdev;
}
if (devnode->type != NETDEV)
goto out;
link = devnode_readlink(devnode);
if (!link)
goto out;
/* ../../devices/qeth/0.0.f503/net/enccw0.0.f503/ */
curr = strstr(link, "/devices/");
if (!curr)
goto out;
pattern = misc_asprintf("/devices/%s/%%x.%%x.%%x/net%%c",
data->rootdrv);
/* /devices/qeth/0.0.f503/net/enccw0.0.f503/ */
if (sscanf(curr, pattern, &cssid, &ssid, &devno, &dummy) != 4 ||
dummy != '/')
goto out;
id = misc_asprintf("%x.%x.%04x", cssid, ssid, devno);
if (read_full_id(&devid, data->ccwgroupdrv, id)) {
free(id);
id = ccwgroup_devid_to_str(&devid);
}
out:
free(netdev);
free(pattern);
free(link);
return id;
}
static bool ccwgroup_is_id_blacklisted(const char *id)
{
struct ccwgroup_devid devid;
char *ccw_id;
unsigned int i;
bool result = false;
if (!ccwgroup_parse_devid_simple(&devid, id))
return false;
for (i = 0; i < devid.num; i++) {
ccw_id = ccw_devid_to_str(&devid.devid[i]);
result = ccw_is_id_blacklisted(ccw_id);
free(ccw_id);
if (result)
break;
}
return result;
}
static void ccwgroup_unblacklist_id(const char *id)
{
struct ccwgroup_devid devid;
char *ccw_id;
unsigned int i;
if (!ccwgroup_parse_devid_simple(&devid, id))
return;
for (i = 0; i < devid.num; i++) {
ccw_id = ccw_devid_to_str(&devid.devid[i]);
if (ccw_is_id_blacklisted(ccw_id))
ccw_unblacklist_id(ccw_id);
free(ccw_id);
}
}
/* Determine if the specified namespace is compatible with the CCWGROUP
* namespace. */
bool ccwgroup_compatible_namespace(struct namespace *ns)
{
if ((void *) ns->cmp_parsed_ids == (void *) &ccwgroup_cmp_parsed_ids)
return true;
return false;
}
/*
* CCWGROUP device ID namespace.
*/
struct namespace ccwgroup_namespace = {
.devname = DEVNAME,
.is_id_valid = ccwgroup_is_id_valid,
.cmp_ids = ccwgroup_cmp_ids,
.normalize_id = ccwgroup_normalize_id,
.parse_id = ccwgroup_parse_id,
.cmp_parsed_ids = ccwgroup_cmp_parsed_ids,
.qsort_cmp = ccwgroup_qsort_cmp,
.is_id_range_valid = ccwgroup_is_id_range_valid,
.num_ids_in_range = ccwgroup_num_ids_in_range,
.is_id_in_range = ccwgroup_is_id_in_range,
.range_start = ccwgroup_range_start,
.range_next = ccwgroup_range_next,
/* Blacklist handling. */
.is_blacklist_active = ccw_is_blacklist_active,
.is_id_blacklisted = ccwgroup_is_id_blacklisted,
.is_id_range_blacklisted = ccw_is_id_range_blacklisted,
.unblacklist_id = ccwgroup_unblacklist_id,
.unblacklist_id_range = ccw_unblacklist_id_range,
.blacklist_persist = ccw_blacklist_persist,
};
/*
* CCWGROUP device subtype.
*/
/* The methods of this subtype assume that @data points to a
* struct ccwgroup_subtype_data. */
struct subtype ccwgroup_subtype = {
.super = &subtype_base,
.exists_active = &ccwgroup_st_exists_active,
.exists_persistent = &ccwgroup_st_exists_persistent,
.exists_autoconf = &ccwgroup_st_exists_autoconf,
.add_active_ids = &ccwgroup_st_add_active_ids,
.add_persistent_ids = &ccwgroup_st_add_persistent_ids,
.add_autoconf_ids = &ccwgroup_st_add_autoconf_ids,
.read_active = &ccwgroup_st_read_active,
.read_persistent = &ccwgroup_st_read_persistent,
.read_autoconf = &ccwgroup_st_read_autoconf,
.configure_active = &ccwgroup_st_configure_active,
.configure_persistent = &ccwgroup_st_configure_persistent,
.configure_autoconf = &ccwgroup_st_configure_autoconf,
.deconfigure_active = &ccwgroup_st_deconfigure_active,
.deconfigure_persistent = &ccwgroup_st_deconfigure_persistent,
.deconfigure_autoconf = &ccwgroup_st_deconfigure_autoconf,
.online_set = &ccwgroup_st_online_set,
.online_get = &ccwgroup_st_online_get,
.online_specified = &ccwgroup_st_online_specified,
.add_errors = &ccwgroup_st_add_errors,
.add_devnodes = &ccwgroup_st_add_devnodes,
.resolve_devnode = &ccwgroup_st_resolve_devnode,
.rem_combined = &ccwgroup_st_rem_combined,
.get_active_attrib_path = &ccwgroup_st_get_active_attrib_path,
.get_active_attrib = &ccwgroup_st_get_active_attrib,
.device_define = &ccwgroup_st_device_define,
.device_undefine = &ccwgroup_st_device_undefine,
};