glib-aux: refactor nm_strvarray_get_strv*() and nm_strvarray_set_strv*() helpers

Unfortunately, there are several possibilities how to handle NULL and
empty arrays. Therefore we have different variants.

Clean this up, and add a way to preserve whether the array is empty
(previous variants could not distinguish that).

Functions are also renamed, so that if you backport a user of the new
API, you'll get a compiler error if this patch is missing.

Also, nm_strvarray_get_strv_notnull() no longer takes a pointer to a
"GArray*". Previously, it used that to fake an empty strv array. Now
this returns NM_STRV_EMPTY_CC().
This commit is contained in:
Thomas Haller 2023-11-16 10:34:11 +01:00
parent e48fc3ee3e
commit 563fad718c
No known key found for this signature in database
GPG key ID: 29C2366E4DFC5728
6 changed files with 198 additions and 73 deletions

View file

@ -908,7 +908,7 @@ _nm_setting_connection_verify_secondaries(GArray *secondaries, GError **error)
* Now, when we find any invalid/non-normalized values, we reject/normalize
* them. We also filter out duplicates. */
strv = nm_strvarray_get_strv_non_empty(secondaries, NULL);
strv = nm_strvarray_get_strv_notempty(secondaries, NULL);
for (i = 0; i < len; i++) {
const char *uuid = strv[i];
@ -977,7 +977,7 @@ _normalize_connection_secondaries(NMConnection *self)
if (_nm_setting_connection_verify_secondaries(secondaries, NULL))
return FALSE;
strv = nm_strvarray_get_strv_non_empty_dup(secondaries, NULL);
strv = nm_strvarray_get_strv_notempty_dup(secondaries, NULL);
for (i = 0, j = 0; strv[i]; i++) {
gs_free char *s = g_steal_pointer(&strv[i]);
char uuid_normalized[37];

View file

@ -5352,8 +5352,8 @@ nm_setting_ip_config_get_dhcp_reject_servers(NMSettingIPConfig *setting, guint *
{
g_return_val_if_fail(NM_IS_SETTING_IP_CONFIG(setting), NULL);
return nm_strvarray_get_strv(
&NM_SETTING_IP_CONFIG_GET_PRIVATE(setting)->dhcp_reject_servers.arr,
return nm_strvarray_get_strv_notnull(
NM_SETTING_IP_CONFIG_GET_PRIVATE(setting)->dhcp_reject_servers.arr,
out_len);
}

View file

@ -182,7 +182,7 @@ nm_setting_match_get_interface_names(NMSettingMatch *setting, guint *length)
{
g_return_val_if_fail(NM_IS_SETTING_MATCH(setting), NULL);
return nm_strvarray_get_strv(&setting->interface_name.arr, length);
return nm_strvarray_get_strv_notnull(setting->interface_name.arr, length);
}
/*****************************************************************************/
@ -320,7 +320,7 @@ nm_setting_match_get_kernel_command_lines(NMSettingMatch *setting, guint *length
{
g_return_val_if_fail(NM_IS_SETTING_MATCH(setting), NULL);
return nm_strvarray_get_strv(&setting->kernel_command_line.arr, length);
return nm_strvarray_get_strv_notnull(setting->kernel_command_line.arr, length);
}
/*****************************************************************************/
@ -456,7 +456,7 @@ nm_setting_match_get_drivers(NMSettingMatch *setting, guint *length)
{
g_return_val_if_fail(NM_IS_SETTING_MATCH(setting), NULL);
return nm_strvarray_get_strv(&setting->driver.arr, length);
return nm_strvarray_get_strv_notnull(setting->driver.arr, length);
}
/*****************************************************************************/
@ -592,7 +592,7 @@ nm_setting_match_get_paths(NMSettingMatch *setting, guint *length)
{
g_return_val_if_fail(NM_IS_SETTING_MATCH(setting), NULL);
return nm_strvarray_get_strv(&setting->path.arr, length);
return nm_strvarray_get_strv_notnull(setting->path.arr, length);
}
/*****************************************************************************/

View file

@ -817,7 +817,7 @@ _nm_setting_property_get_property_direct(GObject *object,
{
const NMValueStrv *p_val = _nm_setting_get_private_field(setting, sett_info, property_info);
g_value_take_boxed(value, nm_strvarray_get_strv_non_empty_dup(p_val->arr, NULL));
g_value_take_boxed(value, nm_strvarray_get_strv_notempty_dup(p_val->arr, NULL));
return;
}
default:

View file

@ -5108,44 +5108,42 @@ test_setting_connection_secondaries_verify(void)
g_object_set(s_con, NM_SETTING_CONNECTION_SECONDARIES, arr->pdata, NULL);
#define _assert_secondaries(s_con, expected) \
G_STMT_START \
{ \
NMSettingConnection *const _s_con = (s_con); \
const char *const *_expected = (expected); \
GArray *_secondaries; \
const guint _expected_len = NM_PTRARRAY_LEN(_expected); \
gs_strfreev char **_sec_strv = NULL; \
guint _i; \
\
g_assert(_expected); \
\
if (nmtst_get_rand_bool()) { \
_secondaries = _nm_setting_connection_get_secondaries(_s_con); \
g_assert_cmpint(_expected_len, ==, nm_g_array_len(_secondaries)); \
g_assert((_expected_len == 0) == (!_secondaries)); \
g_assert(nm_strv_equal(_expected, \
_secondaries ? nm_strvarray_get_strv(&_secondaries, NULL) \
: NM_PTRARRAY_EMPTY(const char *))); \
} \
\
if (nmtst_get_rand_bool()) { \
g_object_get(_s_con, NM_SETTING_CONNECTION_SECONDARIES, &_sec_strv, NULL); \
g_assert_cmpint(_expected_len, ==, NM_PTRARRAY_LEN(_sec_strv)); \
g_assert((_expected_len == 0) == (!_sec_strv)); \
g_assert(nm_strv_equal(_expected, _sec_strv ?: NM_STRV_EMPTY())); \
} \
\
g_assert_cmpint(nm_setting_connection_get_num_secondaries(_s_con), ==, _expected_len); \
if (nmtst_get_rand_bool()) { \
for (_i = 0; _i < _expected_len; _i++) { \
g_assert_cmpstr(nm_setting_connection_get_secondary(_s_con, _i), \
==, \
_expected[_i]); \
} \
g_assert_null(nm_setting_connection_get_secondary(_s_con, _expected_len)); \
} \
} \
#define _assert_secondaries(s_con, expected) \
G_STMT_START \
{ \
NMSettingConnection *const _s_con = (s_con); \
const char *const *_expected = (expected); \
GArray *_secondaries; \
const guint _expected_len = NM_PTRARRAY_LEN(_expected); \
gs_strfreev char **_sec_strv = NULL; \
guint _i; \
\
g_assert(_expected); \
\
if (nmtst_get_rand_bool()) { \
_secondaries = _nm_setting_connection_get_secondaries(_s_con); \
g_assert_cmpint(_expected_len, ==, nm_g_array_len(_secondaries)); \
g_assert((_expected_len == 0) == (!_secondaries)); \
g_assert(nm_strv_equal(_expected, nm_strvarray_get_strv_notnull(_secondaries, NULL))); \
} \
\
if (nmtst_get_rand_bool()) { \
g_object_get(_s_con, NM_SETTING_CONNECTION_SECONDARIES, &_sec_strv, NULL); \
g_assert_cmpint(_expected_len, ==, NM_PTRARRAY_LEN(_sec_strv)); \
g_assert((_expected_len == 0) == (!_sec_strv)); \
g_assert(nm_strv_equal(_expected, _sec_strv ?: NM_STRV_EMPTY())); \
} \
\
g_assert_cmpint(nm_setting_connection_get_num_secondaries(_s_con), ==, _expected_len); \
if (nmtst_get_rand_bool()) { \
for (_i = 0; _i < _expected_len; _i++) { \
g_assert_cmpstr(nm_setting_connection_get_secondary(_s_con, _i), \
==, \
_expected[_i]); \
} \
g_assert_null(nm_setting_connection_get_secondary(_s_con, _expected_len)); \
} \
} \
G_STMT_END
_assert_secondaries(s_con, (const char *const *) arr->pdata);

View file

@ -3040,53 +3040,159 @@ nm_strvarray_get_idx(const GArray *array, guint idx)
_idx == _len ? NULL : nm_strvarray_get_idx(_arr, _idx); \
})
/**
* nm_strvarray_get_strv_full:
* @arr: the strvarray.
* @length: (out) (nullable): optionally return the length of the result.
* @not_null: if true and @arr is NULL, return NM_STRV_EMPTY_CC() (otherwise NULL).
* @preserve_empty: if true and the array is empty, return an empty
* strv array. Otherwise, return NULL.
*
* If "arr" is NULL, this returns NULL, unless "not_null" is true (in which
* case the static NM_STRV_EMPTY_CC() is returned).
* If "arr" is empty, it depends on:
* - if "preserve_empty" or "not_null", then the resulting strv array is the empty "arr".
* - otherwise NULL is returned.
* Otherwise, returns the non-empty, non-deep-cloned strv array.
*
* Like nm_strvarray_get_strv_full_dup(), but the strings are not cloned.
*
* Returns: (transfer none): a strv list or NULL.
*/
static inline const char *const *
nm_strvarray_get_strv_non_empty(GArray *arr, guint *length)
nm_strvarray_get_strv_full(const GArray *arr,
guint *length,
gboolean not_null,
gboolean preserve_empty)
{
nm_assert(!arr || sizeof(char *) == g_array_get_element_size(arr));
if (!arr || arr->len == 0) {
if (!arr) {
NM_SET_OUT(length, 0);
return NULL;
return not_null ? NM_STRV_EMPTY_CC() : NULL;
}
nm_assert(sizeof(char *) == g_array_get_element_size((GArray *) arr));
NM_SET_OUT(length, arr->len);
if (arr->len == 0 && !(preserve_empty || not_null))
return NULL;
return &g_array_index(arr, const char *, 0);
}
/**
* nm_strvarray_get_strv_full_dup:
* @arr: the strvarray.
* @length: (out) (nullable): optionally return the length of the result.
* @not_null: if true, never return NULL but allocate an empty strv array.
* @preserve_empty: if true and the array is empty, return an empty
* strv array. Otherwise, return NULL.
*
* If "arr" is NULL, this returns NULL, unless "not_null" is true (in which case
* am empty strv array is allocated.
* If "arr" is empty, it depends on:
* - if "preserve_empty" || "not_null", then the resulting strv array is allocated (and empty).
* - otherwise, NULL is returned.
* Otherwise, return the non-empty, deep-cloned strv array.
*
* Like nm_strvarray_get_strv_full(), but the strings are cloned.
*
* Returns: (transfer full): a deep-cloned strv list or NULL.
*/
static inline char **
nm_strvarray_get_strv_non_empty_dup(GArray *arr, guint *length)
nm_strvarray_get_strv_full_dup(const GArray *arr,
guint *length,
gboolean not_null,
gboolean preserve_empty)
{
const char *const *strv;
nm_assert(!arr || sizeof(char *) == g_array_get_element_size(arr));
if (!arr || arr->len == 0) {
if (!arr) {
NM_SET_OUT(length, 0);
return not_null ? g_new0(char *, 1) : NULL;
}
nm_assert(sizeof(char *) == g_array_get_element_size((GArray *) arr));
NM_SET_OUT(length, arr->len);
if (arr->len == 0) {
if (preserve_empty || not_null)
return g_new0(char *, 1);
return NULL;
}
NM_SET_OUT(length, arr->len);
strv = &g_array_index(arr, const char *, 0);
return nm_strv_dup(strv, arr->len, TRUE);
return nm_strv_dup(&g_array_index(arr, const char *, 0), arr->len, TRUE);
}
/**
* nm_strvarray_get_strv_notnull:
* @arr: the strvarray.
* @length: (out) (nullable): optionally return the length of the result.
*
* This never returns NULL. If @arr is NULL, this returns NM_STRV_EMPTY_CC().
*
* Like nm_strvarray_get_strv_notempty(), but never returns NULL.
*
* Returns: (transfer none): a pointer to the strv list in @arr or NM_STRV_EMPTY_CC().
*/
static inline const char *const *
nm_strvarray_get_strv(GArray **arr, guint *length)
nm_strvarray_get_strv_notnull(const GArray *arr, guint *length)
{
if (!*arr) {
NM_SET_OUT(length, 0);
return (const char *const *) arr;
}
nm_assert(sizeof(char *) == g_array_get_element_size(*arr));
NM_SET_OUT(length, (*arr)->len);
return &g_array_index(*arr, const char *, 0);
return nm_strvarray_get_strv_full(arr, length, TRUE, TRUE);
}
/**
* nm_strvarray_get_strv_notempty:
* @arr: the strvarray.
* @length: (out) (nullable): optionally return the length of the result.
*
* This never returns an empty strv array. If @arr is NULL or empty, this
* returns NULL.
*
* Like nm_strvarray_get_strv_notempty_dup(), but does not clone strings.
*
* Returns: (transfer none): a pointer to the strv list in @arr or NULL.
*/
static inline const char *const *
nm_strvarray_get_strv_notempty(const GArray *arr, guint *length)
{
return nm_strvarray_get_strv_full(arr, length, FALSE, FALSE);
}
/**
* nm_strvarray_get_strv_notempty_dup:
* @arr: the strvarray.
* @length: (out) (nullable): optionally return the length of the result.
*
* This never returns an empty strv array. If @arr is NULL or empty, this
* returns NULL.
*
* Like nm_strvarray_get_strv_notempty(), but clones strings.
*
* Returns: (transfer full): a deep-cloned strv list or NULL.
*/
static inline char **
nm_strvarray_get_strv_notempty_dup(const GArray *arr, guint *length)
{
return nm_strvarray_get_strv_full_dup(arr, length, FALSE, FALSE);
}
/**
* nm_strvarray_set_strv_full:
* @array: a pointer to the array to set.
* @strv: the strv array. May be NULL.
* @preserve_empty: how to treat if strv is empty (strv[0]==NULL).
*
* The old array will be freed (in a way so that the function is self-assignment
* safe).
*
* If "strv" is NULL, then the resulting GArray is NULL.
* If "strv" is empty, then it depends on "preserve_empty":
* - if "preserve_empty", then the resulting GArray is allocated (and empty).
* - if "!preserve_empty", then the resulting GArray is NULL.
* If "strv" is not empty, a GArray gets allocated and the strv array deep-cloned.
*/
static inline void
nm_strvarray_set_strv(GArray **array, const char *const *strv)
nm_strvarray_set_strv_full(GArray **array, const char *const *strv, gboolean preserve_empty)
{
gs_unref_array GArray *array_old = NULL;
@ -3094,14 +3200,35 @@ nm_strvarray_set_strv(GArray **array, const char *const *strv)
nm_assert(!array_old || sizeof(char *) == g_array_get_element_size(array_old));
if (!strv || !strv[0])
if (!strv)
return;
if (!strv[0] && !preserve_empty) {
/* An empty strv array is treated like NULL. Don't allocate a GArray. */
return;
}
nm_strvarray_ensure(array);
for (; strv[0]; strv++)
nm_strvarray_add(*array, strv[0]);
}
/**
* nm_strvarray_set_strv:
* @array: a pointer to the array to set.
* @strv: the strv array. May be NULL.
*
* The old array will be freed (in a way so that the function is self-assignment
* safe).
*
* Note that this will never initialize an empty GArray. If strv is NULL or
* empty, the @array pointer will be set to NULL. */
static inline void
nm_strvarray_set_strv(GArray **array, const char *const *strv)
{
nm_strvarray_set_strv_full(array, strv, FALSE);
}
static inline gssize
nm_strvarray_find_first(const GArray *strv, const char *needle)
{