/* * 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 #include #include #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 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, };