Initial s390-tools-2.0.0 import

This commit is based on the s390-tools-1.39.0 version.

Changes on top of s390-tools-1.39.0:

 - Add MIT license to all source files
 - Add LICENSE file
 - Transform REAMDE to README.md (markdown)
 - Add AUTHORS.md file
 - Add CONTRIBUTING.md file
 - Move changelog from README to CHANGELOG.md file

Reviewed-by: Stefan Haberland <sth@linux.vnet.ibm.com>
Signed-off-by: Michael Holzheu <holzheu@linux.vnet.ibm.com>
This commit is contained in:
Michael Holzheu
2017-08-07 16:13:17 +02:00
commit b627b8d8e1
647 changed files with 168974 additions and 0 deletions

100
zdev/src/Makefile Normal file
View File

@@ -0,0 +1,100 @@
# Common definitions
include ../../common.mak
ALL_CPPFLAGS += -I ../include -std=gnu99 -Wno-unused-parameter \
-Wno-missing-field-initializers
# Core
chzdev_objects += attrib.o chzdev.o device.o devnode.o devtype.o exit_code.o \
export.o hash.o inuse.o misc.o namespace.o opts.o path.o \
root.o select.o setting.o subtype.o table.o table_attribs.o \
table_types.o net.o
# Devtype Helpers
chzdev_objects += blkinfo.o ccw.o ccwgroup.o findmnt.o modprobe.o module.o \
udev.o udev_ccw.o udev_ccwgroup.o iscsi.o
# DASD devtype
chzdev_objects += dasd.o
# FCP devtype
chzdev_objects += zfcp.o zfcp_host.o
chzdev_objects += scsi.o udev_zfcp_lun.o zfcp_lun.o
# QETH devtype
chzdev_objects += nic.o qeth.o qeth_auto.o
# CTC devtype
chzdev_objects += ctc.o ctc_auto.o
# LCS devtype
chzdev_objects += lcs.o lcs_auto.o
# Generic CCW devtype
chzdev_objects += generic_ccw.o
# Core
lszdev_objects += attrib.o lszdev.o device.o devnode.o devtype.o exit_code.o \
export.o hash.o inuse.o misc.o namespace.o opts.o path.o \
root.o select.o setting.o subtype.o table.o table_types.o \
net.o
# Devtype Helpers
lszdev_objects += blkinfo.o ccw.o ccwgroup.o findmnt.o modprobe.o module.o \
udev.o udev_ccw.o udev_ccwgroup.o iscsi.o
# DASD devtype
lszdev_objects += dasd.o
# FCP devtype
lszdev_objects += zfcp.o zfcp_host.o
lszdev_objects += scsi.o udev_zfcp_lun.o zfcp_lun.o
# QETH devtype
lszdev_objects += nic.o qeth.o qeth_auto.o
# CTC devtype
lszdev_objects += ctc.o ctc_auto.o
# LCS devtype
lszdev_objects += lcs.o lcs_auto.o
# Generic CCW devtype
lszdev_objects += generic_ccw.o
all: chzdev lszdev
chzdev_usage.c: chzdev_usage.txt
$(SED) $< >$@ -e 's/\\/\\\\/g' -e 's/"/\\"/g' -e 's/^/"/g' \
-e 's/$$/\\n"/g'
.chzdev.o.d: chzdev_usage.c
lszdev_usage.c: lszdev_usage.txt
$(SED) $< >$@ -e 's/\\/\\\\/g' -e 's/"/\\"/g' -e 's/^/"/g' \
-e 's/$$/\\n"/g'
.lszdev.o.d: lszdev_usage.c
chzdev.o: chzdev_usage.c
lszdev.o: lszdev_usage.c
libs = $(rootdir)/libutil/libutil.a
chzdev: $(chzdev_objects) $(libs)
lszdev: $(lszdev_objects) $(libs)
install: chzdev
$(INSTALL) -d -m 755 $(DESTDIR)$(BINDIR)
$(INSTALL) -c chzdev $(DESTDIR)$(BINDIR)
$(INSTALL) -c lszdev $(DESTDIR)$(BINDIR)
ifeq ($(HAVE_DRACUT),1)
$(INSTALL) -m 755 zdev-root-update.dracut \
$(DESTDIR)$(TOOLS_LIBDIR)/zdev-root-update
endif
clean:
@rm -f *.o chzdev lszdev chzdev_usage.c lszdev_usage.c gmon.out *.gcda *.gcno
.PHONY: all install clean

281
zdev/src/attrib.c Normal file
View File

@@ -0,0 +1,281 @@
/*
* 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 <ctype.h>
#include <stddef.h>
#include <stdio.h>
#include <string.h>
#include "attrib.h"
#include "misc.h"
/* Parse the string in VAL as a number of notation N. If NUM is non-null
* store the resulting number there and return true. Return false
* in case of parse error. */
static bool parse_num(struct notation *n, const char *val, long long *num)
{
int i;
long long r;
char d;
/* Check for octal number first in case of leading 0. */
for (i = 0; val[i]; i++)
if (isspace(val[i]))
return false;
if (n->oct && (strlen(val) > 1) && (val[0] == '0') && isdigit(val[1])) {
if (sscanf(val, "%llo %c", (unsigned long long *) &r, &d) == 1)
goto ok;
}
if (n->dec) {
if (sscanf(val, "%lld %c", &r, &d) == 1)
goto ok;
}
if (n->hex) {
if (sscanf(val, "%llx %c", (unsigned long long *) &r, &d) == 1)
goto ok;
}
if (n->oct) {
if (sscanf(val, "%llo %c", (unsigned long long *) &r, &d) == 1)
goto ok;
}
return false;
ok:
if (num)
*num = r;
return true;
}
/* Check if the specified value is a number in the acceptable notations
* which is equal to n->val. */
static bool check_num(struct val_number *n, const char *val)
{
long long num;
if (!parse_num(&n->notation, val, &num))
return false;
if (num != n->val)
return false;
return true;
}
/* Check if the specified value is a number in the acceptable notations
* which is greater or equal than n->val. */
static bool check_num_ge(struct val_number *n, const char *val)
{
long long num;
if (!parse_num(&n->notation, val, &num))
return false;
if (num < n->val)
return false;
return true;
}
/* Check if the specified value is a number in the acceptable notations
* which is between n->from and n->to (from and to inclusively). */
static bool check_range(struct val_range *r, const char *val)
{
long long num;
if (!parse_num(&r->notation, val, &num))
return false;
if (num < r->from || num > r->to)
return false;
return true;
}
/* Check if value is acceptable for attribute. */
bool attrib_check_value(struct attrib *attrib, const char *val)
{
struct accept_def *a;
int i;
if (!attrib->accept)
return true;
for (i = 0; attrib->accept[i].type != VAL_NONE; i++) {
a = &attrib->accept[i];
switch (a->type) {
case VAL_NUM:
if (check_num(&a->content.number, val))
return true;
break;
case VAL_NUM_GE:
if (check_num_ge(&a->content.number, val))
return true;
break;
case VAL_RANGE:
if (check_range(&a->content.range, val))
return true;
break;
case VAL_STRING:
if (strcmp(a->content.string, val) == 0)
return true;
break;
case VAL_FUNC:
if (a->content.func(attrib, val))
return true;
break;
default:
break;
}
}
return false;
}
static const char *notation_to_str(struct notation *n)
{
if (n->dec && n->hex && n->oct)
return " in decimal, hexadecimal or octal notation";
if (n->dec && n->hex)
return " in decimal or hexadecimal notation";
if (n->dec && n->oct)
return " in decimal or octal notation";
if (n->hex && n->oct)
return " in hexadecimal or octal notation";
if (n->dec)
return "";
if (n->hex)
return " in hexadecimal notation";
if (n->oct)
return " in octal notation";
return "";
}
#define pr_val(x, ...) do { if ((x) < 0) \
delayed_info(__VA_ARGS__); \
else \
indent((x), __VA_ARGS__); } while (0)
/* List acceptable values for the specified attributes. If @ind is a negative
* number, messages are printed using delayed_info(), otherwise messages
* are indented by the corresponding number of blank characters. */
void attrib_print_acceptable(struct attrib *attrib, int ind)
{
struct accept_def *a;
int i;
if (!attrib->accept) {
pr_val(ind, "All values are accepted\n");
return;
}
for (i = 0; attrib->accept[i].type != VAL_NONE; i++) {
a = &attrib->accept[i];
switch (a->type) {
case VAL_NUM:
pr_val(ind, "- Integer %lld%s\n",
a->content.number.val,
notation_to_str(&a->content.number.notation));
break;
case VAL_NUM_GE:
pr_val(ind, "- Integers greater or equal to %lld%s\n",
a->content.number.val,
notation_to_str(&a->content.number.notation));
break;
case VAL_RANGE:
pr_val(ind, "- Integers in the range %lld - %lld%s\n",
a->content.range.from, a->content.range.to,
notation_to_str(&a->content.range.notation));
break;
case VAL_STRING:
pr_val(ind, "- Text string '%s'\n", a->content.string);
break;
case VAL_FUNC:
pr_val(ind, "- Other value checked dynamically\n");
break;
default:
break;
}
}
}
/* Check if provided value matches default value. */
bool attrib_match_default(struct attrib *attrib, const char *val)
{
size_t l1, l2;
if (!attrib->defval)
return false;
/* Exact match. */
if (strcmp(attrib->defval, val) == 0)
return true;
/* Match with new-line at the end. */
l1 = strlen(attrib->defval);
l2 = strlen(val);
if (l2 == (l1 + 1) && val[l2 - 1] == '\n' &&
strncmp(attrib->defval, val, l1) == 0)
return true;
return false;
}
/* Find an attribute by name in a NULL-terminated array of attributes. */
struct attrib *attrib_find(struct attrib **attribs, const char *name)
{
int i;
for (i = 0; attribs[i]; i++) {
if (strcmp(attribs[i]->name, name) == 0)
return attribs[i];
}
return NULL;
}
/* Return a replacement for @value read from attribute @attrib or NULL if
* there is no replacement. */
const char *attrib_map_value(struct attrib *attrib, const char *value)
{
int i;
if (!attrib->map)
return NULL;
for (i = 0; attrib->map[i].from; i++) {
if (strcmp(value, attrib->map[i].from) == 0)
return attrib->map[i].to;
}
return NULL;
}
/* Determine if attribute name @name starts with attribute name prefix
* @prefix. */
bool attrib_match_prefix(const char *name, const char *prefix)
{
size_t len;
len = strlen(prefix);
if (strncmp(name, prefix, len) == 0 && name[len] == '/')
return true;
return false;
}
/* Return attribute name @name without prefix @prefix. */
const char *attrib_rem_prefix(const char *name, const char *prefix)
{
size_t len;
len = strlen(prefix);
if (strncmp(name, prefix, len) == 0 && name[len] == '/')
name += len + 1;
return name;
}

442
zdev/src/blkinfo.c Normal file
View File

@@ -0,0 +1,442 @@
/*
* 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 "blkinfo.h"
#include "devnode.h"
#include "misc.h"
#define LSBLK_CMDLINE "lsblk -P -o NAME,MAJ:MIN,FSTYPE,UUID,MOUNTPOINT,PKNAME 2>/dev/null"
struct blkinfo {
struct devnode *devnode;
char *fstype;
char *uuid;
char *mountpoint;
char *parent;
};
static struct util_list *cached_blkinfos;
static struct blkinfo *blkinfo_new(const char *name, const char *majmin,
const char *fstype, const char *uuid,
const char *mountpoint, const char *parent)
{
struct blkinfo *blkinfo;
unsigned int major, minor;
blkinfo = misc_malloc(sizeof(struct blkinfo));
if (name && majmin) {
if (sscanf(majmin, "%u:%u", &major, &minor) == 2) {
blkinfo->devnode = devnode_new(BLOCKDEV, major, minor,
name);
}
}
if (fstype && *fstype)
blkinfo->fstype = misc_strdup(fstype);
if (uuid && *uuid)
blkinfo->uuid = misc_strdup(uuid);
if (mountpoint && *mountpoint)
blkinfo->mountpoint = misc_strdup(mountpoint);
if (parent && *parent)
blkinfo->parent = misc_strdup(parent);
return blkinfo;
}
static void blkinfo_free(struct blkinfo *blkinfo)
{
if (!blkinfo)
return;
free(blkinfo->devnode);
free(blkinfo->fstype);
free(blkinfo->uuid);
free(blkinfo->mountpoint);
free(blkinfo->parent);
free(blkinfo);
}
/* Used for debugging. */
void blkinfo_print(struct blkinfo *blkinfo, int level)
{
printf("%*sblkinfo at %p\n", level, "", (void *) blkinfo);
level += 2;
if (blkinfo->devnode)
devnode_print(blkinfo->devnode, level);
if (blkinfo->fstype)
printf("%*sfstype=%s\n", level, "", blkinfo->fstype);
if (blkinfo->uuid)
printf("%*suuid=%s\n", level, "", blkinfo->uuid);
if (blkinfo->mountpoint)
printf("%*smountpoint=%s\n", level, "", blkinfo->mountpoint);
if (blkinfo->parent)
printf("%*sparent=%s\n", level, "", blkinfo->parent);
}
static char *isolate_keyword(char **line_ptr, const char *keyword)
{
char *start, *end;
start = strstr(*line_ptr, keyword);
if (!start)
return NULL;
start += strlen(keyword);
end = start;
while (*end && *end != '"')
end++;
if (*end) {
*end = 0;
*line_ptr = end + 1;
} else
*line_ptr = end;
return start;
}
static struct blkinfo *blkinfo_from_line(char *line)
{
char *name, *majmin, *fstype, *uuid, *mountpoint, *parent;
name = isolate_keyword(&line, "NAME=\"");
majmin = isolate_keyword(&line, "MAJ:MIN=\"");
fstype = isolate_keyword(&line, "FSTYPE=\"");
uuid = isolate_keyword(&line, "UUID=\"");
mountpoint = isolate_keyword(&line, "MOUNTPOINT=\"");
parent = isolate_keyword(&line, "PKNAME=\"");
return blkinfo_new(name, majmin, fstype, uuid, mountpoint, parent);
}
static struct util_list *blkinfos_read(void)
{
char *output, *curr, *next;
struct util_list *blkinfos;
struct blkinfo *blkinfo;
if (cached_blkinfos)
return cached_blkinfos;
output = misc_read_cmd_output(LSBLK_CMDLINE, 0, 1);
if (!output)
return NULL;
blkinfos = ptrlist_new();
/* Iterate over each line. */
next = output;
while ((curr = strsep(&next, "\n"))) {
blkinfo = blkinfo_from_line(curr);
if (blkinfo)
ptrlist_add(blkinfos, blkinfo);
}
free(output);
cached_blkinfos = blkinfos;
return blkinfos;
}
static void blkinfos_free(struct util_list *blkinfos)
{
struct ptrlist_node *p, *n;
if (!blkinfos)
return;
util_list_iterate_safe(blkinfos, p, n) {
util_list_remove(blkinfos, p);
blkinfo_free(p->ptr);
free(p);
}
free(blkinfos);
}
/* Used for debugging. */
void blkinfos_print(struct util_list *blkinfos, int level)
{
struct ptrlist_node *p;
printf("%*sblkinfos at %p\n", level, "", (void *) blkinfos);
if (!blkinfos)
return;
level += 2;
util_list_iterate(blkinfos, p)
blkinfo_print(p->ptr, level);
}
/* Find a blkinfo for the specified devnode or NULL of none was found. */
static struct blkinfo *blkinfo_get_by_devnode(struct devnode *devnode)
{
struct util_list *blkinfos;
struct ptrlist_node *p;
struct blkinfo *b;
blkinfos = blkinfos_read();
if (!blkinfos)
return NULL;
/* Get UUID for the specified devnode. */
util_list_iterate(blkinfos, p) {
b = p->ptr;
if (b->devnode && devnode_cmp(b->devnode, devnode) == 0)
return b;
}
return NULL;
}
/* Find a blkinfo for the specified device name or NULL if none was found. */
static struct blkinfo *blkinfo_get_by_name(const char *name)
{
struct util_list *blkinfos;
struct ptrlist_node *p;
struct blkinfo *b;
blkinfos = blkinfos_read();
if (!blkinfos)
return NULL;
/* Get UUID for the specified devnode. */
util_list_iterate(blkinfos, p) {
b = p->ptr;
if (b->devnode && strcmp(b->devnode->name, name) == 0)
return b;
}
return NULL;
}
/* Find a blkinfo for the specified major+minor or NULL if none was found. */
static struct blkinfo *blkinfo_get_by_majmin(unsigned int major,
unsigned int minor)
{
struct util_list *blkinfos;
struct ptrlist_node *p;
struct blkinfo *b;
blkinfos = blkinfos_read();
if (!blkinfos)
return NULL;
/* Get UUID for the specified devnode. */
util_list_iterate(blkinfos, p) {
b = p->ptr;
if (b->devnode && b->devnode->major == major &&
b->devnode->minor == minor)
return b;
}
return NULL;
}
/* Return a newly allocated ptrlist of devnodes of block devices which are
* ancestors of the specified block device or NULL if no ancestors are found.
* An ancestor is a block device which is the parent of another block device
* according to lsblk data. */
struct util_list *blkinfo_get_ancestor_devnodes(struct devnode *devnode)
{
struct util_list *blkinfos;
struct util_list *todos, *result;
struct util_list *done;
struct devnode *curr, *p_devnode;
struct blkinfo *blkinfo, *parent;
struct ptrlist_node *p_curr, *p;
int num_parents;
blkinfos = blkinfos_read();
if (!blkinfos)
return NULL;
todos = ptrlist_new();
result = ptrlist_new();
done = strlist_new();
/* Repeat the resolution process until only devices with no parent
* remain. */
ptrlist_add(todos, devnode_copy(devnode));
while ((p_curr = util_list_start(todos))) {
util_list_remove(todos, p_curr);
curr = p_curr->ptr;
free(p_curr);
/* Check for parents. */
num_parents = 0;
util_list_iterate(blkinfos, p) {
blkinfo = p->ptr;
if (!blkinfo->devnode || !blkinfo->parent)
continue;
if (devnode_cmp(blkinfo->devnode, curr) != 0)
continue;
parent = blkinfo_get_by_name(blkinfo->parent);
if (parent && parent->devnode)
goto add;
/* Try to resolve name to major:minor. */
p_devnode = devnode_from_devfile(NULL, blkinfo->parent,
BLOCKDEV);
if (!p_devnode)
continue;
parent = blkinfo_get_by_majmin(p_devnode->major,
p_devnode->minor);
free(p_devnode);
if (!parent || !parent->devnode)
continue;
add:
ptrlist_add(todos, devnode_copy(parent->devnode));
num_parents++;
}
if (num_parents == 0 && devnode_cmp(curr, devnode) != 0 &&
!strlist_find(done, curr->name)) {
/* Add device with no parent to results (except for
* the initially specified device). */
ptrlist_add(result, curr);
strlist_add(done, curr->name);
} else
free(curr);
}
ptrlist_free(todos, 0);
strlist_free(done);
/* Check if any ancestors were found. */
if (util_list_is_empty(result)) {
ptrlist_free(result, 0);
return NULL;
}
return result;
}
/* Return a newly allocated ptrlist of devnodes of block devices which have
* the same file system UUID and type or NULL if no such devices are found. */
struct util_list *blkinfo_get_same_uuid_devnodes(struct devnode *devnode)
{
struct util_list *blkinfos;
struct util_list *devnodes;
struct ptrlist_node *p;
struct blkinfo *b;
char *uuid;
blkinfos = blkinfos_read();
if (!blkinfos)
return NULL;
b = blkinfo_get_by_devnode(devnode);
if (!b)
return NULL;
uuid = b->uuid;
if (!uuid)
return NULL;
/* Get all devnodes of devices with the same UUID. */
devnodes = ptrlist_new();
util_list_iterate(blkinfos, p) {
b = p->ptr;
if (!b->devnode || !b->uuid)
continue;
if (strcmp(b->uuid, uuid) != 0)
continue;
if (devnode_cmp(b->devnode, devnode) == 0)
continue;
ptrlist_add(devnodes, devnode_copy(b->devnode));
}
if (util_list_is_empty(devnodes)) {
strlist_free(devnodes);
devnodes = NULL;
}
return devnodes;
}
/* Check mountpoints in blkinfo for an entry that provides path. */
struct devnode *blkinfo_get_devnode_by_path(const char *path)
{
struct util_list *blkinfos;
struct ptrlist_node *p;
struct blkinfo *b, *match = NULL;
size_t blen, mlen = 0;
blkinfos = blkinfos_read();
if (!blkinfos)
return NULL;
util_list_iterate(blkinfos, p) {
b = p->ptr;
if (!b->devnode || !b->mountpoint)
continue;
blen = strlen(b->mountpoint);
/* path: /path/to/file
* mountpoint: /path */
if (strncmp(path, b->mountpoint, blen) != 0)
continue;
if (path[blen] && path[blen] != '/')
continue;
/* Ensure longest match. */
if (!match || blen > mlen) {
match = b;
mlen = blen;
}
}
return match ? devnode_copy(match->devnode) : NULL;
}
/* Add list of mountpoints for mounted file systems to strlist @list. */
void blkinfo_add_mountpoints(struct util_list *list)
{
struct util_list *blkinfos;
struct ptrlist_node *p;
struct blkinfo *b;
blkinfos = blkinfos_read();
if (!blkinfos)
return;
util_list_iterate(blkinfos, p) {
b = p->ptr;
if (!b->devnode || !b->mountpoint || *b->mountpoint != '/')
continue;
strlist_add_unique(list, b->mountpoint);
}
}
/* Add list of newly allocated devnodes for active swap devices to ptrlist
* @list. */
void blkinfo_add_swap_devnodes(struct util_list *list)
{
struct util_list *blkinfos;
struct ptrlist_node *p;
struct blkinfo *b;
blkinfos = blkinfos_read();
if (!blkinfos)
return;
util_list_iterate(blkinfos, p) {
b = p->ptr;
if (!b->devnode || !b->mountpoint ||
strcmp(b->mountpoint, "[SWAP]") != 0)
continue;
ptrlist_add(list, devnode_copy(b->devnode));
}
}
void blkinfo_exit(void)
{
blkinfos_free(cached_blkinfos);
}

1803
zdev/src/ccw.c Normal file

File diff suppressed because it is too large Load Diff

1214
zdev/src/ccwgroup.c Normal file

File diff suppressed because it is too large Load Diff

2980
zdev/src/chzdev.c Normal file

File diff suppressed because it is too large Load Diff

58
zdev/src/chzdev_usage.txt Normal file
View File

@@ -0,0 +1,58 @@
Usage: chzdev TYPE DEVICE [SELECTION] [SETTINGS] [ACTION] [OPTIONS]
Use chzdev to manage the configuration of z Systems specific devices in either:
- active configuration (running system), or
- persistent configuration (configuration files)
Actions apply to both configurations unless specified otherwise.
TYPE
Device type to which this command applies. Use --list-types to display
supported types.
DEVICE
ID Select single device by ID, e.g. 0.0.1234
FROM-TO Select range of devices between FROM and TO
DEV1,DEV2,... Select list of devices or device ranges
SELECTION
--all Select all existing and configured devices
--configured Select devices with a persistent configuration
--existing Select devices found in the active configuration
--online/--offline Select devices that are online/offline
--failed Select devices that are not functioning correctly
--by-path PATH Select device providing file system path, e.g. /usr
--by-node NODE Select device providing device node, e.g. /dev/sda
--by-interface NAME Select device providing network interface, e.g. eth0
--by-attrib KEY=VALUE Select devices with specified attribute value
SETTINGS
Specifications in the form ATTRIB=VALUE used to modify device or device type
settings. Use --list-attributes (also with --type) to list available
attributes.
ACTIONS
-e, --enable Enable device
-d, --disable Disable device
-l, --list-attributes List attributes
-L, --list-types List supported device types
-H, --help-attribute Show detailed help on specified attribute
--export FILENAME Export configuration data to file
--import FILENAME Import configuration data from file
--apply Apply persistent settings to active configuration
-h, --help Print usage information, then exit
-v, --version Print version information, then exit
OPTIONS
-a, --active Apply changes to active configuration only
-p, --persistent Apply changes to persistent configuration only
-t, --type Apply changes to device type
-r, --remove ATTRIB Remove setting for specified attribute
-R, --remove-all Remove settings for all attributes
-f, --force Override safety checks
-y, --yes Answer all confirmation questions with 'yes'
--no-root-update Skip root device update
--dry-run Display changes without applying
--base PATH Use PATH as base for accessing files
-V, --verbose Print additional run-time information
-q, --quiet Print only minimal run-time information

570
zdev/src/ctc.c Normal file
View File

@@ -0,0 +1,570 @@
/*
* 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 "attrib.h"
#include "ccw.h"
#include "ccwgroup.h"
#include "ctc.h"
#include "ctc_auto.h"
#include "device.h"
#include "devtype.h"
#include "misc.h"
#include "namespace.h"
#include "path.h"
#include "setting.h"
#define DEVNAME "CTC device"
/*
* CTC device ID namespace methods.
*/
static exit_code_t ctc_parse_devid(struct ccwgroup_devid *devid_ptr,
const char *id, err_t err)
{
struct ccwgroup_devid devid;
const char *reason = NULL;
exit_code_t rc;
rc = ccwgroup_parse_devid(&devid, id, err);
if (rc)
return rc;
if (devid.num > CTC_NUM_DEVS) {
reason = "Too many CCW device IDs specified";
rc = EXIT_INVALID_ID;
} else if (devid_ptr)
*devid_ptr = devid;
if (reason) {
err_t_print(err, "Error in %s ID format: %s: %s\n", DEVNAME,
reason, id);
}
return rc;
}
static exit_code_t ctc_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 = ctc_parse_devid(&from, from_str, err);
if (rc)
goto out;
rc = ctc_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 ctc_parse_devid_range_simple(struct ccwgroup_devid *from,
struct ccwgroup_devid *to,
const char *range)
{
if (ctc_parse_devid_range(from, to, range, err_ignore) == EXIT_OK)
return true;
return false;
}
static exit_code_t ctc_ns_is_id_valid(const char *id, err_t err)
{
return ctc_parse_devid(NULL, id, err);
}
static char *ctc_ns_normalize_id(const char *id)
{
struct ccwgroup_devid devid;
if (ctc_parse_devid(&devid, id, err_ignore) != EXIT_OK)
return NULL;
return ccwgroup_devid_to_str(&devid);
}
static void *ctc_ns_parse_id(const char *id, err_t err)
{
struct ccwgroup_devid *devid;
devid = misc_malloc(sizeof(struct ccwgroup_devid));
if (ctc_parse_devid(devid, id, err) != EXIT_OK) {
free(devid);
return NULL;
}
return devid;
}
static exit_code_t ctc_ns_is_id_range_valid(const char *range, err_t err)
{
return ctc_parse_devid_range(NULL, NULL, range, err);
}
static unsigned long ctc_ns_num_ids_in_range(const char *range)
{
struct ccwgroup_devid f, t;
if (!ctc_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;
}
static void ctc_ns_range_start(struct ns_range_iterator *it, const char *range)
{
struct ccwgroup_devid from, to;
if (!ctc_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);
}
static bool ctc_ns_is_id_blacklisted(const char *id)
{
struct ccwgroup_devid devid;
char *ccw_id;
unsigned int i;
bool result = false;
if (ctc_parse_devid(&devid, id, err_ignore) != EXIT_OK)
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 ctc_ns_unblacklist_id(const char *id)
{
struct ccwgroup_devid devid;
char *ccw_id;
unsigned int i;
if (ctc_parse_devid(&devid, id, err_ignore) != EXIT_OK)
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);
}
}
/*
* CTC device ID namespace.
*/
struct namespace ctc_namespace = {
.devname = DEVNAME,
.is_id_valid = ctc_ns_is_id_valid,
.is_id_similar = ccwgroup_is_id_similar,
.cmp_ids = ccwgroup_cmp_ids,
.normalize_id = ctc_ns_normalize_id,
.parse_id = ctc_ns_parse_id,
.cmp_parsed_ids = ccwgroup_cmp_parsed_ids,
.qsort_cmp = ccwgroup_qsort_cmp,
.is_id_range_valid = ctc_ns_is_id_range_valid,
.num_ids_in_range = ctc_ns_num_ids_in_range,
.is_id_in_range = ccwgroup_is_id_in_range,
.range_start = ctc_ns_range_start,
.range_next = ccwgroup_range_next,
/* Blacklist handling. */
.is_blacklist_active = ccw_is_blacklist_active,
.is_id_blacklisted = ctc_ns_is_id_blacklisted,
.is_id_range_blacklisted = ccw_is_id_range_blacklisted,
.unblacklist_id = ctc_ns_unblacklist_id,
.unblacklist_id_range = ccw_unblacklist_id_range,
.blacklist_persist = ccw_blacklist_persist,
};
/*
* CTC device attributes.
*/
static struct attrib ctc_attr_buffer = {
.name = "buffer",
.title = "Control maximum buffer size",
.desc =
"Specify the maximum buffer size used for a CTC interface. The value\n"
"must be in the range of <minimum MTU + header size> to\n"
"<maximum MTU + header size> where a header is typically 8 bytes\n"
"long. When this attribute is set, the MTU size of the interface\n"
"is also set accordingly.\n",
.defval = "32768",
.accept = ACCEPT_ARRAY(ACCEPT_RANGE(584, 65535)),
.order_cmp = ccw_online_only_order_cmp,
.check = ccw_online_only_check,
};
static struct attrib ctc_attr_protocol = {
.name = "protocol",
.title = "Specify CTC interface protocol",
.desc =
"Specify the protocol to use for a CTC interface. The correct\n"
"protocol depends on the CTC connection peer:\n"
" 0: Non-z/OS peer such as a z/VM TCP service machine\n"
" 1: Linux peer\n"
" 3: z/OS peer\n"
" 4: MPC connection to VTAM\n",
.defval = "0",
.accept = ACCEPT_ARRAY(ACCEPT_RANGE(0, 1), ACCEPT_RANGE(3, 4)),
.order_cmp = ccw_offline_only_order_cmp,
.check = ccw_offline_only_check,
};
/*
* CTC subtype methods.
*/
/* Check if user attempts to modify an attribute of an online CTC device
* which can only be set while device is offline. */
static exit_code_t check_online_conflict(struct device *dev, config_t config)
{
struct attrib *attribs[] = {
&ctc_attr_protocol,
};
struct setting *online, *s;
unsigned int i;
exit_code_t rc;
/* All is well if:
* 1. We're not configuring the active configuration
* 2. online->actual == 0 or we don't know the actual value
* 3. online->modified && online->value == 0*/
if (!SCOPE_ACTIVE(config) || dev->active.deconfigured)
return EXIT_OK;
online = setting_list_find(dev->active.settings, ccw_attr_online.name);
if (!online)
return EXIT_OK;
if (!online->actual_value || atoi(online->actual_value) == 0)
return EXIT_OK;
if (online->modified && atoi(online->value) == 0)
return EXIT_OK;
rc = EXIT_OK;
for (i = 0; i < ARRAY_SIZE(attribs); i++) {
s = setting_list_find(dev->active.settings, attribs[i]->name);
if (!s || !s->modified)
continue;
delayed_warn("Attribute '%s' can only be changed while device "
"is offline\n", s->name);
rc = EXIT_INVALID_CONFIG;
}
return rc;
}
static exit_code_t ctc_st_check_pre_configure(struct subtype *st,
struct device *dev,
int prereq, config_t config)
{
exit_code_t rc;
rc = check_online_conflict(dev, config);
if (rc)
return rc;
return EXIT_OK;
}
static exit_code_t ctc_st_is_definable(struct subtype *st, const char *id,
err_t err)
{
struct ccwgroup_subtype_data *data = st->data;
struct ccwgroup_devid devid;
exit_code_t rc;
rc = ccwgroup_parse_devid(&devid, id, err);
if (rc)
return rc;
if (subtype_device_exists_active(st, id))
return EXIT_OK;
if (devid.num == data->num_devs)
return ctc_auto_is_possible(&devid, err);
if (devid.num == 1)
return ctc_auto_get_devid(NULL, &devid.devid[0], err);
err_t_print(err, "Invalid number of CCW device IDs\n");
return EXIT_INVALID_ID;
}
/**
* device_detect_definable - Detect configuration of definable device
* @st: Device subtype
* @dev: Device
*
* Detect the full ID and default parameters for non-existing but definable
* device @dev and update active.definable. Return %EXIT_OK on success, or an
* error code otherwise.
*/
static exit_code_t ctc_st_detect_definable(struct subtype *st,
struct device *dev)
{
struct ccwgroup_devid *devid;
exit_code_t rc;
devid = dev->devid;
if (devid->num == 1) {
/* Detect possible group for this device. */
rc = ctc_auto_get_devid(devid, &devid->devid[0],
err_delayed_print);
if (rc) {
error("Auto-detection failed for %s %s\n"
"Please be sure to specify full CCWGROUP ID!\n",
st->devname, dev->id);
return rc;
}
free(dev->id);
dev->id = ccwgroup_devid_to_str(dev->devid);
}
dev->active.definable = 1;
return EXIT_OK;
}
static void ctc_st_add_definable_ids(struct subtype *st, struct util_list *ids)
{
ctc_auto_add_ids(ids);
}
/*
* CTC subtype.
*/
static struct ccwgroup_subtype_data ctc_data = {
.ccwgroupdrv = CTC_CCWGROUPDRV_NAME,
.ccwdrv = CTC_CCWDRV_NAME,
.rootdrv = CTC_ROOTDRV_NAME,
.mod = CTC_MOD_NAME,
.num_devs = CTC_NUM_DEVS,
};
static struct subtype ctc_subtype = {
.super = &ccwgroup_subtype,
.devtype = &ctc_devtype,
.name = "ctc",
.title = "Channel-To-Channel (CTC) and CTC-MPC network "
"devices",
.devname = DEVNAME,
.modules = STRING_ARRAY(CTC_MOD_NAME),
.namespace = &ctc_namespace,
.data = &ctc_data,
.dev_attribs = ATTRIB_ARRAY(
&ccw_attr_online,
&ctc_attr_buffer,
&ctc_attr_protocol,
),
.unknown_dev_attribs = 1,
.support_definable = 1,
.check_pre_configure = &ctc_st_check_pre_configure,
.is_definable = &ctc_st_is_definable,
.detect_definable = &ctc_st_detect_definable,
.add_definable_ids = &ctc_st_add_definable_ids,
};
/*
* CTC devtype methods.
*/
/* Clean up all resources used by devtype object. */
static void ctc_devtype_exit(struct devtype *dt)
{
setting_list_free(dt->active_settings);
setting_list_free(dt->persistent_settings);
}
static exit_code_t ctc_devtype_read_settings(struct devtype *dt,
config_t config)
{
/* No kernel or module parameters exist for the ctc device driver,
* but at least determine module loaded state. */
dt->active_settings = setting_list_new();
dt->persistent_settings = setting_list_new();
if (SCOPE_ACTIVE(config))
dt->active_exists = devtype_is_module_loaded(dt);
return EXIT_OK;
}
static exit_code_t ctc_devtype_write_settings(struct devtype *dt,
config_t config)
{
/* No kernel or module parameters exist for the ctc device driver. */
return EXIT_OK;
}
/*
* CTC devtype.
*/
struct devtype ctc_devtype = {
.name = "ctc",
.title = "", /* Only use subtypes. */
.devname = "CTC",
.subtypes = SUBTYPE_ARRAY(
&ctc_subtype,
),
.type_attribs = ATTRIB_ARRAY(
),
.exit = &ctc_devtype_exit,
.read_settings = &ctc_devtype_read_settings,
.write_settings = &ctc_devtype_write_settings,
};
/*
* Helper functions.
*/
static struct util_list *ctc_list;
static void add_query(struct util_list *list, const char *cmd)
{
char *vmcp, *curr, *next, **argv;
int argc;
struct ccw_devid devid;
/* Query real CTC adapters. */
vmcp = misc_read_cmd_output(cmd, 0, 1);
if (!vmcp)
return;
next = vmcp;
while ((curr = strsep(&next, "\n"))) {
line_split(curr, &argc, &argv);
if (argc >= 2 && ccw_parse_devid_simple(&devid, argv[1]))
ptrlist_add(list, ccw_copy_devid(&devid));
line_free(argc, argv);
}
free(vmcp);
}
/* Perform a CP QUERY CTCA ALL command and return a ptrlist of CCW device IDs
* for all found CTC devices. */
static struct util_list *query_ctc(void)
{
struct util_list *list;
char *cmd;
list = ptrlist_new();
/* Query real CTC adapters. */
cmd = misc_asprintf("%s query ctca all 2>/dev/null", PATH_VMCP);
add_query(list, cmd);
free(cmd);
/* Query virtual CTC adapters. */
cmd = misc_asprintf("%s query virtual ctca 2>/dev/null", PATH_VMCP);
add_query(list, cmd);
free(cmd);
return list;
}
/* Release all allocated resources. */
void ctc_exit(void)
{
ptrlist_free(ctc_list, 1);
}
/* Try to confirm that the specified CCW device ID refers to CTC device.
* Return %true it is a CTC device, %false if it cannot be confirmed. */
bool ctc_confirm(struct ccw_devid *devid)
{
struct ptrlist_node *p;
struct ccw_devid *d;
if (devid->cssid != 0 || devid->ssid != 0 || !is_zvm())
return false;
if (!ctc_list)
ctc_list = query_ctc();
util_list_iterate(ctc_list, p) {
d = p->ptr;
if (ccw_cmp_devids(devid, d) == 0)
return true;
}
return false;
}

322
zdev/src/ctc_auto.c Normal file
View File

@@ -0,0 +1,322 @@
/*
* 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 <string.h>
#include "ccw.h"
#include "ccwgroup.h"
#include "ctc.h"
#include "ctc_auto.h"
#include "device.h"
#include "lcs.h"
#include "module.h"
#include "path.h"
struct cutype {
unsigned int cutype:16;
unsigned int cumodel:8;
};
static struct cutype ctc_cutypes[] = {
{ .cutype = 0x3088, .cumodel = 0x08, },
{ .cutype = 0x3088, .cumodel = 0x1e, },
{ .cutype = 0x3088, .cumodel = 0x1f, },
};
/*
* CTC autodetection
*
* A CTC device must be grouped before it can be used. The following
* rules apply to grouping:
*
* 1. A CTC device can be grouped from 2 CCW devices
* a) Read device
* b) Write device
* 2. All CCW devices must be bound to the CTC CCW device driver. Note that
* due to an overlap in CU-Types, CCW device could also be bound to
* the LCS device driver.
* 3. The subchannel of all CCW devices must be defined with the same CHPID
* 4. None of the CCW devices is part of an existing CCWGROUP device
*/
/* Compare by: 1. CHPID, 2. CUTYPE, 3. DEVTYPE, 4. CCW device ID
* -1 = a < b 1 = a > b 0 = a == b. */
static int info_cmp(void *a, void *b, void *data)
{
struct ptrlist_node *pa = a, *pb = b;
struct ccw_devinfo *ia = pa->ptr, *ib = pb->ptr;
int r;
r = ccw_devinfo_chpids_cmp(ia, ib);
if (r)
return r;
r = ccw_devinfo_cutype_cmp(ia, ib);
if (r)
return r;
r = ccw_devinfo_devtype_cmp(ia, ib);
if (r)
return r;
return ccw_cmp_devids(&ia->devid, &ib->devid);
}
static bool is_compatible(struct ccw_devinfo *a, struct ccw_devinfo *b)
{
if (ccw_devinfo_chpids_cmp(a, b) == 0 &&
ccw_devinfo_cutype_cmp(a, b) == 0 &&
ccw_devinfo_devtype_cmp(a, b) == 0)
return true;
return false;
}
static bool is_ctc(struct ccw_devinfo *info)
{
unsigned int i;
for (i = 0; i < ARRAY_SIZE(ctc_cutypes); i++) {
if (info->cutype == ctc_cutypes[i].cutype &&
info->cumodel == ctc_cutypes[i].cumodel)
return true;
}
return false;
}
/* Add device info for all CTC CCW devices to ptrlist in data. */
static exit_code_t add_cb(const char *path, const char *filename, void *data)
{
struct ccw_devid devid;
struct util_list *infos = data;
struct ccw_devinfo *devinfo;
if (!strchr(filename, '.'))
return EXIT_OK;
if (ccw_parse_devid(&devid, filename, err_ignore) != EXIT_OK)
return EXIT_OK;
devinfo = ccw_devinfo_get(&devid, 0);
if (devinfo->exists && !devinfo->grouped && is_ctc(devinfo))
ptrlist_add(infos, devinfo);
else
free(devinfo);
return EXIT_OK;
}
/* Return a sorted ptrlist of struct ccw_devinfos for all CCW devices
* bound to the ctc or lcs CCW device driver with matching CUTYPE.
* The result must be freed using ptrlist_free(,1); */
static struct util_list *read_sorted_ctc_devinfos(void)
{
struct util_list *infos;
char *path;
/* Get CHPID information for all devices handled by the CTC driver. */
infos = ptrlist_new();
/* Add CCW devices bound to the CTC CCW device driver. */
module_try_load_once(CTC_MOD_NAME, NULL);
path = path_get_sys_bus_drv(CCW_BUS_NAME, CTC_CCWDRV_NAME);
if (dir_exists(path))
path_for_each(path, add_cb, infos);
free(path);
/* Add CCW devices bound to the LCS CCW device driver. */
path = path_get_sys_bus_drv(CCW_BUS_NAME, LCS_CCWDRV_NAME);
if (dir_exists(path))
path_for_each(path, add_cb, infos);
free(path);
/* For each CHPID: Find groups. Add to result list. */
util_list_sort(infos, info_cmp, NULL);
return infos;
}
static void add_ccwgroup_devid(struct util_list *devids, struct ccw_devid *read,
struct ccw_devid *write)
{
struct ccwgroup_devid devid;
devid.devid[0] = *read;
devid.devid[1] = *write;
devid.num = CTC_NUM_DEVS;
ptrlist_add(devids, ccwgroup_copy_devid(&devid));
}
static void add_groupable_devids(struct util_list *devids,
struct util_list *infos)
{
struct ptrlist_node *curr, *next;
struct ccw_devinfo *r, *w;
/* For each CHPID: Find groups. Add to result list. */
curr = util_list_start(infos);
while (curr) {
next = util_list_next(infos, curr);
if (!next)
break;
r = curr->ptr;
w = next->ptr;
if (is_compatible(r, w)) {
add_ccwgroup_devid(devids, &r->devid, &w->devid);
curr = util_list_next(infos, next);
} else
curr = next;
}
}
/* Add CCWGROUP IDs of ctc devices that can be grouped to strlist @ids. */
void ctc_auto_add_ids(struct util_list *ids)
{
struct util_list *infos, *devids;
struct ptrlist_node *p;
char *id;
infos = read_sorted_ctc_devinfos();
devids = ptrlist_new();
add_groupable_devids(devids, infos);
util_list_iterate(devids, p) {
id = ccwgroup_devid_to_str(p->ptr);
strlist_add(ids, id);
free(id);
}
ptrlist_free(devids, 1);
ptrlist_free(infos, 1);
}
exit_code_t ctc_auto_get_devid(struct ccwgroup_devid *devid_ptr,
struct ccw_devid *ccw_devid, err_t err)
{
struct util_list *devids, *infos;
struct ptrlist_node *p, *read, *write;
struct ccw_devinfo *r, *w;
struct ccwgroup_devid *devid;
exit_code_t rc;
infos = read_sorted_ctc_devinfos();
/* Try to find an ID from the canonical auto-generated list. */
devids = ptrlist_new();
add_groupable_devids(devids, infos);
util_list_iterate(devids, p) {
devid = p->ptr;
if (ccw_cmp_devids(ccw_devid, &devid->devid[0]) != 0)
continue;
rc = EXIT_OK;
if (devid_ptr)
*devid_ptr = *devid;
goto out;
}
/* Try to create a CCWGROUP ID with the specified ID as read device. */
/* Get CCW device info for read device. */
util_list_iterate(infos, read) {
r = read->ptr;
if (ccw_cmp_devids(&r->devid, ccw_devid) == 0)
break;
}
if (!read) {
err_t_print(err, "Read CCW device not found\n");
rc = EXIT_GROUP_NOT_FOUND;
goto out;
}
/* Get CCW device ID for write device. */
write = util_list_next(infos, read);
if (!write) {
err_t_print(err, "Write CCW device not found\n");
rc = EXIT_GROUP_NOT_FOUND;
goto out;
}
w = write->ptr;
if (!is_compatible(r, w)) {
err_t_print(err, "No compatible write CCW device found\n");
rc = EXIT_GROUP_INVALID;
goto out;
}
rc = EXIT_OK;
if (devid_ptr) {
devid_ptr->devid[0] = r->devid;
devid_ptr->devid[1] = w->devid;
devid_ptr->num = CTC_NUM_DEVS;
}
out:
ptrlist_free(devids, 1);
ptrlist_free(infos, 1);
return rc;
}
exit_code_t ctc_auto_is_possible(struct ccwgroup_devid *devid, err_t err)
{
struct ccw_devinfo *info[CTC_NUM_DEVS];
unsigned int i;
char *ccwid;
const char *msg;
exit_code_t rc;
if (devid->num < CTC_NUM_DEVS) {
err_t_print(err, "Not enough CCW device IDs in CTC device "
"ID\n");
return EXIT_INCOMPLETE_ID;
}
if (devid->num > CTC_NUM_DEVS) {
err_t_print(err, "CTC device ID contains too many CCW device "
"IDs\n");
return EXIT_INVALID_ID;
}
for (i = 0; i < ARRAY_SIZE(info); i++)
info[i] = ccw_devinfo_get(&devid->devid[i], 1);
rc = EXIT_OK;
msg = NULL;
for (i = 0; i < ARRAY_SIZE(info); i++) {
if (!info[i]->exists) {
msg = "CCW device %s does not exist\n";
rc = EXIT_GROUP_NOT_FOUND;
} else if (info[i]->grouped) {
msg = "CCW device %s is in another group device\n";
rc = EXIT_GROUP_ALREADY;
} else if (i > 0 &&
ccw_devinfo_chpids_cmp(info[i - 1], info[i]) != 0) {
msg = "CCW device %s is not on the same CHPID\n";
rc = EXIT_GROUP_INVALID;
} else if (i > 0 &&
ccw_devinfo_cutype_cmp(info[i - 1], info[i]) != 0) {
msg = "CUTYPE of CCW device %s differs\n";
rc = EXIT_GROUP_INVALID;
} else if (i > 0 &&
ccw_devinfo_devtype_cmp(info[i - 1], info[i]) != 0) {
msg = "DEVTYPE of CCW device %s differs\n";
rc = EXIT_GROUP_INVALID;
}
if (!msg)
continue;
ccwid = ccw_devid_to_str(&devid->devid[i]);
err_t_print(err, msg, ccwid);
free(ccwid);
break;
}
for (i = 0; i < ARRAY_SIZE(info); i++)
free(info[i]);
return rc;
}

687
zdev/src/dasd.c Normal file
View File

@@ -0,0 +1,687 @@
/*
* 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 "attrib.h"
#include "ccw.h"
#include "dasd.h"
#include "device.h"
#include "devtype.h"
#include "misc.h"
#include "modprobe.h"
#include "module.h"
#include "path.h"
#include "setting.h"
#include "udev.h"
#define DASD_MOD_NAME "dasd_mod"
#define DASD_ECKD_MOD_NAME "dasd_eckd_mod"
#define DASD_FBA_MOD_NAME "dasd_fba_mod"
#define DASD_DIAG_MOD_NAME "dasd_diag_mod"
/* Subtype specific data. */
struct dasd_subtype_data {
const char *ccwdrv;
const char *module;
};
/* List of dependent modules. */
static const char *dasd_mods[] = {
DASD_ECKD_MOD_NAME,
DASD_FBA_MOD_NAME,
DASD_DIAG_MOD_NAME,
NULL,
};
/*
* DASD type attributes.
*/
static struct attrib dasd_tattr_autodetect = {
.name = "autodetect",
.title = "Enable automatic DASD activation",
.desc =
"Control whether the DASD driver automatically enables all detected\n"
"DASDs:\n"
" 0: Only configured DASDs are enabled\n"
" 1: All detected DASDs are automatically enabled\n",
.defval = "0",
.activerem = 1,
.defunset = 1,
.accept = ACCEPT_ARRAY(ACCEPT_RANGE(0, 1)),
};
static struct attrib dasd_tattr_probeonly = {
.name = "probeonly",
.title = "Inhibit access to DASD device nodes",
.desc =
"Control whether the DASD driver allows access to DASD devices:\n"
" 0: All enabled DASDs can be accessed normally\n"
" 1: Reject any attempt to open DASD devices\n",
.defval = "0",
.activerem = 1,
.defunset = 1,
.accept = ACCEPT_ARRAY(ACCEPT_RANGE(0, 1)),
};
static struct attrib dasd_tattr_nopav = {
.name = "nopav",
.title = "Deactivate the Parallel Access Volume feature",
.desc = "Control the use of the Parallel Access Volume (PAV) feature:\n"
" 0: PAV is used when supported by the storage system\n"
" 1: The use of PAV is suppressed when running in LPAR\n",
.defval = "0",
.activerem = 1,
.defunset = 1,
.accept = ACCEPT_ARRAY(ACCEPT_RANGE(0, 1)),
};
static struct attrib dasd_tattr_nofcx = {
.name = "nofcx",
.title = "Deactivate the High Performance FICON feature",
.desc = "Control the use of the High Performance FICON (HPF) feature:\n"
" 0: HPF is used when supported by the storage hardware\n"
" 1: HPF is not used\n",
.defval = "0",
.activerem = 1,
.defunset = 1,
.accept = ACCEPT_ARRAY(ACCEPT_RANGE(0, 1)),
};
static struct attrib dasd_tattr_eer_pages = {
.name = "eer_pages",
.title = "Modify buffer size for error records",
.desc =
"Control the number of 4 KB pages that are used for internal "
"buffering\n"
"of error records generated by the Extended Error Reporting (EER)\n"
"feature.\n",
.defval = "5",
.activerem = 1,
.nounload = 1,
.accept = ACCEPT_ARRAY(ACCEPT_NUM_GE(1)),
};
/*
* DASD device attributes.
*/
static struct attrib dasd_attr_failfast = {
.name = "failfast",
.title = "Modify error recovery in no-path scenario",
.desc =
"Control I/O handling when all paths to a DASD have been lost:\n"
" 0: Queue I/O until path becomes available\n"
" 1: Fail I/O request immediately\n",
.defval = "0",
.accept = ACCEPT_ARRAY(ACCEPT_RANGE(0, 1)),
};
static struct attrib dasd_attr_readonly = {
.name = "readonly",
.title = "Inhibit write access to DASD",
.desc = "Control the DASD driver read-only setting for a DASD:\n"
" 0: Allow writing to the device\n"
" 1: Deny writing to the device\n",
.defval = "0",
.accept = ACCEPT_ARRAY(ACCEPT_RANGE(0, 1)),
};
static struct attrib dasd_attr_erplog = {
.name = "erplog",
.title = "Enable logging of Error Recovery Processing",
.desc = "Control logging of Error Recovery Processing (ERP), such as\n"
"failing channel programs:\n"
" 0: ERP logging is disabled\n"
" 1: ERP logging is enabled\n",
.defval = "0",
.accept = ACCEPT_ARRAY(ACCEPT_RANGE(0, 1)),
};
static int dasd_raw_diag_order_cmp(struct setting *, struct setting *);
static bool dasd_raw_diag_check(struct setting *, struct setting *, config_t);
static struct attrib dasd_attr_use_diag = {
.name = "use_diag",
.title = "Activate z/VM hypervisor assisted I/O processing",
.desc =
"Control I/O access mode for a DASD:\n"
" 0: I/O is performed using standard channel programs\n"
" 1: I/O is performed using the z/VM DIAGNOSE X'250' interface\n"
"\n"
"The DIAGNOSE X'250' access mode works only when:\n"
" - Running Linux as z/VM guest\n"
" - Using devices formatted with consistent block sizes, such as\n"
" ECKD DASDs with LDL or CMS format, or FBA devices\n",
.order_cmp = dasd_raw_diag_order_cmp,
.check = dasd_raw_diag_check,
.defval = "0",
.accept = ACCEPT_ARRAY(ACCEPT_RANGE(0, 1)),
};
static struct attrib dasd_attr_raw_track_access = {
.name = "raw_track_access",
.title = "Enable access to ECKD track metadata",
.desc =
"Control whether the DASD driver provides access to full ECKD tracks,\n"
"including ECKD-specific meta-data:\n"
" 0: Only access the data portion of ECKD tracks\n"
" 1: Access the full track\n",
.order_cmp = dasd_raw_diag_order_cmp,
.check = dasd_raw_diag_check,
.defval = "0",
.accept = ACCEPT_ARRAY(ACCEPT_RANGE(0, 1)),
};
/* Ensure ordering if both use_diag and raw_track_access are modified. */
static int dasd_raw_diag_order_cmp(struct setting *a, struct setting *b)
{
int a_val, b_val;
if ((a->attrib == &dasd_attr_use_diag &&
b->attrib == &dasd_attr_raw_track_access) ||
(a->attrib == &dasd_attr_raw_track_access &&
b->attrib == &dasd_attr_use_diag)) {
/* Define order to ensure that =0 is done before =1. */
a_val = atoi(a->value);
b_val = atoi(b->value);
if (a_val == 1 && b_val == 0)
return 1;
if (a_val == 0 && b_val == 1)
return -1;
}
return ccw_offline_only_order_cmp(a, b);
}
/* Conflict if both use_diag and raw_track_access are set. */
static bool dasd_raw_diag_check(struct setting *a, struct setting *b,
config_t config)
{
if ((a->attrib == &dasd_attr_use_diag &&
b->attrib == &dasd_attr_raw_track_access) ||
(a->attrib == &dasd_attr_raw_track_access &&
b->attrib == &dasd_attr_use_diag)) {
if (atoi(a->value) == 1 && atoi(b->value) == 1)
return false;
}
return ccw_offline_only_check(a, b, config);
}
static struct attrib dasd_attr_eer_enabled = {
.name = "eer_enabled",
.title = "Enable Extended Error Reporting",
.desc =
"Control the Extended Error Reporting (EER) feature for a DASD:\n"
" 0: EER is disabled\n"
" 1: EER is enabled\n",
.order_cmp = ccw_online_only_order_cmp,
.check = ccw_online_only_check,
.defval = "0",
.accept = ACCEPT_ARRAY(ACCEPT_RANGE(0, 1)),
};
static struct attrib dasd_attr_expires = {
.name = "expires",
.title = "Modify I/O operation timeout",
.desc =
"Specify the time in seconds that the DASD driver waits for the\n"
"completion of a single I/O operation before considering that I/O\n"
"operation to have failed.\n"
"\n"
"The default value depends on the DASD type.\n",
.order_cmp = ccw_online_only_order_cmp,
.check = ccw_online_only_check,
.accept = ACCEPT_ARRAY(ACCEPT_RANGE(1, 40000000)),
};
static struct attrib dasd_attr_retries = {
.name = "retries",
.title = "Modify I/O operation retry counter",
.desc =
"Specify the number of times that a failed I/O should be retried\n"
"before reporting it as failed.\n"
"\n"
"The default value is dependent on the DASD type.\n",
.order_cmp = ccw_online_only_order_cmp,
.check = ccw_online_only_check,
.accept = ACCEPT_ARRAY(ACCEPT_RANGE(0, 32768)),
};
static struct attrib dasd_attr_timeout = {
.name = "timeout",
.title = "Modify I/O request timeout",
.desc =
"Specify the total time in seconds that the Linux block device layer\n"
"waits for the completion of an I/O request issued to the DASD driver\n"
"before considering that I/O to have failed. Specify 0 to deactivate\n"
"timeout handling.\n",
.order_cmp = ccw_online_only_order_cmp,
.check = ccw_online_only_check,
.defval = "0",
.accept = ACCEPT_ARRAY(ACCEPT_NUM_GE(0)),
};
static struct attrib dasd_attr_reservation_policy = {
.name = "reservation_policy",
.title = "Modify lost device reservation behavior",
.desc =
"Control the DASD driver behavior if an existing DASD reservation of\n"
"this system for a DASD is lost:\n"
" ignore: I/O operations are blocked until the external reservation\n"
" is released (default)\n"
" fail: All I/O operations are considered to have failed\n",
.defval = "ignore",
.accept = ACCEPT_ARRAY(ACCEPT_STR("ignore"), ACCEPT_STR("fail")),
};
static struct attrib dasd_attr_last_known_reservation_state = {
.name = "last_known_reservation_state",
.title = "Display and reset driver device reservation view",
.desc =
"Display the DASD driver's view of device reservations held by this\n"
"system:\n"
" none: No reservation held or no information available\n"
" reserved: Device is assumed to be reserved by this system\n"
" lost: A reservation held by this system was lost\n"
"\n"
"To reset the reservation state from 'lost' to 'none', set this\n"
"attribute to 'reset'.\n",
.unstable = 1,
.accept = ACCEPT_ARRAY(ACCEPT_RANGE(0, 1)),
};
static struct attrib dasd_attr_safe_offline = {
.name = "safe_offline",
.title = "Deactivate DASD after processing pending I/Os",
.desc = "Write an arbitrary value to this attribute to attempt to set\n"
"the DASD offline after all outstanding I/O requests have\n"
"been processed.\n",
.activeonly = 1,
.writeonly = 1,
};
/*
* DASD subtype methods.
*/
/* Check if use_diag setting can be correctly applied. */
static exit_code_t check_use_diag(struct device *dev, config_t config)
{
struct setting *u;
int zvm = is_zvm();
if (SCOPE_ACTIVE(config)) {
u = setting_list_find(dev->active.settings,
dasd_attr_use_diag.name);
if (u && u->modified) {
if (u->value && atoi(u->value) == 1 && !zvm) {
delayed_warn("Cannot set 'use_diag=1' on "
"non-z/VM system\n");
return EXIT_INVALID_CONFIG;
}
}
}
if (SCOPE_PERSISTENT(config)) {
u = setting_list_find(dev->persistent.settings,
dasd_attr_use_diag.name);
if (u && u->modified) {
if (u->value && atoi(u->value) == 1 && !zvm) {
delayed_warn("Cannot set 'use_diag=1' on "
"non-z/VM system\n");
return EXIT_INVALID_CONFIG;
}
}
}
return EXIT_OK;
}
static exit_code_t dasd_st_check_pre_configure(struct subtype *st,
struct device *dev,
int prereq, config_t config)
{
exit_code_t rc;
if (dev->active.deconfigured)
return EXIT_OK;
rc = check_use_diag(dev, config);
if (rc)
return rc;
return EXIT_OK;
}
static void dasd_st_add_modules(struct subtype *st, struct device *dev,
struct util_list *modules)
{
int changed, aset, pset;
/* Add main module. */
st->super->add_modules(st, dev, modules);
/* Add dasd_diag_mod if use_diag is set. */
setting_list_get_bool_state(dev->active.settings,
dasd_attr_use_diag.name, &changed, &aset);
setting_list_get_bool_state(dev->persistent.settings,
dasd_attr_use_diag.name, &changed, &pset);
if (aset || pset)
strlist_add_unique(modules, DASD_DIAG_MOD_NAME);
}
/*
* DASD methods.
*/
/* Clean up all resources used by devtype object. */
static void dasd_devtype_exit(struct devtype *dt)
{
setting_list_free(dt->active_settings);
setting_list_free(dt->persistent_settings);
}
/* Split a dasd= module parameter string into a setting_list, based on
* known attributes encoded in dasd= parameters. */
static void split_dasd(struct devtype *dt, const char *str,
struct setting_list *list)
{
char *copy, *curr, *next, *dasd_str;
struct attrib *a;
struct util_list *dasd;
dasd = strlist_new();
copy = misc_strdup(str);
next = copy;
while ((curr = strsep(&next, ","))) {
if (strcmp(curr, "(null)") == 0)
continue;
if (*curr == 0) {
/* Handle dasd="" */
continue;
}
/* Check for known attributes. */
a = attrib_find(dt->type_attribs, curr);
if (a) {
/* Currently only boolean attributes implemented. */
setting_list_apply_actual(list, a, curr, "1");
continue;
}
/* An unknown setting or device spec - leave in dasd=. */
strlist_add(dasd, "%s", curr);
}
free(copy);
/* Add dasd= setting if necessary. */
if (!util_list_is_empty(dasd)) {
dasd_str = strlist_flatten(dasd, ",");
setting_list_apply_actual(list, NULL, "dasd", dasd_str);
free(dasd_str);
}
strlist_free(dasd);
}
/* Convert a module parameter list into a devtype settings list. */
static void convert_params_to_settings(struct devtype *dt,
struct setting_list *from,
struct setting_list *to)
{
struct setting *s;
util_list_iterate(&from->list, s) {
if (strcmp(s->name, "dasd") == 0) {
/* Special handling: split known attribs out. */
split_dasd(dt, s->value, to);
continue;
}
/* Just copy remaining attributes. */
setting_list_apply_actual(to, s->attrib, s->name, s->value);
}
}
/* Convert a devtype settings list to a module parameter list. */
static void convert_settings_to_params(struct devtype *dt,
struct setting_list *from,
struct setting_list **to)
{
struct setting *s_from, *s_to;
struct util_list *dasd;
int dasd_mod;
char *flat;
*to = setting_list_new();
dasd = strlist_new();
dasd_mod = 0;
util_list_iterate(&from->list, s_from) {
if (s_from->removed)
continue;
if (s_from->derived && !s_from->modified)
continue;
if (strcmp(s_from->name, "eer_pages") == 0) {
/* Copy stand-alone parameter. */
setting_list_add(*to, setting_copy(s_from));
continue;
}
/* All other settings need to go into dasd=. */
if (strcmp(s_from->name, "dasd") == 0) {
/* Just add explicit dasd= content. */
strlist_add(dasd, "%s", s_from->value);
} else if (strcmp(s_from->value, "0") == 0) {
/* A flag in dasd= counts as set, so skip the unset
* ones. */
continue;
} else {
/* Just add the name for set attributes. */
strlist_add(dasd, "%s", s_from->name);
dasd_mod += s_from->modified;
}
}
flat = strlist_flatten(dasd, ",");
s_to = setting_new(NULL, "dasd", flat);
free(flat);
if (dasd_mod > 0)
s_to->modified = 1;
if (util_list_is_empty(dasd))
s_to->removed = 1;
setting_list_add(*to, s_to);
strlist_free(dasd);
}
static exit_code_t dasd_devtype_read_settings(struct devtype *dt,
config_t config)
{
struct setting_list *list;
char *path;
exit_code_t rc = EXIT_OK;
if (SCOPE_ACTIVE(config) && !dt->active_settings) {
dt->active_exists = 0;
rc = module_get_params(DASD_MOD_NAME, dt->type_attribs, &list);
if (rc)
return rc;
dt->active_settings = setting_list_new();
if (list) {
convert_params_to_settings(dt, list,
dt->active_settings);
setting_list_mark_default_derived(dt->active_settings);
setting_list_apply_defaults(dt->active_settings,
dt->type_attribs, false);
setting_list_free(list);
dt->active_exists = 1;
}
}
if (SCOPE_PERSISTENT(config) && !dt->persistent_settings) {
dt->persistent_exists = 0;
path = path_get_modprobe_conf(dt);
rc = modprobe_read_settings(path, DASD_MOD_NAME,
dt->type_attribs, &list);
free(path);
if (rc)
return rc;
dt->persistent_settings = setting_list_new();
if (list) {
convert_params_to_settings(dt, list,
dt->persistent_settings);
setting_list_apply_defaults(dt->persistent_settings,
dt->type_attribs, false);
setting_list_free(list);
dt->persistent_exists = 1;
}
}
return rc;
}
static exit_code_t dasd_devtype_write_settings(struct devtype *dt,
config_t config)
{
struct setting_list *list;
char *path;
exit_code_t rc = EXIT_OK;
if (SCOPE_ACTIVE(config) && dt->active_settings) {
/* Try setting parameters directly via Sysfs. */
if (module_set_params(DASD_MOD_NAME, dt->active_settings))
goto persistent;
convert_settings_to_params(dt, dt->active_settings, &list);
rc = module_load(DASD_MOD_NAME, dasd_mods, list,
err_delayed_print);
setting_list_free(list);
if (rc)
return rc;
}
persistent:
if (SCOPE_PERSISTENT(config) && dt->persistent_settings) {
path = path_get_modprobe_conf(dt);
if (!rc) {
convert_settings_to_params(dt, dt->persistent_settings,
&list);
rc = modprobe_write_settings(path, DASD_MOD_NAME, list);
setting_list_free(list);
}
free(path);
}
return rc;
}
/*
* DASD device sub-types.
*/
static struct ccw_subtype_data dasd_eckd_data = {
.ccwdrv = "dasd-eckd",
.mod = "dasd_eckd_mod",
};
static struct subtype dasd_subtype_eckd = {
.super = &ccw_subtype,
.devtype = &dasd_devtype,
.name = "dasd-eckd",
.title = "Enhanced Count Key Data (ECKD) DASDs",
.devname = "ECKD DASD",
.modules = STRING_ARRAY(DASD_ECKD_MOD_NAME),
.namespace = &ccw_namespace,
.data = &dasd_eckd_data,
.dev_attribs = ATTRIB_ARRAY(
&ccw_attr_online_force,
&ccw_attr_cmb_enable,
&dasd_attr_failfast,
&dasd_attr_readonly,
&dasd_attr_erplog,
&dasd_attr_use_diag,
&dasd_attr_raw_track_access,
&dasd_attr_eer_enabled,
&dasd_attr_expires,
&dasd_attr_retries,
&dasd_attr_timeout,
&dasd_attr_reservation_policy,
&dasd_attr_last_known_reservation_state,
&dasd_attr_safe_offline,
),
.unknown_dev_attribs = 1,
.check_pre_configure = &dasd_st_check_pre_configure,
.add_modules = &dasd_st_add_modules,
};
static struct ccw_subtype_data dasd_fba_data = {
.ccwdrv = "dasd-fba",
.mod = "dasd_fba_mod",
};
static struct subtype dasd_subtype_fba = {
.super = &ccw_subtype,
.devtype = &dasd_devtype,
.name = "dasd-fba",
.title = "Fixed Block Architecture (FBA) DASDs",
.devname = "FBA DASD",
.modules = STRING_ARRAY(DASD_FBA_MOD_NAME),
.namespace = &ccw_namespace,
.data = &dasd_fba_data,
.dev_attribs = ATTRIB_ARRAY(
&ccw_attr_online_force,
&ccw_attr_cmb_enable,
&dasd_attr_failfast,
&dasd_attr_readonly,
&dasd_attr_erplog,
&dasd_attr_use_diag,
&dasd_attr_expires,
&dasd_attr_retries,
&dasd_attr_timeout,
&dasd_attr_reservation_policy,
&dasd_attr_last_known_reservation_state,
&dasd_attr_safe_offline,
),
.unknown_dev_attribs = 1,
.check_pre_configure = &dasd_st_check_pre_configure,
.add_modules = &dasd_st_add_modules,
};
/*
* DASD device type.
*/
struct devtype dasd_devtype = {
.name = "dasd",
.title = "FICON-attached Direct Access Storage Devices "
"(DASDs)",
.devname = "DASD",
.modules = STRING_ARRAY(DASD_MOD_NAME),
.subtypes = SUBTYPE_ARRAY(
&dasd_subtype_eckd,
&dasd_subtype_fba,
),
.type_attribs = ATTRIB_ARRAY(
&dasd_tattr_autodetect,
&dasd_tattr_probeonly,
&dasd_tattr_nopav,
&dasd_tattr_nofcx,
&dasd_tattr_eer_pages,
),
.exit = &dasd_devtype_exit,
.read_settings = &dasd_devtype_read_settings,
.write_settings = &dasd_devtype_write_settings,
};

569
zdev/src/device.c Normal file
View File

@@ -0,0 +1,569 @@
/*
* 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 <stdarg.h>
#include <stdlib.h>
#include <string.h>
#include "attrib.h"
#include "device.h"
#include "devtype.h"
#include "misc.h"
#include "namespace.h"
#include "setting.h"
#include "subtype.h"
#include "udev.h"
/* Create and initialize a new device. */
struct device *device_new(struct subtype *st, const char *id)
{
struct device *dev;
struct namespace *ns = st->namespace;
dev = misc_malloc(sizeof(struct device));
dev->subtype = st;
dev->id = ns->normalize_id(id);
dev->devid = ns->parse_id(id, 0);
if (!dev->id || !dev->devid) {
device_free(dev);
return NULL;
}
dev->active.settings = setting_list_new();
dev->persistent.settings = setting_list_new();
return dev;
}
/* Release all resources associated with the specified device. */
void device_free(struct device *dev)
{
if (!dev)
return;
free(dev->id);
free(dev->devid);
setting_list_free(dev->active.settings);
setting_list_free(dev->persistent.settings);
free(dev);
}
/* Used for debugging. */
void device_print(struct device *dev, int level)
{
printf("%*sdevice at %p:\n", level, "", (void *) dev);
if (!dev)
return;
printf("%*stype=%s id=%s devid=%p proc=%d\n", level + 4, "",
dev->subtype->name, dev->id, dev->devid, dev->processed);
printf("%*sactive:\n", level + 4, "");
printf("%*sexists=%d mod=%d deconf=%d def=%d blacklisted=%d\n",
level + 8, "", dev->active.exists, dev->active.modified,
dev->active.deconfigured, dev->active.definable,
dev->active.blacklisted);
if (dev->active.settings)
setting_list_print(dev->active.settings, level + 8);
else
printf("%*s<none>\n", level + 8, "");
printf("%*spersistent:\n", level + 4, "");
printf("%*sexists=%d mod=%d deconf=%d\n",
level + 8, "", dev->persistent.exists, dev->persistent.modified,
dev->persistent.deconfigured);
if (dev->persistent.settings)
setting_list_print(dev->persistent.settings, level + 8);
else
printf("%*s<none>\n", level + 8, "");
}
static const void *device_hash_get_id(void *dev_ptr)
{
struct device *dev = dev_ptr;
return dev->devid;
}
/* Create and initialize a new device_list. */
struct device_list *device_list_new(struct subtype *st)
{
struct device_list *list;
struct namespace *ns = st->namespace;
list = misc_malloc(sizeof(struct device_list));
hash_init(&list->hash, ns->hash_buckets, device_hash_get_id,
ns->cmp_parsed_ids, ns->hash_parsed_id,
struct device, node);
return list;
}
/* Release resources used by list and enlisted devices. */
void device_list_free(struct device_list *list)
{
if (!list)
return;
hash_clear(&list->hash, (void (*)(void *)) device_free);
free(list);
}
/* Add a new element to a list and mark the list as modified. */
void device_list_add(struct device_list *list, struct device *device)
{
hash_add(&list->hash, device);
list->modified = 1;
}
/* Find an element in the list. */
struct device *device_list_find(struct device_list *list, const char *id,
struct device *start)
{
struct device *dev;
struct namespace *ns;
void *devid;
if (!list)
return NULL;
dev = start ? start : util_list_start(&list->hash.list);
if (!dev)
return NULL;
ns = dev->subtype->namespace;
devid = ns->parse_id(id, err_ignore);
if (!devid)
goto out;
/* Try to find using hashed ID data. */
if (!start && list->hash.get_hash) {
dev = hash_find_by_id(&list->hash, devid);
goto out;
}
/* Find the slow way. */
while (dev) {
if (ns->cmp_parsed_ids(dev->devid, devid) == 0)
goto out;
dev = util_list_next(&list->hash.list, dev);
}
out:
free(devid);
return dev;
}
/* Used for debugging. */
void device_list_print(struct device_list *list, int level)
{
struct device *dev;
printf("%*sdevice list at %p:\n", level, "", (void *) list);
util_list_iterate(&list->hash.list, dev)
device_print(dev, level + 4);
}
/* Check if a device configuration needs to be written. */
bool device_needs_writing(struct device *dev, config_t config)
{
if (SCOPE_ACTIVE(config) &&
(dev->active.modified || dev->active.deconfigured ||
setting_list_modified(dev->active.settings)))
return true;
if (SCOPE_PERSISTENT(config) &&
(dev->persistent.modified || dev->persistent.deconfigured ||
setting_list_modified(dev->persistent.settings)))
return true;
return false;
}
static exit_code_t apply_setting(struct device *dev, config_t config,
const char *key, const char *value,
struct util_list *processed)
{
struct subtype *st = dev->subtype;
struct attrib *a;
struct setting *s;
bool warn_readonly = false;
/* Check for known attribute. */
a = subtype_find_dev_attrib(st, key);
if (a) {
/* Check for acceptable value of known attribute. */
if (!force && !attrib_check_value(a, value))
goto err_invalid_forceable;
/* Check for activeonly. */
if (!force && SCOPE_PERSISTENT(config) && a->activeonly)
goto err_activeonly_forceable;
/* Check for multiple values. */
if (!force && !a->multi && strlist_find(processed, key))
goto err_multi_forceable;
} else {
/* Handle unknown attribute. */
if (!st->unknown_dev_attribs)
goto err_unknown;
if (!force)
goto err_unknown_forceable;
}
strlist_add(processed, "%s", key);
/* Apply to active config. */
if (SCOPE_ACTIVE(config)) {
s = setting_list_apply_specified(dev->active.settings, a,
key, value);
if (s->readonly)
warn_readonly = true;
}
/* Apply to persistent config. */
if (SCOPE_PERSISTENT(config)) {
setting_list_apply_specified(dev->persistent.settings,
a, key, value);
}
/* Additional warning when trying to persist read-only setting. */
if (config == config_persistent) {
s = setting_list_find(dev->active.settings, key);
if (s && s->readonly)
warn_readonly = true;
}
if (warn_readonly)
delayed_warn("Modifying read-only attribute: %s\n", key);
return EXIT_OK;
err_invalid_forceable:
delayed_forceable("Invalid value for %s attribute: %s=%s\n",
dev->subtype->name, key, value);
delayed_info("Acceptable values:\n");
attrib_print_acceptable(a, -1);
delayed_info("Use '%s %s --help-attribute %s' for more "
"information\n", toolname, dev->subtype->name, key);
return EXIT_INVALID_SETTING;
err_multi_forceable:
delayed_forceable("Cannot specify multiple values for attribute '%s'\n",
key);
return EXIT_INVALID_SETTING;
err_unknown:
delayed_err("Unknown %s attribute specified: %s\n", st->devname, key);
return EXIT_ATTRIB_NOT_FOUND;
err_unknown_forceable:
delayed_forceable("Unknown %s attribute specified: %s\n",
st->devname, key);
return EXIT_ATTRIB_NOT_FOUND;
err_activeonly_forceable:
delayed_forceable("Attribute '%s' should only be changed in the active "
"config\n", a->name);
return EXIT_INVALID_SETTING;
}
/* Apply device settings from strlist to device. */
exit_code_t device_apply_strlist(struct device *dev, config_t config,
struct util_list *settings)
{
struct util_list *processed;
struct strlist_node *s;
exit_code_t rc;
char *key, *value;
/* Apply settings. */
processed = strlist_new();
rc = EXIT_OK;
util_list_iterate(settings, s) {
key = misc_strdup(s->str);
value = strchr(key, '=');
*value = 0;
value++;
rc = apply_setting(dev, config, key, value, processed);
free(key);
if (rc)
break;
}
strlist_free(processed);
return rc;
}
/* Apply device settings from setting_list to device. */
exit_code_t device_apply_settings(struct device *dev, config_t config,
struct util_list *settings)
{
struct util_list *processed;
struct setting *s;
exit_code_t rc;
/* Apply settings. */
processed = strlist_new();
rc = EXIT_OK;
util_list_iterate(settings, s) {
rc = apply_setting(dev, config, s->name, s->value, processed);
if (rc)
break;
}
strlist_free(processed);
return rc;
}
static void reset_device_state(struct device_state *state)
{
setting_list_free(state->settings);
state->settings = setting_list_new();
state->exists = 0;
state->modified = 0;
state->deconfigured = 0;
state->definable = 0;
state->blacklisted = 0;
}
void device_reset(struct device *dev, config_t config)
{
if (SCOPE_ACTIVE(config))
reset_device_state(&dev->active);
if (SCOPE_PERSISTENT(config))
reset_device_state(&dev->persistent);
dev->processed = 0;
}
void device_add_modules(struct util_list *modules, struct device *dev)
{
struct subtype *st = dev->subtype;
/* Add dynamic module info. */
subtype_add_modules(st, dev, modules);
/* Add static subtype module info. */
subtype_add_static_modules(modules, st);
/* Add static devtype module info. */
devtype_add_modules(modules, st->devtype, 0);
}
char *device_read_active_attrib(struct device *dev, const char *name)
{
struct subtype *st = dev->subtype;
char *path, *value, *link;
/* Try direct approach. */
value = subtype_get_active_attrib(st, dev, name);
if (value)
return value;
/* Try reading from path. */
path = subtype_get_active_attrib_path(st, dev, name);
if (!path)
return NULL;
value = misc_read_text_file(path, 1, err_ignore);
if (!value) {
/* Symbolic links count as read-only attributes. */
link = misc_readlink(path);
if (link) {
value = misc_strdup(basename(link));
free(link);
}
}
free(path);
return value;
}
/* Return a newly allocated strlist of attribute names for @dev based on
* @scope. */
static struct util_list *get_attrib_names(struct device *dev,
read_scope_t scope)
{
struct subtype *st = dev->subtype;
struct util_list *names, *files;
struct strlist_node *s;
int i;
struct attrib *a;
const char *prefix;
char *path;
names = strlist_new();
/* Start with known and mandatory attributes. */
for (i = 0; (a = st->dev_attribs[i]); i++) {
if (scope != scope_mandatory || a->mandatory)
strlist_add(names, a->name);
}
if (scope != scope_all)
goto out;
/* Add attributes based on readable files in any of the prefix
* directories. */
prefix = "";
i = 0;
do {
path = subtype_get_active_attrib_path(st, dev, prefix);
if (!path)
continue;
/* Add attribute name for each file in path. */
files = strlist_new();
misc_read_dir(path, files, NULL, NULL);
util_list_iterate(files, s) {
if (*prefix) {
strlist_add_unique(names, "%s/%s", prefix,
s->str);
} else
strlist_add_unique(names, s->str);
}
strlist_free(files);
free(path);
} while (st->prefixes && (prefix = st->prefixes[i++]));
out:
return names;
}
/* Read settings according to @scope for device @dev from active
* configuration and add them to dev->active.settings. */
void device_read_active_settings(struct device *dev, read_scope_t scope)
{
struct subtype *st = dev->subtype;
struct util_list *names;
struct strlist_node *str;
char *path, *name, *value, *link;
struct attrib *a;
struct setting *s;
/* Expand scope. */
names = get_attrib_names(dev, scope);
util_list_iterate(names, str) {
name = str->str;
/* Don't add uevent attribute. */
if (strcmp(name, "uevent") == 0 || ends_with(name, "/uevent"))
continue;
/* Determine full attribute path. */
path = subtype_get_active_attrib_path(st, dev, name);
if (!path)
continue;
/* Get attribute value. */
link = NULL;
value = misc_read_text_file(path, 1, err_ignore);
if (!value) {
if (scope != scope_all)
goto next;
/* Register symbolic links as readonly attributes. */
link = misc_readlink(path);
if (!link)
goto next;
value = basename(link);
}
/* Apply setting to list. */
a = attrib_find(st->dev_attribs, name);
s = setting_list_apply_actual(dev->active.settings, a, name,
value);
if (link || (scope == scope_all && !file_writable(path)))
s->readonly = 1;
if (link)
free(link);
else
free(value);
next:
free(path);
}
strlist_free(names);
}
/* Apply modified settings in @dev->active.settings to active configuration.
* Abort on first error. This requires that @dev defines
* get_active_attrib_path. */
exit_code_t device_write_active_settings(struct device *dev)
{
struct subtype *st = dev->subtype;
struct util_list *list;
struct ptrlist_node *p;
struct setting *s;
char *path;
exit_code_t rc = EXIT_OK;
/* Get order of applying attributes. */
list = setting_list_get_sorted(dev->active.settings);
/* Apply settings in order. */
util_list_iterate(list, p) {
s = p->ptr;
if (!s->modified || s->removed)
continue;
path = subtype_get_active_attrib_path(st, dev, s->name);
if (!path) {
delayed_err("Could not determine path for attribute "
"'%s'\n", s->name);
rc = EXIT_SETTING_NOT_FOUND;
break;
}
rc = setting_write(path, s);
free(path);
if (rc)
break;
}
ptrlist_free(list, 0);
/* Changing device configuration could generate uevents. */
udev_need_settle = 1;
return rc;
}
/* Check if there are any conflicts in the settings list of @dev for the
* specified configuration. */
exit_code_t device_check_settings(struct device *dev, config_t config,
err_t err)
{
struct setting *s;
if (SCOPE_ACTIVE(config)) {
/* Ensure that actual values are available or we might
* report false positives. */
util_list_iterate(&dev->active.settings->list, s) {
if (s->actual_value || s->removed ||
(!s->modified && !s->specified))
continue;
s->actual_value =
device_read_active_attrib(dev, s->name);
}
if (!setting_list_check_conflict(dev->active.settings,
config_active,
err))
return EXIT_INVALID_CONFIG;
}
if (SCOPE_PERSISTENT(config)) {
if (!setting_list_check_conflict(dev->persistent.settings,
config_persistent,
err))
return EXIT_INVALID_CONFIG;
}
return EXIT_OK;
}

316
zdev/src/devnode.c Normal file
View File

@@ -0,0 +1,316 @@
/*
* 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 <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/sysmacros.h>
#include <unistd.h>
#include "devnode.h"
#include "misc.h"
#include "path.h"
/* Create a newly allocated devnode object from the specified data. */
struct devnode *devnode_new(devnode_t type, unsigned int major,
unsigned int minor, const char *name)
{
struct devnode *devnode;
size_t len;
len = strlen(name);
devnode = misc_malloc(sizeof(struct devnode) + len + 1);
devnode->type = type;
devnode->major = major;
devnode->minor = minor;
memcpy(devnode->name, name, len);
return devnode;
}
struct devnode *devnode_copy(struct devnode *d)
{
return devnode_new(d->type, d->major, d->minor, d->name);
}
/* Used for debugging. */
void devnode_print(struct devnode *devnode, int level)
{
printf("%*sdevnode at %p\n", level, "", (void *) devnode);
level += 2;
printf("%*stype=%d\n", level, "", devnode->type);
printf("%*smajor=%d\n", level, "", devnode->major);
printf("%*sminor=%d\n", level, "", devnode->minor);
printf("%*sname=%s\n", level, "", devnode->name);
}
/* Create a newly allocated devnode object from a device special file. */
struct devnode *devnode_from_node(const char *path, err_t err)
{
struct stat s;
const char *name;
if (stat(path, &s)) {
err_t_print(err, "Could not get information for %s: %s\n",
path, strerror(errno));
return NULL;
}
if (!S_ISBLK(s.st_mode) && !S_ISCHR(s.st_mode)) {
err_t_print(err, "File is not a device special file: %s\n",
path);
return NULL;
}
name = strrchr(path, '/');
if (name)
name++;
else
name = path;
return devnode_new(S_ISBLK(s.st_mode) ? BLOCKDEV : CHARDEV,
major(s.st_rdev), minor(s.st_rdev), name);
}
/* Create a newly allocated devnode object from a sysfs dev file containing
* major:minor data in text form. */
struct devnode *devnode_from_devfile(const char *path, const char *name,
devnode_t type)
{
char *dev, *syspath = NULL;
struct devnode *devnode = NULL;
unsigned int major, minor;
if (!path) {
syspath = path_get_sys_block_dev(name);
path = syspath;
}
dev = misc_read_text_file(path, 1, err_ignore);
if (!dev)
goto out;
if (sscanf(dev, "%u:%u", &major, &minor) == 2)
devnode = devnode_new(type, major, minor, name);
out:
free(dev);
free(syspath);
return devnode;
}
/* Create a newly allocated devnode object from a major:minor combination. */
static struct devnode *devnode_from_majmin(devnode_t type, unsigned int major,
unsigned int minor)
{
char *path, *link, *name;
struct devnode *devnode = NULL;
switch (type) {
case BLOCKDEV:
path = path_get_sys_dev_block(major, minor);
break;
case CHARDEV:
path = path_get_sys_dev_char(major, minor);
break;
default:
return NULL;
}
link = misc_readlink(path);
if (!link)
goto out;
name = strrchr(link, '/');
if (name)
name++;
else
name = link;
devnode = devnode_new(type, major, minor, name);
out:
free(link);
free(path);
return devnode;
}
/* Return a devnode that represents the device on which path is located. */
struct devnode *devnode_from_path(const char *path)
{
struct stat s;
if (stat(path, &s))
return NULL;
return devnode_from_majmin(BLOCKDEV, major(s.st_dev), minor(s.st_dev));
}
/* Compare two devnodes by type, major and minor. Return:
* -1: a < b
* 1: a > b
* 0: a == b
**/
int devnode_cmp(struct devnode *a, struct devnode *b)
{
if (a->type < b->type)
return -1;
if (a->type > b->type)
return 1;
if (a->type == BLOCKDEV || a->type == CHARDEV) {
/* Block and character devices are compared by maj:min only. */
if (a->major < b->major)
return -1;
if (a->major > b->major)
return 1;
if (a->minor < b->minor)
return -1;
if (a->minor > b->minor)
return 1;
} else {
/* Network interfaces are compared by name only. */
return strcmp(a->name, b->name);
}
return 0;
}
struct add_cb_data {
struct util_list *list;
int num;
const char *prefix;
};
static exit_code_t add_part_cb(const char *path, const char *filename,
void *data)
{
struct add_cb_data *cb_data = data;
struct devnode *node;
char *devpath;
if (!starts_with(filename, cb_data->prefix))
return EXIT_OK;
devpath = misc_asprintf("%s/dev", path);
node = devnode_from_devfile(devpath, filename, BLOCKDEV);
free(devpath);
if (node) {
ptrlist_add(cb_data->list, node);
cb_data->num++;
}
return EXIT_OK;
}
/* Add block device names to ptrlist found in @DATA. Note: the first entry
* is always the main block device - when available, partitions will be
* reported second. */
static exit_code_t add_block_cb(const char *path, const char *filename,
void *data)
{
struct add_cb_data *cb_data = data;
struct devnode *node;
char *devpath;
/* Add main node. */
devpath = misc_asprintf("%s/dev", path);
node = devnode_from_devfile(devpath, filename, BLOCKDEV);
free(devpath);
if (!node)
return EXIT_OK;
ptrlist_add(cb_data->list, node);
cb_data->num++;
/* Add additional nodes. */
cb_data->prefix = filename;
if (dir_exists(path))
path_for_each(path, add_part_cb, cb_data);
return EXIT_OK;
}
/* Add devnode objects to ptrlist LIST. Each devnode object represents one
* block device node that is provided by the device found at sysfs path PATH.
* Return the number of added objects. */
int devnode_add_block_from_sysfs(struct util_list *list, const char *path)
{
struct add_cb_data cb_data;
char *blkpath;
cb_data.list = list;
cb_data.num = 0;
cb_data.prefix = NULL;
blkpath = misc_asprintf("%s/block", path);
if (dir_exists(blkpath))
path_for_each(blkpath, add_block_cb, &cb_data);
free(blkpath);
return cb_data.num;
}
static exit_code_t add_net_cb(const char *path, const char *filename,
void *data)
{
struct add_cb_data *cb_data = data;
struct devnode *node;
/* Add main node. */
node = devnode_new(NETDEV, 0, 0, filename);
ptrlist_add(cb_data->list, node);
cb_data->num++;
return EXIT_OK;
}
/* Add devnode objects to ptrlist LIST. Each devnode object represents one
* network interface that is provided by the device found at sysfs path PATH.
* Return the number of added objects. */
int devnode_add_net_from_sysfs(struct util_list *list, const char *path)
{
struct add_cb_data cb_data;
char *netpath;
cb_data.list = list;
cb_data.num = 0;
cb_data.prefix = NULL;
netpath = misc_asprintf("%s/net", path);
if (dir_exists(netpath))
path_for_each(netpath, add_net_cb, &cb_data);
free(netpath);
return cb_data.num;
}
/* Return the contents of the link in /sys/dev/ for @devnode or %NULL if the
* link could not be read. */
char *devnode_readlink(struct devnode *devnode)
{
char *path, *link;
switch (devnode->type) {
case BLOCKDEV:
path = path_get_sys_dev_block(devnode->major, devnode->minor);
break;
case CHARDEV:
path = path_get_sys_dev_char(devnode->major, devnode->minor);
break;
case NETDEV:
path = path_get_sys_class("net", devnode->name);
break;
default:
return NULL;
}
link = misc_readlink(path);
free(path);
return link;
}

412
zdev/src/devtype.c Normal file
View File

@@ -0,0 +1,412 @@
/*
* 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 <strings.h>
#include "attrib.h"
#include "ctc.h"
#include "dasd.h"
#include "device.h"
#include "devnode.h"
#include "devtype.h"
#include "generic_ccw.h"
#include "lcs.h"
#include "misc.h"
#include "module.h"
#include "namespace.h"
#include "qeth.h"
#include "select.h"
#include "setting.h"
#include "subtype.h"
#include "zfcp.h"
/* Array of pointers to known device types. */
struct devtype *devtypes[] = {
&dasd_devtype,
&zfcp_devtype,
&qeth_devtype,
&ctc_devtype,
&lcs_devtype,
&generic_ccw_devtype, /* Generic types should come last. */
NULL
};
/* Call the init() function of each registered devtype and subtype. */
void devtypes_init(void)
{
struct devtype *dt;
struct subtype *st;
int i, j;
for (i = 0; (dt = devtypes[i]); i++) {
if (dt->init)
dt->init(dt);
for (j = 0; (st = dt->subtypes[j]); j++)
subtype_init(st);
}
}
/* Call the exit() function of each registered devtype and subtype. */
void devtypes_exit(void)
{
struct devtype *dt;
struct subtype *st;
int i, j;
for (i = 0; (dt = devtypes[i]); i++) {
if (dt->exit)
dt->exit(dt);
for (j = 0; (st = dt->subtypes[j]); j++)
subtype_exit(st);
}
}
/* Return struct devtype associated with NAME or NULL if type could not
* be found. */
struct devtype *devtype_find(const char *name)
{
int i;
struct devtype *dt;
for (i = 0; (dt = devtypes[i]); i++) {
if (strcasecmp(dt->name, name) == 0)
return dt;
}
return NULL;
}
/* Search for a device type attribute named STR. */
struct attrib *devtype_find_type_attrib(struct devtype *devtype,
const char *str)
{
struct attrib *a;
int i;
for (i = 0; (a = devtype->type_attribs[i]); i++) {
if (strcmp(str, a->name) == 0)
return a;
}
return NULL;
}
/* Search for a device attribute named STR in any subtype of DT. */
struct attrib *devtype_find_dev_attrib(struct devtype *dt, const char *str)
{
struct attrib *a;
struct subtype *st;
int i, j;
for (i = 0; (st = dt->subtypes[i]); i++) {
for (j = 0; (a = st->dev_attribs[j]); j++) {
if (strcmp(str, a->name) == 0)
return a;
}
}
return NULL;
}
/* Apply device type settings string list to settings list. */
static exit_code_t apply_setting(struct devtype *dt, config_t config,
const char *key, const char *value,
struct util_list *processed)
{
struct attrib *a;
/* Check for known attribute. */
a = devtype_find_type_attrib(dt, key);
if (a) {
/* Check for acceptable value of known attribute. */
if (!force && !attrib_check_value(a, value))
goto err_invalid_forceable;
/* Check for activeonly. */
if (!force && SCOPE_PERSISTENT(config) && a->activeonly)
goto err_activeonly_forceable;
/* Check for multiple values. */
if (!force && !a->multi && strlist_find(processed, key))
goto err_multi_forceable;
} else {
/* Handle unknown attribute. */
if (!dt->unknown_type_attribs)
goto err_unknown;
if (!force)
goto err_unknown_forceable;
}
strlist_add(processed, "%s", key);
/* Apply to active config. */
if (SCOPE_ACTIVE(config)) {
setting_list_apply_specified(dt->active_settings, a, key,
value);
}
/* Apply to persistent config. */
if (SCOPE_PERSISTENT(config)) {
setting_list_apply_specified(dt->persistent_settings, a, key,
value);
}
return EXIT_OK;
err_invalid_forceable:
delayed_forceable("Invalid value for attribute '%s': %s\n", key, value);
return EXIT_INVALID_SETTING;
err_multi_forceable:
delayed_forceable("Cannot specify multiple values for attribute '%s'\n",
key);
return EXIT_INVALID_SETTING;
err_unknown:
delayed_err("Unknown device type attribute specified: %s\n", key);
return EXIT_ATTRIB_NOT_FOUND;
err_unknown_forceable:
delayed_forceable("Unknown device type attribute specified: %s\n", key);
return EXIT_ATTRIB_NOT_FOUND;
err_activeonly_forceable:
delayed_forceable("Device type attribute should only be changed in the "
"active configuration: %s\n", a->name);
return EXIT_INVALID_SETTING;
}
/* Apply device type settings from strlist to devtype. */
exit_code_t devtype_apply_strlist(struct devtype *dt, config_t config,
struct util_list *settings)
{
struct util_list *processed;
struct strlist_node *s;
exit_code_t rc;
char *key, *value;
/* Apply settings. */
processed = strlist_new();
rc = EXIT_OK;
util_list_iterate(settings, s) {
key = misc_strdup(s->str);
value = strchr(key, '=');
*value = 0;
value++;
rc = apply_setting(dt, config, key, value, processed);
free(key);
if (rc)
break;
}
strlist_free(processed);
return rc;
}
/* Apply device type settings from setting_list to devtype. */
exit_code_t devtype_apply_settings(struct devtype *dt, config_t config,
struct util_list *settings)
{
struct util_list *processed;
struct setting *s;
exit_code_t rc;
/* Apply settings. */
processed = strlist_new();
rc = EXIT_OK;
util_list_iterate(settings, s) {
rc = apply_setting(dt, config, s->name, s->value, processed);
if (rc)
break;
}
strlist_free(processed);
return rc;
}
/* Check if a device ID is valid for a subtype of the specified devtype. */
bool devtype_is_id_valid(struct devtype *dt, const char *id)
{
int i;
struct subtype *st;
for (i = 0; (st = dt->subtypes[i]); i++) {
if (ns_is_id_valid(st->namespace, id))
return true;
}
return false;
}
/* Check if a device ID range is valid for a subtype of the specified
* devtype. */
bool devtype_is_id_range_valid(struct devtype *dt, const char *id)
{
int i;
struct subtype *st;
for (i = 0; (st = dt->subtypes[i]); i++) {
if (st->namespace->is_id_range_valid(id, err_ignore) == EXIT_OK)
return true;
}
return false;
}
/* Check if a device type configuration needs to be written. */
bool devtype_needs_writing(struct devtype *dt, config_t config)
{
if (SCOPE_ACTIVE(config) && dt->active_settings &&
setting_list_modified(dt->active_settings))
return true;
if (SCOPE_PERSISTENT(config) && dt->persistent_settings &&
setting_list_modified(dt->persistent_settings))
return true;
return false;
}
void devtype_print(struct devtype *dt, int indent)
{
int i;
struct subtype *st;
printf("%*sdevtype at %p\n", indent, "", (void *) dt);
if (!dt)
return;
indent += 2;
printf("%*sname=%s proc=%d\n", indent, "", dt->name, dt->processed);
printf("%*sactive_settings exists=%d:\n", indent, "",
dt->active_exists);
setting_list_print(dt->active_settings, indent + 2);
printf("%*spersistent_settings exists=%d:\n", indent, "",
dt->persistent_exists);
setting_list_print(dt->persistent_settings, indent + 2);
printf("%*ssubtypes:\n", indent, "");
for (i = 0; (st = dt->subtypes[i]); i++)
subtype_print(st, indent + 2);
}
/* Add name of all modules required by devtype @dt to strlist @modules.
* If @subtypes is set, also add modules required by all subtypes. */
void devtype_add_modules(struct util_list *modules, struct devtype *dt,
int subtypes)
{
const char **mod = dt->modules;
int i;
struct subtype *st;
if (!mod)
goto out;
for (i = 0; mod[i]; i++)
strlist_add_unique(modules, "%s", mod[i]);
out:
if (subtypes) {
for (i = 0; (st = dt->subtypes[i]); i++)
subtype_add_static_modules(modules, st);
}
}
/* Check if any of the kernel modules used by devtype @dt is loaded. */
bool devtype_is_module_loaded(struct devtype *dt)
{
struct util_list *modules;
struct strlist_node *s;
bool result = false;
modules = strlist_new();
devtype_add_modules(modules, dt, 1);
util_list_iterate(modules, s) {
if (module_loaded(s->str)) {
result = true;
break;
}
}
strlist_free(modules);
return result;
}
/* Return the number of different namespaces found in subtypes of @dt. */
int devtype_count_namespaces(struct devtype *dt)
{
int found[NUM_NAMESPACES];
int i, j, num;
struct subtype *st;
/* Reset array. */
for (i = 0; i < NUM_NAMESPACES; i++)
found[i] = 0;
/* Set array entries for namespaces found. */
for (i = 0; (st = dt->subtypes[i]); i++) {
j = namespaces_index(st->namespace);
if (j < 0)
continue;
found[j] = 1;
}
/* Count namespaces. */
num = 0;
for (i = 0; i < NUM_NAMESPACES; i++) {
if (found[i])
num++;
}
return num;
}
/* Return the number of subtypes defined for devtype @dt. */
int devtype_count_subtypes(struct devtype *dt)
{
int i;
for (i = 0; dt->subtypes[i]; i++) ;
return i;
}
/* Try to find namespace in which @ID is could be an ID. Return %NULL if no
* namespace or more than one namespace was found. */
struct namespace *devtype_most_similar_namespace(struct devtype *only_dt,
struct subtype *only_st,
const char *id)
{
struct namespace *match = NULL;
struct devtype *dt;
struct subtype *st;
int i, j;
for (i = 0; (dt = devtypes[i]); i++) {
if (only_dt && dt != only_dt)
continue;
for (j = 0; (st = dt->subtypes[j]); j++) {
if (only_st && st != only_st)
continue;
if (ns_is_id_valid(st->namespace, id))
goto found;
if (!st->namespace->is_id_similar ||
!st->namespace->is_id_similar(id))
continue;
found:
if (match && match != st->namespace) {
/* Multiple matches. */
return NULL;
}
match = st->namespace;
}
}
return match;
}

89
zdev/src/exit_code.c Normal file
View File

@@ -0,0 +1,89 @@
/*
* 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 "exit_code.h"
/* Textual representation of program exit codes. */
const char *exit_code_to_str(exit_code_t rc)
{
switch (rc) {
case EXIT_OK:
return "Program finished successfully";
case EXIT_USAGE_ERROR:
return "Usage error";
case EXIT_UNKNOWN_DEVTYPE:
return "Unknown device type specified";
case EXIT_DEVICE_NOT_FOUND:
return "Device not found";
case EXIT_ATTRIB_NOT_FOUND:
return "Unknown attribute specified";
case EXIT_INVALID_DEVTYPE:
return "Invalid device type specified";
case EXIT_INVALID_SETTING:
return "Invalid attribute value specified";
case EXIT_SETTING_NOT_FOUND:
return "Setting not found";
case EXIT_EMPTY_SELECTION:
return "Empty selection";
case EXIT_INVALID_CONFIG:
return "Invalid configuration";
case EXIT_INVALID_ID:
return "Invalid device ID specified";
case EXIT_INCOMPLETE_ID:
return "Incomplete device ID specified";
case EXIT_NO_DATA:
return "Configuration data not found";
case EXIT_UNKNOWN_COLUMN:
return "Unknown column specified";
case EXIT_INCOMPLETE_TYPE:
return "None or incomplete type specified";
case EXIT_RUNTIME_ERROR:
return "A run-time error occurred";
case EXIT_ABORTED:
return "Operation aborted on user request";
case EXIT_SETTING_FAILED:
return "Error while applying setting";
case EXIT_FORMAT_ERROR:
return "File format error";
case EXIT_MOD_BUSY:
return "Kernel module is in use";
case EXIT_MOD_UNLOAD_FAILED:
return "Kernel module could not be unloaded";
case EXIT_MOD_LOAD_FAILED:
return "Kernel module could not be loaded";
case EXIT_OUT_OF_MEMORY:
return "Not enough available memory";
case EXIT_ZFCP_FCP_NOT_FOUND:
return "FCP device not found";
case EXIT_ZFCP_INVALID_WWPN:
return "Invalid WWPN specified";
case EXIT_ZFCP_WWPN_NOT_FOUND:
return "WWPN not found";
case EXIT_ZFCP_INVALID_LUN:
return "Invalid LUN specified";
case EXIT_ZFCP_SCSI_NOT_FOUND:
return "SCSI device not found";
case EXIT_GROUP_NOT_FOUND:
return "CCW group device: CCW device not found";
case EXIT_GROUP_INVALID:
return "CCW group device: CCW devices are not a valid group";
case EXIT_GROUP_ALREADY:
return "CCW group device: CCW device already grouped";
case EXIT_GROUP_FAILED:
return "CCW group device: Grouping failed";
case EXIT_UNGROUP_FAILED:
return "CCW group device: Ungrouping failed";
case EXIT_INTERNAL_ERROR:
return "An internal error occurred";
default:
break;
}
return "An unknown error occurred";
}

574
zdev/src/export.c Normal file
View File

@@ -0,0 +1,574 @@
/*
* 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 <ctype.h>
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/utsname.h>
#include "attrib.h"
#include "device.h"
#include "devtype.h"
#include "export.h"
#include "setting.h"
#include "subtype.h"
struct export_header {
export_t type;
config_t config;
char *type_id;
char *id;
};
static struct export_header *header_new(const char *config_str,
const char *type, const char *id)
{
config_t config;
struct export_header *hdr;
if (!str_to_config(config_str, &config))
return NULL;
hdr = misc_malloc(sizeof(struct export_header));
if (!id)
hdr->type = export_devtype;
else
hdr->type = export_device;
hdr->config = config;
hdr->type_id = misc_strdup(type);
if (id)
hdr->id = misc_strdup(id);
return hdr;
}
static void header_free(struct export_header *hdr)
{
free(hdr->type_id);
free(hdr->id);
free(hdr);
}
static void write_key_val(FILE *fd, const char *name, const char *value)
{
char *str;
str = quote_str(value, 0);
fprintf(fd, "%s=%s\n", name, str);
free(str);
}
static bool is_exportable(struct setting *s, config_t config)
{
struct attrib *a = s->attrib;
if (!a)
return true;
if (a->mandatory)
return true;
if (SCOPE_ACTIVE(config)) {
if (a->unstable && !a->map) {
/* Skip values that cannot be determined. */
return false;
}
if (!attrib_match_default(s->attrib, s->value)) {
/* All non-default values should be exported. */
return true;
}
}
if (SCOPE_PERSISTENT(config)) {
if (setting_is_set(s))
return true;
}
return false;
}
static void write_settings(FILE *fd, struct setting_list *settings,
config_t config)
{
struct setting *s;
struct strlist_node *v;
if (!settings)
return;
util_list_iterate(&settings->list, s) {
if (!is_exportable(s, config))
continue;
if (s->values) {
/* Multiple values. */
util_list_iterate(s->values, v)
write_key_val(fd, s->name, v->str);
} else {
/* Single value. */
write_key_val(fd, s->name, s->value);
}
}
}
/* Write an initial comment. */
static void export_write_comment(FILE *fd)
{
struct utsname uts;
if (uname(&uts) == 0)
fprintf(fd, "# Generated by chzdev on %s\n", uts.nodename);
else
fprintf(fd, "# Generated by chzdev\n");
}
/* Write an export file header. */
static void write_header(FILE *fd, config_t config, const char *type,
const char *id, int *first_ptr)
{
if (first_ptr) {
if (*first_ptr) {
export_write_comment(fd);
*first_ptr = 0;
} else
fprintf(fd, "\n");
}
if (id)
fprintf(fd, "[%s %s %s]\n", config_to_str(config), type, id);
else
fprintf(fd, "[%s %s]\n", config_to_str(config), type);
}
static int count_exportable(struct device *dev, config_t config)
{
struct setting_list *list;
struct setting *s;
int count;
if (config == config_active)
list = dev->active.settings;
else
list = dev->persistent.settings;
count = 0;
util_list_iterate(&list->list, s) {
if (is_exportable(s, config))
count++;
}
return count;
}
/* Write settings for device @dev in the specified @config to @fd. */
exit_code_t export_write_device(FILE *fd, struct device *dev, config_t config,
int *first_ptr)
{
exit_code_t rc;
struct setting_list *settings;
if (config == config_all) {
rc = export_write_device(fd, dev, config_active, first_ptr);
if (rc)
return rc;
return export_write_device(fd, dev, config_persistent,
first_ptr);
}
if (config == config_active) {
if (!dev->active.exists)
return EXIT_OK;
/* No need to export device with no settings. */
if (count_exportable(dev, config_active) == 0 &&
!dev->subtype->support_definable)
return EXIT_OK;
settings = dev->active.settings;
} else {
if (!dev->persistent.exists)
return EXIT_OK;
settings = dev->persistent.settings;
}
write_header(fd, config, dev->subtype->name, dev->id, first_ptr);
write_settings(fd, settings, config);
return ferror(fd) ? EXIT_RUNTIME_ERROR : EXIT_OK;
}
/* Write settings for device type @dt in the specified @config to @fd. */
exit_code_t export_write_devtype(FILE *fd, struct devtype *dt, config_t config,
int *first_ptr)
{
exit_code_t rc;
struct setting_list *settings;
if (config == config_all) {
rc = export_write_devtype(fd, dt, config_active, first_ptr);
if (rc)
return rc;
return export_write_devtype(fd, dt, config_persistent,
first_ptr);
}
if (config == config_active)
settings = dt->active_settings;
else
settings = dt->persistent_settings;
if (!settings || setting_list_count_set(settings) == 0)
return EXIT_OK;
write_header(fd, config, dt->name, NULL, first_ptr);
write_settings(fd, settings, config);
return ferror(fd) ? EXIT_RUNTIME_ERROR : EXIT_OK;
}
static bool parse_header(const char *line, struct export_header **header_ptr)
{
char *copy;
size_t end;
int argc;
char **argv;
struct export_header *header;
if (*line != '[')
return false;
end = strlen(line) - 1;
if (line[end] != ']')
return false;
copy = misc_strdup(line);
copy[end] = 0;
line_split(&copy[1], &argc, &argv);
free(copy);
if (argc == 2)
header = header_new(argv[0], argv[1], NULL);
else if (argc == 3)
header = header_new(argv[0], argv[1], argv[2]);
else
header = NULL;
line_free(argc, argv);
if (!header)
return false;
*header_ptr = header;
return true;
}
static bool parse_setting(const char *line, char **key_ptr, char **val_ptr)
{
char *copy, *value;
copy = misc_strdup(line);
value = strchr(copy, '=');
if (!value) {
free(copy);
return false;
}
*value = 0;
value++;
*key_ptr = shrink_str(copy);
*val_ptr = unquote_str(value);
free(copy);
return true;
}
static struct setting_list *dev_get_setting_list(struct device *dev,
config_t config)
{
struct setting_list *settings = NULL;
if (config == config_active)
settings = dev->active.settings;
else
settings = dev->persistent.settings;
return settings;
}
static struct setting_list *dt_get_setting_list(struct devtype *dt,
config_t config)
{
struct setting_list *settings = NULL;
if (config == config_active)
settings = dt->active_settings;
else
settings = dt->persistent_settings;
return settings;
}
/* Display a warning related to a line in an export data file. */
static void fwarn(const char *filename, int lineno, const char *format, ...)
{
va_list args;
va_start(args, format);
fprintf(stderr, "%s:%d: ", filename, lineno);
vfprintf(stderr, format, args);
va_end(args);
}
static exit_code_t handle_header(const char *filename, int lineno,
struct export_header *hdr,
struct devtype **dt_ptr,
struct device **dev_ptr)
{
struct devtype *dt = NULL;
struct device *dev = NULL;
struct subtype *st;
exit_code_t rc = EXIT_OK;
if (hdr->type == export_devtype) {
/* Section header indicates device type settings. */
dt = devtype_find(hdr->type_id);
if (!dt) {
fwarn(filename, lineno, "Unknown device type '%s' in "
"section header\n", hdr->type_id);
rc = EXIT_FORMAT_ERROR;
goto out;
}
/* Prepare device type for new settings. */
if (SCOPE_ACTIVE(hdr->config)) {
setting_list_free(dt->active_settings);
dt->active_settings = setting_list_new();
}
if (SCOPE_PERSISTENT(hdr->config)) {
setting_list_free(dt->persistent_settings);
dt->persistent_settings = setting_list_new();
}
} else if (hdr->type == export_device) {
/* Section header indicates device settings. */
st = subtype_find(hdr->type_id);
if (!st) {
if (devtype_find(hdr->type_id)) {
fwarn(filename, lineno, "Ambiguous device "
"type '%s' in section header\n",
hdr->type_id);
} else {
fwarn(filename, lineno, "Unknown device type "
"'%s' in section header\n", hdr->type_id);
}
rc = EXIT_FORMAT_ERROR;
goto out;
}
if (!st->devices)
st->devices = device_list_new(st);
dev = device_list_find(st->devices, hdr->id, NULL);
if (!dev) {
/* Register a new device. */
dev = device_new(st, hdr->id);
if (!dev) {
fwarn(filename, lineno, "Unknown device ID "
"format '%s' in section header\n",
hdr->id);
rc = EXIT_FORMAT_ERROR;
goto out;
}
device_list_add(st->devices, dev);
}
/* Prepare device for new settings. */
if (SCOPE_ACTIVE(hdr->config)) {
setting_list_clear(dev->active.settings);
if (dev->subtype->support_definable)
dev->active.definable = 1;
else
dev->active.exists = 1;
}
if (SCOPE_PERSISTENT(hdr->config)) {
setting_list_clear(dev->persistent.settings);
dev->persistent.exists = 1;
}
}
out:
*dt_ptr = dt;
*dev_ptr = dev;
return rc;
}
static exit_code_t handle_setting(const char *filename, int lineno,
const char *key, const char *value,
struct devtype *dt, struct device *dev,
config_t config)
{
exit_code_t rc;
struct attrib **attribs, *a;
struct setting_list *list;
if (config == config_all) {
rc = handle_setting(filename, lineno, key, value, dt, dev,
config_active);
if (rc)
return rc;
return handle_setting(filename, lineno, key, value, dt, dev,
config_persistent);
}
if (dt) {
/* We're inside a device type section. */
attribs = dt->type_attribs;
list = dt_get_setting_list(dt, config);
} else if (dev) {
/* We're inside a device section. */
attribs = dev->subtype->dev_attribs;
list = dev_get_setting_list(dev, config);
} else
return EXIT_OK;
a = attrib_find(attribs, key);
if (a)
setting_list_apply(list, a, a->name, value);
else if (dt) {
fwarn(filename, lineno, "Skipping unknown device type "
"setting %s=%s\n", key, value);
} else {
fwarn(filename, lineno, "Skipping unknown device setting "
"%s=%s\n", key, value);
}
return EXIT_OK;
}
static struct export_object *object_new(export_t type, void *ptr)
{
struct export_object *obj;
obj = misc_malloc(sizeof(struct export_object));
obj->type = type;
if (type == export_devtype)
obj->ptr.dt = ptr;
else
obj->ptr.dev = ptr;
return obj;
}
static bool empty_device(struct device *dev)
{
/* Empty device settings are ok for subtypes supporting definable
* devices because there the device ID is information enough. */
if (dev->subtype->support_definable)
return false;
if (util_list_is_empty(&dev->active.settings->list) &&
util_list_is_empty(&dev->persistent.settings->list))
return true;
return false;
}
static bool empty_devtype(struct devtype *dt)
{
if ((!dt->active_settings ||
util_list_is_empty(&dt->active_settings->list)) &&
(!dt->persistent_settings ||
util_list_is_empty(&dt->persistent_settings->list)))
return true;
return false;
}
static bool check_section(const char *filename, int lineno,
struct devtype *dt, struct device *dev)
{
if (dt && empty_devtype(dt)) {
fwarn(filename, lineno, "Empty device type section\n");
return false;
}
if (dev && empty_device(dev)) {
fwarn(filename, lineno, "Empty device section\n");
return false;
}
return true;
}
/* Read configuration objects from @fd. Add pointer to newly allocated
* struct export_objects to ptrlist @objects. */
exit_code_t export_read(FILE *fd, const char *filename,
struct util_list *objects)
{
char *line, *l, *key, *value;
struct export_header *hdr;
size_t end;
exit_code_t rc = EXIT_OK;
struct devtype *dt;
int lineno;
config_t config = config_all;
struct device *dev;
lineno = 0;
line = NULL;
dt = NULL;
dev = NULL;
while (rc == EXIT_OK && getline(&line, &end, fd) > 0) {
lineno++;
l = shrink_str(line);
if (!*l || *l == '#') {
free(l);
continue;
}
if (parse_header(l, &hdr)) {
if (!check_section(filename, lineno, dt, dev)) {
header_free(hdr);
rc = EXIT_FORMAT_ERROR;
goto err;
}
/* Found [<config> <subtype> <devid>] or
* [<config> <devtype>]. */
rc = handle_header(filename, lineno, hdr, &dt, &dev);
config = hdr->config;
header_free(hdr);
if (dt) {
ptrlist_add(objects, object_new(export_devtype,
dt));
} else if (dev) {
ptrlist_add(objects, object_new(export_device,
dev));
}
} else if (parse_setting(l, &key, &value)) {
/* Found <key>=<value>. */
if (!dt && !dev) {
fwarn(filename, lineno, "Setting outside of "
"valid section\n");
rc = EXIT_FORMAT_ERROR;
} else {
rc = handle_setting(filename, lineno, key,
value, dt, dev, config);
}
free(key);
free(value);
} else {
warn("%s:%d: Unrecognized line: %s\n", filename,
lineno, l);
rc = EXIT_FORMAT_ERROR;
}
err:
free(l);
}
free(line);
if (rc == EXIT_OK) {
if (!check_section(filename, lineno, dt, dev))
rc = EXIT_FORMAT_ERROR;
}
if (rc)
return rc;
return ferror(fd) ? EXIT_RUNTIME_ERROR : EXIT_OK;
}

76
zdev/src/findmnt.c Normal file
View File

@@ -0,0 +1,76 @@
/*
* 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 "devnode.h"
#include "findmnt.h"
#include "misc.h"
#define FINDMNT_CMDLINE "findmnt -n -T %s -o SOURCE 2>/dev/null"
static struct devnode *devnode_from_line(const char *line)
{
char *copy, *end;
struct devnode *devnode;
copy = misc_strdup(line);
/* Could be /dev/sda[/subvolname] */
end = strchr(copy, '[');
if (end)
*end = 0;
devnode = devnode_from_node(copy, err_ignore);
free(copy);
return devnode;
}
/* Return a newly allocated ptrlist containing all devnodes that provide
* the file system on which @path is located. Return %NULL if no devnodes
* were found. We need this in addition to lsblk output because lsblk doesn't
* work with btrfs subvolumes. */
struct util_list *findmnt_get_devnodes_by_path(const char *path)
{
struct util_list *devnodes;
char *quoted_path, *cmd, *output, *next, *curr;
struct devnode *devnode;
devnodes = ptrlist_new();
quoted_path = quote_str(path, 1);
cmd = misc_asprintf(FINDMNT_CMDLINE, quoted_path);
output = misc_read_cmd_output(cmd, 0, err_ignore);
if (!output)
goto out;
/* Iterate over each line. */
next = output;
while ((curr = strsep(&next, "\n"))) {
devnode = devnode_from_line(curr);
if (devnode)
ptrlist_add(devnodes, devnode);
}
out:
free(output);
free(cmd);
free(quoted_path);
/* Make empty list detection easier for calling function. */
if (util_list_is_empty(devnodes)) {
ptrlist_free(devnodes, 1);
devnodes = NULL;
}
return devnodes;
}

238
zdev/src/generic_ccw.c Normal file
View File

@@ -0,0 +1,238 @@
/*
* 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 <string.h>
#include "attrib.h"
#include "ccw.h"
#include "ccwgroup.h"
#include "devnode.h"
#include "devtype.h"
#include "generic_ccw.h"
#include "namespace.h"
#include "path.h"
#include "subtype.h"
/*
* Generic CCW device sub-type.
*/
static struct ccw_subtype_data generic_ccw_data = {
.ccwdrv = NULL,
.mod = NULL,
};
/* Check if there is a non-generic subtype in the CCW namespace that uses the
* specified CCW device driver. */
static bool match_non_generic(const char *drv)
{
int i, j;
struct devtype *dt;
struct subtype *st;
struct ccw_subtype_data *ccwdata;
struct ccwgroup_subtype_data *ccwgroupdata;
const char *ccwdrv;
for (i = 0; (dt = devtypes[i]); i++) {
for (j = 0; (st = dt->subtypes[j]); j++) {
if (st->generic || !st->data)
continue;
if (st->namespace == &ccw_namespace) {
ccwdata = st->data;
ccwdrv = ccwdata->ccwdrv;
} else if (st->namespace->cmp_parsed_ids ==
&ccwgroup_cmp_parsed_ids) {
ccwgroupdata = st->data;
ccwdrv = ccwgroupdata->ccwdrv;
} else
continue;
if (!ccwdrv)
continue;
if (strcmp(drv, ccwdrv) == 0)
return true;
}
}
return false;
}
/* Determine if the specified device exists in the CCW namespace and is not
* handled by another subtype. */
static bool generic_exists(struct subtype *st, const char *id, int fast)
{
struct ccw_devid devid;
char *drv = NULL;
bool result = false;
if (ccw_parse_devid(&devid, id, err_ignore) != EXIT_OK)
goto out;
if (!fast && !ccw_exists(NULL, NULL, id))
goto out;
drv = ccw_get_driver(&devid);
if (!drv) {
/* Account devices with no driver to generic-ccw to make them
* visible. */
result = true;
goto out;
}
result = !match_non_generic(drv);
out:
free(drv);
return result;
}
static bool generic_ccw_st_exists_active(struct subtype *st, const char *id)
{
return generic_exists(st, id, 0);
}
static bool get_ids_cb(const char *file, void *data)
{
/* We use fast=1 here to prevent another stat() syscall per device
* when we already know that the directory exists. */
return generic_exists(data, file, 1);
}
/* Add the IDs of all CCW devices existing in the active configuration which
* are not handled by other subtypes. */
static void generic_ccw_st_add_active_ids(struct subtype *st,
struct util_list *ids)
{
char *path;
cio_settle(0);
path = path_get_ccw_devices(NULL);
misc_read_dir(path, ids, get_ids_cb, st);
free(path);
}
struct add_cb_data {
struct util_list *devnodes;
const char *id;
};
static exit_code_t add_cb(const char *abs_path, const char *rel_path,
void *data)
{
struct add_cb_data *cb_data = data;
char *link, *target, *name;
static const char *prefix[] = { "vmrdr-", "vmpun-", "vmprt-" };
unsigned int i, major, minor;
struct devnode *devnode;
link = misc_readlink(abs_path);
if (!link)
return EXIT_OK;
target = basename(link);
for (i = 0; i < ARRAY_SIZE(prefix); i++) {
name = misc_asprintf("%s%s", prefix[i], cb_data->id);
if (strcmp(target, name) != 0)
goto next;
if (sscanf(rel_path, "%u:%u", &major, &minor) != 2)
goto next;
devnode = devnode_new(CHARDEV, major, minor, target);
ptrlist_add(cb_data->devnodes, devnode);
next:
free(name);
}
free(link);
return EXIT_OK;
}
/* Add struct devnodes to ptrlist @devnodes for each Linux device that is
* provided by the CCW device with the specified ID. Since this is a generic
* CCW driver covering multiple device drivers, this is somewhat of trial
* and error work. */
static void generic_ccw_st_add_devnodes(struct subtype *st, const char *id,
struct util_list *devnodes)
{
struct add_cb_data cb_data;
char *path;
cb_data.devnodes = devnodes;
cb_data.id = id;
path = path_get_sys_dev_char_devices();
if (dir_exists(path))
path_for_each(path, add_cb, &cb_data);
free(path);
}
static struct subtype generic_ccw_subtype = {
.super = &ccw_subtype,
.devtype = &generic_ccw_devtype,
.name = "generic-ccw",
.title = "Generic Channel-Command-Word (CCW) devices",
.devname = "Generic CCW device",
.modules = NULL,
.namespace = &ccw_namespace,
.data = &generic_ccw_data,
.dev_attribs = ATTRIB_ARRAY(
&ccw_attr_online,
&ccw_attr_cmb_enable,
),
.unknown_dev_attribs = 1,
.generic = 1,
.exists_active = &generic_ccw_st_exists_active,
.add_active_ids = &generic_ccw_st_add_active_ids,
.add_devnodes = &generic_ccw_st_add_devnodes,
};
/*
* Generic CCW device type methods.
*/
/* Clean up all resources used by devtype object. */
static void generic_ccw_devtype_exit(struct devtype *dt)
{
setting_list_free(dt->active_settings);
setting_list_free(dt->persistent_settings);
}
static exit_code_t generic_ccw_devtype_read_settings(struct devtype *dt,
config_t config)
{
dt->active_settings = setting_list_new();
dt->persistent_settings = setting_list_new();
return EXIT_OK;
}
static exit_code_t generic_ccw_devtype_write_settings(struct devtype *dt,
config_t config)
{
return EXIT_OK;
}
/*
* Generic CCW device type.
*/
struct devtype generic_ccw_devtype = {
.name = "generic-ccw",
.title = "", /* Only use subtypes. */
.devname = "Generic CCW device",
.modules = NULL,
.subtypes = SUBTYPE_ARRAY(
&generic_ccw_subtype,
),
.type_attribs = ATTRIB_ARRAY(),
.exit = &generic_ccw_devtype_exit,
.read_settings = &generic_ccw_devtype_read_settings,
.write_settings = &generic_ccw_devtype_write_settings,
};

128
zdev/src/hash.c Normal file
View File

@@ -0,0 +1,128 @@
/*
* 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 <string.h>
#include "hash.h"
#include "misc.h"
/* Initialize hash. */
void _hash_init(struct hash *hash, int buckets, hash_id_fn_t get_id,
hash_cmp_fn_t cmp_id, hash_fn_t get_hash, unsigned long offset)
{
int i;
memset(hash, 0, sizeof(struct hash));
util_list_init_offset(&hash->list, offset);
hash->buckets = buckets;
hash->get_id = get_id;
hash->cmp_id = cmp_id;
hash->get_hash = get_hash;
hash->hash = misc_malloc(sizeof(struct util_list) * buckets);
for (i = 0; i < buckets; i++)
hash->hash[i] = ptrlist_new();
}
/* Return a newly allocated hash. */
struct hash *_hash_new(int buckets, hash_id_fn_t get_id, hash_cmp_fn_t cmp_id,
hash_fn_t get_hash, unsigned long offset)
{
struct hash *hash;
hash = misc_malloc(sizeof(struct hash));
_hash_init(hash, buckets, get_id, cmp_id, get_hash, offset);
return hash;
}
/* Release all resources associated with @hash, excluding @hash. If @free_fn
* is specified, this function is called to release all resources associated
* with each entry. */
void hash_clear(struct hash *hash, void (*free_fn)(void *))
{
void *c, *n;
int i;
util_list_iterate_safe(&hash->list, c, n) {
util_list_remove(&hash->list, c);
if (free_fn)
free_fn(c);
}
for (i = 0; i < hash->buckets; i++)
ptrlist_free(hash->hash[i], 0);
free(hash->hash);
}
/* Release all resources associated with @hash. If @free_fn is specified,
* this function is called to release all resources associated with each
* entry. */
void hash_free(struct hash *hash, void (*free_fn)(void *))
{
hash_clear(hash, free_fn);
free(hash);
}
/* Add a new entry to the hash. */
void hash_add(struct hash *hash, void *entry)
{
int bucket;
util_list_add_tail(&hash->list, entry);
if (hash->buckets > 0) {
bucket = hash->get_hash(hash->get_id(entry));
ptrlist_add(hash->hash[bucket], entry);
}
}
/* Remove an entry from the hash. */
void hash_remove(struct hash *hash, void *entry)
{
int bucket;
util_list_remove(&hash->list, entry);
if (hash->buckets > 0) {
bucket = hash->get_hash(hash->get_id(entry));
ptrlist_remove(hash->hash[bucket], entry);
}
}
void hash_print(struct hash *hash, int ind)
{
int i;
indent(ind, "hash at %p\n", hash);
ind += 2;
indent(ind, "buckets=%d\n", hash->buckets);
indent(ind, "get_id=%p\n", hash->get_id);
indent(ind, "cmp_id=%p\n", hash->cmp_id);
indent(ind, "get_hash=%p\n", hash->get_hash);
for (i = 0; i < hash->buckets; i++) {
indent(ind, "bucket[%d]: len=%d\n", i,
util_list_len(hash->hash[i]));
}
}
/* Find entry by ID. @cmp_fn compares two IDs and returns 0 when IDs match. */
void *hash_find_by_id(struct hash *hash, const void *id)
{
int bucket;
struct ptrlist_node *p;
if (hash->buckets > 0) {
bucket = hash->get_hash(id);
util_list_iterate(hash->hash[bucket], p) {
if (hash->cmp_id(hash->get_id(p->ptr), id) == 0)
return p->ptr;
}
}
return NULL;
}

385
zdev/src/inuse.c Normal file
View File

@@ -0,0 +1,385 @@
/*
* 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 <mntent.h>
#include <stdbool.h>
#include <stdlib.h>
#include <string.h>
#include "blkinfo.h"
#include "device.h"
#include "devnode.h"
#include "inuse.h"
#include "misc.h"
#include "path.h"
#include "select.h"
#include "subtype.h"
/* struct resource - A resource that is currently in use
* @st: Subtype of device providing resource
* @id: ID of device providing resource
* @name: Name of the resource, e.g. the mount point. */
struct resource {
struct subtype *st;
char *id;
char *name;
};
/* ptrlist of struct resources. */
static struct util_list *resources;
/* Return a newly allocated struct resource. */
static struct resource *resource_new(struct subtype *st, const char *id,
const char *name)
{
struct resource *res;
res = misc_malloc(sizeof(struct resource));
res->st = st;
res->id = misc_strdup(id);
res->name = misc_strdup(name);
return res;
}
/* Release all resources associated with @res. */
static void resource_free(struct resource *res)
{
if (!res)
return;
free(res->id);
free(res->name);
free(res);
}
/* Release all allocated resources. */
void inuse_exit(void)
{
struct ptrlist_node *p, *n;
struct resource *res;
if (!resources)
return;
util_list_iterate_safe(resources, p, n) {
util_list_remove(resources, p);
res = p->ptr;
resource_free(res);
free(p);
}
free(resources);
}
/* Return newly allocated strlist containing names of mountpoints for all
* mounted file systems. */
static struct util_list *get_mountpoints(void)
{
struct util_list *list;
char *path;
FILE *fd;
struct mntent *m;
list = strlist_new();
/* Try lsblk output first because it doesn't contain non-blockdevice
* file systems. */
blkinfo_add_mountpoints(list);
/* Use /proc/mounts as well to get btrfs subvolume mounts. */
path = path_get_proc("mounts");
fd = setmntent(path, "r");
if (!fd)
goto out;
while ((m = getmntent(fd))) {
if (!m->mnt_dir || !m->mnt_fsname || !*m->mnt_dir ||
*m->mnt_fsname != '/')
continue;
strlist_add_unique(list, m->mnt_dir);
}
endmntent(fd);
out:
free(path);
return list;
}
static void add_mounts(struct util_list *list)
{
struct util_list *mountpoints;
struct strlist_node *mp;
struct resource *res;
struct util_list *selected;
struct selected_dev_node *sel;
char *r;
mountpoints = get_mountpoints();
util_list_iterate(mountpoints, mp) {
selected = selected_dev_list_new();
/* Determine list of devices providing mountpoint. */
if (select_by_path(NULL, selected, config_active, scope_mandatory,
NULL, NULL, mp->str, err_ignore) != EXIT_OK)
goto next;
/* Process list. */
util_list_iterate(selected, sel) {
if (sel->rc != EXIT_OK || !sel->st || !sel->id)
continue;
r = misc_asprintf("Mount point %s", mp->str);
res = resource_new(sel->st, sel->id, r);
free(r);
ptrlist_add(list, res);
/* Expand list to also contain prereq-devices. */
subtype_add_prereqs(sel->st, sel->id, selected);
}
next:
selected_dev_list_free(selected);
}
strlist_free(mountpoints);
}
/* Return newly allocated ptrlist containing devnodes of active swap devices. */
static struct util_list *get_swapdevs(void)
{
struct util_list *list;
char *path, *text, *curr, *next, **argv;
struct devnode *devnode;
int argc;
list = ptrlist_new();
/* Try lsblk output first because it doesn't contain non-blockdevice
* file systems. */
blkinfo_add_swap_devnodes(list);
if (!util_list_is_empty(list))
return list;
/* Fall back to /proc/swaps. */
path = path_get_proc("swaps");
text = misc_read_text_file(path, 0, err_ignore);
free(path);
if (!text)
goto out;
next = text;
while ((curr = strsep(&next, "\n"))) {
line_split(curr, &argc, &argv);
if (argc > 0 && *(argv[0]) == '/') {
devnode = devnode_from_node(argv[0], err_ignore);
if (devnode)
ptrlist_add(list, devnode);
}
line_free(argc, argv);
}
out:
free(text);
return list;
}
static void add_swap(struct util_list *list)
{
struct util_list *swapdevs;
struct ptrlist_node *swap;
struct devnode *devnode;
struct resource *res;
struct util_list *selected;
struct selected_dev_node *sel;
char *r;
swapdevs = get_swapdevs();
util_list_iterate(swapdevs, swap) {
devnode = swap->ptr;
selected = selected_dev_list_new();
/* Determine list of devices providing mountpoint. */
if (select_by_devnode(NULL, selected, config_active,
scope_mandatory, NULL, NULL, devnode,
NULL, err_ignore) != EXIT_OK)
goto next;
/* Process list. */
util_list_iterate(selected, sel) {
if (sel->rc != EXIT_OK || !sel->st || !sel->id)
continue;
r = misc_asprintf("Swap device %s", devnode->name);
res = resource_new(sel->st, sel->id, r);
free(r);
ptrlist_add(list, res);
/* Expand list to also contain prereq-devices. */
subtype_add_prereqs(sel->st, sel->id, selected);
}
next:
selected_dev_list_free(selected);
}
ptrlist_free(swapdevs, 1);
}
/* Determine IPv4 and IPv6 addresses of networking interface @name. */
static void add_ip_addresses(struct util_list *addrs, const char *name)
{
char *cmd, *text, *next, *curr, **argv;
int argc;
cmd = misc_asprintf("%s address show %s 2>/dev/null", PATH_IP, name);
text = misc_read_cmd_output(cmd, 0, err_ignore);
if (!text)
goto out;
next = text;
while ((curr = strsep(&next, "\n"))) {
line_split(curr, &argc, &argv);
if (argc < 2)
goto next;
if (strcmp(argv[0], "inet") == 0)
strlist_add(addrs, "IPv4 address %s", argv[1]);
else if (strcmp(argv[0], "inet6") == 0)
strlist_add(addrs, "IPv6 address %s", argv[1]);
next:
line_free(argc, argv);
}
out:
free(text);
free(cmd);
}
/* Add struct resources to @list for each device providing an interface with
* IP address. */
static void add_network(struct util_list *list)
{
char *path;
struct util_list *interfaces, *addrs, *selected;
struct strlist_node *net, *addr;
struct selected_dev_node *sel;
struct resource *res;
path = path_get_sys_class("net", NULL);
interfaces = strlist_new();
if (!misc_read_dir(path, interfaces, NULL, NULL))
goto out;
/* Process all known networking interfaces. */
util_list_iterate(interfaces, net) {
/* Skip loopback. */
if (strcmp(net->str, "lo") == 0)
continue;
selected = NULL;
/* Determine IP addresses. */
addrs = strlist_new();
add_ip_addresses(addrs, net->str);
if (util_list_is_empty(addrs))
goto next;
/* Get z Systems specific devices. */
selected = selected_dev_list_new();
if (select_by_interface(NULL, selected, config_active,
scope_mandatory, NULL, NULL,
net->str, err_ignore) != EXIT_OK)
goto next;
/* Process devices. */
util_list_iterate(selected, sel) {
if (sel->rc != EXIT_OK || !sel->st || !sel->id)
continue;
/* Add one resource per IP address provided. */
util_list_iterate(addrs, addr) {
res = resource_new(sel->st, sel->id, addr->str);
ptrlist_add(list, res);
}
/* Expand list to also contain prereq-devices. */
subtype_add_prereqs(sel->st, sel->id, selected);
}
next:
selected_dev_list_free(selected);
strlist_free(addrs);
}
out:
strlist_free(interfaces);
free(path);
}
/* Return a ptrlist of struct resources of Linux devices which are in use
* by the system. The following devices are considered:
* - block devices providing a mounted file system
* - block devices providing swap space
* - networking interfaces providing an IP address. */
static struct util_list *get_resources(void)
{
struct util_list *list;
list = ptrlist_new();
/* Add all devices providing mounted file systems. */
add_mounts(list);
/* Add all devices providing swap space. */
add_swap(list);
/* Add all devices providing networking interfaces in the up state. */
add_network(list);
return list;
}
/* Return a strlist of names of resources that are provided by device @dev
* or %NULL if device is not in use. */
struct util_list *inuse_get_resources(struct device *dev)
{
struct subtype *st = dev->subtype;
struct resource *res;
struct ptrlist_node *p;
struct util_list *list;
/* Filter out offline devices. */
if (subtype_online_get(st, dev, config_active) != 1)
return NULL;
/* Match device against list of devices that are in use. */
if (!resources)
resources = get_resources();
list = strlist_new();
util_list_iterate(resources, p) {
res = p->ptr;
if (res->st == dev->subtype && strcmp(res->id, dev->id) == 0)
strlist_add_unique(list, "%s", res->name);
}
/* Make handling of empty lists easier for caller. */
if (util_list_is_empty(list)) {
strlist_free(list);
list = NULL;
}
return list;
}

110
zdev/src/iscsi.c Normal file
View File

@@ -0,0 +1,110 @@
/*
* 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 <ctype.h>
#include <string.h>
#include "devnode.h"
#include "iscsi.h"
#include "misc.h"
#include "path.h"
/* Retrieve the IP address of the iSCSI initiator networking portal associated
* with block device @blkdev. */
static char *ip_from_blockdev(struct devnode *blkdev)
{
char *link, *curr, *end, *path = NULL, *ip = NULL;
link = devnode_readlink(blkdev);
if (!link)
return NULL;
/* ../../devices/platform/host0/session1/target0:0:0/0:0:0:1/... */
curr = strstr(link, "/devices/");
if (!curr)
goto out;
curr++;
/* curr=devices/platform/host0/session1/target0:0:0/0:0:0:1/... */
end = strstr(curr, "/session");
if (!end)
goto out;
*end = 0;
/* curr=devices/platform/host0 */
end = strstr(curr, "/host");
if (!end)
goto out;
end++;
/* curr=devices/platform/host0
* end=host0 */
path = path_get("/sys/%s/iscsi_host/%s/ipaddress", curr, end);
ip = misc_read_text_file(path, 0, err_ignore);
out:
free(path);
free(link);
return ip;
}
static struct devnode *netdev_from_ip(char *ip)
{
char *cmd, *text, *dev, *end;
struct devnode *devnode = NULL;
cmd = misc_asprintf("%s -o address show to %s 2>/dev/null", PATH_IP,
ip);
text = misc_read_cmd_output(cmd, 0, err_ignore);
if (!text)
goto out;
/* text=2: enccw0.0.f5f0 inet ... */
for (dev = text; *dev && !isspace(*dev); dev++);
/* dev= enccw0.0.f5f0 inet ... */
for (; isspace(*dev); dev++);
/* dev=enccw0.0.f5f0 inet ... */
for (end = dev; *end && !isspace(*end); end++);
if (end == dev)
goto out;
*end = 0;
/* dev=enccw0.0.f5f0 */
devnode = devnode_new(NETDEV, 0, 0, dev);
out:
free(cmd);
free(text);
return devnode;
}
/* Retrieve the networking device devnode that provides the specified
* iSCSI block device @blkdev. */
struct devnode *iscsi_get_net_devnode(struct devnode *blkdev)
{
struct devnode *devnode = NULL;
char *ip;
if (blkdev->type != BLOCKDEV)
return NULL;
ip = ip_from_blockdev(blkdev);
if (!ip)
return NULL;
devnode = netdev_from_ip(ip);
free(ip);
return devnode;
}

427
zdev/src/lcs.c Normal file
View File

@@ -0,0 +1,427 @@
/*
* 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 "attrib.h"
#include "ccw.h"
#include "ccwgroup.h"
#include "device.h"
#include "devtype.h"
#include "lcs.h"
#include "lcs_auto.h"
#include "misc.h"
#include "namespace.h"
#include "setting.h"
#define DEVNAME "LCS device"
/*
* LCS device ID namespace methods.
*/
static exit_code_t lcs_parse_devid(struct ccwgroup_devid *devid_ptr,
const char *id, err_t err)
{
struct ccwgroup_devid devid;
const char *reason = NULL;
exit_code_t rc;
rc = ccwgroup_parse_devid(&devid, id, err);
if (rc)
return rc;
if (devid.num > LCS_NUM_DEVS) {
reason = "Too many CCW device IDs specified";
rc = EXIT_INVALID_ID;
} else if (devid_ptr)
*devid_ptr = devid;
if (reason) {
err_t_print(err, "Error in %s ID format: %s: %s\n", DEVNAME,
reason, id);
}
return rc;
}
static exit_code_t lcs_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 = lcs_parse_devid(&from, from_str, err);
if (rc)
goto out;
rc = lcs_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 lcs_parse_devid_range_simple(struct ccwgroup_devid *from,
struct ccwgroup_devid *to,
const char *range)
{
if (lcs_parse_devid_range(from, to, range, err_ignore) == EXIT_OK)
return true;
return false;
}
static exit_code_t lcs_ns_is_id_valid(const char *id, err_t err)
{
return lcs_parse_devid(NULL, id, err);
}
static char *lcs_ns_normalize_id(const char *id)
{
struct ccwgroup_devid devid;
if (lcs_parse_devid(&devid, id, err_ignore) != EXIT_OK)
return NULL;
return ccwgroup_devid_to_str(&devid);
}
static void *lcs_ns_parse_id(const char *id, err_t err)
{
struct ccwgroup_devid *devid;
devid = misc_malloc(sizeof(struct ccwgroup_devid));
if (lcs_parse_devid(devid, id, err) != EXIT_OK) {
free(devid);
return NULL;
}
return devid;
}
static exit_code_t lcs_ns_is_id_range_valid(const char *range, err_t err)
{
return lcs_parse_devid_range(NULL, NULL, range, err);
}
static unsigned long lcs_ns_num_ids_in_range(const char *range)
{
struct ccwgroup_devid f, t;
if (!lcs_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;
}
static void lcs_ns_range_start(struct ns_range_iterator *it, const char *range)
{
struct ccwgroup_devid from, to;
if (!lcs_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);
}
static bool lcs_ns_is_id_blacklisted(const char *id)
{
struct ccwgroup_devid devid;
char *ccw_id;
unsigned int i;
bool result = false;
if (lcs_parse_devid(&devid, id, err_ignore) != EXIT_OK)
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 lcs_ns_unblacklist_id(const char *id)
{
struct ccwgroup_devid devid;
char *ccw_id;
unsigned int i;
if (lcs_parse_devid(&devid, id, err_ignore) != EXIT_OK)
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);
}
}
/*
* LCS device ID namespace.
*/
struct namespace lcs_namespace = {
.devname = DEVNAME,
.is_id_valid = lcs_ns_is_id_valid,
.is_id_similar = ccwgroup_is_id_similar,
.cmp_ids = ccwgroup_cmp_ids,
.normalize_id = lcs_ns_normalize_id,
.parse_id = lcs_ns_parse_id,
.cmp_parsed_ids = ccwgroup_cmp_parsed_ids,
.qsort_cmp = ccwgroup_qsort_cmp,
.is_id_range_valid = lcs_ns_is_id_range_valid,
.num_ids_in_range = lcs_ns_num_ids_in_range,
.is_id_in_range = ccwgroup_is_id_in_range,
.range_start = lcs_ns_range_start,
.range_next = ccwgroup_range_next,
/* Blacklist handling. */
.is_blacklist_active = ccw_is_blacklist_active,
.is_id_blacklisted = lcs_ns_is_id_blacklisted,
.is_id_range_blacklisted = ccw_is_id_range_blacklisted,
.unblacklist_id = lcs_ns_unblacklist_id,
.unblacklist_id_range = ccw_unblacklist_id_range,
.blacklist_persist = ccw_blacklist_persist,
};
/*
* LCS device attributes.
*/
static struct attrib lcs_attr_lancmd_timeout = {
.name = "lancmd_timeout",
.title = "Modify LAN command timeout",
.desc =
"Specify the time in seconds that the LCS driver waits for a reply\n"
"after issuing a LAN command to the LAN adapter.\n",
.defval = "5",
.accept = ACCEPT_ARRAY(ACCEPT_NUM_GE(1)),
};
static struct attrib lcs_attr_recover = {
.name = "recover",
.title = "Trigger device recovery",
.desc =
"Write '1' to this attribute to restart the recovery process for the\n"
"QETH device.\n",
.accept = ACCEPT_ARRAY(ACCEPT_NUM(1)),
.writeonly = 1,
.activeonly = 1,
};
/*
* LCS subtype methods.
*/
static exit_code_t lcs_st_is_definable(struct subtype *st, const char *id,
err_t err)
{
struct ccwgroup_subtype_data *data = st->data;
struct ccwgroup_devid devid;
exit_code_t rc;
rc = ccwgroup_parse_devid(&devid, id, err);
if (rc)
return rc;
if (subtype_device_exists_active(st, id))
return EXIT_OK;
if (devid.num == data->num_devs)
return lcs_auto_is_possible(&devid, err);
if (devid.num == 1)
return lcs_auto_get_devid(NULL, &devid.devid[0], err);
err_t_print(err, "Invalid number of CCW device IDs\n");
return EXIT_INVALID_ID;
}
/**
* device_detect_definable - Detect configuration of definable device
* @st: Device subtype
* @dev: Device
*
* Detect the full ID and default parameters for non-existing but definable
* device @dev and update active.definable. Return %EXIT_OK on success, or an
* error code otherwise.
*/
static exit_code_t lcs_st_detect_definable(struct subtype *st,
struct device *dev)
{
struct ccwgroup_devid *devid;
exit_code_t rc;
devid = dev->devid;
if (devid->num == 1) {
/* Detect possible group for this device. */
rc = lcs_auto_get_devid(devid, &devid->devid[0],
err_delayed_print);
if (rc) {
error("Auto-detection failed for %s %s\n"
"Please be sure to specify full CCWGROUP ID!\n",
st->devname, dev->id);
return rc;
}
free(dev->id);
dev->id = ccwgroup_devid_to_str(dev->devid);
}
dev->active.definable = 1;
return EXIT_OK;
}
static void lcs_st_add_definable_ids(struct subtype *st, struct util_list *ids)
{
lcs_auto_add_ids(ids);
}
/*
* LCS subtype.
*/
static struct ccwgroup_subtype_data lcs_data = {
.ccwgroupdrv = LCS_CCWGROUPDRV_NAME,
.ccwdrv = LCS_CCWDRV_NAME,
.rootdrv = LCS_ROOTDRV_NAME,
.mod = LCS_MOD_NAME,
.num_devs = LCS_NUM_DEVS,
};
static struct subtype lcs_subtype = {
.super = &ccwgroup_subtype,
.devtype = &lcs_devtype,
.name = "lcs",
.title = "LAN-Channel-Station (LCS) network devices",
.devname = DEVNAME,
.modules = STRING_ARRAY(LCS_MOD_NAME),
.namespace = &lcs_namespace,
.data = &lcs_data,
.dev_attribs = ATTRIB_ARRAY(
&ccw_attr_online,
&lcs_attr_lancmd_timeout,
&lcs_attr_recover,
),
.unknown_dev_attribs = 1,
.support_definable = 1,
.is_definable = &lcs_st_is_definable,
.detect_definable = &lcs_st_detect_definable,
.add_definable_ids = &lcs_st_add_definable_ids,
};
/*
* LCS devtype methods.
*/
/* Clean up all resources used by devtype object. */
static void lcs_devtype_exit(struct devtype *dt)
{
setting_list_free(dt->active_settings);
setting_list_free(dt->persistent_settings);
}
static exit_code_t lcs_devtype_read_settings(struct devtype *dt,
config_t config)
{
/* No kernel or module parameters exist for the lcs device driver,
* but at least determine module loaded state. */
dt->active_settings = setting_list_new();
dt->persistent_settings = setting_list_new();
if (SCOPE_ACTIVE(config))
dt->active_exists = devtype_is_module_loaded(dt);
return EXIT_OK;
}
static exit_code_t lcs_devtype_write_settings(struct devtype *dt,
config_t config)
{
/* No kernel or module parameters exist for the lcs device driver. */
return EXIT_OK;
}
/*
* LCS devtype.
*/
struct devtype lcs_devtype = {
.name = "lcs",
.title = "", /* Only use subtypes. */
.devname = "LCS",
.subtypes = SUBTYPE_ARRAY(
&lcs_subtype,
),
.type_attribs = ATTRIB_ARRAY(
),
.exit = &lcs_devtype_exit,
.read_settings = &lcs_devtype_read_settings,
.write_settings = &lcs_devtype_write_settings,
};

347
zdev/src/lcs_auto.c Normal file
View File

@@ -0,0 +1,347 @@
/*
* 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 <string.h>
#include "ccw.h"
#include "ccwgroup.h"
#include "ctc.h"
#include "device.h"
#include "lcs.h"
#include "lcs_auto.h"
#include "module.h"
#include "path.h"
struct cutype {
unsigned int cutype:16;
unsigned int cumodel:8;
};
static struct cutype lcs_cutypes[] = {
{ .cutype = 0x3088, .cumodel = 0x08, },
{ .cutype = 0x3088, .cumodel = 0x1f, },
{ .cutype = 0x3088, .cumodel = 0x60, },
};
/*
* LCS autodetection
*
* A LCS device must be grouped before it can be used. The following
* rules apply to grouping:
*
* 1. A LCS device can be grouped from 2 CCW devices
* a) Read device
* b) Write device
* 2. All CCW devices must be bound to the LCS CCW device driver. Note that
* due to an overlap in CU-Types, CCW device could also be bound to
* the LCS device driver.
* 3. The subchannel of all CCW devices must be defined with the same CHPID
* 4. None of the CCW devices is part of an existing CCWGROUP device
*/
/* Compare by: 1. CHPID, 2. CUTYPE, 3. DEVTYPE, 4. CCW device ID
* -1 = a < b 1 = a > b 0 = a == b. */
static int info_cmp(void *a, void *b, void *data)
{
struct ptrlist_node *pa = a, *pb = b;
struct ccw_devinfo *ia = pa->ptr, *ib = pb->ptr;
int r;
r = ccw_devinfo_chpids_cmp(ia, ib);
if (r)
return r;
r = ccw_devinfo_cutype_cmp(ia, ib);
if (r)
return r;
r = ccw_devinfo_devtype_cmp(ia, ib);
if (r)
return r;
return ccw_cmp_devids(&ia->devid, &ib->devid);
}
static bool is_compatible(struct ccw_devinfo *a, struct ccw_devinfo *b)
{
if (ccw_devinfo_chpids_cmp(a, b) == 0 &&
ccw_devinfo_cutype_cmp(a, b) == 0 &&
ccw_devinfo_devtype_cmp(a, b) == 0)
return true;
return false;
}
static bool is_lcs(struct ccw_devinfo *info)
{
unsigned int i;
/* Rule out devices which can be confirmed to be CTC devices. */
if (ctc_confirm(&info->devid))
return false;
for (i = 0; i < ARRAY_SIZE(lcs_cutypes); i++) {
if (info->cutype == lcs_cutypes[i].cutype &&
info->cumodel == lcs_cutypes[i].cumodel)
return true;
}
return false;
}
/* Add device info for all LCS CCW devices to ptrlist in data. */
static exit_code_t add_cb(const char *path, const char *filename, void *data)
{
struct ccw_devid devid;
struct util_list *infos = data;
struct ccw_devinfo *devinfo;
if (!strchr(filename, '.'))
return EXIT_OK;
if (!ccw_parse_devid_simple(&devid, filename))
return EXIT_OK;
devinfo = ccw_devinfo_get(&devid, 0);
if (devinfo->exists && is_lcs(devinfo))
ptrlist_add(infos, devinfo);
else
free(devinfo);
return EXIT_OK;
}
/* Return a sorted ptrlist of struct ccw_devinfos for all CCW devices
* bound to the lcs or ctc CCW device driver with matching CUTYPE.
* The result must be freed using ptrlist_free(,1); */
static struct util_list *read_sorted_lcs_devinfos(void)
{
struct util_list *infos;
char *path;
/* Get CHPID information for all devices handled by the LCS driver. */
infos = ptrlist_new();
/* Add CCW devices bound to the LCS CCW device driver. */
module_try_load_once(LCS_MOD_NAME, NULL);
path = path_get_sys_bus_drv(CCW_BUS_NAME, LCS_CCWDRV_NAME);
if (dir_exists(path))
path_for_each(path, add_cb, infos);
free(path);
/* Add CCW devices bound to the CTC CCW device driver. */
path = path_get_sys_bus_drv(CCW_BUS_NAME, CTC_CCWDRV_NAME);
if (dir_exists(path))
path_for_each(path, add_cb, infos);
free(path);
/* For each CHPID: Find groups. Add to result list. */
util_list_sort(infos, info_cmp, NULL);
return infos;
}
static void add_ccwgroup_devid(struct util_list *devids, struct ccw_devid *read,
struct ccw_devid *write)
{
struct ccwgroup_devid devid;
devid.devid[0] = *read;
devid.devid[1] = *write;
devid.num = LCS_NUM_DEVS;
ptrlist_add(devids, ccwgroup_copy_devid(&devid));
}
static void add_groupable_devids(struct util_list *devids,
struct util_list *infos)
{
struct ptrlist_node *curr, *next;
struct ccw_devinfo *r, *w;
/* For each CHPID: Find groups. Add to result list. */
curr = util_list_start(infos);
while (curr) {
next = util_list_next(infos, curr);
if (!next)
break;
r = curr->ptr;
w = next->ptr;
if (is_compatible(r, w) && EVEN(r->devid.devno) &&
ccw_devid_distance(&r->devid, &w->devid) == 1) {
add_ccwgroup_devid(devids, &r->devid, &w->devid);
curr = util_list_next(infos, next);
} else
curr = next;
}
}
/* Add CCWGROUP IDs of lcs devices that can be grouped to strlist @ids. */
void lcs_auto_add_ids(struct util_list *ids)
{
struct util_list *infos, *devids;
struct ptrlist_node *p;
char *id;
infos = read_sorted_lcs_devinfos();
devids = ptrlist_new();
add_groupable_devids(devids, infos);
util_list_iterate(devids, p) {
id = ccwgroup_devid_to_str(p->ptr);
strlist_add(ids, id);
free(id);
}
ptrlist_free(devids, 1);
ptrlist_free(infos, 1);
}
exit_code_t lcs_auto_get_devid(struct ccwgroup_devid *devid_ptr,
struct ccw_devid *ccw_devid, err_t err)
{
struct util_list *devids, *infos;
struct ptrlist_node *p, *read, *write;
struct ccw_devinfo *r, *w;
struct ccwgroup_devid *devid;
exit_code_t rc;
infos = read_sorted_lcs_devinfos();
/* Try to find an ID from the canonical auto-generated list. */
devids = ptrlist_new();
add_groupable_devids(devids, infos);
util_list_iterate(devids, p) {
devid = p->ptr;
if (ccw_cmp_devids(ccw_devid, &devid->devid[0]) != 0)
continue;
rc = EXIT_OK;
if (devid_ptr)
*devid_ptr = *devid;
goto out;
}
/* Try to create a CCWGROUP ID with the specified ID as read device. */
/* Get CCW device info for read device. */
read = NULL;
util_list_iterate(infos, read) {
r = read->ptr;
if (ccw_cmp_devids(&r->devid, ccw_devid) == 0)
break;
}
if (!read) {
err_t_print(err, "Read CCW device not found\n");
rc = EXIT_GROUP_NOT_FOUND;
goto out;
}
if (!EVEN(r->devid.devno)) {
err_t_print(err, "Read CCW device number not even\n");
rc = EXIT_GROUP_INVALID;
goto out;
}
/* Get CCW device ID for write device. */
write = util_list_next(infos, read);
if (!write) {
err_t_print(err, "Write CCW device not found\n");
rc = EXIT_GROUP_NOT_FOUND;
goto out;
}
w = write->ptr;
if (!is_compatible(r, w)) {
err_t_print(err, "No compatible write CCW device found\n");
rc = EXIT_GROUP_INVALID;
goto out;
}
if (ccw_devid_distance(&r->devid, &w->devid) != 1) {
err_t_print(err, "Write CCW device ID must be read plus one\n");
rc = EXIT_GROUP_INVALID;
goto out;
}
rc = EXIT_OK;
if (devid_ptr) {
devid_ptr->devid[0] = r->devid;
devid_ptr->devid[1] = w->devid;
devid_ptr->num = LCS_NUM_DEVS;
}
out:
ptrlist_free(devids, 1);
ptrlist_free(infos, 1);
return rc;
}
exit_code_t lcs_auto_is_possible(struct ccwgroup_devid *devid, err_t err)
{
struct ccw_devinfo *info[LCS_NUM_DEVS];
unsigned int i;
char *ccwid;
const char *msg;
exit_code_t rc;
if (devid->num < LCS_NUM_DEVS) {
err_t_print(err, "Not enough CCW device IDs in LCS device "
"ID\n");
return EXIT_INCOMPLETE_ID;
}
if (devid->num > LCS_NUM_DEVS) {
err_t_print(err, "LCS device ID contains too many CCW device "
"IDs\n");
return EXIT_INVALID_ID;
}
if (ccw_devid_distance(&devid->devid[0], &devid->devid[1]) != 1) {
err_t_print(err, "Write device ID must be read plus one\n");
return EXIT_GROUP_INVALID;
}
if (!EVEN(devid->devid[0].devno)) {
err_t_print(err, "Device number of read device ID must be "
"even\n");
return EXIT_GROUP_INVALID;
}
for (i = 0; i < ARRAY_SIZE(info); i++)
info[i] = ccw_devinfo_get(&devid->devid[i], 1);
rc = EXIT_OK;
msg = NULL;
for (i = 0; i < ARRAY_SIZE(info); i++) {
if (!info[i]->exists) {
msg = "CCW device %s does not exist\n";
rc = EXIT_GROUP_NOT_FOUND;
} else if (info[i]->grouped) {
msg = "CCW device %s already grouped\n";
rc = EXIT_GROUP_ALREADY;
} else if (i > 0 &&
ccw_devinfo_chpids_cmp(info[i - 1], info[i]) != 0) {
msg = "CCW device %s is not on the same CHPID\n";
rc = EXIT_GROUP_INVALID;
} else if (i > 0 &&
ccw_devinfo_cutype_cmp(info[i - 1], info[i]) != 0) {
msg = "CUTYPE of CCW device %s differs\n";
rc = EXIT_GROUP_INVALID;
} else if (i > 0 &&
ccw_devinfo_devtype_cmp(info[i - 1], info[i]) != 0) {
msg = "DEVTYPE of CCW device %s differs\n";
rc = EXIT_GROUP_INVALID;
}
if (!msg)
continue;
ccwid = ccw_devid_to_str(&devid->devid[i]);
err_t_print(err, msg, ccwid);
free(ccwid);
break;
}
for (i = 0; i < ARRAY_SIZE(info); i++)
free(info[i]);
return rc;
}

1538
zdev/src/lszdev.c Normal file

File diff suppressed because it is too large Load Diff

45
zdev/src/lszdev_usage.txt Normal file
View File

@@ -0,0 +1,45 @@
Usage: lszdev [TYPE] [DEVICE] [SELECTION] [ACTION] [OPTIONS]
Use lszdev to view the configuration of z Systems specific devices in either:
- active configuration (running system), or
- persistent configuration (configuration files)
Actions apply to both configurations unless specified otherwise.
TYPE
Device type to which this command applies. Use --list-types to display
supported types.
DEVICE
ID Select single device by ID, e.g. 0.0.1234
FROM-TO Select range of devices between FROM and TO
DEV1,DEV2,... Select list of devices or device ranges
SELECTION
--all Select all existing and configured devices (default)
--configured Select devices with a persistent configuration
--existing Select devices found in the active configuration
--online/--offline Select devices that are online/offline
--failed Select devices that are not functioning correctly
--by-path PATH Select device providing file system path, e.g. /usr
--by-node NODE Select device providing device node, e.g. /dev/sda
--by-interface NAME Select device providing network interface, e.g. eth0
--by-attrib KEY=VALUE Select devices with specified attribute value
ACTIONS
-i, --info Display detailed information
-l, --list-columns List available output columns
-L, --list-types List supported device types
-h, --help Print usage information, then exit
-v, --version Print version information, then exit
OPTIONS
-a, --active Only show data from the active configuration
-p, --persistent Only show data from the persistent configuration
-t, --type List information about device type
-c, --columns COLUMNS Specify comma-separated list of columns to display
-n, --no-headings Do not print column headings
--base PATH Use PATH as base for accessing files
--pairs Produce output in KEY="VALUE" format
-V, --verbose Print additional run-time information
-q, --quiet Print only minimal run-time information

1753
zdev/src/misc.c Normal file

File diff suppressed because it is too large Load Diff

424
zdev/src/modprobe.c Normal file
View File

@@ -0,0 +1,424 @@
/*
* 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 <ctype.h>
#include <errno.h>
#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "attrib.h"
#include "misc.h"
#include "modprobe.h"
#include "path.h"
#include "setting.h"
/**
* struct modprobe_line - Representation of a line in a modprobe.conf file
* @node: List node for adding to list
* @line: Original line contents or NULL if unavailable
* @argv: Array of command arguments
* @argc: Count of command arguments
*/
struct modprobe_line {
struct util_list_node node;
char *line;
char **argv;
int argc;
};
/**
* struct modprobe_file - Representation of a modprobe.conf file
* @path: Full path and filename of modprobe.conf file
* @list: List of lines in file
*/
struct modprobe_file {
char *path;
struct util_list lines;
};
/* Used for debugging. */
void modprobe_file_print(struct modprobe_file *mf)
{
struct modprobe_line *l;
int i;
printf("modprobe_file(%p)\n", (void *) mf);
printf(" path='%s'\n", mf->path);
util_list_iterate(&mf->lines, l) {
printf(" line(%p)\n", (void *) l);
printf(" line='%s'\n", l->line);
printf(" argc=%d\n", l->argc);
for (i = 0; i < l->argc; i++)
printf(" argv[%d]='%s'\n", i, l->argv[i]);
}
}
/* Remove the line continuation character \\ at end of line. */
static void remove_linecont(char *line)
{
int i;
for (i = 0; line[i]; i++) {
if (line[i] == '\\' && line[i + 1] == '\n')
line[i++] = ' ';
}
}
/* Allocate and initialize a struct modprobe_line from a line of text. */
static void set_line_content(struct modprobe_line *m, char *line)
{
char c, *copy;
m->line = misc_strdup(line);
if (sscanf(line, " %c", &c) != 1) {
/* Empty line. */
m->argv = misc_malloc(sizeof(char *));
m->argv[0] = misc_strdup("");
m->argc = 1;
} else if (c == '#') {
/* Comment line. */
m->argv = misc_malloc(sizeof(char *));
m->argv[0] = misc_strdup("#");
m->argc = 1;
} else {
/* Line containing a command. */
copy = misc_strdup(line);
remove_linecont(copy);
line_split(copy, &m->argc, &m->argv);
free(copy);
}
}
/* Allocate and initialize a struct modprobe_line from a line of text. */
static struct modprobe_line *modprobe_line_new(char *line)
{
struct modprobe_line *m;
m = misc_malloc(sizeof(struct modprobe_line));
set_line_content(m, line);
return m;
}
/* Release all resources associated with a modprobe_line. */
static void modprobe_line_free(struct modprobe_line *m)
{
int i;
if (!m)
return;
free(m->line);
for (i = 0; i < m->argc; i++)
free(m->argv[i]);
free(m->argv);
free(m);
}
/* Create and initialize a new modprobe_file. */
static struct modprobe_file *modprobe_file_new(const char *path)
{
struct modprobe_file *file;
file = misc_malloc(sizeof(struct modprobe_file));
file->path = misc_strdup(path);
util_list_init(&file->lines, struct modprobe_line, node);
return file;
}
/* Release resources used by file. */
static void modprobe_file_free(struct modprobe_file *file)
{
struct modprobe_line *l, *n;
if (!file)
return;
free(file->path);
util_list_iterate_safe(&file->lines, l, n) {
util_list_remove(&file->lines, l);
modprobe_line_free(l);
}
free(file);
}
/* Add a new line to the file. */
static void modprobe_file_add(struct modprobe_file *file,
struct modprobe_line *line)
{
util_list_add_tail(&file->lines, line);
}
#define MODPROBE_MAX_LINE 2048
/* Read a modprobe.conf file and return a modprobe_file. */
static exit_code_t modprobe_read(const char *path, struct modprobe_file **mf)
{
char line[MODPROBE_MAX_LINE];
char *l, *last;
int len;
FILE *fd;
struct modprobe_file *mfile;
debug("Reading udev file %s\n", path);
mfile = modprobe_file_new(path);
fd = misc_fopen(path, "r");
if (!fd) {
error("Could not read file %s: %s\n", path, strerror(errno));
modprobe_file_free(mfile);
return EXIT_RUNTIME_ERROR;
}
last = NULL;
while (fgets(line, sizeof(line), fd)) {
if (last) {
/* The previous line had a continuation mark. */
l = misc_asprintf("%s%s", last, line);
free(last);
last = NULL;
} else
l = misc_strdup(line);
len = strlen(l);
if (len > 2 && line[len - 1] == '\n' && line[len - 2] == '\\') {
/* This line is continued on the next one. */
last = l;
continue;
}
modprobe_file_add(mfile, modprobe_line_new(l));
free(l);
}
if (last) {
/* Handle last line with a broken continuation mark
* gracefully. */
modprobe_file_add(mfile, modprobe_line_new(last));
free(last);
}
if (misc_fclose(fd))
warn("Could not close file %s: %s\n", path, strerror(errno));
*mf = mfile;
return EXIT_OK;
}
/* Check modprobe file for a leading chzdev comment. */
static bool find_chzdev_comment(struct modprobe_file *mf)
{
struct modprobe_line *l;
util_list_iterate(&mf->lines, l) {
/* Newly created line at start. */
if (!l->line)
return false;
/* Skip empty line. */
if (strlen(l->argv[0]) == 0)
continue;
/* Non-comment line. */
if (strcmp(l->argv[0], "#") != 0)
return false;
/* Check for chzdev comment. */
if (strstr(l->line, "chzdev"))
return true;
}
return false;
}
/* Write a modprobe.conf file. */
static exit_code_t modprobe_write(struct modprobe_file *mf)
{
FILE *fd;
struct modprobe_line *l;
int i;
debug("Writing udev file %s\n", mf->path);
fd = misc_fopen(mf->path, "w");
if (!fd) {
error("Could not write to file %s: %s\n", mf->path,
strerror(errno));
return EXIT_RUNTIME_ERROR;
}
/* Add leading comment. */
if (!find_chzdev_comment(mf))
fprintf(fd, "# Generated by chzdev\n");
util_list_iterate(&mf->lines, l) {
if (l->line) {
/* Use existing line. */
fprintf(fd, "%s", l->line);
} else {
/* Create line by concatenating arguments. */
for (i = 0; i < l->argc; i++) {
fprintf(fd, "%s%s", i == 0 ? "" : " ",
l->argv[i]);
}
fprintf(fd, "\n");
}
}
if (misc_fclose(fd)) {
warn("Could not close file %s: %s\n", mf->path,
strerror(errno));
}
return EXIT_OK;
}
/* Convert a single modprobe.conf option argument into a newly allocated
* struct setting. */
static struct setting *arg_to_setting(struct attrib **attribs, char *arg)
{
struct setting *s;
struct attrib *a;
char *key;
char *val;
key = misc_strdup(arg);
val = strchr(key, '=');
if (val) {
*val = 0;
val++;
} else {
/* Assume boolean parameter. */
val = "1";
}
/* Get attribute pointer for known attributes. */
a = attrib_find(attribs, key);
s = setting_new(a, key, val);
free(key);
return s;
}
/* Return a newly allocated struct setting_list for module options found
* in modprobe.conf file for specified module name. */
static struct setting_list *modprobe_get_settings(struct modprobe_file *mf,
const char *mod,
struct attrib **attribs)
{
struct setting_list *sl;
struct modprobe_line *l;
struct setting *s;
int i;
sl = setting_list_new();
util_list_iterate(&mf->lines, l) {
/* argv[]="option", "<mod>", "<key>=<value>", ... */
if (l->argc < 3)
continue;
if (strcmp(l->argv[0], "options") != 0)
continue;
if (strcmp(l->argv[1], mod) != 0)
continue;
for (i = 2; i < l->argc; i++) {
s = arg_to_setting(attribs, l->argv[i]);
setting_list_add(sl, s);
}
}
return sl;
}
/* Apply settings list as module parameters to modprobe file. */
static void modprobe_apply_settings(struct modprobe_file *mf, const char *mod,
struct setting_list *sl)
{
struct modprobe_line *l, *n;
struct setting *s;
unsigned long num;
/* Remove all lines containing parameters for this module. */
util_list_iterate_safe(&mf->lines, l, n) {
if (l->argc < 2)
continue;
if (strcmp(l->argv[0], "options") != 0)
continue;
if (strcmp(l->argv[1], mod) != 0)
continue;
util_list_remove(&mf->lines, l);
modprobe_line_free(l);
}
/* Add new line with specified settings. */
num = 0;
util_list_iterate(&sl->list, s) {
if (setting_is_set(s))
num++;
}
if (num == 0)
return;
l = misc_malloc(sizeof(struct modprobe_line));
l->argv = misc_malloc(sizeof(char *) * (num + 2));
l->argv[0] = misc_strdup("options");
l->argv[1] = misc_strdup(mod);
l->argc = 2;
util_list_iterate(&sl->list, s) {
if (!setting_is_set(s))
continue;
l->argv[(l->argc)++] = misc_asprintf("%s=%s", s->name,
s->value);
}
modprobe_file_add(mf, l);
}
/* Read attribute settings from a modprobe.conf file into a newly
* allocated struct setting_list. */
exit_code_t modprobe_read_settings(const char *path, const char *mod,
struct attrib **attribs,
struct setting_list **settings)
{
struct modprobe_file *mf;
exit_code_t rc;
if (!file_exists(path)) {
*settings = NULL;
return EXIT_OK;
}
rc = modprobe_read(path, &mf);
if (rc)
return rc;
*settings = modprobe_get_settings(mf, mod, attribs);
modprobe_file_free(mf);
return EXIT_OK;
}
/* Write attribute settings to a modprobe.conf file.*/
exit_code_t modprobe_write_settings(const char *path, const char *mod,
struct setting_list *settings)
{
struct modprobe_file *mf;
exit_code_t rc;
unsigned long lines;
if (file_exists(path)) {
rc = modprobe_read(path, &mf);
if (rc)
return rc;
} else {
rc = path_create(path);
if (rc)
return rc;
mf = modprobe_file_new(path);
}
modprobe_apply_settings(mf, mod, settings);
lines = util_list_len(&mf->lines);
if (lines == 0 || (lines == 1 && find_chzdev_comment(mf))) {
/* Do not write empty files. */
if (file_exists(path))
rc = remove_file(path);
} else
rc = modprobe_write(mf);
modprobe_file_free(mf);
return rc;
}

305
zdev/src/module.c Normal file
View File

@@ -0,0 +1,305 @@
/*
* 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 <dirent.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include "attrib.h"
#include "misc.h"
#include "module.h"
#include "path.h"
#include "setting.h"
#include "udev.h"
static struct util_list *tried_loading;
static int suppress_module_load;
/* Allow suppression of module loading. */
void module_load_suppress(int state)
{
suppress_module_load = 1;
}
/* Check if a module is currently loaded. */
bool module_loaded(const char *mod)
{
char *path = path_get_sys_module(mod);
bool rc;
rc = dir_exists(path);
free(path);
return rc;
}
static int module_get_refcnt(const char *mod)
{
char *path, *refcnt_path, *text;
int refcnt = 0;
path = path_get_sys_module(mod);
refcnt_path = misc_asprintf("%s/refcnt", path);
text = misc_read_text_file(refcnt_path, 1, err_delayed_print);
if (text)
refcnt = atoi(text);
free(refcnt_path);
free(path);
return refcnt;
}
/* Attempt to unload specified kernel module. */
static exit_code_t module_unload(const char *mod, err_t err)
{
char *mp;
int rc;
if (!dryrun && module_get_refcnt(mod) > 0) {
err_t_print(err, "Cannot unload module %s: Module is in use\n",
mod);
return EXIT_MOD_BUSY;
}
mp = path_get_modprobe();
rc = misc_system(err, "%s -r %s", mp, mod);
free(mp);
if (rc != 0)
return EXIT_MOD_UNLOAD_FAILED;
return EXIT_OK;
}
/* Attempt to load kernel module with specified parameters. PARAMS may be
* NULL to load the module with default parameters. */
static exit_code_t do_load(const char *mod, char *params, err_t err)
{
char *empty_file;
char *mp;
exit_code_t rc;
rc = EXIT_OK;
mp = path_get_modprobe();
if (params) {
/* Note: We need to pass an empty configuration file to
* modprobe or the persistent parameters in /etc/modprobe.d
* would always overwrite the specified parameters. */
rc = misc_mktemp(&empty_file, NULL);
if (rc)
goto out;
if (misc_system(err, "%s %s %s -C %s %s", mp, mod, params,
empty_file,
err == err_ignore ? " 2>/dev/null" : ""))
rc = EXIT_MOD_LOAD_FAILED;
remove_file(empty_file);
free(empty_file);
} else {
if (misc_system(err, "%s %s %s", mp, mod,
err == err_ignore ? " 2>/dev/null" : ""))
rc = EXIT_MOD_LOAD_FAILED;
}
out:
free(mp);
return rc;
}
/* Apply kernel module parameters. */
exit_code_t module_load(const char *mod, const char **deps,
struct setting_list *settings, err_t err)
{
char *params;
struct util_list *unloaded = NULL;
struct strlist_node *s;
exit_code_t rc;
int i;
if (suppress_module_load)
return EXIT_OK;
/* Unload modules depending on mod. */
if (deps) {
unloaded = strlist_new();
for (i = 0; deps[i]; i++) {
if (!module_loaded(deps[i]) && !dryrun)
continue;
rc = module_unload(deps[i], err);
if (rc)
goto out;
strlist_add(unloaded, "%s", deps[i]);
}
}
if (module_loaded(mod) || dryrun) {
rc = module_unload(mod, err);
if (rc)
goto out;
}
params = settings ? setting_list_flatten(settings) : NULL;
rc = do_load(mod, params, err);
free(params);
if (rc == EXIT_OK && unloaded) {
/* Re-load modules depending on mod. */
util_list_iterate(unloaded, s) {
rc = do_load(s->str, "", err);
if (rc)
break;
}
}
out:
strlist_free(unloaded);
return rc;
}
struct add_setting_data {
struct setting_list *list;
struct attrib **attribs;
};
/* Add all attribute values to settings list. */
static exit_code_t add_setting(const char *path, const char *name, void *data)
{
struct add_setting_data *sdata = data;
struct setting_list *list = sdata->list;
struct attrib **attribs = sdata->attribs;
struct attrib *a;
char *value;
value = misc_read_text_file(path, 1, err_print);
if (!value)
return EXIT_RUNTIME_ERROR;
if (strcmp(value, "(null)") == 0)
goto out;
a = attrib_find(attribs, name);
setting_list_apply_actual(list, a, name, value);
out:
free(value);
return EXIT_OK;
}
/* Retrieve currently active module parameters. */
exit_code_t module_get_params(const char *mod, struct attrib **attribs,
struct setting_list **settings)
{
struct add_setting_data data;
struct setting_list *list = NULL;
char *path;
exit_code_t rc = EXIT_OK;
if (!module_loaded(mod))
goto out;
list = setting_list_new();
path = path_get_sys_module_param(mod, NULL);
data.list = list;
data.attribs = attribs;
rc = path_for_each(path, add_setting, &data);
free(path);
out:
if (rc == EXIT_OK)
*settings = list;
else
setting_list_free(list);
return rc;
}
/* Try to load a kernel module once. If @path is not %NULL, don't try loading
* the module if @path exists. */
void module_try_load_once(const char *mod, const char *path)
{
if (suppress_module_load)
return;
if (tried_loading) {
if (strlist_find(tried_loading, mod))
return;
} else
tried_loading = strlist_new();
strlist_add(tried_loading, mod);
if (path && path_exists(path))
return;
if (module_loaded(mod))
return;
verb("Loading required kernel module: %s\n", mod);
if (module_load(mod, NULL, NULL, err_ignore))
verb("Failed to load kernel module: %s\n", mod);
else {
/* Let udev rules apply. */
udev_settle();
}
}
/* Release any global memory. */
void module_exit(void)
{
strlist_free(tried_loading);
}
/* Apply module parameters for kernel module @mod found in @settings via
* /sys/module/../parameters. Return %true if all modified settings
* could be set via this method. */
bool module_set_params(const char *mod, struct setting_list *settings)
{
struct setting *s;
char *path;
const char *value;
bool result;
exit_code_t rc;
/* First check if all modified settings can be applied this way. */
util_list_iterate(&settings->list, s) {
if (!s->modified && !s->removed) {
/* Nothing to do. */
continue;
}
if (!s->attrib || !s->attrib->nounload) {
/* Attribute does not support setting via Sysfs. */
return false;
}
if (s->removed && !s->attrib->defval) {
/* Cannot remove setting with no default value. */
return false;
}
path = path_get_sys_module_param(mod, s->name);
result = file_writable(path);
free(path);
if (!result) {
/* Sysfs file is not writable. */
return false;
}
}
/* Apply settings. */
util_list_iterate(&settings->list, s) {
if (s->removed)
value = s->attrib->defval;
else if (s->modified)
value = s->value;
else {
/* Nothing to do. */
continue;
}
path = path_get_sys_module_param(mod, s->name);
rc = misc_write_text_file(path, value, err_ignore);
free(path);
if (rc)
return false;
}
return true;
}

174
zdev/src/namespace.c Normal file
View File

@@ -0,0 +1,174 @@
/*
* 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 <stddef.h>
#include <stdlib.h>
#include "ccw.h"
#include "ctc.h"
#include "devtype.h"
#include "lcs.h"
#include "namespace.h"
#include "qeth.h"
#include "zfcp_lun.h"
struct namespace *namespaces[] = {
&ccw_namespace,
&zfcp_lun_namespace,
&qeth_namespace,
&ctc_namespace,
&lcs_namespace,
NULL,
};
static int ns_modified[NUM_NAMESPACES];
/* Return the index of NS in the namespaces array. */
int namespaces_index(struct namespace *ns)
{
int i;
for (i = 0; namespaces[i]; i++) {
if (namespaces[i] == ns)
return i;
}
/* Should not happen. */
return -1;
}
/* Check if the specified string is a valid ID for any known namespace. */
bool namespaces_is_id_valid(const char *id)
{
struct namespace *ns;
int i;
for (i = 0; (ns = namespaces[i]); i++) {
if (ns_is_id_valid(ns, id))
return true;
}
return false;
}
/* Check if the specified string is a valid ID range for any known namespace. */
bool namespaces_is_id_range_valid(const char *range)
{
struct namespace *ns;
int i;
for (i = 0; (ns = namespaces[i]); i++) {
if (ns_is_id_range_valid(ns, range))
return true;
}
return false;
}
/* Check known subtypes of the same namespace for the existence of a device
* with the specified ID. Return pointer to first matching subtype found. */
bool namespaces_device_exists(struct namespace *ns, const char *id,
config_t config, struct subtype **st_ptr)
{
int i, j;
struct devtype *dt;
struct subtype *st;
for (i = 0; (dt = devtypes[i]); i++) {
for (j = 0; (st = dt->subtypes[j]); j++) {
if (st->namespace != ns)
continue;
if (subtype_device_exists(st, id, config)) {
if (st_ptr)
*st_ptr = st;
return true;
}
}
}
return false;
}
/* Mark a namespace as modified. */
void namespace_set_modified(struct namespace *target)
{
struct namespace *ns;
int i;
for (i = 0; (ns = namespaces[i]); i++) {
if (ns == target) {
ns_modified[i] = 1;
break;
}
}
}
/* Make all modified blacklists persistent. */
exit_code_t namespace_exit(void)
{
struct namespace *ns, *ns_done;
int i, j;
exit_code_t rc, drc = EXIT_OK;
for (i = 0; (ns = namespaces[i]); i++) {
if (!ns->blacklist_persist || !ns_modified[i])
continue;
/* In case of shared blacklist persist functions, call function
* only once. */
for (j = 0; j < i; j++) {
ns_done = namespaces[j];
if (!ns_modified[j])
continue;
if (ns->blacklist_persist == ns_done->blacklist_persist)
break;
}
if (j < i)
continue;
rc = ns->blacklist_persist();
if (rc && !drc)
drc = rc;
}
return drc;
}
/* Return a newly allocated and initialized namespace iterator object. */
struct ns_range_iterator *ns_range_iterator_new(void)
{
return misc_malloc(sizeof(struct ns_range_iterator));
}
/* Release all resources associated the specified namespace iterator object. */
void ns_range_iterator_free(struct ns_range_iterator *it)
{
free(it->devid);
free(it->devid_last);
free(it->id);
free(it);
}
/* Check if the specified @id is valid for the given namespace @ns. */
bool ns_is_id_valid(struct namespace *ns, const char *id)
{
if (ns->is_id_valid(id, err_ignore) == EXIT_OK)
return true;
return false;
}
/* Check if the specified @range is valid for the given namespace @ns. */
bool ns_is_id_range_valid(struct namespace *ns, const char *range)
{
if (ns->is_id_range_valid(range, err_ignore) == EXIT_OK)
return true;
return false;
}

110
zdev/src/net.c Normal file
View File

@@ -0,0 +1,110 @@
/*
* 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 <ctype.h>
#include <stdbool.h>
#include <string.h>
#include "devnode.h"
#include "misc.h"
#include "net.h"
#include "path.h"
struct add_linked_cb_data {
struct util_list *list;
const char *prefix;
size_t prefix_len;
bool result;
};
/* Add a devnode to data->list for each sysfs link that indicates a linked
* device. */
static exit_code_t add_linked_cb(const char *abs_path, const char *rel_path,
void *data)
{
struct add_linked_cb_data *cb_data = data;
const char *name;
struct devnode *d;
if (starts_with(rel_path, cb_data->prefix)) {
name = rel_path + cb_data->prefix_len;
d = devnode_new(NETDEV, 0, 0, name);
ptrlist_add(cb_data->list, d);
cb_data->result = true;
}
return EXIT_OK;
}
/* Add devnodes for all networking devices that are linked to @devnode via
* a link starting with @prefix to @list. */
static bool add_devnodes_from_link(struct util_list *list,
struct devnode *devnode, const char *prefix)
{
struct add_linked_cb_data cb_data;
char *path;
cb_data.list = list;
cb_data.prefix = prefix;
cb_data.prefix_len = strlen(prefix);
cb_data.result = false;
path = path_get_sys_class("net", devnode->name);
path_for_each(path, add_linked_cb, &cb_data);
free(path);
return cb_data.result;
}
/* Add devnodes for all devices to @list that are linked as "lower" devices
* of network interface @devnode. */
bool net_add_linked_devnodes(struct util_list *list, struct devnode *devnode)
{
return add_devnodes_from_link(list, devnode, "lower_");
}
#define DEVICE_PREFIX "Device:"
/* If @devnode refers to a vlan device, add a devnode representing its
* base device to @list. */
bool net_add_vlan_base(struct util_list *list, struct devnode *devnode)
{
char *path, *text, *name, *end;
bool rc = false;
path = path_get("/proc/net/vlan/%s", devnode->name);
text = misc_read_text_file(path, 0, err_ignore);
if (!text)
goto out;
name = strstr(text, DEVICE_PREFIX);
if (!name)
goto out;
name += sizeof(DEVICE_PREFIX) - 1;
for (; *name && isspace(*name); name++) ;
for (end = name; *end && !isspace(*end); end++) ;
if (name == end)
goto out;
*end = 0;
ptrlist_add(list, devnode_new(NETDEV, 0, 0, name));
rc = true;
out:
free(text);
free(path);
return rc;
}
/* If @devnode refers to a bonding device, add a devnode representing its
* base device to @list. */
bool net_add_bonding_base(struct util_list *list, struct devnode *devnode)
{
return add_devnodes_from_link(list, devnode, "slave_");
}

151
zdev/src/nic.c Normal file
View File

@@ -0,0 +1,151 @@
/*
* 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 "misc.h"
#include "nic.h"
#include "path.h"
/* Determine NIC data for the specified NIC. */
bool nic_data_get(const char *id, struct nic_data *data_ptr)
{
struct nic_data data;
char *cmd, *vmcp;
char **argv = NULL;
int argc = 0;
bool result = false;
cmd = misc_asprintf("%s query virtual nic %s 2>/dev/null", PATH_VMCP,
id);
vmcp = misc_read_cmd_output(cmd, 0, 1);
if (!vmcp)
goto out;
line_split(vmcp, &argc, &argv);
/* Type. */
if (argc < 4)
goto out;
if (strcmp(argv[3], "QDIO") == 0)
data.type = nic_qdio;
else if (strcmp(argv[3], "HIPERS") == 0)
data.type = nic_hipers;
else if (strcmp(argv[3], "IEDN") == 0)
data.type = nic_iedn;
else if (strcmp(argv[3], "INMN") == 0)
data.type = nic_inmn;
else
goto out;
/* Target. */
if (argc < 13)
goto out;
if (strcmp(argv[10], "VSWITCH:") == 0)
data.target = nic_vswitch;
else if (strcmp(argv[10], "LAN:") == 0)
data.target = nic_lan;
else
goto out;
strncpy(data.owner, argv[11], sizeof(data.owner));
strncpy(data.name, argv[12], sizeof(data.name));
result = true;
*data_ptr = data;
out:
line_free(argc, argv);
free(vmcp);
free(cmd);
return result;
}
/* Used for debugging. */
void nic_data_print(struct nic_data *data, int level)
{
printf("%*snic_data at %p\n", level, "", (void *) data);
level += 2;
printf("%*stype=%d\n", level, "", data->type);
printf("%*starget=%d\n", level, "", data->target);
printf("%*starget owner=%s\n", level, "", data->owner);
printf("%*starget name=%s\n", level, "", data->name);
}
/* Determine layer2 setting required for the specified vswitch. */
bool nic_vswitch_get_layer2(const char *name, int *layer2)
{
char *cmd, *vmcp;
char **argv = NULL;
int argc = 0;
bool result = false;
cmd = misc_asprintf("%s query vswitch %s 2>/dev/null", PATH_VMCP, name);
vmcp = misc_read_cmd_output(cmd, 0, 1);
if (!vmcp)
goto out;
line_split(vmcp, &argc, &argv);
if (argc < 12)
goto out;
if (strcmp(argv[11], "ETHERNET") == 0)
*layer2 = 1;
else if (strcmp(argv[11], "NONROUTER") == 0 ||
strcmp(argv[11], "PRIROUTER") == 0 ||
strcmp(argv[11], "IP") == 0)
*layer2 = 0;
else
goto out;
result = true;
out:
line_free(argc, argv);
free(vmcp);
free(cmd);
return result;
}
/* Determine layer2 setting required for the specified guest lan. */
bool nic_lan_get_layer2(const char *name, const char *owner, int *layer2)
{
char *cmd, *vmcp;
char **argv = NULL;
int argc = 0;
bool result = false;
if (strcmp(name, "*") == 0)
return false;
cmd = misc_asprintf("%s query lan %s owner %s 2>/dev/null", PATH_VMCP,
name, owner);
vmcp = misc_read_cmd_output(cmd, 0, 1);
if (!vmcp)
goto out;
line_split(vmcp, &argc, &argv);
if (argc < 12)
goto out;
if (strcmp(argv[11], "ETHERNET") == 0)
*layer2 = 1;
else if (strcmp(argv[11], "IP") == 0)
*layer2 = 0;
else
goto out;
result = true;
out:
line_free(argc, argv);
free(vmcp);
free(cmd);
return result;
}

55
zdev/src/opts.c Normal file
View File

@@ -0,0 +1,55 @@
/*
* 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 <getopt.h>
#include "misc.h"
#include "opts.h"
static const char *get_name(const struct option *opt_list, int op)
{
int i;
for (i = 0; opt_list[i].name; i++) {
if (opt_list[i].val == op)
break;
}
return opt_list[i].name;
}
exit_code_t opts_check_conflict(int op, int selected[OPTS_MAX + 1],
struct opts_conflict *conf_list,
const struct option *opt_list)
{
int i, j, b;
for (i = 0; conf_list[i].op; i++) {
for (j = 0; conf_list[i].conflicts[j]; j++) {
if (conf_list[i].op == op &&
selected[conf_list[i].conflicts[j]]) {
b = conf_list[i].conflicts[j];
goto err;
}
if (conf_list[i].conflicts[j] == op &&
selected[conf_list[i].op]) {
b = conf_list[i].op;
goto err;
}
}
}
return EXIT_OK;
err:
error("Cannot specify '--%s' together with '--%s'\n",
get_name(opt_list, op), get_name(opt_list, b));
return EXIT_USAGE_ERROR;
}

396
zdev/src/path.c Normal file
View File

@@ -0,0 +1,396 @@
/*
* 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 <dirent.h>
#include <errno.h>
#include <inttypes.h>
#include <stdarg.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
#include "devtype.h"
#include "misc.h"
#include "path.h"
#include "zfcp.h"
#include "zfcp_lun.h"
#define PATH_MODE (S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH)
struct base_prefix {
char *from;
char *to;
};
/* prlist of struct base_prefix sorted descending by prefix length. */
static struct util_list *base_prefixes;
/* Add an entry to the prefix list. */
static void prefix_add(const char *key, const char *value)
{
struct base_prefix *prefix, *curr;
size_t len;
struct ptrlist_node *p;
prefix = misc_malloc(sizeof(struct base_prefix));
prefix->from = misc_strdup(key);
prefix->to = misc_strdup(value);
/* Add to list according to length. */
if (!base_prefixes)
base_prefixes = ptrlist_new();
len = strlen(key);
p = NULL;
util_list_iterate(base_prefixes, p) {
curr = p->ptr;
if (strlen(curr->from) < len)
break;
}
if (p)
ptrlist_add_before(base_prefixes, p, prefix);
else
ptrlist_add(base_prefixes, prefix);
}
/* Initialize the prefix list from a strlist. */
void path_set_base(struct util_list *base)
{
struct strlist_node *s;
char *copy, *value;
if (!base)
return;
util_list_iterate(base, s) {
copy = misc_strdup(s->str);
value = strchr(copy, '=');
if (value) {
*value = 0;
value++;
prefix_add(copy, value);
} else {
prefix_add("", copy);
}
free(copy);
}
}
/* Release the prefix list. */
void path_exit(void)
{
struct ptrlist_node *p, *n;
struct base_prefix *prefix;
if (!base_prefixes)
return;
util_list_iterate_safe(base_prefixes, p, n) {
util_list_remove(base_prefixes, p);
prefix = p->ptr;
free(prefix->from);
free(prefix->to);
free(prefix);
free(p);
}
free(base_prefixes);
base_prefixes = NULL;
}
/* Modify @path according to specified prefix conversion. */
static void apply_base(char **path)
{
struct ptrlist_node *p;
struct base_prefix *prefix;
char *new_path;
if (!base_prefixes)
return;
util_list_iterate(base_prefixes, p) {
prefix = p->ptr;
if (!starts_with(*path, prefix->from))
continue;
new_path = misc_asprintf("%s%s", prefix->to,
*path + strlen(prefix->from));
free(*path);
*path = new_path;
break;
}
}
/* Return a path that is created by resolving the specified format string
* @fmt and applying any base prefixes. */
char *path_get(const char *fmt, ...)
{
va_list args;
char *path;
/* Get original path. */
va_start(args, fmt);
if (vasprintf(&path, fmt, args) == -1)
oom();
va_end(args);
/* Apply base prefix if necessary. */
apply_base(&path);
return path;
}
/* Create all directories leading up to path. */
exit_code_t path_create(const char *path)
{
char *copy, *curr, *next;
struct stat s;
int rc;
copy = misc_strdup(path);
curr = (*copy == '/') ? copy + 1 : copy;
curr = strchr(curr, '/');
if (!curr) {
free(copy);
return EXIT_OK;
}
do {
next = strchr(curr + 1, '/');
*curr = 0;
/* Ensure sub-path exists and is a directory. */
rc = stat(copy, &s);
if (rc == -1 && errno == EACCES)
goto err_access;
if (rc == 0 && !S_ISDIR(s.st_mode))
goto err_file;
if (rc == -1) {
/* Create directory. */
rc = mkdir(copy, PATH_MODE);
if (rc)
goto err_mkdir;
}
*curr = '/';
curr = next;
} while (curr);
free(copy);
return EXIT_OK;
err_access:
error("Could not access '%s'\n", copy);
free(copy);
return EXIT_RUNTIME_ERROR;
err_file:
error("Non-directory found in path '%s': %s\n", path, copy);
free(copy);
return EXIT_RUNTIME_ERROR;
err_mkdir:
error("Could not create directory '%s': %s\n", copy, strerror(errno));
free(copy);
return EXIT_RUNTIME_ERROR;
}
/* Return path to modprobe.conf file for the specified device type. */
char *path_get_modprobe_conf(struct devtype *dt)
{
return path_get("%s/%s-%s.conf", PATH_MODPROBE_CONF, MODPROBE_PREFIX,
dt->name);
}
/* Return sysfs path to module directory. */
char *path_get_sys_module(const char *mod)
{
return path_get("/sys/module/%s", mod);
}
/* Return sysfs path to module parameter file. */
char *path_get_sys_module_param(const char *mod, const char *name)
{
if (name)
return path_get("/sys/module/%s/parameters/%s", mod, name);
return path_get("/sys/module/%s/parameters", mod);
}
/* Return sysfs path to block device dev file. */
char *path_get_sys_block_dev(const char *name)
{
return path_get("/sys/block/%s/dev", name);
}
/* Return sysfs path to /sys/dev/block/major:minor directory. */
char *path_get_sys_dev_block(unsigned int major, unsigned int minor)
{
return path_get("/sys/dev/block/%d:%d", major, minor);
}
/* Return sysfs path to /sys/dev/char/major:minor directory. */
char *path_get_sys_dev_char(unsigned int major, unsigned int minor)
{
return path_get("/sys/dev/char/%d:%d", major, minor);
}
/* Return sysfs path to /sys/dev/char directory. */
char *path_get_sys_dev_char_devices(void)
{
return path_get("/sys/dev/char");
}
/* Return sysfs path to class directory. */
char *path_get_sys_class(const char *class, const char *name)
{
if (name)
return path_get("/sys/class/%s/%s", class, name);
return path_get("/sys/class/%s", class);
}
/* Return path to modprobe executable. */
char *path_get_modprobe(void)
{
return path_get("%s", PATH_MODPROBE);
}
/* Return sysfs path to CCW device. */
char *path_get_ccw_device(const char *drv, const char *id)
{
if (drv)
return path_get("%s/drivers/%s/%s", PATH_CCW_BUS, drv, id);
return path_get("%s/devices/%s", PATH_CCW_BUS, id);
}
/* Return sysfs path to directory containing all CCW devices. */
char *path_get_ccw_devices(const char *drv)
{
if (drv)
return path_get("%s/drivers/%s/", PATH_CCW_BUS, drv);
return path_get("%s/devices/", PATH_CCW_BUS);
}
/* Return sysfs path to CCWGROUP device. */
char *path_get_ccwgroup_device(const char *drv, const char *id)
{
if (drv)
return path_get("%s/drivers/%s/%s", PATH_CCWGROUP_BUS, drv, id);
return path_get("%s/devices/%s", PATH_CCWGROUP_BUS, id);
}
/* Return sysfs path to directory containing all CCWGROUP devices. */
char *path_get_ccwgroup_devices(const char *drv)
{
if (drv)
return path_get("%s/drivers/%s/", PATH_CCWGROUP_BUS, drv);
return path_get("%s/devices/", PATH_CCWGROUP_BUS);
}
/* Return path to udev rule. */
char *path_get_udev_rule(const char *type, const char *id)
{
if (id) {
return path_get("%s/%s-%s-%s%s", PATH_UDEV_RULES,
UDEV_PREFIX, type, id, UDEV_SUFFIX);
}
return path_get("%s/%s-%s%s", PATH_UDEV_RULES,
UDEV_PREFIX, type, UDEV_SUFFIX);
}
/* Return path to directory containing all udev rules. */
char *path_get_udev_rules(void)
{
return path_get("%s", PATH_UDEV_RULES);
}
/* Return path to the specified file in the proc file system. */
char *path_get_proc(const char *filename)
{
return path_get("%s/%s", PATH_PROC, filename);
}
/* Call a function for each entry in a directory:
* exit_code_t callback(const char *abs_path, const char *rel_path, void *data)
* Aborts when callback returns any value other than EXIT_OK.
*/
exit_code_t path_for_each(const char *path,
exit_code_t (*callback)(const char *, const char *,
void *), void *data)
{
DIR *dir;
struct dirent *de;
char *p;
exit_code_t rc = EXIT_OK;
dir = opendir(path);
if (!dir) {
warn("Could not open directory %s: %s\n", path,
strerror(errno));
return EXIT_RUNTIME_ERROR;
}
while (rc == EXIT_OK && (de = readdir(dir))) {
if (strcmp(de->d_name, ".") == 0 ||
strcmp(de->d_name, "..") == 0)
continue;
p = path_get("%s/%s", path, de->d_name);
rc = callback(p, de->d_name, data);
free(p);
}
closedir(dir);
return rc;
}
/* Return sysfs path to device or devices directory. */
char *path_get_sys_bus_dev(const char *bus, const char *id)
{
if (!id)
return path_get("/sys/bus/%s/devices", bus);
return path_get("/sys/bus/%s/devices/%s", bus, id);
}
/* Return sysfs path to scsi drivers directory. */
char *path_get_sys_bus_drv(const char *bus, const char *drv)
{
if (!drv)
return path_get("/sys/bus/%s/drivers", bus);
return path_get("/sys/bus/%s/drivers/%s", bus, drv);
}
/* Return sysfs path to zFCP LUN directory. */
char *path_get_zfcp_lun_dev(struct zfcp_lun_devid *id)
{
return path_get("%s/drivers/%s/%x.%x.%04x/0x%016" PRIx64
"/0x%016" PRIx64, PATH_CCW_BUS,
ZFCP_CCWDRV_NAME, id->fcp_dev.cssid,
id->fcp_dev.ssid, id->fcp_dev.devno, id->wwpn, id->lun);
}
/* Return sysfs path to zFCP target port directory. */
char *path_get_zfcp_port_dev(struct zfcp_lun_devid *id)
{
return path_get("%s/drivers/%s/%x.%x.%04x/0x%016" PRIx64,
PATH_CCW_BUS, ZFCP_CCWDRV_NAME,
id->fcp_dev.cssid, id->fcp_dev.ssid,
id->fcp_dev.devno, id->wwpn);
}
/* Return sysfs path to SCSI device directory. */
char *path_get_scsi_hctl_dev(const char *hctl)
{
return path_get("/sys/bus/scsi/devices/%s", hctl);
}

1262
zdev/src/qeth.c Normal file

File diff suppressed because it is too large Load Diff

482
zdev/src/qeth_auto.c Normal file
View File

@@ -0,0 +1,482 @@
/*
* 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 <inttypes.h>
#include <stdlib.h>
#include <string.h>
#include "ccw.h"
#include "ccwgroup.h"
#include "device.h"
#include "misc.h"
#include "path.h"
#include "qeth.h"
#include "qeth_auto.h"
/*
* QETH autodetection
*
* A QETH device must be grouped before it can be used. The following
* rules apply to grouping:
*
* 1. A QETH device can be grouped from 3 CCW devices
* a) Read device
* b) Write device
* c) Data device
* 2. All CCW devices must be bound to the QETH CCW device driver
* 3. The subchannel of all CCW devices must be defined with the same
* CHPID
* 4. The CCW devices must have the same CUTYPE
* 5. The CCW devices must have the same DEVTYPE
* 6. The write device ID must be the read device ID plus one
* 7. None of the CCW devices is part of an existing CCWGROUP device
*/
/* Compare by: 1. CHPID, 2. CUTYPE, 3. DEVTYPE, 4. CCW device ID
* -1 = a < b 1 = a > b 0 = a == b. */
static int info_cmp(void *a, void *b, void *data)
{
struct ptrlist_node *pa = a, *pb = b;
struct ccw_devinfo *ia = pa->ptr, *ib = pb->ptr;
int r;
r = ccw_devinfo_chpids_cmp(ia, ib);
if (r)
return r;
r = ccw_devinfo_cutype_cmp(ia, ib);
if (r)
return r;
r = ccw_devinfo_devtype_cmp(ia, ib);
if (r)
return r;
return ccw_cmp_devids(&ia->devid, &ib->devid);
}
static bool is_compatible(struct ccw_devinfo *a, struct ccw_devinfo *b)
{
if (ccw_devinfo_chpids_cmp(a, b) == 0 &&
ccw_devinfo_cutype_cmp(a, b) == 0 &&
ccw_devinfo_devtype_cmp(a, b) == 0)
return true;
return false;
}
/* Return the number of consecutive ptrlist_nodes pointing to ccw_devinfos
* which are compatible with each other. */
static unsigned int count_compatible(struct util_list *infos,
struct ptrlist_node *start)
{
struct ptrlist_node *curr;
unsigned int num;
num = 0;
for (curr = start; curr && is_compatible(start->ptr, curr->ptr);
curr = util_list_next(infos, curr))
num++;
return num;
}
/* Check if sequential fill is possible without holes. */
static bool check_seq(struct util_list *infos, struct ptrlist_node *start,
unsigned int num)
{
struct ptrlist_node *curr;
struct ccw_devinfo *read = NULL, *write = NULL;
unsigned int i;
curr = start;
for (i = 0; i < num; i++) {
if (write) {
/* Expect data. */
read = NULL;
write = NULL;
} else if (read) {
/* Expect write. */
write = curr->ptr;
if (ccw_devid_distance(&read->devid,
&write->devid) != 1)
return false;
} else {
/* Expect read. */
read = curr->ptr;
}
curr = util_list_next(infos, curr);
}
return true;
}
static void add_ccwgroup_devid(struct util_list *devids, struct ccw_devid *read,
struct ccw_devid *write, struct ccw_devid *data)
{
struct ccwgroup_devid devid;
devid.devid[0] = *read;
devid.devid[1] = *write;
devid.devid[2] = *data;
devid.num = QETH_NUM_DEVS;
ptrlist_add(devids, ccwgroup_copy_devid(&devid));
}
static struct ptrlist_node *add_seq(struct util_list *devids,
struct util_list *infos,
struct ptrlist_node *start,
unsigned int num)
{
struct ptrlist_node *curr;
struct ccw_devinfo *read = NULL, *write = NULL, *data;
unsigned int i;
curr = start;
for (i = 0; i < num; i++) {
if (write) {
/* Expect data. */
data = curr->ptr;
add_ccwgroup_devid(devids, &read->devid, &write->devid,
&data->devid);
read = NULL;
write = NULL;
data = NULL;
} else if (read) {
/* Expect write. */
write = curr->ptr;
} else {
/* Expect read. */
read = curr->ptr;
}
curr = util_list_next(infos, curr);
}
return curr;
}
/* Create IDs by searching for consecutive pairs of CCW device IDs first. */
static struct ptrlist_node *add_pairs_first(struct util_list *devids,
struct util_list *infos,
struct ptrlist_node *start,
unsigned int num)
{
struct util_list *pairs, *all;
struct ptrlist_node *curr, *next, *read, *data, *cont;
struct ccw_devinfo *r, *w, *d;
unsigned int i, max_pairs, num_pairs;
all = ptrlist_new();
pairs = ptrlist_new();
/* Copy devinfos to all list. */
curr = start;
for (i = 0; i < num; i++) {
ptrlist_add(all, curr->ptr);
curr = util_list_next(infos, curr);
}
cont = curr;
/* Move valid read-write pairs to pairs list. */
max_pairs = num / QETH_NUM_DEVS;
num_pairs = 0;
read = NULL;
util_list_iterate_safe(all, curr, next) {
if (read) {
r = read->ptr;
w = curr->ptr;
/* Check for write = read + 1. */
if (ccw_devid_distance(&r->devid, &w->devid) == 1) {
ptrlist_move(pairs, all, read);
ptrlist_move(pairs, all, curr);
num_pairs++;
if (num_pairs >= max_pairs)
break;
read = NULL;
} else
read = curr;
} else
read = curr;
}
/* Create full groups by combining pairs + remaining IDs. */
read = NULL;
util_list_iterate(pairs, curr) {
if (read) {
/* Got read and write - check for data. */
data = util_list_start(all);
if (!data)
break;
util_list_remove(all, data);
r = read->ptr;
w = curr->ptr;
d = data->ptr;
add_ccwgroup_devid(devids, &r->devid, &w->devid,
&d->devid);
read = NULL;
} else
read = curr;
}
ptrlist_free(all, 0);
ptrlist_free(pairs, 0);
return cont;
}
static struct ptrlist_node *add_groups(struct util_list *devids,
struct util_list *infos,
struct ptrlist_node *start,
unsigned int num)
{
struct ptrlist_node *next;
if (check_seq(infos, start, num))
next = add_seq(devids, infos, start, num);
else
next = add_pairs_first(devids, infos, start, num);
return next;
}
/* Add device info for all QETH CCW devices to ptrlist in data. */
static exit_code_t add_cb(const char *path, const char *filename, void *data)
{
struct ccw_devid devid;
struct util_list *infos = data;
struct ccw_devinfo *devinfo;
if (!strchr(filename, '.'))
return EXIT_OK;
if (ccw_parse_devid(&devid, filename, err_ignore) != EXIT_OK)
return EXIT_OK;
devinfo = ccw_devinfo_get(&devid, 0);
if (devinfo->exists && !devinfo->grouped)
ptrlist_add(infos, devinfo);
else
free(devinfo);
return EXIT_OK;
}
/* Return a sorted ptrlist of struct ccw_devinfos for all CCW devices
* bound to the qeth CCW device driver. The result must be freed using
* ptrlist_free(,1); */
static struct util_list *read_sorted_qeth_devinfos(void)
{
struct util_list *infos;
char *path;
/* Get CHPID information for all devices bound to the QETH driver. */
infos = ptrlist_new();
path = path_get_sys_bus_drv(CCW_BUS_NAME, QETH_CCWDRV_NAME);
if (dir_exists(path))
path_for_each(path, add_cb, infos);
free(path);
/* For each CHPID: Find groups. Add to result list. */
util_list_sort(infos, info_cmp, NULL);
return infos;
}
static void add_groupable_devids(struct util_list *devids,
struct util_list *infos)
{
struct ptrlist_node *curr;
unsigned int num;
/* For each group of compatible devices: Find valid CCWGROUPs.
* Add to result list. */
curr = util_list_start(infos);
while (curr) {
num = count_compatible(infos, curr);
curr = add_groups(devids, infos, curr, num);
}
}
/* Add CCWGROUP IDs of qeth devices that can be grouped to strlist IDs.*/
void qeth_auto_add_ids(struct util_list *ids)
{
struct util_list *infos, *devids;
struct ptrlist_node *p;
char *id;
infos = read_sorted_qeth_devinfos();
devids = ptrlist_new();
add_groupable_devids(devids, infos);
util_list_iterate(devids, p) {
id = ccwgroup_devid_to_str(p->ptr);
strlist_add(ids, id);
free(id);
}
ptrlist_free(devids, 1);
ptrlist_free(infos, 1);
}
/* Determine the CCWGROUP ID of the qeth device that can be grouped with the
* specified CCW device ID as first ID. */
exit_code_t qeth_auto_get_devid(struct ccwgroup_devid *devid_ptr,
struct ccw_devid *ccw_devid, err_t err)
{
struct util_list *devids, *infos;
struct ptrlist_node *p, *read, *write, *data;
struct ccw_devinfo *r, *w, *d;
struct ccwgroup_devid *devid;
exit_code_t rc;
infos = read_sorted_qeth_devinfos();
/* Try to find an ID from the canonical auto-generated list. */
devids = ptrlist_new();
add_groupable_devids(devids, infos);
util_list_iterate(devids, p) {
devid = p->ptr;
if (ccw_cmp_devids(ccw_devid, &devid->devid[0]) != 0)
continue;
rc = EXIT_OK;
if (devid_ptr)
*devid_ptr = *devid;
goto out;
}
/* Try to create a CCWGROUP ID with the specified ID as read device. */
/* Get CCW device info for read device. */
util_list_iterate(infos, read) {
r = read->ptr;
if (ccw_cmp_devids(&r->devid, ccw_devid) == 0)
break;
}
if (!read) {
err_t_print(err, "Read CCW device not found\n");
rc = EXIT_GROUP_NOT_FOUND;
goto out;
}
/* Get CCW device ID for write device. */
write = util_list_next(infos, read);
if (!write) {
err_t_print(err, "Write CCW device not found\n");
rc = EXIT_GROUP_NOT_FOUND;
goto out;
}
w = write->ptr;
if (!is_compatible(r, w)) {
err_t_print(err, "No compatible write CCW device found\n");
rc = EXIT_GROUP_INVALID;
goto out;
}
if (ccw_devid_distance(&r->devid, &w->devid) != 1) {
err_t_print(err, "Write CCW device ID must be read plus one\n");
rc = EXIT_GROUP_INVALID;
goto out;
}
/* Get CCW device ID for data device. */
/* 1. Prefer ID after the write ID. */
data = util_list_next(infos, write);
if (!data || !is_compatible(r, data->ptr)) {
/* Try ID before read ID. */
data = util_list_prev(infos, read);
}
if (!data) {
err_t_print(err, "Data CCW device not found\n");
rc = EXIT_GROUP_NOT_FOUND;
goto out;
}
if (!is_compatible(r, data->ptr)) {
err_t_print(err, "No compatible data CCW device found\n");
rc = EXIT_GROUP_INVALID;
goto out;
}
d = data->ptr;
rc = EXIT_OK;
if (devid_ptr) {
devid_ptr->devid[0] = r->devid;
devid_ptr->devid[1] = w->devid;
devid_ptr->devid[2] = d->devid;
devid_ptr->num = QETH_NUM_DEVS;
}
out:
ptrlist_free(devids, 1);
ptrlist_free(infos, 1);
return rc;
}
/* Check if the specified QETH device can be grouped .*/
exit_code_t qeth_auto_is_possible(struct ccwgroup_devid *devid, err_t err)
{
struct ccw_devinfo *info[QETH_NUM_DEVS];
unsigned int i;
char *ccwid;
const char *msg;
exit_code_t rc;
if (devid->num < QETH_NUM_DEVS) {
err_t_print(err, "Not enough CCW device IDs in QETH device "
"ID\n");
return EXIT_INCOMPLETE_ID;
}
if (devid->num > QETH_NUM_DEVS) {
err_t_print(err, "QETH device ID contains too many CCW device "
"IDs\n");
return EXIT_INVALID_ID;
}
if (ccw_devid_distance(&devid->devid[0], &devid->devid[1]) != 1) {
err_t_print(err, "Write device ID must be read plus one\n");
return EXIT_GROUP_INVALID;
}
for (i = 0; i < ARRAY_SIZE(info); i++)
info[i] = ccw_devinfo_get(&devid->devid[i], 1);
rc = EXIT_OK;
msg = NULL;
for (i = 0; i < ARRAY_SIZE(info); i++) {
if (!info[i]->exists) {
msg = "CCW device %s does not exist\n";
rc = EXIT_GROUP_NOT_FOUND;
} else if (info[i]->grouped) {
msg = "CCW device %s is in another group device\n";
rc = EXIT_GROUP_ALREADY;
} else if (i > 0 &&
ccw_devinfo_chpids_cmp(info[i - 1], info[i]) != 0) {
msg = "CCW device %s is not on the same CHPID\n";
rc = EXIT_GROUP_INVALID;
} else if (i > 0 &&
ccw_devinfo_cutype_cmp(info[i - 1], info[i]) != 0) {
msg = "CUTYPE of CCW device %s differs\n";
rc = EXIT_GROUP_INVALID;
} else if (i > 0 &&
ccw_devinfo_devtype_cmp(info[i - 1], info[i]) != 0) {
msg = "DEVTYPE of CCW device %s differs\n";
rc = EXIT_GROUP_INVALID;
}
if (!msg)
continue;
ccwid = ccw_devid_to_str(&devid->devid[i]);
err_t_print(err, msg, ccwid);
free(ccwid);
break;
}
for (i = 0; i < ARRAY_SIZE(info); i++)
free(info[i]);
return rc;
}

104
zdev/src/root.c Normal file
View File

@@ -0,0 +1,104 @@
/*
* 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 "device.h"
#include "devtype.h"
#include "misc.h"
#include "path.h"
#include "root.h"
#include "select.h"
#include "setting.h"
#include "subtype.h"
/* Determine if the root device was modified. If it was modified, run the
* corresponding root-install scripts. */
exit_code_t root_check(void)
{
struct util_list *selected, *params, *mod = NULL;
struct selected_dev_node *sel;
struct device *dev;
char *params_str;
exit_code_t rc;
struct strlist_node *s;
struct devtype *dt;
debug("Checking for modified root device configuration\n");
/* Get list of devices that provide the root device. */
selected = selected_dev_list_new();
rc = select_by_path(NULL, selected, config_active, scope_mandatory,
NULL, NULL, PATH_ROOT, err_print);
if (rc)
goto out;
/* Determine if any of the devices or device types has been modified. */
mod = strlist_new();
util_list_iterate(selected, sel) {
dt = sel->st->devtype;
/* Check devtype. */
if (devtype_needs_writing(dt, config_persistent)) {
strlist_add(mod, "Device type %s",
sel->st->devtype->name);
}
/* Check devices. */
dev = device_list_find(sel->st->devices, sel->id, NULL);
if (dev && dev->persistent.exists &&
device_needs_writing(dev, config_persistent)) {
strlist_add(mod, "%s %s", dev->subtype->devname,
dev->id);
}
}
if (util_list_is_empty(mod))
goto out;
info("Note: Some of the changes affect devices providing the root "
"file system:\n");
util_list_iterate(mod, s)
info(" - %s\n", s->str);
info(" Additional steps such as rebuilding the RAM-disk might be "
"required.\n");
/* Check if script is available. */
if (!file_exists(PATH_ROOT_SCRIPT))
goto out;
/* Ask for confirmation. */
if (!confirm("Update persistent root device configuration now?")) {
rc = EXIT_ABORTED;
goto out;
}
/* Build the command line. */
params = strlist_new();
util_list_iterate(selected, sel) {
strlist_add(params, "%s", sel->st->name);
strlist_add(params, "%s", sel->id);
}
params_str = strlist_flatten(params, " ");
strlist_free(params);
/* Run update command. */
if (misc_system(err_delayed_print, "%s %s", PATH_ROOT_SCRIPT,
params_str) != 0) {
error("Failure while updating root device configuration\n");
delayed_print(DELAY_INDENT);
rc = EXIT_RUNTIME_ERROR;
}
free(params_str);
out:
strlist_free(mod);
selected_dev_list_free(selected);
return rc;
}

362
zdev/src/scsi.c Normal file
View File

@@ -0,0 +1,362 @@
/*
* 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 <inttypes.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "misc.h"
#include "path.h"
#include "scsi.h"
#include "zfcp_lun.h"
struct scsi_hctl_devid {
unsigned int host;
unsigned int channel;
unsigned int target;
uint64_t lun;
};
struct scsi_zfcp {
struct scsi_hctl_devid scsi;
struct zfcp_lun_devid zfcp;
};
static struct util_list *scsi_zfcp_list;
static char *scsi_hctl_devid_to_str(struct scsi_hctl_devid *devid)
{
return misc_asprintf("%u:%u:%u:%" PRIu64, devid->host, devid->channel,
devid->target, devid->lun);
}
/* Used for debugging. */
void scsi_zfcp_print(struct scsi_zfcp *s, int i)
{
char *scsi, *zfcp;
indent(i, "scsi_zfcp at %p:\n", (void *) s);
i += 2;
scsi = scsi_hctl_devid_to_str(&s->scsi);
zfcp = zfcp_lun_devid_to_str(&s->zfcp);
indent(i, "scsi=%s\n", scsi);
indent(i, "zfcp=%s\n", zfcp);
free(zfcp);
free(scsi);
}
void scsi_reread(void)
{
ptrlist_free(scsi_zfcp_list, 1);
scsi_zfcp_list = NULL;
}
void scsi_exit(void)
{
ptrlist_free(scsi_zfcp_list, 1);
}
static bool scsi_hctl_parse_devid(struct scsi_hctl_devid *id, const char *str)
{
unsigned int host, channel, target;
uint64_t lun;
char dummy;
if (sscanf(str, "%u:%u:%u:%" SCNu64 " %c", &host, &channel, &target,
&lun, &dummy) != 4)
return false;
if (id) {
id->host = host;
id->channel = channel;
id->target = target;
id->lun = lun;
}
return true;
}
/* Retrieve CCW device ID of HBA for specified SCSI device path. */
static char *devpath_to_hba_id(const char *path)
{
char *copy, *start, *end, *hba_id = NULL;
copy = misc_strdup(path);
/* copy=/devices/css0/0.0.001c/0.0.1940/host0/... */
end = strstr(copy, "/host");
if (!end)
goto out;
*end = 0;
/* copy=/devices/css0/0.0.001c/0.0.1940 */
start = strrchr(copy, '/');
if (!start)
goto out;
start++;
hba_id = misc_strdup(start);
out:
free(copy);
return hba_id;
}
/* Retrieve WWPN from specified SCSI device path. */
static char *devpath_to_wwpn(const char *devpath)
{
char *copy, *rport, *end, *path = NULL, *wwpn = NULL;
/* devpath=/devices/css0/0.0.001c/0.0.1940/host0/rport-0:0-16/
* target0:0:16/0:0:16:1085030433/ */
copy = misc_strdup(devpath);
rport = strstr(copy, "/rport-");
end = skip_comp(rport);
if (!end)
goto out;
*end = 0;
/* devpath=/devices/css0/0.0.001c/0.0.1940/host0/rport-0:0-16
* rport=rport-0:0-16 */
path = path_get("/sys%s/fc_remote_ports%s/port_name", copy, rport);
wwpn = misc_read_text_file(path, 1, err_ignore);
out:
free(path);
free(copy);
return wwpn;
}
static unsigned int lun_swap[] = { 6, 7, 4, 5 };
uint64_t scsi_lun_to_fcp_lun(uint64_t lun)
{
byte_swap((uint8_t *) &lun, lun_swap, ARRAY_SIZE(lun_swap));
return lun;
}
uint64_t scsi_lun_from_fcp_lun(uint64_t lun)
{
byte_swap((uint8_t *) &lun, lun_swap, ARRAY_SIZE(lun_swap));
return lun;
}
/* Retrieve FCP LUN from specified HCTL. */
static char *hctl_to_fcp_lun(const char *hctl)
{
struct scsi_hctl_devid devid;
if (!scsi_hctl_parse_devid(&devid, hctl))
return NULL;
return misc_asprintf("0x%016" PRIx64, scsi_lun_to_fcp_lun(devid.lun));
}
/* Retrieve FCP LUN from specified SCSI device path. Works with paths to
* SCSI device and sub-directories (required for paths from /sys/dev/block and
* /sys/dev/char). */
static char *devpath_to_fcp_lun(const char *devpath)
{
char *hctl, *fcp_lun = NULL;
hctl = scsi_hctl_from_devpath(devpath);
if (hctl) {
fcp_lun = hctl_to_fcp_lun(hctl);
free(hctl);
}
return fcp_lun;
}
/* Try to determine the zfcp LUN ID from the specified zfcp SCSI device path. */
static char *devpath_to_zfcp_lun_id(const char *path)
{
const char *devpath;
char *hba_id, *wwpn, *fcp_lun, *zfcp_lun_id = NULL;
devpath = strstr(path, "/devices/");
if (!devpath)
return NULL;
hba_id = devpath_to_hba_id(devpath);
wwpn = devpath_to_wwpn(devpath);
fcp_lun = devpath_to_fcp_lun(devpath);
if (hba_id && wwpn && fcp_lun)
zfcp_lun_id = misc_asprintf("%s:%s:%s", hba_id, wwpn, fcp_lun);
free(fcp_lun);
free(wwpn);
free(hba_id);
return zfcp_lun_id;
}
/* Return the zfcp LUN ID from the specified SCSI HCTL ID. */
char *scsi_hctl_to_zfcp_lun_id(const char *hctl)
{
char *buspath, *link = NULL, *zfcp_lun_id = NULL;
buspath = path_get_sys_bus_dev("scsi", hctl);
link = misc_readlink(buspath);
if (!link)
goto out;
zfcp_lun_id = devpath_to_zfcp_lun_id(link);
out:
free(link);
free(buspath);
return zfcp_lun_id;
}
static exit_code_t add_ids_cb(const char *path, const char *name, void *data)
{
struct util_list *list = data;
struct scsi_hctl_devid scsi_devid;
struct zfcp_lun_devid zfcp_devid;
char *zfcp_id;
struct scsi_zfcp *s;
if (starts_with(name, "host") || starts_with(name, "target"))
return EXIT_OK;
if (!scsi_hctl_parse_devid(&scsi_devid, name))
return EXIT_OK;
zfcp_id = scsi_hctl_to_zfcp_lun_id(name);
if (!zfcp_id)
return EXIT_OK;
if (zfcp_lun_parse_devid(&zfcp_devid, zfcp_id, err_ignore) == EXIT_OK) {
s = misc_malloc(sizeof(struct scsi_zfcp));
s->scsi = scsi_devid;
s->zfcp = zfcp_devid;
ptrlist_add(list, s);
}
free(zfcp_id);
return EXIT_OK;
}
static struct util_list *read_scsi_zfcp_list(void)
{
struct util_list *list;
char *path;
list = ptrlist_new();
path = path_get_sys_bus_dev("scsi", NULL);
if (dir_exists(path))
path_for_each(path, add_ids_cb, list);
free(path);
return list;
}
static struct scsi_zfcp *get_scsi_zfcp(struct zfcp_lun_devid *devid)
{
struct ptrlist_node *p;
struct scsi_zfcp *s;
if (!scsi_zfcp_list)
scsi_zfcp_list = read_scsi_zfcp_list();
/* Search for zfcp LUN device ID in cached list. */
util_list_iterate(scsi_zfcp_list, p) {
s = p->ptr;
if (zfcp_lun_cmp_devids(&s->zfcp, devid) == 0)
return s;
}
return NULL;
}
/* Check for SCSI device associated with zfcp lun device @devid. Return newly
* allocated HCTL ID of SCSI device on success, %NULL on failure. */
char *scsi_hctl_from_zfcp_lun_devid(struct zfcp_lun_devid *devid)
{
struct scsi_zfcp *s;
s = get_scsi_zfcp(devid);
if (s)
return scsi_hctl_devid_to_str(&s->scsi);
return NULL;
}
/* Check for SCSI device associated with zfcp LUN device @id. Return newly
* allocated HCTL ID of SCSI device on success, %NULL on failure. */
char *scsi_hctl_from_zfcp_lun_id(const char *id)
{
struct zfcp_lun_devid devid;
if (zfcp_lun_parse_devid(&devid, id, err_ignore) != EXIT_OK)
return NULL;
return scsi_hctl_from_zfcp_lun_devid(&devid);
}
/* Check if SCSI device exists for zfcp LUN @id. */
bool scsi_hctl_exists(const char *id)
{
struct zfcp_lun_devid devid;
if (zfcp_lun_parse_devid(&devid, id, err_ignore) != EXIT_OK)
return false;
if (!get_scsi_zfcp(&devid))
return false;
return true;
}
/* Add zfcp LUN ids for all SCSI devices to strlist @list. */
void scsi_hctl_add_zfcp_lun_ids(struct util_list *list)
{
struct ptrlist_node *p;
struct scsi_zfcp *s;
char *id;
if (!scsi_zfcp_list)
scsi_zfcp_list = read_scsi_zfcp_list();
util_list_iterate(scsi_zfcp_list, p) {
s = p->ptr;
id = zfcp_lun_devid_to_str(&s->zfcp);
strlist_add(list, id);
free(id);
}
}
/* Return SCSI HCTL ID from SCSI device path. Works with paths to SCSI device
* and sub-directories (required for paths from /sys/dev/block and
* /sys/dev/char). */
char *scsi_hctl_from_devpath(const char *path)
{
char *copy, *start, *end, *hctl = NULL;
/* ../../devices/css0/0.0.001c/0.0.1940/host0/rport-0:0-16/
* target0:0:16/0:0:16:1085030433/ */
copy = misc_strdup(path);
start = strstr(copy, "/target");
start = skip_comp(start);
if (!start)
goto out;
start++;
end = strchr(start, '/');
if (end)
*end = 0;
hctl = misc_strdup(start);
out:
free(copy);
return hctl;
}

1291
zdev/src/select.c Normal file

File diff suppressed because it is too large Load Diff

713
zdev/src/setting.c Normal file
View File

@@ -0,0 +1,713 @@
/*
* 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 "attrib.h"
#include "misc.h"
#include "path.h"
#include "setting.h"
/* Create and initialize a new setting. */
struct setting *setting_new(struct attrib *attrib, const char *name,
const char *val)
{
struct setting *s;
s = misc_malloc(sizeof(struct setting));
s->attrib = attrib;
if (attrib && !name)
s->name = misc_strdup(attrib->name);
else
s->name = misc_strdup(name);
s->value = misc_strdup(val);
if (attrib && attrib->multi) {
/* Split multi-line value into multiple values. */
s->values = strlist_new();
strlist_add_multi(s->values, val, "\n", 0);
}
return s;
}
/* Release all resources associated with the specified setting. */
static void setting_free(struct setting *s)
{
if (!s)
return;
free(s->name);
free(s->value);
free(s->actual_value);
strlist_free(s->values);
strlist_free(s->actual_values);
free(s);
}
/* Create a newly allocated setting copy. */
struct setting *setting_copy(const struct setting *s)
{
struct setting *copy;
copy = misc_malloc(sizeof(struct setting));
copy->attrib = s->attrib;
copy->name = misc_strdup(s->name);
copy->value = misc_strdup(s->value);
if (s->values)
copy->values = strlist_copy(s->values);
if (s->actual_value)
copy->actual_value = misc_strdup(s->actual_value);
if (s->actual_values)
copy->actual_values = strlist_copy(s->actual_values);
copy->modified = s->modified;
copy->removed = s->removed;
return copy;
}
/* Replace or add to the value of a setting. */
static void setting_mod_value(struct setting *s, const char *value)
{
char *new_value;
if (s->values) {
/* Check if this is a rewrite of a previous value. */
if (!s->derived && s->attrib && !s->attrib->rewrite &&
strlist_find(s->values, value))
return;
/* Attribute supports multiple values - add. */
new_value = misc_asprintf("%s%s%s", s->value,
*s->value ? "\n" : "", value);
free(s->value);
s->value = new_value;
strlist_add(s->values, value);
} else {
/* Check if this is a rewrite of a previous value. */
if (!s->derived && s->attrib && !s->attrib->rewrite &&
strcmp(s->value, value) == 0)
return;
/* Attribute unknown or does not support multiple values -
* replace. */
free(s->value);
s->value = misc_strdup(value);
}
s->modified = 1;
s->removed = 0;
}
/* Determine if a setting is set (that is configured). */
bool setting_is_set(struct setting *s)
{
if (s->removed)
return false;
if (s->modified)
return true;
if (s->derived)
return false;
return true;
}
static void setting_set_actual(struct setting *s, const char *value)
{
free(s->actual_value);
s->actual_value = misc_strdup(value);
if (s->attrib && s->attrib->multi) {
strlist_free(s->actual_values);
s->actual_values = strlist_new();
strlist_add_multi(s->actual_values, value, "\n", 0);
}
}
/* Write a single setting value to a sysfs attribute. */
static exit_code_t write_setting_value(const char *path, struct setting *s,
const char *value)
{
struct attrib *a = s->attrib;
int newline, rewrite, unstable, writeonly;
exit_code_t rc = EXIT_OK;
char *currvalue = NULL, *newvalue = NULL;
newline = a ? a->newline : 0;
rewrite = a ? a->rewrite : 0;
unstable = a ? a->unstable : 0;
writeonly = a ? a->writeonly : 0;
/* Do we need to check for a rewrite? */
if (rewrite || unstable || force)
goto do_write;
/* Get current value if necessary. */
if (!s->actual_value && !writeonly) {
currvalue = misc_read_text_file(path, 1, err_ignore);
if (!currvalue)
goto do_write;
setting_set_actual(s, currvalue);
free(currvalue);
}
/* Check if value is already set. */
if (s->actual_values) {
if (strlist_find(s->actual_values, value))
goto out;
} else if (s->actual_value) {
if (strcmp(s->actual_value, value) == 0)
goto out;
}
do_write:
/* Ensure newline if required. */
if (newline && !ends_with(value, "\n"))
newvalue = misc_asprintf("%s\n", value);
rc = misc_write_text_file_retry(path, newvalue ? newvalue : value,
err_delayed_print);
if (rc)
rc = EXIT_SETTING_FAILED;
out:
free(newvalue);
return rc;
}
/* Write a single setting to a sysfs attribute. */
exit_code_t setting_write(const char *path, struct setting *s)
{
exit_code_t rc;
struct strlist_node *str;
if (!s->values) {
/* Single value attribute. */
return write_setting_value(path, s, s->value);
}
util_list_iterate(s->values, str) {
rc = write_setting_value(path, s, str->str);
if (rc)
return rc;
}
return EXIT_OK;
}
/* Used for debugging. */
void setting_print(struct setting *s, int level)
{
struct strlist_node *str;
int i;
printf("%*ssetting at %p\n", level, "", (void *) s);
if (!s)
return;
level += 4;
if (s->attrib)
printf("%*sattrib=%s\n", level, "", s->attrib->name);
else
printf("%*sattrib=<none>\n", level, "");
printf("%*sname='%s'\n", level, "", s->name);
printf("%*svalue='%s'\n", level, "", s->value);
if (s->values) {
i = 0;
util_list_iterate(s->values, str) {
printf("%*svalues[%d]='%s'\n", level, "", i++,
str->str);
}
}
if (s->actual_value)
printf("%*sactual_value='%s'\n", level, "", s->actual_value);
else
printf("%*sactual_value=<none>\n", level, "");
if (s->actual_values) {
i = 0;
util_list_iterate(s->actual_values, str) {
printf("%*sactual_values[%d]='%s'\n", level, "", i++,
str->str);
}
}
printf("%*smodified='%d'\n", level, "", s->modified);
printf("%*sspecified='%d'\n", level, "", s->specified);
printf("%*sremoved='%d'\n", level, "", s->removed);
printf("%*sderived='%d'\n", level, "", s->derived);
printf("%*sreadonly='%d'\n", level, "", s->readonly);
}
/* Create and initialize a new setting_list. */
struct setting_list *setting_list_new(void)
{
struct setting_list *list;
list = misc_malloc(sizeof(struct setting_list));
util_list_init(&list->list, struct setting, node);
return list;
}
/* Remove all settings from list. */
void setting_list_clear(struct setting_list *list)
{
struct setting *s, *n;
util_list_iterate_safe(&list->list, s, n) {
util_list_remove(&list->list, s);
setting_free(s);
}
}
/* Release resources used by list and enlisted settings. */
void setting_list_free(struct setting_list *list)
{
if (!list)
return;
setting_list_clear(list);
free(list);
}
/* Add a new element to a list and mark the list as modified. */
void setting_list_add(struct setting_list *list, struct setting *setting)
{
util_list_add_tail(&list->list, setting);
list->modified = 1;
}
/* Find an element in the list. */
struct setting *setting_list_find(struct setting_list *list, const char *name)
{
struct setting *s;
util_list_iterate(&list->list, s) {
if (strcmp(s->name, name) == 0)
return s;
}
return NULL;
}
static struct setting *add_setting(struct setting_list *list,
struct attrib *attrib,
const char *name, const char *value,
int new_modified)
{
struct setting *s;
s = setting_list_find(list, name);
if (s)
setting_mod_value(s, value);
else {
s = setting_new(attrib, name, value);
setting_list_add(list, s);
if (new_modified)
s->modified = 1;
}
return s;
}
/* Modify existing setting or add new one. */
struct setting *setting_list_apply(struct setting_list *list,
struct attrib *attrib, const char *name,
const char *value)
{
return add_setting(list, attrib, name, value, 1);
}
/* Modify existing setting or add new one based on user request. */
struct setting *setting_list_apply_specified(struct setting_list *list,
struct attrib *attrib,
const char *name,
const char *value)
{
struct setting *s;
s = add_setting(list, attrib, name, value, 1);
s->specified = 1;
return s;
}
/* Set actual value for existing setting or add new one. */
struct setting *setting_list_apply_actual(struct setting_list *list,
struct attrib *attrib,
const char *name, const char *value)
{
struct setting *s;
s = add_setting(list, attrib, name, value, 0);
setting_set_actual(s, value);
return s;
}
/* Check if a setting was modified. */
bool setting_list_modified(struct setting_list *list)
{
struct setting *s;
if (!list)
return false;
util_list_iterate(&list->list, s) {
if (s->modified)
return true;
}
return false;
}
/* Determine the state of a boolean attribute with the given name in the
* specified setting list. */
void setting_list_get_bool_state(struct setting_list *list, const char *name,
int *changed, int *set)
{
struct setting *s;
s = setting_list_find(list, name);
if (!s) {
*changed = 0;
*set = 0;
return;
}
if (strcmp(s->value, "1") == 0)
*set = 1;
else
*set = 0;
if (s->actual_value && strcmp(s->value, s->actual_value) != 0)
*changed = 1;
else
*changed = 0;
}
/* Return a newly allocated space-separated string containing all non-removed
* settings in KEY=VALUE format. */
char *setting_list_flatten(struct setting_list *list)
{
struct setting *s;
int i;
char *str;
/* Determine total string length. */
i = 1;
util_list_iterate(&list->list, s) {
if (s->removed)
continue;
i += strlen(s->name) + 1 + strlen(s->value) + 1;
}
str = misc_malloc(i);
/* Combine string. */
i = 0;
util_list_iterate(&list->list, s) {
if (s->removed)
continue;
i += sprintf(&str[i], "%s%s=%s", i == 0 ? "" : " ", s->name,
s->value);
}
return str;
}
/* Used for debugging. */
void setting_list_print(struct setting_list *list, int level)
{
struct setting *s;
printf("%*ssetting list at %p\n", level, "", (void *) list);
if (!list)
return;
util_list_iterate(&list->list, s)
setting_print(s, level + 4);
}
/* Determine the order of applying attributes based on the attribute order
* information. */
static int setting_cmp(struct setting *a, struct setting *b)
{
int result;
if (!a->attrib || !b->attrib)
return 0;
/* Try order_cmp first if available. */
if (a->attrib && a->attrib->order_cmp) {
result = a->attrib->order_cmp(a, b);
if (result != 0)
return result;
}
if (b->attrib && b->attrib->order_cmp) {
result = b->attrib->order_cmp(b, a);
if (result != 0)
return -result;
}
/* Try static ordering next. */
if (a->attrib->order < b->attrib->order)
return -1;
if (a->attrib->order > b->attrib->order)
return 1;
/* Don't fallback to strcmp or similar here since that could
* conflict with an actual ordering requirement reported by
* a cmp callback. */
return 0;
}
/* Return a newly allocated ptrlist of settings sorted according to
* order_cmp. */
struct util_list *setting_list_get_sorted(struct setting_list *list)
{
struct util_list *result;
struct setting *s;
struct ptrlist_node *p;
result = ptrlist_new();
p = NULL;
util_list_iterate(&list->list, s) {
/* Find first element that should be after new element
* in list. */
util_list_iterate(result, p) {
if (setting_cmp(p->ptr, s) > 0)
break;
}
if (p)
ptrlist_add_before(result, p, s);
else
ptrlist_add(result, s);
}
return result;
}
/* Check if there is any conflict between settings in @list. Display errors
* according to @err. Return %false if there is a conflict, %true otherwise. */
bool setting_list_check_conflict(struct setting_list *list, config_t config,
err_t err)
{
struct setting *a, *b;
util_list_iterate(&list->list, a) {
if (!a->attrib || !a->attrib->check)
continue;
if (a->removed)
continue;
if (!a->modified && !a->specified)
continue;
util_list_iterate(&list->list, b) {
if (a == b)
continue;
if (!a->attrib->check(a, b, config))
goto conflict;
}
}
return true;
conflict:
err_t_print(err, "Cannot set %s='%s' while %s='%s'\n", a->name,
a->value, b->name, b->value);
return false;
}
/* Add settings with default values to @list for all attributes in @attribs
* for which no setting has been added. If @mand_only is specified, only
* apply default values for mandatory settings. */
void setting_list_apply_defaults(struct setting_list *list,
struct attrib **attribs, bool mand_only)
{
int i;
struct attrib *a;
struct setting *s;
for (i = 0; (a = attribs[i]); i++) {
if (!a->defval)
continue;
if (mand_only && !a->mandatory)
continue;
s = setting_list_find(list, a->name);
if (s)
continue;
s = setting_list_apply_actual(list, a, a->name, a->defval);
s->derived = 1;
}
}
/* Merge settings from setting list @from into @to. If @specified is true,
* also copy specified flag. If @modified is true, also copy modified flag. */
void setting_list_merge(struct setting_list *to, struct setting_list *from,
bool specified, bool modified)
{
struct setting *s, *n;
util_list_iterate(&from->list, s) {
n = setting_list_apply(to, s->attrib, s->name, s->value);
if (specified)
n->specified = s->specified;
if (modified)
n->modified = n->modified;
}
}
/* Return a copy of setting list @list. */
struct setting_list *setting_list_copy(struct setting_list *list)
{
struct setting_list *copy;
struct setting *s;
copy = setting_list_new();
util_list_iterate(&list->list, s)
util_list_add_tail(&copy->list, setting_copy(s));
copy->modified = list->modified;
return copy;
}
/* Apply the attribute value replacement map for all settings in list which
* define such a map. */
void setting_list_map_values(struct setting_list *list)
{
struct setting *s;
struct attrib *a;
const char *to;
util_list_iterate(&list->list, s) {
a = s->attrib;
if (!a || !a->map)
continue;
to = attrib_map_value(a, s->value);
if (!to)
continue;
free(s->value);
s->value = misc_strdup(to);
}
}
/* Mark all settings in @list as derived which have an actual_value equal
* to the default value. */
void setting_list_mark_default_derived(struct setting_list *list)
{
struct setting *s;
util_list_iterate(&list->list, s) {
if (!s->attrib || !s->attrib->defval || !s->actual_value)
continue;
if (!attrib_match_default(s->attrib, s->actual_value))
continue;
s->derived = 1;
}
}
/* Return the number of settings in @list that are set. */
int setting_list_count_set(struct setting_list *list)
{
struct setting *s;
int set;
set = 0;
util_list_iterate(&list->list, s) {
if (setting_is_set(s))
set++;
}
return set;
}
/* Remove all settings in @list which are derived. */
void setting_list_remove_derived(struct setting_list *list)
{
struct setting *s, *n;
util_list_iterate_safe(&list->list, s, n) {
if (!s->derived)
continue;
util_list_remove(&list->list, s);
setting_free(s);
}
}
static void add_changes(struct util_list *list, struct setting *s)
{
struct strlist_node *str;
if (s->values) {
/* Multi value attribute. */
util_list_iterate(s->values, str)
strlist_add(list, "%s=%s", s->name, str->str);
} else {
/* Single value attribute. */
strlist_add(list, "%s=%s", s->name, s->value);
}
}
/* Return a newly allocated string containing the setting changes found
* in PERS and ACT or NULL if no change was found. */
char *setting_get_changes(struct setting_list *act, struct setting_list *pers)
{
struct util_list *out;
struct util_list *processed;
struct setting *s;
char *result;
out = strlist_new();
processed = strlist_new();
/* Collect modified settings. */
if (pers) {
util_list_iterate(&pers->list, s) {
if (s->removed)
strlist_add(out, "-%s", s->name);
else if (s->modified)
add_changes(out, s);
else
continue;
strlist_add(processed, s->name);
}
}
if (act) {
util_list_iterate(&act->list, s) {
if (!s->modified && !s->removed)
continue;
if (strlist_find(processed, s->name))
continue;
if (s->removed)
strlist_add(out, "-%s", s->name);
else
add_changes(out, s);
strlist_add(processed, s->name);
}
}
if (!util_list_is_empty(out))
result = strlist_flatten(out, " ");
else
result = NULL;
strlist_free(processed);
strlist_free(out);
return result;
}
/* Check if @value matches the value of setting @s. */
bool setting_match_value(struct setting *s, const char *value)
{
struct strlist_node *str;
if (s->values) {
util_list_iterate(s->values, str) {
if (strcmp(str->str, value) == 0)
return true;
}
}
if (s->value) {
if (strcmp(s->value, value) == 0)
return true;
}
return false;
}

816
zdev/src/subtype.c Normal file
View File

@@ -0,0 +1,816 @@
/*
* 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 <stddef.h>
#include <stdlib.h>
#include <string.h>
#include <strings.h>
#include "lib/util_base.h"
#include "attrib.h"
#include "device.h"
#include "devnode.h"
#include "devtype.h"
#include "misc.h"
#include "namespace.h"
#include "setting.h"
#include "subtype.h"
#include "udev.h"
/*
* Generic subtype helper functions.
*/
/* Return struct subtype associated with NAME or NULL if type could not
* be found. */
struct subtype *subtype_find(const char *name)
{
int i, j;
struct devtype *dt;
struct subtype *st;
for (i = 0; devtypes[i]; i++) {
dt = devtypes[i];
for (j = 0; dt->subtypes[j]; j++) {
st = dt->subtypes[j];
if (strcasecmp(st->name, name) == 0)
return st;
}
}
return NULL;
}
/* Search for a device attribute named STR. */
struct attrib *subtype_find_dev_attrib(struct subtype *st, const char *str)
{
struct attrib *a;
int i;
for (i = 0; (a = st->dev_attribs[i]); i++) {
if (strcmp(str, a->name) == 0)
return a;
}
return NULL;
}
struct devnode_cb_data_t {
struct devnode *devnode;
struct subtype **st_ptr;
char **id_ptr;
};
static exit_code_t devnode_cb(struct subtype *st, const char *id,
config_t config, void *data)
{
struct devnode_cb_data_t *cb_data = data;
struct devnode *devnode;
struct ptrlist_node *p, *n;
struct util_list *devnodes;
exit_code_t rc = EXIT_OK;
devnodes = subtype_get_devnodes(st, id);
if (!devnodes)
return EXIT_OK;
util_list_iterate_safe(devnodes, p, n) {
util_list_remove(devnodes, p);
devnode = p->ptr;
if (!rc && devnode_cmp(devnode, cb_data->devnode) == 0) {
*cb_data->st_ptr = st;
*cb_data->id_ptr = misc_strdup(id);
/* Not an error but aborts the loop. */
rc = EXIT_ABORTED;
}
free(devnode);
free(p);
}
free(devnodes);
return rc;
}
/* Find a device which provides the specified devnode. Note that id_ptr will
* point to a newly allocated string on success. */
bool subtypes_find_by_devnode(struct devnode *devnode, struct subtype **st_ptr,
char **id_ptr)
{
int i, j;
struct devtype *dt;
struct subtype *st;
struct devnode_cb_data_t cb_data;
char *id;
/* Try direct resolution if available. */
for (i = 0; (dt = devtypes[i]); i++) {
for (j = 0; (st = dt->subtypes[j]); j++) {
id = subtype_resolve_devnode(st, devnode);
if (id) {
*st_ptr = st;
*id_ptr = id;
return true;
}
}
}
/* Search list of provided devnodes. */
cb_data.devnode = devnode;
cb_data.st_ptr = st_ptr;
cb_data.id_ptr = id_ptr;
for (i = 0; (dt = devtypes[i]); i++) {
for (j = 0; (st = dt->subtypes[j]); j++) {
if (subtype_for_each_id(st, config_active, devnode_cb,
&cb_data))
return true;
}
}
return false;
}
/*
* Subtype method acessor functions. These functions run the subtype's
* method or, if non was defined, the method of its super-subtype.
*/
/* Follow curr->super until curr->method is non-zero or curr is NULL. */
static void *_super_get(void *curr, size_t method_off, size_t super_off,
int mandatory)
{
void **addr;
while (curr) {
addr = (void *) ((unsigned long) curr + method_off);
if (*addr)
break;
addr = (void *) ((unsigned long) curr + super_off);
curr = *addr;
}
if (!curr && mandatory)
internal("Missing method implementation");
return curr;
}
#define super_get(obj, method, mand) \
_super_get((obj), offsetof(__typeof__(*(obj)), method), \
offsetof(__typeof__(*(obj)), super), \
(mand))
void subtype_init(struct subtype *st)
{
struct subtype *super = super_get(st, init, 0);
if (super)
super->init(st);
}
void subtype_exit(struct subtype *st)
{
struct subtype *super = super_get(st, exit, 0);
if (super)
super->exit(st);
}
bool subtype_device_exists_active(struct subtype *st, const char *id)
{
struct subtype *super = super_get(st, exists_active, 1);
return super->exists_active(st, id);
}
bool subtype_device_exists_persistent(struct subtype *st, const char *id)
{
struct subtype *super = super_get(st, exists_persistent, 1);
return super->exists_persistent(st, id);
}
void subtype_add_active_ids(struct subtype *st, struct util_list *ids)
{
struct subtype *super = super_get(st, add_active_ids, 1);
super->add_active_ids(st, ids);
}
void subtype_add_persistent_ids(struct subtype *st, struct util_list *ids)
{
struct subtype *super = super_get(st, add_persistent_ids, 1);
super->add_persistent_ids(st, ids);
}
exit_code_t subtype_device_read_active(struct subtype *st, struct device *dev,
read_scope_t scope)
{
struct subtype *super = super_get(st, read_active, 1);
return super->read_active(st, dev, scope);
}
exit_code_t subtype_device_read_persistent(struct subtype *st,
struct device *dev,
read_scope_t scope)
{
struct subtype *super = super_get(st, read_persistent, 1);
return super->read_persistent(st, dev, scope);
}
exit_code_t subtype_device_configure_active(struct subtype *st,
struct device *dev)
{
struct subtype *super = super_get(st, configure_active, 1);
return super->configure_active(st, dev);
}
exit_code_t subtype_device_configure_persistent(struct subtype *st,
struct device *dev)
{
struct subtype *super = super_get(st, configure_persistent, 1);
return super->configure_persistent(st, dev);
}
exit_code_t subtype_device_deconfigure_active(struct subtype *st,
struct device *dev)
{
struct subtype *super = super_get(st, deconfigure_active, 1);
return super->deconfigure_active(st, dev);
}
exit_code_t subtype_device_deconfigure_persistent(struct subtype *st,
struct device *dev)
{
struct subtype *super = super_get(st, deconfigure_persistent, 1);
return super->deconfigure_persistent(st, dev);
}
exit_code_t subtype_check_pre_configure(struct subtype *st, struct device *dev,
int prereq, config_t config)
{
struct subtype *super = super_get(st, check_pre_configure, 0);
exit_code_t rc;
/* Generic checking. */
rc = device_check_settings(dev, config, err_delayed_forceable);
if (rc)
return rc;
/* Type-specific checking. */
if (super)
return super->check_pre_configure(st, dev, prereq, config);
return EXIT_OK;
}
exit_code_t subtype_check_post_configure(struct subtype *st, struct device *dev,
int prereq, config_t config)
{
struct subtype *super = super_get(st, check_post_configure, 0);
if (super)
return super->check_post_configure(st, dev, prereq, config);
return EXIT_OK;
}
void subtype_online_set(struct subtype *st, struct device *dev, int online,
config_t config)
{
struct subtype *super = super_get(st, online_set, 0);
if (super)
super->online_set(st, dev, online, config);
}
int subtype_online_get(struct subtype *st, struct device *dev, config_t config)
{
struct subtype *super = super_get(st, online_get, 0);
int act_online = 1, pers_online = 1;
if (super)
return super->online_get(st, dev, config);
/* Devices that do not support online setting are online when they
* exist. */
if (SCOPE_ACTIVE(config))
act_online = dev->active.exists;
if (SCOPE_PERSISTENT(config))
pers_online = dev->persistent.exists;
return MIN(act_online, pers_online);
}
bool subtype_online_specified(struct subtype *st, struct device *dev,
config_t config)
{
struct subtype *super = super_get(st, online_specified, 0);
if (super)
return super->online_specified(st, dev, config);
return false;
}
void subtype_add_errors(struct subtype *st, const char *id,
struct util_list *errors)
{
struct subtype *super = super_get(st, add_errors, 0);
if (super)
super->add_errors(st, id, errors);
}
void subtype_add_modules(struct subtype *st, struct device *dev,
struct util_list *modules)
{
struct subtype *super = super_get(st, add_modules, 0);
if (super)
super->add_modules(st, dev, modules);
}
void subtype_add_devnodes(struct subtype *st, const char *id,
struct util_list *devnodes)
{
struct subtype *super = super_get(st, add_devnodes, 0);
if (super)
super->add_devnodes(st, id, devnodes);
}
char *subtype_resolve_devnode(struct subtype *st, struct devnode *devnode)
{
struct subtype *super = super_get(st, resolve_devnode, 0);
if (super)
return super->resolve_devnode(st, devnode);
return NULL;
}
void subtype_add_prereqs(struct subtype *st, const char *id,
struct util_list *selected)
{
struct subtype *super = super_get(st, add_prereqs, 0);
if (super)
super->add_prereqs(st, id, selected);
}
void subtype_rem_combined(struct subtype *st, struct device *dev,
struct selected_dev_node *curr,
struct util_list *selected)
{
struct subtype *super = super_get(st, rem_combined, 0);
if (super)
super->rem_combined(st, dev, curr, selected);
}
char *subtype_get_active_attrib_path(struct subtype *st, struct device *dev,
const char *name)
{
struct subtype *super = super_get(st, get_active_attrib_path, 0);
if (super)
return super->get_active_attrib_path(st, dev, name);
return NULL;
}
char *subtype_get_active_attrib(struct subtype *st, struct device *dev,
const char *name)
{
struct subtype *super = super_get(st, get_active_attrib, 0);
if (super)
return super->get_active_attrib(st, dev, name);
return NULL;
}
exit_code_t subtype_device_is_definable(struct subtype *st, const char *id,
err_t err)
{
struct subtype *super = super_get(st, is_definable, 0);
if (super)
return super->is_definable(st, id, err);
return EXIT_GROUP_NOT_FOUND;
}
exit_code_t subtype_detect_definable(struct subtype *st, struct device *dev)
{
struct subtype *super = super_get(st, detect_definable, 0);
if (super)
return super->detect_definable(st, dev);
return EXIT_OK;
}
exit_code_t subtype_device_define(struct subtype *st, struct device *dev)
{
struct subtype *super = super_get(st, device_define, 0);
exit_code_t rc;
int proc;
struct setting_list *settings;
if (super) {
rc = super->device_define(st, dev);
if (rc)
return rc;
/* After device was defined, udev might have already applied
* existing persistent settings. Need to wait for udev and
* reread active configuration. */
udev_settle();
proc = dev->processed;
settings = dev->active.settings;
dev->active.settings = setting_list_new();
if (subtype_reread_device(st, dev->id, config_active,
scope_known, &dev) == EXIT_OK)
dev->active.modified = 1;
/* Re-apply settings. */
setting_list_merge(dev->active.settings, settings, true, true);
setting_list_free(settings);
/* Need to restore dev->proc since it might have been cleared
* by subtype_reread_device() . */
dev->processed = proc;
}
return EXIT_OK;
}
exit_code_t subtype_device_undefine(struct subtype *st, struct device *dev)
{
struct subtype *super = super_get(st, device_undefine, 0);
if (super)
return super->device_undefine(st, dev);
return EXIT_OK;
}
void subtype_add_definable_ids(struct subtype *st, struct util_list *ids)
{
struct subtype *super = super_get(st, add_definable_ids, 0);
if (super)
super->add_definable_ids(st, ids);
}
/*
* Subtype helpers. These functions combine some of the subtype methods
* to implement more complex functions.
*/
bool subtype_device_exists(struct subtype *st, const char *id, config_t config)
{
if (SCOPE_ACTIVE(config)) {
if (!subtype_device_exists_active(st, id) &&
subtype_device_is_definable(st, id, err_ignore) != EXIT_OK)
return false;
}
if (SCOPE_PERSISTENT(config)) {
if (!subtype_device_exists_persistent(st, id))
return false;
}
return true;
}
static struct util_list *get_ids(struct subtype *st, config_t config)
{
struct util_list *ids;
ids = strlist_new();
/* Get IDs from each configuration. */
if (SCOPE_ACTIVE(config)) {
subtype_add_active_ids(st, ids);
subtype_add_definable_ids(st, ids);
}
if (SCOPE_PERSISTENT(config))
subtype_add_persistent_ids(st, ids);
/* Provide a sorted view. */
strlist_sort_unique(ids, st->namespace->qsort_cmp);
return ids;
}
unsigned long subtype_count_ids(struct subtype *st, config_t config)
{
struct util_list *ids;
unsigned long num;
ids = get_ids(st, config);
num = util_list_len(ids);
strlist_free(ids);
return num;
}
exit_code_t subtype_for_each_id(struct subtype *st, config_t config,
subtype_cb_t cb, void *data)
{
struct util_list *ids;
struct strlist_node *s;
exit_code_t rc;
ids = get_ids(st, config);
rc = EXIT_OK;
util_list_iterate(ids, s) {
rc = cb(st, s->str, config, data);
if (rc)
break;
}
strlist_free(ids);
return rc;
}
struct util_list *subtype_get_devnodes(struct subtype *st, const char *id)
{
struct util_list *devnodes;
devnodes = ptrlist_new();
subtype_add_devnodes(st, id, devnodes);
if (util_list_is_empty(devnodes)) {
ptrlist_free(devnodes, 1);
devnodes = NULL;
}
return devnodes;
}
/* Return a space-separated list of device names provided by device with
* subtype @st and @id. */
char *subtype_get_devnodes_str(struct subtype *st, const char *id, int bdev,
int bdev_part, int cdev, int netdev)
{
struct util_list *devnodes, *names;
struct ptrlist_node *p;
struct devnode *d;
char *str;
int first_bdev = 1;
devnodes = ptrlist_new();
subtype_add_devnodes(st, id, devnodes);
names = strlist_new();
util_list_iterate(devnodes, p) {
d = p->ptr;
switch (d->type) {
case BLOCKDEV:
if (bdev_part || (bdev && first_bdev))
strlist_add(names, "%s", d->name);
first_bdev = 0;
break;
case CHARDEV:
if (cdev)
strlist_add(names, "%s", d->name);
break;
case NETDEV:
if (netdev)
strlist_add(names, "%s", d->name);
break;
}
}
str = strlist_flatten(names, " ");
strlist_free(names);
ptrlist_free(devnodes, 1);
return str;
}
struct util_list *subtype_get_errors(struct subtype *st, const char *id)
{
struct util_list *errors;
errors = strlist_new();
subtype_add_errors(st, id, errors);
if (util_list_is_empty(errors)) {
strlist_free(errors);
errors = NULL;
}
return errors;
}
static exit_code_t read_device(struct subtype *st, const char *id,
config_t config, struct device **dev_ptr,
int reread, read_scope_t scope)
{
struct device *dev;
exit_code_t rc = EXIT_OK;
int add, defined;
dev = device_list_find(st->devices, id, NULL);
if (dev) {
if (!reread) {
if (SCOPE_ACTIVE(config) && dev->active.blacklisted) {
/* Reread active device information because
* device might have been removed from
* blacklist. . */
config = config_active;
} else
goto out;
}
add = 0;
device_reset(dev, config);
} else {
dev = device_new(st, id);
if (!dev)
return EXIT_INVALID_ID;
add = 1;
}
defined = 0;
if (SCOPE_ACTIVE(config)) {
if (subtype_device_exists_active(st, id))
rc = subtype_device_read_active(st, dev, scope);
else if (subtype_device_is_definable(st, id,
err_ignore) == EXIT_OK) {
rc = subtype_detect_definable(st, dev);
defined = 1;
}
if (rc)
goto out;
/* Apply attribute value mapping. */
setting_list_map_values(dev->active.settings);
/* Add blacklisted flag. */
if (st->namespace->is_id_blacklisted &&
st->namespace->is_id_blacklisted(id))
dev->active.blacklisted = 1;
}
if (SCOPE_PERSISTENT(config)) {
if (subtype_device_exists_persistent(st, id))
rc = subtype_device_read_persistent(st, dev, scope);
else if (defined) {
/* Need to copy detected settings to persistent
* config. */
setting_list_merge(dev->persistent.settings,
dev->active.settings, false, false);
}
if (rc)
goto out;
}
if (add)
device_list_add(st->devices, dev);
out:
if (rc)
device_free(dev);
else if (dev_ptr)
*dev_ptr = dev;
return rc;
}
exit_code_t subtype_read_device(struct subtype *st, const char *id,
config_t config, read_scope_t scope,
struct device **dev_ptr)
{
return read_device(st, id, config, dev_ptr, 0, scope);
}
exit_code_t subtype_reread_device(struct subtype *st, const char *id,
config_t config, read_scope_t scope,
struct device **dev_ptr)
{
return read_device(st, id, config, dev_ptr, 1, scope);
}
/* Apply device configuration %dev to the configuration sets %config. */
exit_code_t subtype_write_device(struct subtype *st, struct device *dev,
config_t config)
{
exit_code_t rc = EXIT_OK;
if (SCOPE_ACTIVE(config)) {
if (dev->active.deconfigured) {
/* Deconfigure device. */
if (dev->active.exists) {
rc = subtype_device_deconfigure_active(st, dev);
if (rc)
return rc;
rc = subtype_device_undefine(st, dev);
if (rc)
return rc;
}
} else if (dev->active.exists || dev->active.definable) {
/* Configure device. */
if (dev->active.definable) {
rc = subtype_device_define(st, dev);
if (rc)
return rc;
}
rc = subtype_device_configure_active(st, dev);
if (rc)
return rc;
}
}
if (SCOPE_PERSISTENT(config)) {
if (dev->persistent.deconfigured) {
/* Deconfigure device. */
if (dev->persistent.exists) {
rc = subtype_device_deconfigure_persistent(st,
dev);
if (rc)
return rc;
}
} else if (dev->persistent.exists) {
/* Configure device. */
rc = subtype_device_configure_persistent(st, dev);
if (rc)
return rc;
}
if (!rc)
namespace_set_modified(dev->subtype->namespace);
}
return EXIT_OK;
}
/* Used for debugging. */
void subtype_devices_print_all(void)
{
int i, j;
struct devtype *dt;
struct subtype *st;
for (i = 0; devtypes[i]; i++) {
dt = devtypes[i];
for (j = 0; dt->subtypes[j]; j++) {
st = dt->subtypes[j];
device_list_print(st->devices, 0);
}
}
}
/* Add name of all modules required by subtype @st to strlist @modules. */
void subtype_add_static_modules(struct util_list *modules, struct subtype *st)
{
const char **mod = st->modules;
int i;
if (!mod)
return;
for (i = 0; mod[i]; i++)
strlist_add_unique(modules, "%s", mod[i]);
}
void subtype_print(struct subtype *st, int indent)
{
printf("%*ssubtype at %p\n", indent, "", (void *) st);
if (!st)
return;
indent += 2;
printf("%*sname=%s\n", indent, "", st->name);
printf("%*sdevices:\n", indent, "");
device_list_print(st->devices, indent + 2);
}
static void base_st_init(struct subtype *st)
{
st->devices = device_list_new(st);
}
static void base_st_exit(struct subtype *st)
{
device_list_free(st->devices);
}
struct subtype subtype_base = {
.init = &base_st_init,
.exit = &base_st_exit,
};

452
zdev/src/table.c Normal file
View File

@@ -0,0 +1,452 @@
/*
* 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_base.h"
#include "misc.h"
#include "table.h"
/* Search for a column in @columns which matches the specified @name. Return
* corresponding struct column on success, NULL otherwise. */
struct column *table_get_column(struct column *columns, const char *name)
{
struct column *c;
int i;
for (i = 0; columns[i].name; i++) {
c = &columns[i];
if (strcasecmp(c->name, name) == 0)
return c;
if (strchr(c->name, ':') && starts_with_nocase(name, c->name))
return c;
}
return NULL;
}
/**
* cell - Representation of a single table cell
* @id: Column ID of the cell
* @heading: Actual column heading
* @value: Cell contents
* @width: Total width of cell
* @align: Alignment of cell text
*
*/
struct cell {
char *value;
char *heading;
int id;
int width;
align_t align;
};
/* Used for debugging. */
void cells_print(struct cell *cells, int indent)
{
int i;
printf("%*scells at %p\n", indent, "", (void *) cells);
if (!cells)
return;
indent += 2;
for (i = 0; cells[i].heading; i++) {
printf("%*scells[%d]:\n", indent, "", i);
printf("%*svalue=%s\n", indent + 2, "", cells[i].value);
printf("%*sheading=%s\n", indent + 2, "", cells[i].heading);
printf("%*sid=%d\n", indent + 2, "", cells[i].id);
printf("%*swidth=%d\n", indent + 2, "", cells[i].width);
printf("%*salign=%d\n", indent + 2, "", cells[i].align);
}
}
static void cells_free(struct cell *cells)
{
int i;
struct cell *c;
if (!cells)
return;
for (i = 0; cells[i].heading; i++) {
c = &cells[i];
free(c->value);
free(c->heading);
}
free(cells);
}
/* Return a newly allocated array of struct cells containing a struct cell for
* each default column in the specified table. */
static struct cell *cells_get_default(struct column *columns)
{
int i, num;
struct cell *cells;
/* Determine number of default columns. */
num = 0;
for (i = 0; columns[i].name; i++) {
if (!columns[i].def)
continue;
num++;
}
/* Allocated cells array. */
cells = misc_malloc((num + 1) * sizeof(struct cell));
/* Initialize cells for default columns. */
num = 0;
for (i = 0; columns[i].name; i++) {
if (!columns[i].def)
continue;
cells[num].heading = misc_strdup(columns[i].name);
cells[num].id = columns[i].id;
cells[num].align = columns[i].align;
num++;
}
return cells;
}
/* Return a newly allocated array of struct cells containing a struct cell for
* each column definition whose name was specified in strlist @names. */
static struct cell *cells_get(struct column *columns, struct util_list *names)
{
unsigned long num;
struct cell *cells;
int i;
const struct column *column;
struct strlist_node *s;
num = util_list_len(names);
cells = misc_malloc((num + 1) * sizeof(struct cell));
i = 0;
util_list_iterate(names, s) {
column = table_get_column(columns, s->str);
if (!column)
goto err_unknown;
cells[i].heading = misc_strdup(s->str);
cells[i].id = column->id;
cells[i].align = column->align;
i++;
}
return cells;
err_unknown:
cells_free(cells);
error("Unknown column name specified: %s\n", s->str);
return NULL;
}
/* Determine maximum width for values and headings in table. */
static void cells_get_width(struct cell *cells, struct util_list *items,
table_value_cb_t value_cb, void *data)
{
int i, len;
struct cell *c;
struct ptrlist_node *p;
char *val;
/* Initialize widths. */
for (i = 0; cells[i].heading; i++)
cells[i].width = strlen(cells[i].heading);
/* Determine largest width from items. */
util_list_iterate(items, p) {
for (i = 0; cells[i].heading; i++) {
c = &cells[i];
val = value_cb(p->ptr, c->id, c->heading, data);
if (!val)
continue;
len = strlen(val);
free(val);
if (c->width < len)
c->width = len;
}
}
}
/* Return the optimal space delimiter for the specified cells. */
static const char *cells_get_space(struct cell *cells, struct util_list *items,
table_value_cb_t value_cb, void *data,
int indent)
{
char *val;
int columns, i, off_one, off_two, last, wrap_one, wrap_two, len;
struct ptrlist_node *p;
struct cell *c;
/* Quick exit on special case to make processing simpler. */
if (!cells[0].heading)
return " ";
/* Determine column offset of last column. */
off_one = indent;
off_two = indent;
for (i = 0; cells[i + 1].heading; i++) {
off_one += cells[i].width + 1;
off_two += cells[i].width + 2;
}
last = i;
/* Determine how many rows would wrap with either one or two spaces. */
columns = get_columns();
wrap_one = 0;
wrap_two = 0;
c = &cells[last];
util_list_iterate(items, p) {
val = value_cb(p->ptr, c->id, c->heading, data);
if (val) {
len = strlen(val);
free(val);
} else
len = 0;
if (off_one + len >= columns)
wrap_one++;
if (off_two + len >= columns)
wrap_two++;
}
/* Use two spaces if the number of lines that would wrap is the same
* for one and two spaces. */
if (wrap_two <= wrap_one)
return " ";
return " ";
}
/* Get all values for an item. */
static void cells_get_values(struct cell *cells, void *item,
table_value_cb_t value_cb, void *data)
{
int i;
struct cell *c;
for (i = 0; cells[i].heading; i++) {
c = &cells[i];
free(c->value);
c->value = value_cb(item, c->id, c->heading, data);
}
}
/* Print all headings. */
static void print_heading(struct cell *cells, const char *space, int indent)
{
int i, width;
struct cell *c;
if (indent > 0)
printf("%*s", indent, "");
for (i = 0; cells[i].heading; i++) {
c = &cells[i];
/* Don't print spaces in last cell. */
if (!cells[i + 1].heading)
width = 0;
else if (c->align == align_left)
width = -c->width;
else
width = c->width;
printf("%s%*s", i > 0 ? space : "", width, c->heading);
}
printf("\n");
}
/* Print all cells in a row in list format. */
static void print_row(struct cell *cells, const char *space, int indent,
int wrap)
{
int i, width, columns, offset, slen;
struct cell *c;
columns = get_columns();
slen = strlen(space);
offset = indent;
if (indent > 0)
printf("%*s", indent, "");
for (i = 0; cells[i].heading; i++) {
c = &cells[i];
/* Don't print spaces in last cell. */
if (!cells[i + 1].heading)
width = 0;
else if (c->align == align_left)
width = -c->width;
else
width = c->width;
if (!wrap)
goto do_print;
offset += (i > 0 ? slen : 0);
if (c->value)
offset += MAX((size_t) abs(width), strlen(c->value));
else
offset += abs(width);
if (offset >= columns && i > 0) {
/* Printing this cell would wrap the line - start on
* the next line, indented to the second column. */
offset = indent + cells[0].width;
printf("\n%*s", offset, "");
if (c->value) {
offset += MAX((size_t) abs(width),
strlen(c->value));
} else
offset += abs(width);
}
do_print:
printf("%s%*s", i > 0 ? space : "", width,
c->value ? c->value : "");
}
printf("\n");
}
/* Print all cells in a row in pairs format. */
static void print_row_pairs(struct cell *cells)
{
int i;
struct cell *c;
char *val;
for (i = 0; cells[i].heading; i++) {
c = &cells[i];
val = quote_str(c->value ? c->value : "", 1);
printf("%s%s=%s", i > 0 ? " " : "", c->heading, val);
free(val);
}
printf("\n");
}
/* Print a table. Either in list form or in pairs form (if @pairs is set).
* In list form, print a heading if @heading is set. Show data from items
* in ptrlist @items. Specify column names of columns to be printed in
* @names. If @wrap is set, wrap overlong lines. , */
exit_code_t table_print(struct column *columns, table_value_cb_t get_value_cb,
void *data, struct util_list *items,
struct util_list *names, int heading, int pairs,
int indent, int wrap)
{
struct cell *cells;
struct ptrlist_node *p;
const char *space = " ";
/* Initialize cells array. */
if (!names || util_list_is_empty(names))
cells = cells_get_default(columns);
else {
cells = cells_get(columns, names);
if (!cells)
return EXIT_UNKNOWN_COLUMN;
}
if (!pairs) {
cells_get_width(cells, items, get_value_cb, data);
if (wrap) {
space = cells_get_space(cells, items, get_value_cb,
data, indent);
} else
space = " ";
if (heading)
print_heading(cells, space, indent);
}
/* Print rows. */
util_list_iterate(items, p) {
cells_get_values(cells, p->ptr, get_value_cb, data);
if (pairs)
print_row_pairs(cells);
else
print_row(cells, space, indent, wrap);
}
cells_free(cells);
return EXIT_OK;
}
exit_code_t table_check_columns(struct column *columns, struct util_list *names)
{
struct cell *cells;
if (!names || util_list_is_empty(names))
return EXIT_OK;
cells = cells_get(columns, names);
if (!cells)
return EXIT_UNKNOWN_COLUMN;
cells_free(cells);
return EXIT_OK;
}
enum {
column_name,
column_desc,
};
static struct column *columns_table = COLUMN_ARRAY(
COLUMN("COLUMN", align_left, column_name, 1, ""),
COLUMN("DESCRIPTION", align_left, column_desc, 1, "")
);
static char *columns_table_get_value(void *item, int id, const char *heading,
void *data)
{
struct column *c = item;
switch (id) {
case column_name:
return misc_strdup(c->name);
case column_desc:
return misc_strdup(c->desc);
}
return NULL;
}
/* Print a table listing available columns and their description. */
void table_print_columns(struct column *columns, struct util_list *names,
int heading, int pairs)
{
int i;
struct util_list *items;
items = ptrlist_new();
for (i = 0; columns[i].name; i++)
ptrlist_add(items, &columns[i]);
table_print(columns_table, columns_table_get_value, NULL, items, names,
heading, pairs, 0, 0);
ptrlist_free(items, 0);
}
/* Change the default state of column with @id in table @columns to @def. */
void table_set_default(struct column *columns, int id, int def)
{
int i;
for (i = 0; columns[i].name; i++) {
if (columns[i].id == id) {
columns[i].def = def;
break;
}
}
}

279
zdev/src/table_attribs.c Normal file
View File

@@ -0,0 +1,279 @@
/*
* 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 "attrib.h"
#include "ccw.h"
#include "devtype.h"
#include "misc.h"
#include "subtype.h"
#include "table.h"
#include "table_attribs.h"
/* Column IDs for the types table. */
enum table_attribs_id {
table_attribs_name,
table_attribs_desc,
};
/* Definition of output table for --list-types. */
static struct column *table_attribs = COLUMN_ARRAY(
COLUMN("NAME", align_left, table_attribs_name, 1, ""),
COLUMN("DESCRIPTION", align_left, table_attribs_desc, 1, "")
);
/* Return a newly allocated struct table_attrib. */
struct table_attrib *table_attrib_new(struct subtype *st, struct attrib *a)
{
struct table_attrib *t;
t = misc_malloc(sizeof(struct table_attrib));
t->st = st;
t->attrib = a;
return t;
}
/* Retrieve value of a cell for devtype/subtype name @item in column @id in the
* types table. */
static char *table_attribs_get_value(void *item, int id, const char *heading,
void *data)
{
struct table_attrib *t = item;
struct attrib *a = t->attrib;
switch (id) {
case table_attribs_name:
return misc_strdup(a->name);
case table_attribs_desc:
return misc_strdup(a->title);
}
return NULL;
}
static struct util_list *get_subtypes(struct util_list *attribs)
{
struct ptrlist_node *p, *q;
struct table_attrib *t;
struct util_list *list;
list = ptrlist_new();
util_list_iterate(attribs, p) {
t = p->ptr;
util_list_iterate(list, q) {
if (t->st == q->ptr)
break;
}
if (!q)
ptrlist_add(list, t->st);
}
return list;
}
struct util_list *get_subtype_attribs(struct util_list *all, struct subtype *st)
{
struct util_list *subattribs;
struct ptrlist_node *p;
struct table_attrib *t;
subattribs = ptrlist_new();
util_list_iterate(all, p) {
t = p->ptr;
if (t->st == st)
ptrlist_add(subattribs, t);
}
return subattribs;
}
void print_type(struct devtype *dt, struct subtype *st, bool multiple)
{
int i;
if (!multiple)
return;
if (st)
printf("TYPE %s\n", st->name);
else {
printf("TYPE ");
for (i = 0; dt->subtypes[i]; i++) {
printf("%s%s", i == 0 ? "" : ", ",
dt->subtypes[i]->name);
}
printf("\n");
}
}
/* Display table of attributes. */
void table_attribs_show(struct util_list *attribs, int headings, int pairs,
struct devtype *dt)
{
struct util_list *subtypes, *subattribs;
struct ptrlist_node *p;
struct subtype *st;
bool multiple, first;
int indent;
subtypes = get_subtypes(attribs);
if (util_list_len(subtypes) > 1) {
multiple = true;
indent = 2;
} else {
multiple = false;
indent = 0;
}
first = true;
util_list_iterate(subtypes, p) {
st = p->ptr;
subattribs = get_subtype_attribs(attribs, st);
if (first)
first = false;
else
printf("\n");
print_type(dt, st, multiple);
table_print(table_attribs, table_attribs_get_value, st,
subattribs, NULL, headings, pairs, indent, 0);
ptrlist_free(subattribs, 0);
}
ptrlist_free(subtypes, 0);
}
static void table_attribs_show_details_one(struct table_attrib *t,
struct devtype *dt, bool multiple)
{
struct subtype *st = t->st;
struct attrib *a = t->attrib;
const int i = 2, j = 4;
int k;
printf("ATTRIBUTE %s\n\n", a->name);
if (multiple) {
indent(i, "APPLICABLE TYPES\n");
printf("%*s", j, "");
if (st)
printf("%s", st->name);
else {
for (k = 0; dt->subtypes[k]; k++) {
printf("%s%s", k == 0 ? "" : ", ",
dt->subtypes[k]->name);
}
}
printf("\n\n");
}
indent(i, "DESCRIPTION\n");
indent(j, "%s", a->desc);
if (a->defval) {
printf("\n");
indent(i, "DEFAULT VALUE\n");
indent(j, "The default value is '%s'.\n", a->defval);
}
printf("\n");
indent(i, "ACCEPTED VALUES\n");
attrib_print_acceptable(a, j);
if (!(a->multi || a->activeonly || a->unstable || a->writeonly ||
a->rewrite || a->mandatory || a->newline || a->activerem ||
a->nounload || a->check))
return;
printf("\n");
indent(i, "NOTES\n");
if (a->multi) {
indent(j, "- This attribute maintains a list of values "
"written to it\n");
}
if (a->activeonly) {
indent(j, "- Only specify this attribute in the active "
"configuration\n");
}
if (a->unstable) {
indent(j, "- The value read from this attribute is different "
"from the last value\n written to it\n");
}
if (a->writeonly)
indent(j, "- You cannot read this attribute\n");
if (a->rewrite) {
indent(j, "- Setting the same value multiple times may have "
"additional effects\n");
}
if (a->mandatory)
indent(j, "- Settings for this attribute cannot be removed\n");
if (a->newline) {
indent(j, "- A value written to this attribute must be "
"followed by a newline character\n");
}
if (a->activerem) {
indent(j, "- Settings for this attribute can be removed in "
"the active configuration\n");
}
if (a->check == ccw_offline_only_check) {
indent(j, "- This attribute cannot be changed while the device "
"is online\n");
}
if (a->check == ccw_online_only_check) {
indent(j, "- This attribute cannot be changed while the device "
"is offline\n");
}
if (a->nounload) {
indent(j, "- Settings for this attribute can be changed "
"without reloading the\n associated kernel "
"module\n");
}
}
static bool check_multiple_types(struct util_list *list)
{
struct ptrlist_node *p;
struct table_attrib *t;
struct subtype *st = NULL;
bool first;
first = true;
util_list_iterate(list, p) {
t = p->ptr;
if (first) {
first = false;
st = t->st;
} else if (t->st != st)
return true;
}
return false;
}
/* Display detailed attribute information*/
void table_attribs_show_details(struct util_list *attribs, struct devtype *dt)
{
struct ptrlist_node *p;
bool first, multiple;
first = true;
multiple = check_multiple_types(attribs);
util_list_iterate(attribs, p) {
if (first)
first = false;
else
printf("\n");
table_attribs_show_details_one(p->ptr, dt, multiple);
}
}

90
zdev/src/table_types.c Normal file
View File

@@ -0,0 +1,90 @@
/*
* 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 "devtype.h"
#include "misc.h"
#include "subtype.h"
#include "table.h"
#include "table_types.h"
/* Column IDs for the types table. */
enum table_types_id {
table_types_name,
table_types_title,
};
/* Definition of output table for --list-types. */
static struct column *table_types = COLUMN_ARRAY(
COLUMN("TYPE", align_left, table_types_name, 1, ""),
COLUMN("DESCRIPTION", align_left, table_types_title, 1, "")
);
/* Retrieve value of a cell for devtype/subtype name @item in column @id in the
* types table. */
static char *table_types_get_value(void *item, int id, const char *heading,
void *data)
{
char *name = item;
struct devtype *dt;
struct subtype *st;
switch (id) {
case table_types_name:
return misc_strdup(name);
case table_types_title:
st = subtype_find(name);
if (st)
return misc_strdup(st->title);
dt = devtype_find(name);
if (dt)
return misc_strdup(dt->title);
break;
default:
break;
}
return NULL;
}
/* Build list of items in table. Note that we're adding names here instead
* of the actual object because of the different types (devtype/subtype). */
static struct util_list *table_types_build(void)
{
struct util_list *items;
int i, j;
struct devtype *dt;
struct subtype *st;
items = ptrlist_new();
for (i = 0; devtypes[i]; i++) {
dt = devtypes[i];
if (*(dt->title))
ptrlist_add(items, misc_strdup(dt->name));
for (j = 0; dt->subtypes[j]; j++) {
st = dt->subtypes[j];
ptrlist_add(items, misc_strdup(st->name));
}
}
return items;
}
/* Perform --list-types. */
exit_code_t table_types_show(struct util_list *columns, int headings, int pairs)
{
struct util_list *items;
exit_code_t rc;
items = table_types_build();
rc = table_print(table_types, table_types_get_value, NULL,
items, columns, headings, pairs, 0, 0);
ptrlist_free(items, 1);
return rc;
}

405
zdev/src/udev.c Normal file
View File

@@ -0,0 +1,405 @@
/*
* 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 <ctype.h>
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "attrib.h"
#include "ccw.h"
#include "device.h"
#include "misc.h"
#include "path.h"
#include "setting.h"
#include "udev.h"
int udev_need_settle = 0;
/* Create a newly allocated udev entry. */
static struct udev_entry_node *udev_entry_node_new(const char *key,
const char *op,
const char *value)
{
struct udev_entry_node *entry;
entry = misc_malloc(sizeof(struct udev_entry_node));
entry->key = misc_strdup(key);
entry->op = misc_strdup(op);
entry->value = misc_strdup(value);
return entry;
}
/* Release resources associated with udev entry. */
static void udev_entry_node_free(struct udev_entry_node *entry)
{
if (!entry)
return;
free(entry->key);
free(entry->op);
free(entry->value);
free(entry);
}
/* Create a newly allocated udev line. */
static struct udev_line_node *udev_line_node_new(void)
{
struct udev_line_node *line;
line = misc_malloc(sizeof(struct udev_line_node));
util_list_init(&line->entries, struct udev_entry_node, node);
return line;
}
/* Release resources associated with udev line. */
static void udev_line_node_free(struct udev_line_node *line)
{
struct udev_entry_node *e, *n;
if (!line)
return;
util_list_iterate_safe(&line->entries, e, n) {
util_list_remove(&line->entries, e);
udev_entry_node_free(e);
}
free(line->line);
free(line);
}
/* Create a newly allocated udev file. */
static struct udev_file *udev_file_new(void)
{
struct udev_file *file;
file = misc_malloc(sizeof(struct udev_file));
util_list_init(&file->lines, struct udev_line_node, node);
return file;
}
/* Release resources associated with udev file. */
void udev_free_file(struct udev_file *file)
{
struct udev_line_node *l, *n;
if (!file)
return;
util_list_iterate_safe(&file->lines, l, n) {
util_list_remove(&file->lines, l);
udev_line_node_free(l);
}
free(file);
}
/* Used for debugging. */
void udev_file_print(struct udev_file *file)
{
struct udev_line_node *l;
struct udev_entry_node *e;
printf("udev_file at %p\n", (void *) file);
if (!file)
return;
util_list_iterate(&file->lines, l) {
printf(" udev_line_node at %p\n", (void *) l);
printf(" line='%s'\n", l->line);
util_list_iterate(&l->entries, e) {
printf(" udev_entry at %p\n", (void *) e);
printf(" '%s' '%s' '%s'\n", e->key, e->op,
e->value);
}
}
}
static void skip_whitespace(const char **s_ptr)
{
const char *s = *s_ptr;
while (*s && isspace(*s))
s++;
*s_ptr = s;
}
static char *parse_key(const char **s_ptr)
{
const char *s, *e;
char *key;
s = *s_ptr;
/* Parse \w+(\{[^\}]*\})? */
e = s;
while (*e && (isalnum(*e) || *e == '_'))
e++;
if (*e == '{') {
while (*e && *e != '}')
e++;
if (*e == '}')
e++;
}
if (e == s)
return NULL;
/* s points to key start, e to character after key end. */
key = misc_malloc(e - s + 1);
memcpy(key, s, e - s);
*s_ptr = e;
return key;
}
static char *parse_op(const char **s_ptr)
{
const char *ops[] = { "==", "!=", "=", "+=", ":=", NULL };
const char *entry;
size_t len;
int i;
entry = *s_ptr;
for (i = 0; ops[i]; i++) {
len = strlen(ops[i]);
if (strncmp(entry, ops[i], len) == 0) {
*s_ptr += len;
return misc_strdup(ops[i]);
}
}
return NULL;
}
static char *parse_value(const char **s_ptr)
{
const char *s, *e;
char *value;
/* Parse: ^\s*(.*)\s*$ */
s = *s_ptr;
skip_whitespace(&s);
e = s;
while (*e)
e++;
e--;
while (e > s && isspace(*e))
e--;
e++;
*s_ptr = e;
/* Remove quotes. */
if ((*s == '"' && *(e - 1) == '"') ||
(*s == '\'' && *(e - 1) == '\'')) {
s++;
e--;
}
/* s points to value start, e to character after value end. */
value = misc_malloc(e - s + 1);
memcpy(value, s, e - s);
return value;
}
static bool parse_udev_entry(struct udev_line_node *line, const char *entry)
{
char *key = NULL, *op = NULL, *value = NULL;
struct udev_entry_node *e;
bool rc = false;
/* Parse: ^\s*(\w+)\s*(==|!=|=|\+=|:=)\s*"?([^"]*)"\s*$ */
/* Parse key. */
skip_whitespace(&entry);
key = parse_key(&entry);
if (!key)
goto out;
/* Parse operator. */
skip_whitespace(&entry);
op = parse_op(&entry);
if (!op)
goto out;
/* Parse value. */
skip_whitespace(&entry);
value = parse_value(&entry);
if (!value)
goto out;
skip_whitespace(&entry);
/* Check for unrecognized characters at end of entry. */
if (*entry != 0)
goto out;
/* Add entry to list. */
e = udev_entry_node_new(key, op, value);
util_list_add_tail(&line->entries, e);
rc = true;
out:
free(key);
free(op);
free(value);
return rc;
}
static void replace_unquoted(char *s, char from, char to)
{
char quoted = 0;
for (; *s; s++) {
if (quoted) {
/* Skip until quote end is found. */
if (*s == quoted)
quoted = 0;
continue;
}
if (*s == '"' || *s == '\'') {
quoted = *s;
continue;
}
if (*s == from)
*s = to;
}
}
static bool parse_udev_line(struct udev_file *file, const char *line)
{
char *copy, *curr, *next;
struct udev_line_node *l;
int i;
bool result = true;
l = udev_line_node_new();
l->line = misc_strdup(line);
/* Check for empty lines and comment lines. */
for (i = 0; line[i] && isspace(line[i]); i++);
if (line[i] == 0 || line[i] == '#')
goto ok;
/* Parse each comma-separated entry. */
copy = misc_strdup(line);
/* A hack to differentiate between quoted and unquoted commas. */
replace_unquoted(copy, ',', 1);
next = copy;
while ((curr = strsep(&next, "\1"))) {
if (!parse_udev_entry(l, curr)) {
result = false;
break;
}
}
free(copy);
ok:
if (result)
util_list_add_tail(&file->lines, l);
else
udev_line_node_free(l);
return result;
}
/* Read the contents of a udev rule file. */
exit_code_t udev_read_file(const char *path, struct udev_file **file_ptr)
{
char *text, *curr, *next;
struct udev_file *file;
int once = 0;
text = misc_read_text_file(path, 0, err_print);
if (!text)
return EXIT_RUNTIME_ERROR;
file = udev_file_new();
/* Iterate over each line. */
next = text;
while ((curr = strsep(&next, "\n"))) {
if (parse_udev_line(file, curr))
continue;
if (!once) {
once = 1;
verb("Unrecognized udev rule format in %s:\n", path);
}
verb("%s\n", curr);
}
free(text);
*file_ptr = file;
return EXIT_OK;
}
static bool get_ids_cb(const char *filename, void *data)
{
char *prefix = data;
if (strncmp(filename, prefix, strlen(prefix)) != 0)
return false;
if (!ends_with(filename, UDEV_SUFFIX))
return false;
return true;
}
/* Add the IDs for all devices of the specified subtype name for which a
* udev rule exists to strlist LIST. */
void udev_get_device_ids(const char *type, struct util_list *list)
{
char *path, *prefix;
struct util_list *files;
struct strlist_node *s;
size_t plen, len;
prefix = misc_asprintf("%s-%s-", UDEV_PREFIX, type);
plen = strlen(prefix);
path = path_get_udev_rules();
files = strlist_new();
if (!misc_read_dir(path, files, get_ids_cb, prefix))
goto out;
util_list_iterate(files, s) {
/* 41-dasd-eckd-0.0.1234.rules */
len = strlen(s->str);
s->str[len - sizeof(UDEV_SUFFIX) + 1] = 0;
strlist_add(list, &s->str[plen]);
}
out:
strlist_free(files);
free(path);
free(prefix);
}
/* Remove UDEV rule for device. */
exit_code_t udev_remove_rule(const char *type, const char *id)
{
char *path;
exit_code_t rc = EXIT_OK;
path = path_get_udev_rule(type, id);
if (file_exists(path))
rc = remove_file(path);
free(path);
return rc;
}
/* Wait for all current udev events to finish. */
void udev_settle(void)
{
misc_system(err_ignore, "%s settle", PATH_UDEVADM);
}

288
zdev/src/udev_ccw.c Normal file
View File

@@ -0,0 +1,288 @@
/*
* 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 <ctype.h>
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "attrib.h"
#include "ccw.h"
#include "device.h"
#include "misc.h"
#include "path.h"
#include "setting.h"
#include "udev.h"
#include "udev_ccw.h"
/* Check if a udev rule for the specified ccw device exists. */
bool udev_ccw_exists(const char *type, const char *id)
{
char *path, *normid;
bool rc;
normid = ccw_normalize_id(id);
if (!normid)
return false;
path = path_get_udev_rule(type, normid);
rc = file_exists(path);
free(path);
free(normid);
return rc;
}
static void add_setting_from_entry(struct setting_list *list,
struct udev_entry_node *entry,
struct attrib **attribs)
{
char *copy, *name, *end;
struct attrib *a;
/* ATTR{[ccw/0.0.37bf]online}=1 */
if (strncmp(entry->key, "ATTR{[ccw/", 10) != 0 ||
strcmp(entry->op, "=") != 0)
return;
copy = misc_strdup(entry->key);
name = copy;
/* Find attribute name start. */
name = strchr(entry->key, ']');
end = strrchr(entry->key, '}');
if (!name || !end)
goto out;
*end = 0;
name++;
a = attrib_find(attribs, name);
setting_list_apply_actual(list, a, name, entry->value);
out:
free(copy);
}
/* Extract CCW device settings from a CCW device udev rule file. */
static void udev_file_get_settings(struct udev_file *file,
struct attrib **attribs,
struct setting_list *list)
{
struct udev_line_node *line;
struct udev_entry_node *entry;
util_list_iterate(&file->lines, line) {
entry = util_list_start(&line->entries);
if (!entry)
continue;
add_setting_from_entry(list, entry, attribs);
}
}
/* Read the persistent configuration of a CCW device from a udev rule. */
exit_code_t udev_ccw_read_device(struct device *dev)
{
struct subtype *st = dev->subtype;
struct device_state *state = &dev->persistent;
struct udev_file *file = NULL;
exit_code_t rc;
char *path;
path = path_get_udev_rule(st->name, dev->id);
rc = udev_read_file(path, &file);
if (rc)
goto out;
udev_file_get_settings(file, st->dev_attribs, state->settings);
state->exists = 1;
udev_free_file(file);
out:
free(path);
return rc;
}
/* Return an ID suitable for use as udev label. */
static char *get_label_id(const char *prefix, const char *type,
const char *dev_id)
{
char *id;
int i;
id = misc_asprintf("%s_%s_%s", prefix, type, dev_id);
for (i = 0; id[i]; i++) {
if (isalnum(id[i]) || id[i] == '_' || id[i] == '.')
continue;
id[i] = '_';
}
return id;
}
/* Write the persistent configuration of a CCW device to a udev rule. */
exit_code_t udev_ccw_write_device(struct device *dev)
{
struct subtype *st = dev->subtype;
struct ccw_subtype_data *data = st->data;
const char *type = st->name, *drv = data->ccwdrv, *id = dev->id;
struct device_state *state = &dev->persistent;
char *path, *cfg_label = NULL, *end_label = NULL;
struct util_list *list;
struct ptrlist_node *p;
struct setting *s;
exit_code_t rc = EXIT_OK;
FILE *fd;
if (!state->exists)
return udev_remove_rule(type, id);
cfg_label = get_label_id("cfg", type, id);
end_label = get_label_id("end", type, id);
/* Apply attributes in correct order. */
list = setting_list_get_sorted(state->settings);
path = path_get_udev_rule(type, id);
debug("Writing %s udev rule file %s\n", type, path);
if (!path_exists(path)) {
rc = path_create(path);
if (rc)
goto out;
}
fd = misc_fopen(path, "w");
if (!fd) {
error("Could not write to file %s: %s\n", path,
strerror(errno));
rc = EXIT_RUNTIME_ERROR;
goto out;
}
/* Write udev rule prolog. */
fprintf(fd, "# Generated by chzdev\n");
if (drv) {
fprintf(fd, "ACTION==\"add\", SUBSYSTEM==\"ccw\", "
"KERNEL==\"%s\", DRIVER==\"%s\", GOTO=\"%s\"\n", id,
drv, cfg_label);
fprintf(fd, "ACTION==\"add\", SUBSYSTEM==\"drivers\", "
"KERNEL==\"%s\", TEST==\"[ccw/%s]\", "
"GOTO=\"%s\"\n", drv, id, cfg_label);
} else {
fprintf(fd, "ACTION==\"add\", SUBSYSTEM==\"ccw\", "
"KERNEL==\"%s\", GOTO=\"%s\"\n", id, cfg_label);
}
fprintf(fd, "GOTO=\"%s\"\n", end_label);
fprintf(fd, "\n");
fprintf(fd, "LABEL=\"%s\"\n", cfg_label);
/* Write settings. */
util_list_iterate(list, p) {
s = p->ptr;
if (s->removed)
continue;
fprintf(fd, "ATTR{[ccw/%s]%s}=\"%s\"\n", id, s->name, s->value);
}
/* Write udev rule epilog. */
fprintf(fd, "\n");
fprintf(fd, "LABEL=\"%s\"\n", end_label);
if (misc_fclose(fd))
warn("Could not close file %s: %s\n", path, strerror(errno));
out:
ptrlist_free(list, 0);
free(end_label);
free(cfg_label);
free(path);
return rc;
}
#define MARKER "echo free "
static char *read_cio_ignore(const char *path)
{
char *text, *start, *end, *result = NULL;
text = misc_read_text_file(path, 0, err_ignore);
if (!text)
goto out;
start = strstr(text, MARKER);
if (!start)
goto out;
start += strlen(MARKER);
end = strchr(start, ' ');
if (!end)
goto out;
*end = 0;
result = misc_strdup(start);
out:
free(text);
return result;
}
/* Write a udev rule to free devices from the cio-ignore blacklist. */
exit_code_t udev_ccw_write_cio_ignore(const char *id_list)
{
char *path, *curr = NULL;
FILE *fd;
exit_code_t rc = EXIT_OK;
/* Create file. */
path = path_get_udev_rule("cio-ignore", NULL);
if (!*id_list) {
/* Empty id_list string - remove file. */
if (!file_exists(path)) {
/* Already removed. */
goto out;
}
rc = remove_file(path);
goto out;
}
curr = read_cio_ignore(path);
if (curr && strcmp(curr, id_list) == 0)
goto out;
debug("Writing cio-ignore udev rule file %s\n", path);
if (!path_exists(path)) {
rc = path_create(path);
if (rc)
goto out;
}
fd = misc_fopen(path, "w");
if (!fd) {
error("Could not write to file %s: %s\n", path,
strerror(errno));
rc = EXIT_RUNTIME_ERROR;
goto out;
}
/* Write udev rule. */
fprintf(fd, "# Generated by chzdev\n");
fprintf(fd, "ACTION==\"add\", SUBSYSTEM==\"subsystem\", "
"KERNEL==\"ccw\", RUN{program}+=\"/bin/sh -c "
"'echo free %s > /proc/cio_ignore'\"\n", id_list);
/* Close file. */
if (misc_fclose(fd))
warn("Could not close file %s: %s\n", path, strerror(errno));
out:
free(curr);
free(path);
return rc;
}

391
zdev/src/udev_ccwgroup.c Normal file
View File

@@ -0,0 +1,391 @@
/*
* 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 <ctype.h>
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "attrib.h"
#include "ccwgroup.h"
#include "device.h"
#include "misc.h"
#include "path.h"
#include "setting.h"
#include "udev.h"
#include "udev_ccwgroup.h"
static char *get_rule_path_by_devid(const char *type,
struct ccwgroup_devid *devid)
{
char *ccw_id, *path;
ccw_id = ccw_devid_to_str(&devid->devid[0]);
path = path_get_udev_rule(type, ccw_id);
free(ccw_id);
return path;
}
static char *get_rule_path(const char *type, const char *id)
{
struct ccwgroup_devid devid;
if (ccwgroup_parse_devid(&devid, id, err_ignore) != EXIT_OK)
return NULL;
return get_rule_path_by_devid(type, &devid);
}
/* Check if a udev rule for the specified CCWGROUP device exists. */
bool udev_ccwgroup_exists(const char *type, const char *id)
{
char *path;
bool rc;
path = get_rule_path(type, id);
if (!path)
return false;
rc = file_exists(path);
free(path);
return rc;
}
static void add_setting_from_entry(struct setting_list *list,
struct udev_entry_node *entry,
struct attrib **attribs)
{
char *copy, *name, *end;
struct attrib *a;
/* ATTR{[ccwgroup/0.0.f5f0]online}=1 */
if (strncmp(entry->key, "ATTR{[ccwgroup/", 10) != 0 ||
strcmp(entry->op, "=") != 0)
return;
copy = misc_strdup(entry->key);
name = copy;
/* Find attribute name start. */
name = strchr(entry->key, ']');
end = strrchr(entry->key, '}');
if (!name || !end)
goto out;
*end = 0;
name++;
a = attrib_find(attribs, name);
setting_list_apply_actual(list, a, name, entry->value);
out:
free(copy);
}
/* Extract CCWGROUP device settings from a CCWGROUP device udev rule file. */
static void udev_file_get_settings(struct udev_file *file,
struct attrib **attribs,
struct setting_list *list)
{
struct udev_line_node *line;
struct udev_entry_node *entry;
util_list_iterate(&file->lines, line) {
entry = util_list_start(&line->entries);
if (!entry)
continue;
add_setting_from_entry(list, entry, attribs);
}
}
/* Determine full CCWGROUP from data in udev rule file. */
static void expand_id(struct device *dev, struct udev_file *file)
{
struct ccwgroup_devid devid;
struct ccwgroup_devid *devid_ptr = dev->devid;
struct udev_line_node *line;
struct udev_entry_node *entry;
char *id;
int i;
if (devid_ptr->num != 1)
return;
util_list_iterate(&file->lines, line) {
entry = util_list_start(&line->entries);
if (!entry)
continue;
if (!starts_with(entry->key, "ATTR{[drivers/ccwgroup:") ||
!ends_with(entry->key, "]group}"))
continue;
/* Extract CCWGROUP ID from comma-separated list of CCW
* device IDs. */
id = misc_strdup(entry->value);
for (i = 0; id[i]; i++) {
if (id[i] == ',')
id[i] = ':';
}
if (ccwgroup_parse_devid(&devid, id, err_ignore) == EXIT_OK)
*devid_ptr = devid;
free(dev->id);
dev->id = ccwgroup_devid_to_str(&devid);
free(id);
break;
}
}
/* Read the persistent configuration of a CCWGROUP device from a udev rule. */
exit_code_t udev_ccwgroup_read_device(struct device *dev)
{
struct subtype *st = dev->subtype;
struct device_state *state = &dev->persistent;
struct udev_file *file = NULL;
exit_code_t rc;
char *path;
path = get_rule_path_by_devid(st->name, dev->devid);
rc = udev_read_file(path, &file);
if (rc)
goto out;
udev_file_get_settings(file, st->dev_attribs, state->settings);
expand_id(dev, file);
state->exists = 1;
udev_free_file(file);
out:
free(path);
return rc;
}
/* Return an ID suitable for use as udev label. */
static char *get_label_id(const char *prefix, const char *type,
const char *dev_id)
{
char *id;
int i;
id = misc_asprintf("%s_%s_%s", prefix, type, dev_id);
for (i = 0; id[i]; i++) {
if (isalnum(id[i]) || id[i] == '_' || id[i] == '.')
continue;
id[i] = '_';
}
return id;
}
/* Write the persistent configuration of a CCWGROUP device to a udev rule. */
exit_code_t udev_ccwgroup_write_device(struct device *dev)
{
struct subtype *st = dev->subtype;
struct ccwgroup_subtype_data *data = st->data;
const char *type = st->name, *drv = data->ccwgroupdrv, *id = dev->id;
struct ccwgroup_devid devid;
char *path, *cfg_label = NULL, *group_label = NULL, *end_label = NULL,
*ccw_id, *chan_id;
struct util_list *list;
struct ptrlist_node *p;
struct setting *s;
struct strlist_node *str;
exit_code_t rc = EXIT_OK;
FILE *fd;
unsigned int i;
if (!dev->persistent.exists)
return udev_ccwgroup_remove_rule(type, id);
if (ccwgroup_parse_devid(&devid, id, err_ignore) != EXIT_OK)
return EXIT_INVALID_ID;
ccw_id = ccw_devid_to_str(&devid.devid[0]);
path = get_rule_path(type, ccw_id);
group_label = get_label_id("group", type, ccw_id);
cfg_label = get_label_id("cfg", type, ccw_id);
end_label = get_label_id("end", type, ccw_id);
/* Apply attributes in correct order. */
list = setting_list_get_sorted(dev->persistent.settings);
debug("Writing %s udev rule file %s\n", type, path);
if (!path_exists(path)) {
rc = path_create(path);
if (rc)
goto out;
}
fd = misc_fopen(path, "w");
if (!fd) {
error("Could not write to file %s: %s\n", path,
strerror(errno));
rc = EXIT_RUNTIME_ERROR;
goto out;
}
/* Write udev rule prolog. */
fprintf(fd, "# Generated by chzdev\n");
/* Triggers. */
fprintf(fd, "ACTION==\"add\", SUBSYSTEM==\"drivers\", "
"KERNEL==\"%s\", GOTO=\"%s\"\n", drv, group_label);
for (i = 0; i < devid.num; i++) {
chan_id = ccw_devid_to_str(&devid.devid[i]);
fprintf(fd, "ACTION==\"add\", SUBSYSTEM==\"ccw\", "
"KERNEL==\"%s\", DRIVER==\"%s\", "
"GOTO=\"%s\"\n", chan_id, drv, group_label);
free(chan_id);
}
fprintf(fd, "ACTION==\"add\", SUBSYSTEM==\"ccwgroup\", "
"KERNEL==\"%s\", DRIVER==\"%s\", GOTO=\"%s\"\n", ccw_id,
drv, cfg_label);
fprintf(fd, "GOTO=\"%s\"\n\n", end_label);
/* Group. */
fprintf(fd, "LABEL=\"%s\"\n", group_label);
fprintf(fd, "TEST==\"[ccwgroup/%s]\", GOTO=\"%s\"\n", ccw_id,
end_label);
for (i = 0; i < devid.num; i++) {
chan_id = ccw_devid_to_str(&devid.devid[i]);
fprintf(fd, "TEST!=\"[ccw/%s]\", GOTO=\"%s\"\n", chan_id,
end_label);
free(chan_id);
}
fprintf(fd, "ATTR{[drivers/ccwgroup:%s]group}=\"", drv);
for (i = 0; i < devid.num; i++) {
chan_id = ccw_devid_to_str(&devid.devid[i]);
fprintf(fd, "%s%s", i > 0 ? "," : "", chan_id);
free(chan_id);
}
fprintf(fd, "\"\n");
fprintf(fd, "GOTO=\"%s\"\n\n", end_label);
/* Configure. */
fprintf(fd, "LABEL=\"%s\"\n", cfg_label);
util_list_iterate(list, p) {
s = p->ptr;
if (s->removed)
continue;
if (s->values) {
util_list_iterate(s->values, str) {
fprintf(fd, "ATTR{[ccwgroup/%s]%s}=\"%s\"\n",
ccw_id, s->name, str->str);
}
} else {
fprintf(fd, "ATTR{[ccwgroup/%s]%s}=\"%s\"\n", ccw_id,
s->name, s->value);
}
}
/* Write udev rule epilog. */
fprintf(fd, "\n");
fprintf(fd, "LABEL=\"%s\"\n", end_label);
if (misc_fclose(fd))
warn("Could not close file %s: %s\n", path, strerror(errno));
out:
ptrlist_free(list, 0);
free(end_label);
free(cfg_label);
free(group_label);
free(path);
free(ccw_id);
return rc;
}
static char *read_full_id(const char *path)
{
char *text, *start, *end, *id = NULL;
text = misc_read_text_file(path, 0, err_ignore);
if (!text)
return NULL;
start = strstr(text, "ATTR{[drivers/ccwgroup:");
if (!start)
goto out;
start = strchr(start, '"');
if (!start)
goto out;
start++;
end = strchr(start, '"');
if (!end)
goto out;
*end = 0;
id = misc_strdup(start);
start = id;
while ((start = strchr(start, ',')))
*start = ':';
out:
free(text);
return id;
}
struct get_ids_cb_data {
char *prefix;
struct util_list *ids;
};
static exit_code_t get_ids_cb(const char *path, const char *filename,
void *data)
{
struct get_ids_cb_data *cb_data = data;
char *id;
if (!starts_with(filename, cb_data->prefix))
return EXIT_OK;
id = read_full_id(path);
if (!id)
return EXIT_OK;
strlist_add(cb_data->ids, id);
free(id);
return EXIT_OK;
}
/* Add the IDs for all devices of the specified subtype name for which a
* udev rule exists to strlist LIST. */
void udev_ccwgroup_add_device_ids(const char *type, struct util_list *list)
{
struct get_ids_cb_data cb_data;
char *path;
path = path_get_udev_rules();
cb_data.prefix = misc_asprintf("%s-%s-", UDEV_PREFIX, type);
cb_data.ids = list;
if (dir_exists(path))
path_for_each(path, get_ids_cb, &cb_data);
free(cb_data.prefix);
free(path);
}
/* Remove UDEV rule for CCWGROUP device. */
exit_code_t udev_ccwgroup_remove_rule(const char *type, const char *id)
{
char *partial_id, *path;
exit_code_t rc = EXIT_OK;
partial_id = ccwgroup_get_partial_id(id);
if (!partial_id)
return EXIT_INVALID_ID;
path = path_get_udev_rule(type, partial_id);
if (file_exists(path))
rc = remove_file(path);
free(path);
free(partial_id);
return rc;
}

687
zdev/src/udev_zfcp_lun.c Normal file
View File

@@ -0,0 +1,687 @@
/*
* 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 <ctype.h>
#include <errno.h>
#include <inttypes.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "attrib.h"
#include "device.h"
#include "misc.h"
#include "path.h"
#include "scsi.h"
#include "setting.h"
#include "udev.h"
#include "udev_zfcp_lun.h"
#include "zfcp_lun.h"
#define LABEL_START "start_zfcp_lun_"
#define LABEL_END "end_zfcp_lun_"
#define LABEL_FC "cfg_fc_"
#define LABEL_SCSI "cfg_scsi_"
struct zfcp_lun_node {
struct util_list_node node;
struct zfcp_lun_devid id;
struct setting_list *fc_settings;
struct setting_list *scsi_settings;
};
static void zfcp_lun_node_print(struct zfcp_lun_node *node, int indent)
{
char *id;
printf("%*szfcp_lun_node at %p\n", indent, "", (void *) node);
if (!node)
return;
indent += 2;
id = zfcp_lun_devid_to_str(&node->id);
printf("%*sid=%s\n", indent, "", id);
free(id);
printf("%*sfc_settings:\n", indent, "");
setting_list_print(node->fc_settings, indent + 2);
printf("%*sscsi_settings:\n", indent, "");
setting_list_print(node->scsi_settings, indent + 2);
}
static struct zfcp_lun_node *zfcp_lun_node_new(struct zfcp_lun_devid *id)
{
struct zfcp_lun_node *node;
node = misc_malloc(sizeof(struct zfcp_lun_node));
node->id = *id;
node->fc_settings = setting_list_new();
node->scsi_settings = setting_list_new();
return node;
}
static void zfcp_lun_node_free(struct zfcp_lun_node *node)
{
if (!node)
return;
setting_list_free(node->fc_settings);
setting_list_free(node->scsi_settings);
free(node);
}
static struct zfcp_lun_node *zfcp_lun_node_find(struct util_list *list,
struct zfcp_lun_devid *id)
{
struct zfcp_lun_node *node;
util_list_iterate(list, node) {
if (zfcp_lun_cmp_devids(&node->id, id) == 0)
return node;
}
return NULL;
}
static struct util_list *zfcp_lun_node_list_new(void)
{
struct util_list *list;
list = misc_malloc(sizeof(struct util_list));
util_list_init(list, struct zfcp_lun_node, node);
return list;
}
static void zfcp_lun_node_list_free(struct util_list *list)
{
struct zfcp_lun_node *n, *p;
if (!list)
return;
util_list_iterate_safe(list, n, p) {
util_list_remove(list, n);
zfcp_lun_node_free(n);
}
free(list);
}
/* Used for debugging. */
void zfcp_lun_node_list_print(struct util_list *list, int indent)
{
struct zfcp_lun_node *n;
printf("%*szfcp_lun_node_list at %p\n", indent, "", (void *) list);
if (!list)
return;
util_list_iterate(list, n)
zfcp_lun_node_print(n, indent + 2);
}
static bool zfcp_lun_devid_from_entry(struct zfcp_lun_devid *id_ptr,
struct udev_entry_node *entry)
{
struct zfcp_lun_devid id;
char *copy = NULL, *s;
int i;
bool rc = false;
/* LABEL="cfg_scsi_0.0.1941_0x500507630510c1ae_0x402340d400000000" */
if (strcmp(entry->key, "LABEL") == 0 &&
starts_with(entry->value, LABEL_SCSI)) {
copy = misc_strdup(entry->value);
s = copy + strlen(LABEL_SCSI);
for (i = 0; s[i]; i++) {
if (s[i] == '_')
s[i] = ':';
}
rc = zfcp_lun_parse_devid(&id, s, err_ignore) == EXIT_OK ?
true : false;
goto out;
}
/*ATTR{[ccw/0.0.1941]0x500507630510c1ae/unit_add}="0x402340d400000000"*/
if (starts_with(entry->key, "ATTR{[ccw/") &&
ends_with(entry->key, "/unit_add}")) {
copy = misc_asprintf("%s%s", entry->key, entry->value);
s = copy + strlen("ATTR{[ccw/");
for (i = 0; s[i]; i++) {
if (s[i] == ']')
s[i] = ':';
else if (s[i] == '/')
break;
}
if (s[i] != '/')
goto out;
s[i] = ':';
strcpy(s + i + 1, entry->value);
rc = zfcp_lun_parse_devid(&id, s, err_ignore) == EXIT_OK ?
true : false;
goto out;
}
/*ATTR{[ccw/0.0.1941]0x500507630510c1ae/0x402340d400000000/failed}="0"*/
if (starts_with(entry->key, "ATTR{[ccw/")) {
copy = misc_strdup(entry->key);
s = strrchr(copy, '/');
if (s)
*s = 0;
s = copy + strlen("ATTR{[ccw/");
for (i = 0; s[i]; i++) {
if (s[i] == ']' || s[i] == '/')
s[i] = ':';
}
rc = zfcp_lun_parse_devid(&id, s, err_ignore) == EXIT_OK ?
true : false;
goto out;
}
out:
free(copy);
if (rc)
*id_ptr = id;
return rc;
}
static struct zfcp_lun_node *zfcp_lun_node_from_entry(
struct udev_entry_node *entry,
struct zfcp_lun_node *old,
struct util_list *list)
{
struct zfcp_lun_devid id;
struct zfcp_lun_node *node;
if (!zfcp_lun_devid_from_entry(&id, entry))
return old;
if (old && zfcp_lun_cmp_devids(&old->id, &id) == 0)
return old;
node = zfcp_lun_node_find(list, &id);
if (!node) {
node = zfcp_lun_node_new(&id);
util_list_add_tail(list, node);
}
return node;
}
static void add_fc_setting_from_entry(struct udev_entry_node *entry,
struct zfcp_lun_node *node)
{
char *copy, *s, *e;
/*ATTR{[ccw/0.0.1941]0x500507630510c1ae/0x402340d400000000/failed}="0"*/
if (!starts_with(entry->key, "ATTR{[ccw/"))
return;
if (strstr(entry->key, "unit_add"))
return;
copy = misc_strdup(entry->key);
s = strrchr(copy, '/');
if (!s)
goto out;
s++;
e = strchr(s, '}');
if (!e)
goto out;
*e = 0;
setting_list_add(node->fc_settings, setting_new(NULL, s, entry->value));
out:
free(copy);
}
static void add_scsi_setting_from_entry(struct udev_entry_node *entry,
struct zfcp_lun_node *node)
{
char *copy, *s, *e;
/* ATTR{queue_depth}="64" */
if (!starts_with(entry->key, "ATTR{"))
return;
copy = misc_strdup(entry->key);
s = strchr(copy, '{');
if (!s)
goto out;
s++;
e = strchr(s, '}');
if (!e)
goto out;
*e = 0;
setting_list_add(node->scsi_settings,
setting_new(NULL, s, entry->value));
out:
free(copy);
}
static int zfcp_lun_node_cmp(void *a, void *b, void *data)
{
struct zfcp_lun_node *a_node = a, *b_node = b;
return zfcp_lun_cmp_devids(&a_node->id, &b_node->id);
}
static void sort_zfcp_lun_list(struct util_list *list)
{
util_list_sort(list, zfcp_lun_node_cmp, NULL);
}
/* Read udev rule from FILENAME and extract all LUN settings as zfcp_lun_node
* to list. Note: List entries will be sorted by ID. */
static exit_code_t udev_read_zfcp_lun_rule(const char *filename,
struct util_list *list)
{
exit_code_t rc;
struct udev_file *file = NULL;
struct udev_line_node *line;
struct udev_entry_node *entry;
struct zfcp_lun_node *node = NULL;
enum {
none,
in_fc,
in_scsi,
} state = none;
rc = udev_read_file(filename, &file);
if (rc)
goto out;
util_list_iterate(&file->lines, line) {
entry = util_list_start(&line->entries);
/* Skip comments and empty lines. */
if (!entry)
continue;
/* GOTO resets current state. */
if (strcmp(entry->key, "GOTO") == 0) {
node = NULL;
state = none;
continue;
}
switch (state) {
case none:
if (strcmp(entry->key, "LABEL") != 0)
continue;
if (starts_with(entry->value, LABEL_FC))
state = in_fc;
else if (starts_with(entry->value, LABEL_SCSI)) {
state = in_scsi;
node = zfcp_lun_node_from_entry(entry, node,
list);
}
break;
case in_fc:
node = zfcp_lun_node_from_entry(entry, node, list);
if (node)
add_fc_setting_from_entry(entry, node);
break;
case in_scsi:
if (node)
add_scsi_setting_from_entry(entry, node);
break;
}
}
sort_zfcp_lun_list(list);
out:
udev_free_file(file);
return rc;
}
struct lun_cb_data {
char *prefix;
struct util_list *list;
};
static exit_code_t lun_cb(const char *path, const char *name, void *data)
{
struct lun_cb_data *cb_data = data;
struct util_list *luns;
struct zfcp_lun_node *node;
char *id;
if (!starts_with(name, cb_data->prefix))
return EXIT_OK;
luns = zfcp_lun_node_list_new();
udev_read_zfcp_lun_rule(path, luns);
util_list_iterate(luns, node) {
id = zfcp_lun_devid_to_str(&node->id);
strlist_add(cb_data->list, id);
free(id);
}
zfcp_lun_node_list_free(luns);
return EXIT_OK;
}
/* Add the IDs for all zfcp lun devices for which a configuration exists to
* LIST. */
void udev_zfcp_lun_add_device_ids(struct util_list *list)
{
struct lun_cb_data cb_data;
char *path;
cb_data.prefix = misc_asprintf("%s-%s-", UDEV_PREFIX, ZFCP_LUN_NAME);
cb_data.list = list;
path = path_get_udev_rules();
if (dir_exists(path))
path_for_each(path, lun_cb, &cb_data);
free(path);
free(cb_data.prefix);
}
static char *get_zfcp_lun_path(const char *id)
{
char *copy, *e, *path;
copy = misc_strdup(id);
e = strchr(copy, ':');
if (e)
*e = 0;
path = path_get_udev_rule(ZFCP_LUN_NAME, copy);
free(copy);
return path;
}
/* Apply the settings found in NODE to STATE. */
static void zfcp_lun_node_to_state(struct zfcp_lun_node *node,
struct attrib **attribs,
struct device_state *state)
{
struct setting *s;
struct attrib *a;
char *name;
state->exists = 1;
state->modified = 0;
state->deconfigured = 0;
state->definable = 0;
util_list_iterate(&node->fc_settings->list, s) {
a = attrib_find(attribs, s->name);
setting_list_add(state->settings,
setting_new(a, s->name, s->value));
}
util_list_iterate(&node->scsi_settings->list, s) {
name = misc_asprintf("%s/%s", SCSI_ATTR_PREFIX, s->name);
a = attrib_find(attribs, name);
setting_list_add(state->settings,
setting_new(a, name, s->value));
free(name);
}
}
/* Read the persistent configuration of a zfcp lun from a udev rule. */
exit_code_t udev_zfcp_lun_read_device(struct device *dev)
{
struct subtype *st = dev->subtype;
struct device_state *state = &dev->persistent;
struct util_list *luns;
struct zfcp_lun_node *node;
exit_code_t rc = EXIT_OK;
char *path;
path = get_zfcp_lun_path(dev->id);
/* Get previous rule data. */
luns = zfcp_lun_node_list_new();
rc = udev_read_zfcp_lun_rule(path, luns);
if (rc)
goto out;
node = zfcp_lun_node_find(luns, dev->devid);
if (node)
zfcp_lun_node_to_state(node, st->dev_attribs, state);
else
rc = EXIT_DEVICE_NOT_FOUND;
out:
zfcp_lun_node_list_free(luns);
free(path);
return rc;
}
static struct zfcp_lun_node *state_to_zfcp_lun_node(struct zfcp_lun_devid *id,
struct device_state *state)
{
struct zfcp_lun_node *node;
struct setting *s, *n;
node = zfcp_lun_node_new(id);
util_list_iterate(&state->settings->list, s) {
if (s->removed)
continue;
if (attrib_match_prefix(s->name, SCSI_ATTR_PREFIX)) {
n = setting_new(NULL,
attrib_rem_prefix(s->name,
SCSI_ATTR_PREFIX),
s->value);
setting_list_add(node->scsi_settings, n);
} else {
n = setting_new(NULL, s->name, s->value);
setting_list_add(node->fc_settings, n);
}
}
return node;
}
/* Write udev rule as defined by LIST of struct zfcp_lun_nodes to PATH. */
static exit_code_t write_luns_rule(const char *path, struct util_list *list)
{
FILE *fd;
exit_code_t rc = EXIT_OK;
struct zfcp_lun_node *node, *last_node;
char *hba_id;
struct setting *s;
sort_zfcp_lun_list(list);
node = util_list_start(list);
if (!node)
return EXIT_INTERNAL_ERROR;
hba_id = ccw_devid_to_str(&node->id.fcp_dev);
debug("Writing FCP LUN udev rule file %s\n", path);
if (!path_exists(path)) {
rc = path_create(path);
if (rc)
goto out;
}
fd = misc_fopen(path, "w");
if (!fd) {
error("Could not write to file %s: %s\n", path,
strerror(errno));
rc = EXIT_RUNTIME_ERROR;
goto out;
}
fprintf(fd, "# Generated by chzdev\n");
fprintf(fd, "ACTION==\"add\", SUBSYSTEMS==\"ccw\", KERNELS==\"%s\", "
"GOTO=\"%s%s\"\n", hba_id, LABEL_START, hba_id);
fprintf(fd, "GOTO=\"%s%s\"\n", LABEL_END, hba_id);
fprintf(fd, "\nLABEL=\"%s%s\"\n", LABEL_START, hba_id);
/* Emit FC port triggers. */
last_node = NULL;
util_list_iterate(list, node) {
if (last_node && last_node->id.wwpn == node->id.wwpn) {
/* Only one trigger per WWPN required. */
continue;
}
fprintf(fd, "SUBSYSTEM==\"fc_remote_ports\", "
"ATTR{port_name}==\"0x%016" PRIx64 "\", "
"GOTO=\"%s%s_0x%016" PRIx64 "\"\n",
node->id.wwpn, LABEL_FC, hba_id, node->id.wwpn);
last_node = node;
}
/* Emit SCSI unit triggers. */
util_list_iterate(list, node) {
if (util_list_is_empty(&node->scsi_settings->list))
continue;
fprintf(fd, "SUBSYSTEM==\"scsi\", "
"ENV{DEVTYPE}==\"scsi_device\", "
"KERNEL==\"*:%" PRIu64 "\", "
"KERNELS==\"rport-*\", "
"ATTRS{fc_remote_ports/$id/port_name}==\"0x%016" PRIx64
"\", GOTO=\"%s%s_0x%016" PRIx64 "_0x%016" PRIx64 "\"\n",
scsi_lun_from_fcp_lun(node->id.lun), node->id.wwpn,
LABEL_SCSI, hba_id, node->id.wwpn, node->id.lun);
}
fprintf(fd, "GOTO=\"%s%s\"\n", LABEL_END, hba_id);
/* Emit FC port sections. */
last_node = NULL;
util_list_iterate(list, node) {
if (!last_node || last_node->id.wwpn != node->id.wwpn) {
if (last_node) {
fprintf(fd, "GOTO=\"end_zfcp_lun_%s\"\n",
hba_id);
}
fprintf(fd, "\nLABEL=\"%s%s_0x%016" PRIx64 "\"\n",
LABEL_FC, hba_id, node->id.wwpn);
}
fprintf(fd, "ATTR{[ccw/%s]0x%016" PRIx64 "/unit_add}="
"\"0x%016" PRIx64 "\"\n", hba_id, node->id.wwpn,
node->id.lun);
util_list_iterate(&node->fc_settings->list, s) {
fprintf(fd, "ATTR{[ccw/%s]0x%016" PRIx64 "/0x%016"
PRIx64 "/%s}=\"%s\"\n", hba_id, node->id.wwpn,
node->id.lun, s->name, s->value);
}
last_node = node;
}
if (last_node)
fprintf(fd, "GOTO=\"%s%s\"\n", LABEL_END, hba_id);
/* Emit SCSI unit sections. */
util_list_iterate(list, node) {
if (util_list_is_empty(&node->scsi_settings->list))
continue;
fprintf(fd, "\nLABEL=\"%s%s_0x%016" PRIx64 "_0x%016"
PRIx64 "\"\n", LABEL_SCSI, hba_id, node->id.wwpn,
node->id.lun);
util_list_iterate(&node->scsi_settings->list, s)
fprintf(fd, "ATTR{%s}=\"%s\"\n", s->name, s->value);
fprintf(fd, "GOTO=\"%s%s\"\n", LABEL_END, hba_id);
}
fprintf(fd, "\nLABEL=\"%s%s\"\n", LABEL_END, hba_id);
if (misc_fclose(fd))
warn("Could not close file %s: %s\n", path, strerror(errno));
out:
free(hba_id);
return rc;
}
/* Update the udev rule file that configures the zfcp lun with the specified
* ID. If @state is %NULL, remove the rule, otherwise create a rule that
* applies the corresponding parameters. */
static exit_code_t update_lun_rule(const char *id, struct device_state *state)
{
struct zfcp_lun_devid devid;
struct util_list *luns;
struct zfcp_lun_node *node;
exit_code_t rc = EXIT_OK;
char *path;
bool exists;
rc = zfcp_lun_parse_devid(&devid, id, err_delayed_print);
if (rc)
return rc;
path = get_zfcp_lun_path(id);
/* Get previous rule data. */
luns = zfcp_lun_node_list_new();
exists = file_exists(path);
if (exists)
udev_read_zfcp_lun_rule(path, luns);
/* Replace previous rule data for this ID. */
node = zfcp_lun_node_find(luns, &devid);
if (node) {
util_list_remove(luns, node);
zfcp_lun_node_free(node);
}
if (state && state->exists) {
node = state_to_zfcp_lun_node(&devid, state);
util_list_add_tail(luns, node);
}
if (util_list_is_empty(luns)) {
/* Remove empty file. */
if (exists)
rc = remove_file(path);
} else {
/* Write updated rules file. */
rc = write_luns_rule(path, luns);
}
zfcp_lun_node_list_free(luns);
free(path);
return rc;
}
/* Write a udev-rule to configure the specified zfcp lun and associated
* device state. */
exit_code_t udev_zfcp_lun_write_device(struct device *dev)
{
return update_lun_rule(dev->id, &dev->persistent);
}
/* Remove the UDEV rule used to configure the zfcp lun with the specified ID. */
exit_code_t udev_zfcp_lun_remove_rule(const char *id)
{
return update_lun_rule(id, NULL);
}
/* Determine if a udev rule exists for configuring the specified zfcp lun. */
bool udev_zfcp_lun_exists(const char *id)
{
struct zfcp_lun_devid devid;
char *path, *rule, *pattern = NULL;
bool rc = false;
if (zfcp_lun_parse_devid(&devid, id, err_ignore) != EXIT_OK)
return false;
path = get_zfcp_lun_path(id);
rule = misc_read_text_file(path, 1, err_ignore);
if (!rule)
goto out;
pattern = misc_asprintf("ATTR{[ccw/%x.%x.%04x]0x%016" PRIx64
"/unit_add}=\"0x%016" PRIx64 "\"\n",
devid.fcp_dev.cssid, devid.fcp_dev.ssid,
devid.fcp_dev.devno, devid.wwpn, devid.lun);
if (strstr(rule, pattern))
rc = true;
out:
free(pattern);
free(rule);
free(path);
return rc;
}

View File

@@ -0,0 +1,29 @@
#!/bin/bash
#
# zdev-root-update
# Ensure that the persistent root device configuration is put into effect.
# On typical distributions this requires a rebuild of the initial ram disk
# and the re-installation of the IPL record referencing the ram disk.
#
# Parameters:
# zdev-root-update <devtype> <devid> [<devtype2> <devid2>...]
#
# Where <devtype> is the device type as used by chzdev and <devid> is the
# ID of the root device.
#
TOOLNAME=$(basename $0)
echo "Building initial RAM-disk"
dracut -f || {
echo "${TOOLNAME}: Error: Could not build initial RAM-disk" >&2
exit 1
}
echo "Installing IPL record"
zipl --noninteractive || {
echo "${TOOLNAME}: Error: Could not install IPL record" >&2
exit 1
}
exit 0

View File

@@ -0,0 +1,207 @@
#!/bin/bash
#
# zdev-root-update
# Ensure that the persistent root device configuration is put into effect.
# On typical distributions this requires a rebuild of the initial ram disk
# and the re-installation of the IPL record referencing the ram disk.
#
# Parameters:
# zdev-root-update <devtype> <devid> [<devtype2> <devid2>...]
#
# Where <devtype> is the device type as used by chzdev and <devid> is the
# ID of the root device.
#
TOOLNAME=$(basename $0)
ZIPLCONF=/etc/zipl.conf
# die MESSAGE
# Print MESSAGE on standard error and exit with exit code 1
die()
{
echo "$TOOLNAME: Error: $*" >&2
exit 1
}
# add LIST NEW DELIM
# Add NEW to DELIM-separated LIST.
add()
{
local LIST="$1"
local NEW="$2"
local DELIM="$3"
if [ -z "$LIST" ] ; then
echo "$NEW"
else
echo "$LIST$DELIM$NEW"
fi
}
# add LIST NEW DELIM
# Add NEW to DELIM-separated LIST unless it is already in list.
add_unique()
{
local LIST="$1"
local NEW="$2"
local DELIM="$3"
local ENTRY
local FOUND
local IFS="$DELIM"
FOUND=0
for ENTRY in $LIST ; do
if [ "$ENTRY" == "$NEW" ] ; then
FOUND=1
break
fi
done
if [ "$FOUND" -eq 0 ] ; then
LIST=$(add "$LIST" "$NEW" "$DELIM")
fi
echo "$LIST"
}
# process CMDLINE
# Apply root device settings from globals DASD ZFCP and CIOIGNORE to CMDLINE
process_cmdline()
{
local CMDLINE="$1"
local ENTRY
local NEW
local FOUND_DASD=0
local FOUND_ZFCP=0
local V
local X
for ENTRY in $CMDLINE ; do
K=${ENTRY%%=*}
case "$K" in
dasd)
if [ ! -z "$DASD" ] ; then
NEW=$(add "$NEW" "dasd=$DASD" " ")
FOUND_DASD=1
fi
;;
zfcp.device)
if [ ! -z "$ZFCP" ] ; then
NEW=$(add "$NEW" "zfcp.device=$ZFCP" " ")
FOUND_ZFCP=1
fi
;;
cio_ignore)
V=${ENTRY#*=}
for X in $CIOIGNORE ; do
V=$(add_unique "$V" "$X" ",")
done
NEW=$(add "$NEW" "cio_ignore=$V" " ")
;;
*)
NEW=$(add "$NEW" "$ENTRY" " ")
;;
esac
done
if [ "$FOUND_DASD" -eq 0 -a ! -z "$DASD" ] ; then
NEW=$(add "$NEW" "dasd=$DASD" " ")
fi
if [ "$FOUND_ZFCP" -eq 0 -a ! -z "$ZFCP" ] ; then
NEW=$(add "$NEW" "zfcp.device=$ZFCP" " ")
fi
echo "$NEW"
}
# process_file FILENAME
# Apply root device settings from globals DASD ZFCP and CIOIGNORE to kernel
# command line found in file FILENAME
process_file()
{
local FILENAME=$1
local CMDLINE
CMDLINE=$(cat $FILENAME)
CMDLINE=$(process_cmdline "$CMDLINE")
if ! echo $CMDLINE > "$FILENAME" ; then
die "Could not write to $FILENAME"
fi
}
# Build dasd= zfcp.device= and cio_ignore= parameter values
DASD=
ZFCP=
CIOIGNORE=
while [ $# -gt 0 ] ; do
TYPE=$1
ID=$2
if [ -z "$TYPE" -o -z "$ID" ] ; then
die "Incomplete parameters: $*"
fi
case $TYPE in
dasd-*)
DASD=$(add "$DASD" "$ID" ",")
CIOIGNORE=$(add "$CIOIGNORE" "!$ID" " ")
;;
zfcp-lun)
if [ ! -z "$ZFCP" ] ; then
die "Too many zFCP SCSI devices used for root device"
fi
ZFCP=$(echo $ID | sed -e 's/:/,/g')
FCPDEV=$(echo $ID | cut -d: -f1)
CIOIGNORE=$(add "$CIOIGNORE" "!$FCPDEV" " ")
;;
*)
die "Unsupported root device type: $TYPE"
;;
esac
shift 2
done
if [ -z "$DASD" -a -z "$ZFCP" ] ; then
die "No root device specified"
fi
if [ ! -r "$ZIPLCONF" ] ; then
die "Could not read $ZIPLCONF"
fi
# Create new zipl.conf file
echo "Updating $ZIPLCONF"
TMPFILE=$(mktemp zdev-zipl.conf.XXX)
[ $? -ne 0 ] && die "Could not create temporary file"
while read LINE ; do
OKEY=${LINE%%=*}
KEY=$(echo $OKEY)
case "$KEY" in
parameters)
V=$(echo ${LINE#*=} | sed -e 's/^"//' -e 's/"$//')
V=$(process_cmdline "$V")
echo "$OKEY=\"$V\""
;;
parmfile)
V=$(echo ${LINE#*=} | sed -e 's/^"//' -e 's/"$//')
process_file "$V"
echo "$LINE"
;;
*)
echo "$LINE"
;;
esac
done < "$ZIPLCONF" >"$TMPFILE"
# Swap new and old zipl.conf file
if ! mv "$ZIPLCONF" "${ZIPLCONF}.old" ; then
die "Could not rename $ZIPLCONF to ${ZIPLCONF}.old"
fi
if ! mv $TMPFILE $ZIPLCONF ; then
die "Could not rename $TMPFILE to $ZIPCONF"
fi
rm -f "${ZIPLCONF}.old"
# Install IPL loader
echo "Installing IPL record"
if ! zipl --noninteractive ; then
die "Could not install IPL record"
fi

309
zdev/src/zfcp.c Normal file
View File

@@ -0,0 +1,309 @@
/*
* 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 <inttypes.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "attrib.h"
#include "ccw.h"
#include "device.h"
#include "devtype.h"
#include "misc.h"
#include "modprobe.h"
#include "module.h"
#include "path.h"
#include "setting.h"
#include "udev.h"
#include "zfcp.h"
#include "zfcp_host.h"
#include "zfcp_lun.h"
/*
* zfcp type attributes.
*/
static struct attrib zfcp_tattr_dbfsize = {
.name = "dbfsize",
.title = "Modify buffer size for debugging records",
.desc =
"Control the number of 4 KB pages to be used for the debug feature\n",
.defval = "4",
.accept = ACCEPT_ARRAY(ACCEPT_NUM_GE(1)),
};
static struct attrib zfcp_tattr_dbflevel = {
.name = "dbflevel",
.title = "Modify the minimum log level for debugging records",
.desc =
"Control the initial log level of the debug feature.\n",
.defval = "3",
.accept = ACCEPT_ARRAY(ACCEPT_RANGE(0, 6)),
};
static struct attrib zfcp_tattr_queue_depth = {
.name = "queue_depth",
.title = "Modify the initial maximum SCSI device queue depth",
.desc =
"Control the initial upper limit on the number of outstanding SCSI\n"
"commands per SCSI device.\n",
.nounload = 1,
.defval = "32",
.accept = ACCEPT_ARRAY(ACCEPT_NUM_GE(1)),
};
static struct attrib zfcp_tattr_allow_lun_scan = {
.name = "allow_lun_scan",
.title = "Disable automatic LUN scanning in NPIV mode",
.desc =
"Control the use of the automatic LUN scanning feature for FCP\n"
"devices that are configured in N_PORT ID Virtualization mode.\n"
" 0: Automatic LUN scanning is disabled\n"
" 1: Automatic LUN scanning is enabled\n\n",
.nounload = 1,
.defval = "1",
.accept = ACCEPT_ARRAY(ACCEPT_RANGE(0, 1)),
};
static struct attrib zfcp_tattr_dif = {
.name = "dif",
.title = "Enable DIF/DIX data consistency checking",
.desc =
"Control the use of the end-to-end data consistency checking\n"
"mechanism (DIF/DIX):\n"
" 0: DIF is disabled\n"
" 1: DIF is enabled when supported by the FCP device hardware\n",
.defval = "0",
.accept = ACCEPT_ARRAY(ACCEPT_RANGE(0, 1)),
};
static struct attrib zfcp_tattr_datarouter = {
.name = "datarouter",
.title = "Enable hardware data routing",
.desc =
"Control the use of the hardware data routing (DR) feature:\n"
" 0: DR is disabled\n"
" 1: DR is enabled when supported by the FCP device hardware\n",
.defval = "1",
.accept = ACCEPT_ARRAY(ACCEPT_RANGE(0, 1)),
};
static struct attrib zfcp_tattr_no_auto_port_rescan = {
.name = "no_auto_port_rescan",
.title = "Inhibit automatic port rescan",
.desc =
"Control the automatic port rescan feature:\n"
" 0: Automatic port rescan is enabled\n"
" 1: Automatic port rescan is disabled\n",
.nounload = 1,
.defval = "0",
.accept = ACCEPT_ARRAY(ACCEPT_RANGE(0, 1)),
};
static struct attrib zfcp_tattr_port_scan_ratelimit = {
.name = "port_scan_ratelimit",
.title = "Minimum delay between automatic port scans",
.desc =
"Control the automatic port scan ratelimit:\n"
" 0: Ratelimit is disabled\n"
" >0: Minimum delay in milliseconds\n",
.nounload = 1,
.defval = "60000",
.accept = ACCEPT_ARRAY(ACCEPT_RANGE(0, 4294967295)),
};
static struct attrib zfcp_tattr_port_scan_backoff = {
.name = "port_scan_backoff",
.title = "Avoid simultaneous automatic port scans",
.desc =
"Control the automatic port scan backoff:\n"
" 0: Backoff is disabled\n"
" >0: Random delay between 0 and given value in milliseconds\n",
.nounload = 1,
.defval = "500",
.accept = ACCEPT_ARRAY(ACCEPT_RANGE(0, 4294967295)),
};
/*
* ZFCP methods.
*/
/* Clean up all resources used by devtype object. */
static void zfcp_devtype_exit(struct devtype *dt)
{
setting_list_free(dt->active_settings);
setting_list_free(dt->persistent_settings);
}
static void bool_to_num(char *s)
{
if (!s)
return;
if (strcasecmp(s, "y") == 0)
*s = '1';
else if (strcasecmp(s, "n") == 0)
*s = '0';
}
static void all_bools_to_num(struct setting_list *list)
{
struct setting *s;
util_list_iterate(&list->list, s) {
if (s->attrib == &zfcp_tattr_allow_lun_scan ||
s->attrib == &zfcp_tattr_dif ||
s->attrib == &zfcp_tattr_datarouter ||
s->attrib == &zfcp_tattr_no_auto_port_rescan) {
/* Convert Y to 1 and N to 0. */
bool_to_num(s->value);
bool_to_num(s->actual_value);
}
}
}
static exit_code_t zfcp_devtype_read_settings(struct devtype *dt,
config_t config)
{
struct setting_list *list;
char *path;
exit_code_t rc = EXIT_OK;
if (SCOPE_ACTIVE(config) && !dt->active_settings) {
dt->active_exists = 0;
rc = module_get_params(ZFCP_MOD_NAME, dt->type_attribs, &list);
if (rc)
return rc;
if (list) {
all_bools_to_num(list);
dt->active_settings = list;
setting_list_mark_default_derived(dt->active_settings);
setting_list_apply_defaults(dt->active_settings,
dt->type_attribs, false);
dt->active_exists = 1;
} else
dt->active_settings = setting_list_new();
}
if (SCOPE_PERSISTENT(config) && !dt->persistent_settings) {
dt->persistent_exists = 0;
path = path_get_modprobe_conf(dt);
rc = modprobe_read_settings(path, ZFCP_MOD_NAME,
dt->type_attribs, &list);
free(path);
if (rc)
return rc;
if (list) {
dt->persistent_settings = list;
setting_list_apply_defaults(dt->persistent_settings,
dt->type_attribs, false);
dt->persistent_exists = 1;
} else
dt->persistent_settings = setting_list_new();
}
return rc;
}
static exit_code_t zfcp_devtype_write_settings(struct devtype *dt,
config_t config)
{
char *path;
exit_code_t rc = EXIT_OK;
if (SCOPE_ACTIVE(config) && dt->active_settings) {
/* Try setting parameters directly via Sysfs. */
if (module_set_params(ZFCP_MOD_NAME, dt->active_settings))
goto persistent;
/* Re-load kernel module.*/
rc = module_load(ZFCP_MOD_NAME, NULL, dt->active_settings,
err_delayed_print);
if (rc)
return rc;
}
persistent:
if (SCOPE_PERSISTENT(config) && dt->persistent_settings) {
path = path_get_modprobe_conf(dt);
if (!rc) {
rc = modprobe_write_settings(path, ZFCP_MOD_NAME,
dt->persistent_settings);
}
free(path);
}
return rc;
}
/* Determine the value of the allow_lun_scan zfcp attribute. */
exit_code_t zfcp_check_allow_lun_scan(int *allow, config_t config)
{
struct devtype *dt = &zfcp_devtype;
exit_code_t rc;
struct setting *s;
/* Check auto_lun_scan_setting. */
rc = dt->read_settings(dt, config);
if (rc)
return rc;
*allow = 1;
if (SCOPE_ACTIVE(config)) {
s = setting_list_find(dt->active_settings, "allow_lun_scan");
if (s && strcmp(s->value, "0") == 0)
*allow = 0;
}
if (SCOPE_PERSISTENT(config)) {
s = setting_list_find(dt->persistent_settings,
"allow_lun_scan");
if (s && strcmp(s->value, "0") == 0)
*allow = 0;
}
return EXIT_OK;
}
/*
* ZFCP device type.
*/
struct devtype zfcp_devtype = {
.name = "zfcp",
.title = "SCSI-over-Fibre Channel (FCP) devices and SCSI "
"devices",
.devname = "zFCP device",
.modules = STRING_ARRAY(ZFCP_MOD_NAME),
.subtypes = SUBTYPE_ARRAY(
&zfcp_host_subtype,
&zfcp_lun_subtype,
),
.type_attribs = ATTRIB_ARRAY(
&zfcp_tattr_dbfsize,
&zfcp_tattr_dbflevel,
&zfcp_tattr_queue_depth,
&zfcp_tattr_allow_lun_scan,
&zfcp_tattr_dif,
&zfcp_tattr_datarouter,
&zfcp_tattr_no_auto_port_rescan,
&zfcp_tattr_port_scan_ratelimit,
&zfcp_tattr_port_scan_backoff,
),
.unknown_type_attribs = 1,
.exit = &zfcp_devtype_exit,
.read_settings = &zfcp_devtype_read_settings,
.write_settings = &zfcp_devtype_write_settings,
};

255
zdev/src/zfcp_host.c Normal file
View File

@@ -0,0 +1,255 @@
/*
* 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 <inttypes.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "attrib.h"
#include "ccw.h"
#include "device.h"
#include "devtype.h"
#include "misc.h"
#include "path.h"
#include "setting.h"
#include "zfcp.h"
#include "zfcp_host.h"
/*
* zfcp host attributes.
*/
static struct attrib zfcp_host_attr_failed = {
.name = "failed",
.title = "Check and restart FCP device recovery",
.desc =
"This attribute shows if error recovery of an FCP device has failed:\n"
" 0: Error recovery has not failed or was not started\n"
" 1: Error recovery was started and failed to complete\n"
" successfully even after several automatic retries\n\n"
"Write the value 0 to this attribute to restart the error recovery\n"
"process after resolving the root cause of the failure.\n",
.activeonly = 1,
.rewrite = 1,
.unstable = 1,
.defval = "0",
.accept = ACCEPT_ARRAY(ACCEPT_NUM(0)),
};
static struct attrib zfcp_host_attr_port_remove = {
.name = "port_remove",
.title = "Unregister a remote port from the FCP device",
.desc =
"Unregister a remote port from the FCP device by writing its WWPN\n"
"to this attribute. The WWPN must be in a format with 16 hexadecimal\n"
"digits and 0x prefix and no manually configured FCP LUNs may be\n"
"registered with that remote port.\n\n"
"Note: The next port scan will register all available ports again,\n"
"including any previously removed ports. To prevent removed ports\n"
"from being registered automatically, use zoning.\n",
.writeonly = 1,
};
static struct attrib zfcp_host_attr_port_rescan = {
.name = "port_rescan",
.title = "Trigger a port rescan for the FCP device",
.desc =
"Rescan FCP device for available remote ports by writing the value 1\n"
"to this attribute\n",
.activeonly = 1,
.writeonly = 1,
.accept = ACCEPT_ARRAY(ACCEPT_NUM(1)),
};
/*
* zfcp host methods.
*/
static exit_code_t check_cmb_enable(struct subtype *st, struct device *dev,
config_t config)
{
struct setting *c, *o;
c = setting_list_find(dev->active.settings, ccw_attr_cmb_enable.name);
o = setting_list_find(dev->active.settings, ccw_attr_online.name);
if (!c || !o) {
/* Could not determine attribute states. */
return EXIT_OK;
}
if (c->modified && o->actual_value &&
strcmp(o->actual_value, "1") == 0 && strcmp(o->value, "0") != 0) {
delayed_forceable("Cannot modify cmb_enable setting while "
"device is online\n");
return EXIT_INVALID_CONFIG;
}
return EXIT_OK;
}
static exit_code_t zfcp_host_st_check_pre_configure(struct subtype *st,
struct device *dev,
int prereq, config_t config)
{
exit_code_t rc;
/* No need to check if device is deconfigured. */
if (dev->active.deconfigured)
return EXIT_OK;
rc = check_cmb_enable(st, dev, config);
if (rc)
return rc;
return EXIT_OK;
}
static char *get_port_type_path(const char *id)
{
char *devpath, *path = NULL;
struct util_list *files;
struct strlist_node *s;
devpath = path_get_ccw_device(ZFCP_CCWDRV_NAME, id);
files = strlist_new();
if (!misc_read_dir(devpath, files, NULL, NULL))
goto out;
util_list_iterate(files, s) {
if (!starts_with(s->str, "host"))
continue;
path = misc_asprintf("%s/%s/fc_host/%s/port_type", devpath,
s->str, s->str);
}
out:
free(devpath);
strlist_free(files);
return path;
}
exit_code_t zfcp_host_check_npiv(const char *id, int *enabled)
{
char *path, *type = NULL;
exit_code_t rc = EXIT_RUNTIME_ERROR;
path = get_port_type_path(id);
if (!path)
goto out;
type = misc_read_text_file(path, 1, err_ignore);
if (!type)
goto out;
/* Check FCP port type. */
if (strcmp(type, "NPIV VPORT") == 0)
*enabled = 1;
else
*enabled = 0;
rc = EXIT_OK;
out:
free(path);
free(type);
return rc;
}
static exit_code_t check_npiv(struct subtype *st, struct device *dev,
int prereq, config_t config)
{
int npiv, allow_lun_scan;
static int warn_done;
if (prereq) {
/* FCP device is configured as part of 3-tuple. */
return EXIT_OK;
}
if (subtype_online_get(st, dev, config) != 1) {
/* FCP device is set offline. */
return EXIT_OK;
}
/* Check FCP port type. */
if (zfcp_host_check_npiv(dev->id, &npiv)) {
/* Could not determine NPIV setting. */
return EXIT_OK;
}
if (!npiv) {
delayed_info("Note: NPIV mode disabled - LUNs must be "
"configured manually\n");
return EXIT_INVALID_CONFIG;
}
/* Check allow_lun_scan setting. */
if (zfcp_check_allow_lun_scan(&allow_lun_scan, config)) {
/* Could not determine allow_lun_scan setting. */
return EXIT_OK;
}
if (!allow_lun_scan && !warn_done) {
delayed_info("Note: Automatic LUN scan disabled - LUNs must "
"be configured manually\n");
return EXIT_INVALID_CONFIG;
}
return EXIT_OK;
}
/* Perform post-write checks specific to zfcp hosts. */
static exit_code_t zfcp_host_st_check_post_configure(struct subtype *st,
struct device *dev,
int prereq,
config_t config)
{
/* No need to check if device is deconfigured. */
if (dev->active.deconfigured)
return EXIT_OK;
/* Check for NPIV but don't route exit code to caller - we only
* want to show a warning. */
check_npiv(st, dev, prereq, config);
return EXIT_OK;
}
/*
* zfcp host sub-type.
*/
static struct ccw_subtype_data zfcp_host_data = {
.ccwdrv = ZFCP_CCWDRV_NAME,
.mod = ZFCP_MOD_NAME,
};
struct subtype zfcp_host_subtype = {
.super = &ccw_subtype,
.devtype = &zfcp_devtype,
.name = "zfcp-host",
.title = "FCP devices",
.devname = "FCP device",
.modules = STRING_ARRAY(ZFCP_MOD_NAME),
.namespace = &ccw_namespace,
.data = &zfcp_host_data,
.dev_attribs = ATTRIB_ARRAY(
&ccw_attr_online,
&ccw_attr_cmb_enable,
&zfcp_host_attr_failed,
&zfcp_host_attr_port_remove,
&zfcp_host_attr_port_rescan,
),
.unknown_dev_attribs = 1,
.check_pre_configure = &zfcp_host_st_check_pre_configure,
.check_post_configure = &zfcp_host_st_check_post_configure,
};

1100
zdev/src/zfcp_lun.c Normal file

File diff suppressed because it is too large Load Diff