settings: Don't overwrite values in-place

This is not thread safe.  If threads are reading from pointers to existing
values they could get a partially updated invalid value.

Refactored assignment to a separate function.
This commit is contained in:
Tobias Brunner
2014-05-15 11:28:08 +02:00
parent 725c479f8b
commit 2fbbea55c5
4 changed files with 52 additions and 36 deletions
+1 -20
View File
@@ -474,26 +474,7 @@ static void set_value(private_settings_t *this, section_t *section,
TRUE); TRUE);
if (kv) if (kv)
{ {
if (!value) settings_kv_set(kv, strdupnull(value), this->contents);
{
if (kv->value)
{
array_insert(this->contents, ARRAY_TAIL, kv->value);
}
kv->value = NULL;
}
else if (kv->value && (strlen(value) <= strlen(kv->value)))
{ /* overwrite in-place, if possible */
strcpy(kv->value, value);
}
else
{ /* otherwise clone the string and cache the replaced one */
if (kv->value)
{
array_insert(this->contents, ARRAY_TAIL, kv->value);
}
kv->value = strdup(value);
}
} }
this->lock->unlock(this->lock); this->lock->unlock(this->lock);
} }
+26 -9
View File
@@ -81,6 +81,31 @@ void settings_section_destroy(section_t *this, array_t *contents)
free(this); free(this);
} }
/*
* Described in header
*/
void settings_kv_set(kv_t *kv, char *value, array_t *contents)
{
if (value && kv->value && streq(value, kv->value))
{ /* no update required */
free(value);
return;
}
/* if the new value was shorter we could overwrite the existing one but that
* could lead to reads of partially updated values from other threads that
* have a pointer to the existing value, so we replace it anyway */
if (kv->value && contents)
{
array_insert(contents, ARRAY_TAIL, kv->value);
}
else
{
free(kv->value);
}
kv->value = value;
}
/* /*
* Described in header * Described in header
*/ */
@@ -95,15 +120,7 @@ void settings_kv_add(section_t *section, kv_t *kv, array_t *contents)
} }
else else
{ {
if (contents && found->value) settings_kv_set(found, kv->value, contents);
{
array_insert(contents, ARRAY_TAIL, found->value);
}
else
{
free(found->value);
}
found->value = kv->value;
kv->value = NULL; kv->value = NULL;
settings_kv_destroy(kv, NULL); settings_kv_destroy(kv, NULL);
} }
@@ -87,6 +87,15 @@ kv_t *settings_kv_create(char *key, char *value);
*/ */
void settings_kv_destroy(kv_t *this, array_t *contents); void settings_kv_destroy(kv_t *this, array_t *contents);
/**
* Set the value of the given key/value pair.
*
* @param kv key/value pair
* @param value new value (gets adopted), may be NULL
* @param contents optional array to store replaced values in
*/
void settings_kv_set(kv_t *kv, char *value, array_t *contents);
/** /**
* Add the given key/value pair to the given section. * Add the given key/value pair to the given section.
* *
+16 -7
View File
@@ -133,19 +133,28 @@ END_TEST
START_TEST(test_set_str) START_TEST(test_set_str)
{ {
char *val; char *val1, *val2;
val = settings->get_str(settings, "main.key1", NULL); val1 = settings->get_str(settings, "main.key1", NULL);
ck_assert_str_eq("val1", val); ck_assert_str_eq("val1", val1);
settings->set_str(settings, "main.key1", "val"); settings->set_str(settings, "main.key1", "val");
verify_string("val", "main.key1"); verify_string("val", "main.key1");
/* since it is shorter the pointer we got above points to the new value */ /* the pointer we got before is still valid */
ck_assert_str_eq("val", val); ck_assert_str_eq("val1", val1);
val2 = settings->get_str(settings, "main.key1", NULL);
ck_assert_str_eq("val", val2);
settings->set_str(settings, "main.key1", "longer value"); settings->set_str(settings, "main.key1", "longer value");
verify_string("longer value", "main.key1"); verify_string("longer value", "main.key1");
/* here it gets replaced but the pointer is still valid */ /* the pointers we got before are still valid */
ck_assert_str_eq("val", val); ck_assert_str_eq("val1", val1);
ck_assert_str_eq("val", val2);
val1 = settings->get_str(settings, "main.key1", NULL);
settings->set_str(settings, "main.key1", "longer value");
val2 = settings->get_str(settings, "main.key1", NULL);
/* setting the same string should should get us the same pointer */
ck_assert(val1 == val2);
settings->set_str(settings, "main", "main val"); settings->set_str(settings, "main", "main val");
verify_string("main val", "main"); verify_string("main val", "main");