mirror of
https://github.com/ibm-s390-linux/s390-tools.git
synced 2026-08-05 02:14:52 +00:00
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:
100
zdev/src/Makefile
Normal file
100
zdev/src/Makefile
Normal 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
281
zdev/src/attrib.c
Normal 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
442
zdev/src/blkinfo.c
Normal 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
1803
zdev/src/ccw.c
Normal file
File diff suppressed because it is too large
Load Diff
1214
zdev/src/ccwgroup.c
Normal file
1214
zdev/src/ccwgroup.c
Normal file
File diff suppressed because it is too large
Load Diff
2980
zdev/src/chzdev.c
Normal file
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
58
zdev/src/chzdev_usage.txt
Normal 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
570
zdev/src/ctc.c
Normal 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
322
zdev/src/ctc_auto.c
Normal 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
687
zdev/src/dasd.c
Normal 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
569
zdev/src/device.c
Normal 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
316
zdev/src/devnode.c
Normal 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
412
zdev/src/devtype.c
Normal 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
89
zdev/src/exit_code.c
Normal 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
574
zdev/src/export.c
Normal 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(©[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
76
zdev/src/findmnt.c
Normal 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
238
zdev/src/generic_ccw.c
Normal 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
128
zdev/src/hash.c
Normal 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
385
zdev/src/inuse.c
Normal 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
110
zdev/src/iscsi.c
Normal 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
427
zdev/src/lcs.c
Normal 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
347
zdev/src/lcs_auto.c
Normal 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
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
45
zdev/src/lszdev_usage.txt
Normal 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
1753
zdev/src/misc.c
Normal file
File diff suppressed because it is too large
Load Diff
424
zdev/src/modprobe.c
Normal file
424
zdev/src/modprobe.c
Normal 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
305
zdev/src/module.c
Normal 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
174
zdev/src/namespace.c
Normal 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
110
zdev/src/net.c
Normal 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
151
zdev/src/nic.c
Normal 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
55
zdev/src/opts.c
Normal 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
396
zdev/src/path.c
Normal 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
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
482
zdev/src/qeth_auto.c
Normal 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
104
zdev/src/root.c
Normal 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
362
zdev/src/scsi.c
Normal 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
1291
zdev/src/select.c
Normal file
File diff suppressed because it is too large
Load Diff
713
zdev/src/setting.c
Normal file
713
zdev/src/setting.c
Normal 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(©->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
816
zdev/src/subtype.c
Normal 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
452
zdev/src/table.c
Normal 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
279
zdev/src/table_attribs.c
Normal 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
90
zdev/src/table_types.c
Normal 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
405
zdev/src/udev.c
Normal 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
288
zdev/src/udev_ccw.c
Normal 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
391
zdev/src/udev_ccwgroup.c
Normal 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
687
zdev/src/udev_zfcp_lun.c
Normal 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;
|
||||
}
|
||||
29
zdev/src/zdev-root-update.dracut
Normal file
29
zdev/src/zdev-root-update.dracut
Normal 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
|
||||
207
zdev/src/zdev-root-update.zipl
Normal file
207
zdev/src/zdev-root-update.zipl
Normal 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
309
zdev/src/zfcp.c
Normal 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
255
zdev/src/zfcp_host.c
Normal 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
1100
zdev/src/zfcp_lun.c
Normal file
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user