mirror of
https://github.com/ibm-s390-linux/s390-tools.git
synced 2026-08-05 02:14:52 +00:00
Auto-configuration is the name of a new configuration target that is
supported by chzdev and lszdev besides the existing active and
persistent configuration targets. Directives created in this new
configuration are stored as udev rules in the /run/udev/rules.d
directory.
Auto-configuration directives are only in effect if there are no
directives for the same device in the user-provided persistent
configuration. This allows users to override auto-configuration
directives if necessary.
Due to the volatile nature of the /run directory, auto-configuration
directives are cleared on reboot. Therefore mechanisms that generate
auto-configuration directives must recreate them on every boot.
The lszdev tool displays auto-configuration data both in list view
as well as in detail view. Users can specify the new option --auto-conf
to only show data from this configuration target.
Mechanisms that generate automated configuration directives can use
chzdev together with the --auto-conf option to create the corresponding
udev rules.
Note: This change does not include a mechanism that generates
auto-configuration directives.
Signed-off-by: Peter Oberparleiter <oberpar@linux.vnet.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
606 lines
13 KiB
C
606 lines
13 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_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;
|
|
}
|