mirror of
https://github.com/ibm-s390-linux/s390-tools.git
synced 2026-08-05 02:14:52 +00:00
For some device driver SysFS attributes, values read may not be
acceptable input values for that attribute.
An example would be the group of qeth VNICC attributes that return "n/a"
when VNICC setup is not supported, but only accept "0" and "1" as valid
values that can be written to it.
This leads to errors such as the following when data for such attributes
is imported:
# chzdev f500 --import test.conf
Importing configuration data from test.conf
QETH device 0.0.f500:0.0.f501:0.0.f502 configure failed
Error: Invalid value for qeth attribute: vnicc/flooding=n/a (*)
Acceptable values:
- Integers in the range 0 - 1
Use 'chzdev qeth --help-attribute vnicc/flooding' for more information
Note: You can use --force to override safety checks (*)
To fix this, change chzdev's --export function to skip any attribute
value that is not acceptable for that attribute.
Fixes: e831269e74 ("zdev: Add support for VNIC Characteristics")
Signed-off-by: Peter Oberparleiter <oberpar@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
610 lines
14 KiB
C
610 lines
14 KiB
C
/*
|
|
* zdev - Modify and display the persistent configuration of devices
|
|
*
|
|
* Copyright IBM Corp. 2016, 2017
|
|
*
|
|
* s390-tools is free software; you can redistribute it and/or modify
|
|
* it under the terms of the MIT license. See LICENSE for details.
|
|
*/
|
|
|
|
#include <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_check_value(a, s->value)) {
|
|
/* Skip values that are not acceptable input values. */
|
|
return false;
|
|
}
|
|
if (!attrib_match_default(s->attrib, s->value)) {
|
|
/* All non-default values should be exported. */
|
|
return true;
|
|
}
|
|
}
|
|
if (SCOPE_PERSISTENT(config) || SCOPE_AUTOCONF(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;
|
|
|
|
list = device_get_setting_list(dev, config);
|
|
if (!list)
|
|
return 0;
|
|
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 (!SCOPE_SINGLE(config)) {
|
|
if (SCOPE_ACTIVE(config)) {
|
|
rc = export_write_device(fd, dev, config_active,
|
|
first_ptr);
|
|
if (rc)
|
|
return rc;
|
|
}
|
|
if (SCOPE_PERSISTENT(config)) {
|
|
rc = export_write_device(fd, dev, config_persistent,
|
|
first_ptr);
|
|
if (rc)
|
|
return rc;
|
|
}
|
|
if (SCOPE_AUTOCONF(config)) {
|
|
rc = export_write_device(fd, dev, config_autoconf,
|
|
first_ptr);
|
|
if (rc)
|
|
return rc;
|
|
}
|
|
return EXIT_OK;
|
|
}
|
|
|
|
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 (config == config_persistent) {
|
|
if (!dev->persistent.exists)
|
|
return EXIT_OK;
|
|
settings = dev->persistent.settings;
|
|
} else {
|
|
if (!dev->autoconf.exists)
|
|
return EXIT_OK;
|
|
settings = dev->autoconf.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 (!SCOPE_SINGLE(config)) {
|
|
if (SCOPE_ACTIVE(config)) {
|
|
rc = export_write_devtype(fd, dt, config_active,
|
|
first_ptr);
|
|
if (rc)
|
|
return rc;
|
|
}
|
|
if (SCOPE_PERSISTENT(config)) {
|
|
rc = export_write_devtype(fd, dt, config_persistent,
|
|
first_ptr);
|
|
if (rc)
|
|
return rc;
|
|
}
|
|
/* Scope autoconf not supported for devtype */
|
|
return EXIT_OK;
|
|
}
|
|
|
|
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 *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;
|
|
}
|
|
if (SCOPE_AUTOCONF(hdr->config)) {
|
|
setting_list_clear(dev->autoconf.settings);
|
|
dev->autoconf.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 (!SCOPE_SINGLE(config)) {
|
|
if (SCOPE_ACTIVE(config)) {
|
|
rc = handle_setting(filename, lineno, key, value, dt,
|
|
dev, config_active);
|
|
if (rc)
|
|
return rc;
|
|
}
|
|
if (SCOPE_PERSISTENT(config)) {
|
|
rc = handle_setting(filename, lineno, key, value, dt,
|
|
dev, config_persistent);
|
|
if (rc)
|
|
return rc;
|
|
}
|
|
if (SCOPE_AUTOCONF(config)) {
|
|
rc = handle_setting(filename, lineno, key, value, dt,
|
|
dev, config_autoconf);
|
|
if (rc)
|
|
return rc;
|
|
}
|
|
return EXIT_OK;
|
|
}
|
|
|
|
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 = device_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;
|
|
}
|
|
|
|
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) &&
|
|
util_list_is_empty(&dev->autoconf.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;
|
|
}
|