Files
s390-tools/zdev/src/export.c
Vineeth Vijayan c7ac1e63bd zdev: add export/import support for site
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>
2022-11-03 17:38:53 +01:00

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(&copy[1], &argc, &argv);
free(copy);
if (argc == 2)
header = header_new(argv[0], argv[1], NULL);
else if (argc == 3)
header = header_new(argv[0], argv[1], argv[2]);
else
header = NULL;
line_free(argc, argv);
if (!header)
return false;
*header_ptr = header;
return true;
}
static bool parse_setting(const char *line, char **key_ptr, char **val_ptr)
{
char *copy, *value;
copy = misc_strdup(line);
value = strchr(copy, '=');
if (!value) {
free(copy);
return false;
}
*value = 0;
value++;
*key_ptr = shrink_str(copy);
*val_ptr = unquote_str(value);
free(copy);
return true;
}
static struct setting_list *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;
}