diff --git a/src/libnm-core-impl/nm-connection.c b/src/libnm-core-impl/nm-connection.c index 24304f7d62..d156915682 100644 --- a/src/libnm-core-impl/nm-connection.c +++ b/src/libnm-core-impl/nm-connection.c @@ -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]; diff --git a/src/libnm-core-impl/nm-setting-ip-config.c b/src/libnm-core-impl/nm-setting-ip-config.c index 720048c3bb..1cae1e4447 100644 --- a/src/libnm-core-impl/nm-setting-ip-config.c +++ b/src/libnm-core-impl/nm-setting-ip-config.c @@ -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); } diff --git a/src/libnm-core-impl/nm-setting-match.c b/src/libnm-core-impl/nm-setting-match.c index 4cad4f68a1..7736b7c0b1 100644 --- a/src/libnm-core-impl/nm-setting-match.c +++ b/src/libnm-core-impl/nm-setting-match.c @@ -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); } /*****************************************************************************/ diff --git a/src/libnm-core-impl/nm-setting.c b/src/libnm-core-impl/nm-setting.c index c402174afe..d14fc35856 100644 --- a/src/libnm-core-impl/nm-setting.c +++ b/src/libnm-core-impl/nm-setting.c @@ -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: diff --git a/src/libnm-core-impl/tests/test-setting.c b/src/libnm-core-impl/tests/test-setting.c index 1a8910d8d3..53a7acd2fc 100644 --- a/src/libnm-core-impl/tests/test-setting.c +++ b/src/libnm-core-impl/tests/test-setting.c @@ -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); diff --git a/src/libnm-glib-aux/nm-shared-utils.h b/src/libnm-glib-aux/nm-shared-utils.h index 25f11a9d03..6c553cc085 100644 --- a/src/libnm-glib-aux/nm-shared-utils.h +++ b/src/libnm-glib-aux/nm-shared-utils.h @@ -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) {