mirror of
https://github.com/ibm-s390-linux/s390-tools.git
synced 2026-08-05 02:14:52 +00:00
Add export and import support for site-specific settings. For each site-specific configuration, a new site-block will be generated as below. The same format can be interpreted by the import function and use the values on the device's site-specific configuration. Typical example of a site-block in the export file: [site3 dasd-fba 0.0.f001] online=1 cmb_enable=0 Reviewed-by: Peter Oberparleiter <oberpar@linux.ibm.com> Signed-off-by: Vineeth Vijayan <vneethv@linux.ibm.com> Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
645 lines
14 KiB
C
645 lines
14 KiB
C
/*
|
|
* zdev - Modify and display the persistent configuration of devices
|
|
*
|
|
* Copyright IBM Corp. 2016, 2019
|
|
*
|
|
* 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"
|
|
|
|
static int import_site_id = SITE_FALLBACK;
|
|
|
|
struct export_header {
|
|
export_t type;
|
|
config_t config;
|
|
char *type_id;
|
|
char *id;
|
|
};
|
|
|
|
static bool header_str_to_config(const char *config_str, config_t *config)
|
|
{
|
|
int site;
|
|
|
|
if (strstr(config_str, "site")) {
|
|
if (sscanf(config_str, "site%d", &site) == 1) {
|
|
import_site_id = site;
|
|
*config = config_persistent;
|
|
return true;
|
|
}
|
|
}
|
|
import_site_id = SITE_FALLBACK;
|
|
return str_to_config(config_str, config);
|
|
}
|
|
|
|
static struct export_header *header_new(const char *config_str,
|
|
const char *type, const char *id)
|
|
{
|
|
config_t config;
|
|
struct export_header *hdr;
|
|
|
|
if (!header_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->readonly)
|
|
return false;
|
|
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");
|
|
}
|
|
|
|
static const char *site_specific_header(config_t config)
|
|
{
|
|
if (global_site_id != SITE_FALLBACK)
|
|
return misc_asprintf("site%d", global_site_id);
|
|
else
|
|
return config_to_str(config);
|
|
}
|
|
|
|
/* 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", site_specific_header(config), type, id);
|
|
else
|
|
fprintf(fd, "[%s %s]\n", site_specific_header(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, global_site_id);
|
|
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)) {
|
|
if (import_site_id != SITE_FALLBACK)
|
|
setting_list_clear(dev->site_specific[import_site_id].settings);
|
|
else
|
|
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, SITE_FALLBACK);
|
|
} 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) &&
|
|
import_site_id == SITE_FALLBACK)
|
|
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) {
|
|
if (global_site_id == import_site_id)
|
|
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 {
|
|
if (global_site_id == import_site_id ||
|
|
import_site_id == SITE_FALLBACK)
|
|
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;
|
|
}
|