/* * 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 #include #include #include #include #include #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->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"); } /* 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 [ ] or * [ ]. */ 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 =. */ 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; }