Files
s390-tools/zdev/src/setting.c
Jens Remus a4acf7c991 zdev: fix copying of modified flag
The modified flag was actually never copied during merge of setting lists.
Copy the modified flag if requested.

Found using Cppcheck:
[zdev/src/setting.c:553]: (warning) Redundant assignment of 'n->modified' to itself.

Cc: Peter Oberparleiter <peter.oberparleiter@de.ibm.com>
Signed-off-by: Jens Remus <jremus@linux.ibm.com>
Reviewed-by: Peter Oberparleiter <oberpar@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2018-11-16 15:01:41 +01:00

726 lines
16 KiB
C

/*
* zdev - Modify and display the persistent configuration of devices
*
* Copyright IBM Corp. 2016, 2018
*
* 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 <stdlib.h>
#include <string.h>
#include "attrib.h"
#include "misc.h"
#include "path.h"
#include "setting.h"
/* Create and initialize a new setting. */
struct setting *setting_new(struct attrib *attrib, const char *name,
const char *val)
{
struct setting *s;
s = misc_malloc(sizeof(struct setting));
s->attrib = attrib;
if (attrib && !name)
s->name = misc_strdup(attrib->name);
else
s->name = misc_strdup(name);
s->value = misc_strdup(val);
if (attrib && attrib->multi) {
/* Split multi-line value into multiple values. */
s->values = strlist_new();
strlist_add_multi(s->values, val, "\n", 0);
}
return s;
}
/* Release all resources associated with the specified setting. */
static void setting_free(struct setting *s)
{
if (!s)
return;
free(s->name);
free(s->value);
free(s->actual_value);
strlist_free(s->values);
strlist_free(s->actual_values);
free(s);
}
/* Create a newly allocated setting copy. */
struct setting *setting_copy(const struct setting *s)
{
struct setting *copy;
copy = misc_malloc(sizeof(struct setting));
copy->attrib = s->attrib;
copy->name = misc_strdup(s->name);
copy->value = misc_strdup(s->value);
if (s->values)
copy->values = strlist_copy(s->values);
if (s->actual_value)
copy->actual_value = misc_strdup(s->actual_value);
if (s->actual_values)
copy->actual_values = strlist_copy(s->actual_values);
copy->modified = s->modified;
copy->removed = s->removed;
return copy;
}
/* Replace or add to the value of a setting. */
static void setting_mod_value(struct setting *s, const char *value)
{
char *new_value;
if (s->values) {
/* Check if this is a rewrite of a previous value. */
if (!s->derived && s->attrib && !s->attrib->rewrite &&
strlist_find(s->values, value))
return;
/* Attribute supports multiple values - add. */
new_value = misc_asprintf("%s%s%s", s->value,
*s->value ? "\n" : "", value);
free(s->value);
s->value = new_value;
strlist_add(s->values, value);
} else {
/* Check if this is a rewrite of a previous value. */
if (!s->derived && s->attrib && !s->attrib->rewrite &&
strcmp(s->value, value) == 0)
return;
/* Attribute unknown or does not support multiple values -
* replace. */
free(s->value);
s->value = misc_strdup(value);
}
s->modified = 1;
s->removed = 0;
}
/* Determine if a setting is set (that is configured). */
bool setting_is_set(struct setting *s)
{
if (s->removed)
return false;
if (s->modified)
return true;
if (s->derived)
return false;
return true;
}
static void setting_set_actual(struct setting *s, const char *value)
{
free(s->actual_value);
s->actual_value = misc_strdup(value);
if (s->attrib && s->attrib->multi) {
strlist_free(s->actual_values);
s->actual_values = strlist_new();
strlist_add_multi(s->actual_values, value, "\n", 0);
}
}
/* Write a single setting value to a sysfs attribute. */
static exit_code_t write_setting_value(const char *path, struct setting *s,
const char *value)
{
struct attrib *a = s->attrib;
int newline, rewrite, unstable, writeonly;
exit_code_t rc = EXIT_OK;
char *currvalue = NULL, *newvalue = NULL;
newline = a ? a->newline : 0;
rewrite = a ? a->rewrite : 0;
unstable = a ? a->unstable : 0;
writeonly = a ? a->writeonly : 0;
/* Do we need to check for a rewrite? */
if (rewrite || unstable || force)
goto do_write;
/* Get current value if necessary. */
if (!s->actual_value && !writeonly) {
currvalue = misc_read_text_file(path, 1, err_ignore);
if (!currvalue)
goto do_write;
setting_set_actual(s, currvalue);
free(currvalue);
}
/* Check if value is already set. */
if (s->actual_values) {
if (strlist_find(s->actual_values, value))
goto out;
} else if (s->actual_value) {
if (strcmp(s->actual_value, value) == 0)
goto out;
}
do_write:
/* Ensure newline if required. */
if (newline && !ends_with(value, "\n"))
newvalue = misc_asprintf("%s\n", value);
rc = misc_write_text_file_retry(path, newvalue ? newvalue : value,
err_delayed_print);
if (rc)
rc = EXIT_SETTING_FAILED;
out:
free(newvalue);
return rc;
}
/* Write a single setting to a sysfs attribute. */
exit_code_t setting_write(const char *path, struct setting *s)
{
exit_code_t rc;
struct strlist_node *str;
if (!s->values) {
/* Single value attribute. */
return write_setting_value(path, s, s->value);
}
util_list_iterate(s->values, str) {
rc = write_setting_value(path, s, str->str);
if (rc)
return rc;
}
return EXIT_OK;
}
/* Used for debugging. */
void setting_print(struct setting *s, int level)
{
struct strlist_node *str;
int i;
printf("%*ssetting at %p\n", level, "", (void *) s);
if (!s)
return;
level += 4;
if (s->attrib)
printf("%*sattrib=%s\n", level, "", s->attrib->name);
else
printf("%*sattrib=<none>\n", level, "");
printf("%*sname='%s'\n", level, "", s->name);
printf("%*svalue='%s'\n", level, "", s->value);
if (s->values) {
i = 0;
util_list_iterate(s->values, str) {
printf("%*svalues[%d]='%s'\n", level, "", i++,
str->str);
}
}
if (s->actual_value)
printf("%*sactual_value='%s'\n", level, "", s->actual_value);
else
printf("%*sactual_value=<none>\n", level, "");
if (s->actual_values) {
i = 0;
util_list_iterate(s->actual_values, str) {
printf("%*sactual_values[%d]='%s'\n", level, "", i++,
str->str);
}
}
printf("%*smodified='%d'\n", level, "", s->modified);
printf("%*sspecified='%d'\n", level, "", s->specified);
printf("%*sremoved='%d'\n", level, "", s->removed);
printf("%*sderived='%d'\n", level, "", s->derived);
printf("%*sreadonly='%d'\n", level, "", s->readonly);
}
/* Create and initialize a new setting_list. */
struct setting_list *setting_list_new(void)
{
struct setting_list *list;
list = misc_malloc(sizeof(struct setting_list));
util_list_init(&list->list, struct setting, node);
return list;
}
/* Remove all settings from list. */
void setting_list_clear(struct setting_list *list)
{
struct setting *s, *n;
util_list_iterate_safe(&list->list, s, n) {
util_list_remove(&list->list, s);
setting_free(s);
}
}
/* Release resources used by list and enlisted settings. */
void setting_list_free(struct setting_list *list)
{
if (!list)
return;
setting_list_clear(list);
free(list);
}
/* Add a new element to a list and mark the list as modified. */
void setting_list_add(struct setting_list *list, struct setting *setting)
{
util_list_add_tail(&list->list, setting);
list->modified = 1;
}
/* Find an element in the list. */
struct setting *setting_list_find(struct setting_list *list, const char *name)
{
struct setting *s;
util_list_iterate(&list->list, s) {
if (strcmp(s->name, name) == 0)
return s;
}
return NULL;
}
static struct setting *add_setting(struct setting_list *list,
struct attrib *attrib,
const char *name, const char *value,
int new_modified)
{
struct setting *s;
s = setting_list_find(list, name);
if (s)
setting_mod_value(s, value);
else {
s = setting_new(attrib, name, value);
setting_list_add(list, s);
if (new_modified)
s->modified = 1;
}
return s;
}
/* Modify existing setting or add new one. */
struct setting *setting_list_apply(struct setting_list *list,
struct attrib *attrib, const char *name,
const char *value)
{
return add_setting(list, attrib, name, value, 1);
}
/* Modify existing setting or add new one based on user request. */
struct setting *setting_list_apply_specified(struct setting_list *list,
struct attrib *attrib,
const char *name,
const char *value)
{
struct setting *s;
s = add_setting(list, attrib, name, value, 1);
s->specified = 1;
return s;
}
/* Set actual value for existing setting or add new one. */
struct setting *setting_list_apply_actual(struct setting_list *list,
struct attrib *attrib,
const char *name, const char *value)
{
struct setting *s;
s = add_setting(list, attrib, name, value, 0);
setting_set_actual(s, value);
return s;
}
/* Check if a setting was modified. */
bool setting_list_modified(struct setting_list *list)
{
struct setting *s;
if (!list)
return false;
util_list_iterate(&list->list, s) {
if (s->modified)
return true;
}
return false;
}
/* Determine the state of a boolean attribute with the given name in the
* specified setting list. */
void setting_list_get_bool_state(struct setting_list *list, const char *name,
int *changed, int *set)
{
struct setting *s;
s = setting_list_find(list, name);
if (!s) {
*changed = 0;
*set = 0;
return;
}
if (strcmp(s->value, "1") == 0)
*set = 1;
else
*set = 0;
if (s->actual_value && strcmp(s->value, s->actual_value) != 0)
*changed = 1;
else
*changed = 0;
}
/* Return a newly allocated space-separated string containing all non-removed
* settings in KEY=VALUE format. */
char *setting_list_flatten(struct setting_list *list)
{
struct setting *s;
int i;
char *str;
/* Determine total string length. */
i = 1;
util_list_iterate(&list->list, s) {
if (s->removed)
continue;
i += strlen(s->name) + 1 + strlen(s->value) + 1;
}
str = misc_malloc(i);
/* Combine string. */
i = 0;
util_list_iterate(&list->list, s) {
if (s->removed)
continue;
i += sprintf(&str[i], "%s%s=%s", i == 0 ? "" : " ", s->name,
s->value);
}
return str;
}
/* Used for debugging. */
void setting_list_print(struct setting_list *list, int level)
{
struct setting *s;
printf("%*ssetting list at %p\n", level, "", (void *) list);
if (!list)
return;
util_list_iterate(&list->list, s)
setting_print(s, level + 4);
}
/* Determine the order of applying attributes based on the attribute order
* information. */
static int setting_cmp(struct setting *a, struct setting *b)
{
int result;
if (!a->attrib || !b->attrib)
return 0;
/* Try order_cmp first if available. */
if (a->attrib && a->attrib->order_cmp) {
result = a->attrib->order_cmp(a, b);
if (result != 0)
return result;
}
if (b->attrib && b->attrib->order_cmp) {
result = b->attrib->order_cmp(b, a);
if (result != 0)
return -result;
}
/* Try static ordering next. */
if (a->attrib->order < b->attrib->order)
return -1;
if (a->attrib->order > b->attrib->order)
return 1;
/* Don't fallback to strcmp or similar here since that could
* conflict with an actual ordering requirement reported by
* a cmp callback. */
return 0;
}
/* Return a newly allocated ptrlist of settings sorted according to
* order_cmp. */
struct util_list *setting_list_get_sorted(struct setting_list *list)
{
struct util_list *result;
struct setting *s;
struct ptrlist_node *p;
result = ptrlist_new();
p = NULL;
util_list_iterate(&list->list, s) {
/* Find first element that should be after new element
* in list. */
util_list_iterate(result, p) {
if (setting_cmp(p->ptr, s) > 0)
break;
}
if (p)
ptrlist_add_before(result, p, s);
else
ptrlist_add(result, s);
}
return result;
}
/* Check if there is any conflict between settings in @list. Display errors
* according to @err. Return %false if there is a conflict, %true otherwise. */
bool setting_list_check_conflict(struct setting_list *list, config_t config,
err_t err)
{
struct setting *a, *b;
util_list_iterate(&list->list, a) {
if (!a->attrib || !a->attrib->check)
continue;
if (a->removed)
continue;
if (!a->modified && !a->specified)
continue;
util_list_iterate(&list->list, b) {
if (a == b)
continue;
if (!a->attrib->check(a, b, config))
goto conflict;
}
}
return true;
conflict:
err_t_print(err, "Cannot set %s='%s' while %s='%s'\n", a->name,
a->value, b->name, b->value);
return false;
}
/* Add settings with default values to @list for all attributes in @attribs
* for which no setting has been added. If @mand_only is specified, only
* apply default values for mandatory settings. */
void setting_list_apply_defaults(struct setting_list *list,
struct attrib **attribs, bool mand_only)
{
int i;
struct attrib *a;
struct setting *s;
for (i = 0; (a = attribs[i]); i++) {
if (!a->defval)
continue;
if (mand_only && !a->mandatory)
continue;
s = setting_list_find(list, a->name);
if (s)
continue;
s = setting_list_apply_actual(list, a, a->name, a->defval);
s->derived = 1;
}
}
/* Merge settings from setting list @from into @to. If @specified is true,
* also copy specified flag. If @modified is true, also copy modified flag. */
void setting_list_merge(struct setting_list *to, struct setting_list *from,
bool specified, bool modified)
{
struct setting *s, *n;
util_list_iterate(&from->list, s) {
n = setting_list_apply(to, s->attrib, s->name, s->value);
if (specified)
n->specified = s->specified;
if (modified)
n->modified = s->modified;
}
}
/* Return a copy of setting list @list. */
struct setting_list *setting_list_copy(struct setting_list *list)
{
struct setting_list *copy;
struct setting *s;
copy = setting_list_new();
util_list_iterate(&list->list, s)
util_list_add_tail(&copy->list, setting_copy(s));
copy->modified = list->modified;
return copy;
}
/* Apply the attribute value replacement map for all settings in list which
* define such a map. */
void setting_list_map_values(struct setting_list *list)
{
struct setting *s;
struct attrib *a;
const char *to;
util_list_iterate(&list->list, s) {
a = s->attrib;
if (!a || !a->map)
continue;
to = attrib_map_value(a, s->value);
if (!to)
continue;
free(s->value);
s->value = misc_strdup(to);
}
}
/* Mark all settings in @list as derived which have an actual_value equal
* to the default value. */
void setting_list_mark_default_derived(struct setting_list *list)
{
struct setting *s;
util_list_iterate(&list->list, s) {
if (!s->attrib || !s->attrib->defval || !s->actual_value)
continue;
if (!attrib_match_default(s->attrib, s->actual_value))
continue;
s->derived = 1;
}
}
/* Return the number of settings in @list that are set. */
int setting_list_count_set(struct setting_list *list)
{
struct setting *s;
int set;
set = 0;
util_list_iterate(&list->list, s) {
if (setting_is_set(s))
set++;
}
return set;
}
/* Remove all settings in @list which are derived. */
void setting_list_remove_derived(struct setting_list *list)
{
struct setting *s, *n;
util_list_iterate_safe(&list->list, s, n) {
if (!s->derived)
continue;
util_list_remove(&list->list, s);
setting_free(s);
}
}
static void add_changes(struct util_list *list, struct setting *s)
{
struct strlist_node *str;
if (s->values) {
/* Multi value attribute. */
util_list_iterate(s->values, str)
strlist_add(list, "%s=%s", s->name, str->str);
} else {
/* Single value attribute. */
strlist_add(list, "%s=%s", s->name, s->value);
}
}
/* Return a newly allocated string containing the setting changes found
* in PERS and ACT or NULL if no change was found. */
char *setting_get_changes(struct setting_list *act, struct setting_list *pers,
struct setting_list *ac)
{
struct util_list *out;
struct util_list *processed;
struct setting *s;
char *result;
out = strlist_new();
processed = strlist_new();
/* Collect modified settings. */
if (pers) {
util_list_iterate(&pers->list, s) {
if (s->removed)
strlist_add(out, "-%s", s->name);
else if (s->modified)
add_changes(out, s);
else
continue;
strlist_add(processed, s->name);
}
}
if (ac) {
util_list_iterate(&ac->list, s) {
if (s->removed)
strlist_add(out, "-%s", s->name);
else if (s->modified)
add_changes(out, s);
else
continue;
strlist_add(processed, s->name);
}
}
if (act) {
util_list_iterate(&act->list, s) {
if (!s->modified && !s->removed)
continue;
if (strlist_find(processed, s->name))
continue;
if (s->removed)
strlist_add(out, "-%s", s->name);
else
add_changes(out, s);
strlist_add(processed, s->name);
}
}
if (!util_list_is_empty(out))
result = strlist_flatten(out, " ");
else
result = NULL;
strlist_free(processed);
strlist_free(out);
return result;
}
/* Check if @value matches the value of setting @s. */
bool setting_match_value(struct setting *s, const char *value)
{
struct strlist_node *str;
if (s->values) {
util_list_iterate(s->values, str) {
if (strcmp(str->str, value) == 0)
return true;
}
}
if (s->value) {
if (strcmp(s->value, value) == 0)
return true;
}
return false;
}