diff options
| author | Jeremy Bicha <jeremy.bicha@canonical.com> | 2022-08-18 08:31:29 -0400 |
|---|---|---|
| committer | Jeremy Bicha <jeremy.bicha@canonical.com> | 2022-08-18 08:31:29 -0400 |
| commit | b0887dd4d035acc0d84aa859748d36891548eaaf (patch) | |
| tree | c4dc0dae50954f3c8fb600aa9e7cfaabeb80deb0 /src/libnm-glib-aux | |
| parent | 1a62dcfdc0470be37743914da69fe0b0677c6bfb (diff) | |
| parent | 4741f1a52215c7ba466140912084d6906185bef3 (diff) | |
Merge branch 'debian/master' into ubuntu/master
Diffstat (limited to 'src/libnm-glib-aux')
| -rw-r--r-- | src/libnm-glib-aux/nm-default-glib.h | 44 | ||||
| -rw-r--r-- | src/libnm-glib-aux/nm-enum-utils.c | 2 | ||||
| -rw-r--r-- | src/libnm-glib-aux/nm-errno.c | 1 | ||||
| -rw-r--r-- | src/libnm-glib-aux/nm-errno.h | 1 | ||||
| -rw-r--r-- | src/libnm-glib-aux/nm-glib.h | 27 | ||||
| -rw-r--r-- | src/libnm-glib-aux/nm-hash-utils.c | 2 | ||||
| -rw-r--r-- | src/libnm-glib-aux/nm-hash-utils.h | 24 | ||||
| -rw-r--r-- | src/libnm-glib-aux/nm-io-utils.c | 285 | ||||
| -rw-r--r-- | src/libnm-glib-aux/nm-io-utils.h | 11 | ||||
| -rw-r--r-- | src/libnm-glib-aux/nm-logging-fwd.h | 11 | ||||
| -rw-r--r-- | src/libnm-glib-aux/nm-macros-internal.h | 20 | ||||
| -rw-r--r-- | src/libnm-glib-aux/nm-random-utils.c | 340 | ||||
| -rw-r--r-- | src/libnm-glib-aux/nm-random-utils.h | 10 | ||||
| -rw-r--r-- | src/libnm-glib-aux/nm-ref-string.h | 2 | ||||
| -rw-r--r-- | src/libnm-glib-aux/nm-shared-utils.c | 743 | ||||
| -rw-r--r-- | src/libnm-glib-aux/nm-shared-utils.h | 277 | ||||
| -rw-r--r-- | src/libnm-glib-aux/nm-str-buf.h | 120 | ||||
| -rw-r--r-- | src/libnm-glib-aux/nm-test-utils.h | 3 | ||||
| -rw-r--r-- | src/libnm-glib-aux/nm-uuid.c | 40 | ||||
| -rw-r--r-- | src/libnm-glib-aux/tests/test-shared-general.c | 846 |
20 files changed, 2488 insertions, 321 deletions
diff --git a/src/libnm-glib-aux/nm-default-glib.h b/src/libnm-glib-aux/nm-default-glib.h index 9d04ddc4..f39e16cc 100644 --- a/src/libnm-glib-aux/nm-default-glib.h +++ b/src/libnm-glib-aux/nm-default-glib.h @@ -33,27 +33,27 @@ #if NM_MORE_ASSERTS == 0 #ifndef G_DISABLE_CAST_CHECKS /* Unless compiling with G_DISABLE_CAST_CHECKS, glib performs type checking - * during G_VARIANT_TYPE() via g_variant_type_checked_(). This is not necessary - * because commonly this cast is needed during something like - * - * g_variant_builder_init (&props, G_VARIANT_TYPE ("a{sv}")); - * - * Note that in if the variant type would be invalid, the check still - * wouldn't make the buggy code magically work. Instead of passing a - * bogus type string (bad), it would pass %NULL to g_variant_builder_init() - * (also bad). - * - * Also, a function like g_variant_builder_init() already validates - * the input type via something like - * - * g_return_if_fail (g_variant_type_is_container (type)); - * - * So, by having G_VARIANT_TYPE() also validate the type, we validate - * twice, whereas the first validation is rather pointless because it - * doesn't prevent the function to be called with invalid arguments. - * - * Just patch G_VARIANT_TYPE() to perform no check. - */ + * during G_VARIANT_TYPE() via g_variant_type_checked_(). This is not necessary + * because commonly this cast is needed during something like + * + * g_variant_builder_init (&props, G_VARIANT_TYPE ("a{sv}")); + * + * Note that in if the variant type would be invalid, the check still + * wouldn't make the buggy code magically work. Instead of passing a + * bogus type string (bad), it would pass %NULL to g_variant_builder_init() + * (also bad). + * + * Also, a function like g_variant_builder_init() already validates + * the input type via something like + * + * g_return_if_fail (g_variant_type_is_container (type)); + * + * So, by having G_VARIANT_TYPE() also validate the type, we validate + * twice, whereas the first validation is rather pointless because it + * doesn't prevent the function to be called with invalid arguments. + * + * Just patch G_VARIANT_TYPE() to perform no check. + */ #undef G_VARIANT_TYPE #define G_VARIANT_TYPE(type_string) ((const GVariantType *) (type_string)) #endif @@ -63,8 +63,6 @@ #include "nm-gassert-patch.h" -#include "libnm-std-aux/nm-std-aux.h" -#include "libnm-std-aux/nm-std-utils.h" #include "libnm-glib-aux/nm-macros-internal.h" #include "libnm-glib-aux/nm-shared-utils.h" #include "libnm-glib-aux/nm-errno.h" diff --git a/src/libnm-glib-aux/nm-enum-utils.c b/src/libnm-glib-aux/nm-enum-utils.c index 3b9b7e8d..b7f7a3f6 100644 --- a/src/libnm-glib-aux/nm-enum-utils.c +++ b/src/libnm-glib-aux/nm-enum-utils.c @@ -142,7 +142,7 @@ _nm_utils_enum_to_str_full(GType type, flags_separator = flags_separator ?: " "; - nm_str_buf_init(&strbuf, 16, FALSE); + strbuf = NM_STR_BUF_INIT(16, FALSE); for (; value_infos && value_infos->nick; value_infos++) { nm_assert(_enum_is_valid_flags_nick(value_infos->nick)); diff --git a/src/libnm-glib-aux/nm-errno.c b/src/libnm-glib-aux/nm-errno.c index 14d93d64..b76386a6 100644 --- a/src/libnm-glib-aux/nm-errno.c +++ b/src/libnm-glib-aux/nm-errno.c @@ -28,6 +28,7 @@ static NM_UTILS_LOOKUP_STR_DEFINE( NM_UTILS_LOOKUP_STR_ITEM(NME_NL_ATTRSIZE, "NME_NL_ATTRSIZE"), NM_UTILS_LOOKUP_STR_ITEM(NME_NL_BAD_SOCK, "NME_NL_BAD_SOCK"), NM_UTILS_LOOKUP_STR_ITEM(NME_NL_DUMP_INTR, "NME_NL_DUMP_INTR"), + NM_UTILS_LOOKUP_STR_ITEM(NME_NL_MSG_INVAL, "NME_NL_MSG_INVAL"), NM_UTILS_LOOKUP_STR_ITEM(NME_NL_MSG_OVERFLOW, "NME_NL_MSG_OVERFLOW"), NM_UTILS_LOOKUP_STR_ITEM(NME_NL_MSG_TOOSHORT, "NME_NL_MSG_TOOSHORT"), NM_UTILS_LOOKUP_STR_ITEM(NME_NL_MSG_TRUNC, "NME_NL_MSG_TRUNC"), diff --git a/src/libnm-glib-aux/nm-errno.h b/src/libnm-glib-aux/nm-errno.h index 62c8379f..f893a528 100644 --- a/src/libnm-glib-aux/nm-errno.h +++ b/src/libnm-glib-aux/nm-errno.h @@ -39,6 +39,7 @@ enum _NMErrno { /* netlink errors. */ NME_NL_SEQ_MISMATCH, + NME_NL_MSG_INVAL, NME_NL_MSG_TRUNC, NME_NL_MSG_TOOSHORT, NME_NL_DUMP_INTR, diff --git a/src/libnm-glib-aux/nm-glib.h b/src/libnm-glib-aux/nm-glib.h index f3be3b32..2dc86d7b 100644 --- a/src/libnm-glib-aux/nm-glib.h +++ b/src/libnm-glib-aux/nm-glib.h @@ -307,6 +307,33 @@ _nm_g_ptr_array_insert(GPtrArray *array, int index_, gpointer data) /*****************************************************************************/ +#if !GLIB_CHECK_VERSION(2, 54, 0) +static inline gboolean +g_ptr_array_find(GPtrArray *haystack, gconstpointer needle, guint *index_) +{ + guint i; + g_return_val_if_fail(haystack, FALSE); + + for (i = 0; i < haystack->len; i++) { + if (haystack->pdata[i] == needle) { + if (index_) + *index_ = i; + return TRUE; + } + } + return FALSE; +} +#else +#define g_ptr_array_find(haystack, needle, index_) \ + ({ \ + G_GNUC_BEGIN_IGNORE_DEPRECATIONS \ + g_ptr_array_find(haystack, needle, index_); \ + G_GNUC_END_IGNORE_DEPRECATIONS \ + }) +#endif + +/*****************************************************************************/ + #if !GLIB_CHECK_VERSION(2, 40, 0) static inline gboolean _g_key_file_save_to_file(GKeyFile *key_file, const char *filename, GError **error) diff --git a/src/libnm-glib-aux/nm-hash-utils.c b/src/libnm-glib-aux/nm-hash-utils.c index 68b33998..941aba01 100644 --- a/src/libnm-glib-aux/nm-hash-utils.c +++ b/src/libnm-glib-aux/nm-hash-utils.c @@ -45,7 +45,7 @@ again: guint8 _extra_entropy[3 * HASH_KEY_SIZE]; } t_arr; - nm_utils_random_bytes(&t_arr, sizeof(t_arr)); + nm_random_get_bytes(&t_arr, sizeof(t_arr)); /* We only initialize one random hash key. So we can spend some effort * of getting this right. For one, we collect more random bytes than diff --git a/src/libnm-glib-aux/nm-hash-utils.h b/src/libnm-glib-aux/nm-hash-utils.h index b7ee4dd1..dffa828b 100644 --- a/src/libnm-glib-aux/nm-hash-utils.h +++ b/src/libnm-glib-aux/nm-hash-utils.h @@ -122,26 +122,26 @@ nm_hash_update_bool(NMHashState *state, bool val) nm_hash_update(state, &val, sizeof(val)); } -#define _NM_HASH_COMBINE_BOOLS_OP(x, n) ((x) ? NM_BIT((n)) : 0u) +#define _NM_HASH_COMBINE_BOOLS_OP(x, n, op_arg) ((x) ? NM_BIT((n)) : 0ull) -#define NM_HASH_COMBINE_BOOLS(type, ...) \ - ((type) (NM_STATIC_ASSERT_EXPR_1(NM_NARG(__VA_ARGS__) <= 8 * sizeof(type)) \ - ? (NM_VA_ARGS_FOREACH(, , |, _NM_HASH_COMBINE_BOOLS_OP, __VA_ARGS__)) \ - : 0)) +#define NM_HASH_COMBINE_BOOLS(type, ...) \ + ((type) (NM_STATIC_ASSERT_EXPR_1(NM_NARG(__VA_ARGS__) <= 8 * sizeof(type)) \ + ? (NM_VA_ARGS_FOREACH(, , |, _NM_HASH_COMBINE_BOOLS_OP, , __VA_ARGS__)) \ + : 0ull)) #define nm_hash_update_bools(state, ...) \ nm_hash_update_val(state, NM_HASH_COMBINE_BOOLS(guint8, __VA_ARGS__)) -#define _NM_HASH_COMBINE_VALS_TYPE_OP(x, idx) typeof(x) _v##idx; -#define _NM_HASH_COMBINE_VALS_INIT_OP(x, idx) ._v##idx = (x), +#define _NM_HASH_COMBINE_VALS_TYPE_OP(x, idx, op_arg) typeof(x) _v##idx; +#define _NM_HASH_COMBINE_VALS_INIT_OP(x, idx, op_arg) ._v##idx = (x), /* NM_HASH_COMBINE_VALS() is faster then nm_hash_update_val() as it combines multiple * calls to nm_hash_update() using a packed structure. */ -#define NM_HASH_COMBINE_VALS(var, ...) \ - const struct _nm_packed { \ - NM_VA_ARGS_FOREACH(, , , _NM_HASH_COMBINE_VALS_TYPE_OP, __VA_ARGS__) \ - } var _nm_alignas(guint64) = { \ - NM_VA_ARGS_FOREACH(, , , _NM_HASH_COMBINE_VALS_INIT_OP, __VA_ARGS__)} +#define NM_HASH_COMBINE_VALS(var, ...) \ + const struct _nm_packed { \ + NM_VA_ARGS_FOREACH(, , , _NM_HASH_COMBINE_VALS_TYPE_OP, , __VA_ARGS__) \ + } var _nm_alignas(max_align_t) = { \ + NM_VA_ARGS_FOREACH(, , , _NM_HASH_COMBINE_VALS_INIT_OP, , __VA_ARGS__)} /* nm_hash_update_vals() is faster then nm_hash_update_val() as it combines multiple * calls to nm_hash_update() using a packed structure. */ diff --git a/src/libnm-glib-aux/nm-io-utils.c b/src/libnm-glib-aux/nm-io-utils.c index 503f044f..0823a16c 100644 --- a/src/libnm-glib-aux/nm-io-utils.c +++ b/src/libnm-glib-aux/nm-io-utils.c @@ -723,3 +723,288 @@ nm_sd_notify(const char *state) return 0; } + +/*****************************************************************************/ + +#define SHELL_NEED_ESCAPE "\"\\`$" + +int +nm_parse_env_file_full( + const char *contents, + int (*push)(unsigned line, const char *key, const char *value, void *userdata), + void *userdata) +{ + gsize last_value_whitespace = G_MAXSIZE; + gsize last_key_whitespace = G_MAXSIZE; + nm_auto_str_buf NMStrBuf key = NM_STR_BUF_INIT(0, FALSE); + nm_auto_str_buf NMStrBuf value = NM_STR_BUF_INIT(0, FALSE); + unsigned line = 1; + int r; + enum { + PRE_KEY, + KEY, + PRE_VALUE, + VALUE, + VALUE_ESCAPE, + SINGLE_QUOTE_VALUE, + DOUBLE_QUOTE_VALUE, + DOUBLE_QUOTE_VALUE_ESCAPE, + COMMENT, + COMMENT_ESCAPE + } state = PRE_KEY; + + /* Copied and adjusted from systemd's parse_env_file_internal(). + * https://github.com/systemd/systemd/blob/6247128902ca71ee2ad406cf69af04ea389d3d27/src/basic/env-file.c#L15 */ + + nm_assert(push); + + if (!contents) + return -ENOENT; + + for (const char *p = contents; *p; p++) { + char c = *p; + + switch (state) { + case PRE_KEY: + if (NM_IN_SET(c, '#', ';')) + state = COMMENT; + else if (!nm_ascii_is_whitespace(c)) { + state = KEY; + last_key_whitespace = G_MAXSIZE; + nm_str_buf_append_c(&key, c); + } + break; + + case KEY: + if (nm_ascii_is_newline(c)) { + state = PRE_KEY; + line++; + nm_str_buf_reset(&key); + } else if (c == '=') { + state = PRE_VALUE; + last_value_whitespace = G_MAXSIZE; + } else { + if (!nm_ascii_is_whitespace(c)) + last_key_whitespace = G_MAXSIZE; + else if (last_key_whitespace == G_MAXSIZE) + last_key_whitespace = key.len; + nm_str_buf_append_c(&key, c); + } + break; + + case PRE_VALUE: + if (nm_ascii_is_newline(c)) { + state = PRE_KEY; + line++; + + /* strip trailing whitespace from key */ + if (last_key_whitespace != G_MAXSIZE) + nm_str_buf_get_str_unsafe(&key)[last_key_whitespace] = 0; + + r = push(line, + nm_str_buf_get_str(&key), + nm_str_buf_get_str(&value) ?: "", + userdata); + if (r < 0) + return r; + + nm_str_buf_reset(&key); + nm_str_buf_reset(&value); + } else if (c == '\'') + state = SINGLE_QUOTE_VALUE; + else if (c == '"') + state = DOUBLE_QUOTE_VALUE; + else if (c == '\\') + state = VALUE_ESCAPE; + else if (!nm_ascii_is_whitespace(c)) { + state = VALUE; + nm_str_buf_append_c(&value, c); + } + + break; + + case VALUE: + if (nm_ascii_is_newline(c)) { + state = PRE_KEY; + line++; + + /* Chomp off trailing whitespace from value */ + if (last_value_whitespace != G_MAXSIZE) + nm_str_buf_get_str_unsafe(&value)[last_value_whitespace] = 0; + + /* strip trailing whitespace from key */ + if (last_key_whitespace != G_MAXSIZE) + nm_str_buf_get_str_unsafe(&key)[last_key_whitespace] = 0; + + r = push(line, + nm_str_buf_get_str(&key), + nm_str_buf_get_str(&value) ?: "", + userdata); + if (r < 0) + return r; + + nm_str_buf_reset(&key); + nm_str_buf_reset(&value); + } else if (c == '\\') { + state = VALUE_ESCAPE; + last_value_whitespace = G_MAXSIZE; + } else { + if (!nm_ascii_is_whitespace(c)) + last_value_whitespace = G_MAXSIZE; + else if (last_value_whitespace == G_MAXSIZE) + last_value_whitespace = value.len; + nm_str_buf_append_c(&value, c); + } + break; + + case VALUE_ESCAPE: + state = VALUE; + if (!nm_ascii_is_newline(c)) { + /* Escaped newlines we eat up entirely */ + nm_str_buf_append_c(&value, c); + } + break; + + case SINGLE_QUOTE_VALUE: + if (c == '\'') + state = PRE_VALUE; + else + nm_str_buf_append_c(&value, c); + break; + + case DOUBLE_QUOTE_VALUE: + if (c == '"') + state = PRE_VALUE; + else if (c == '\\') + state = DOUBLE_QUOTE_VALUE_ESCAPE; + else + nm_str_buf_append_c(&value, c); + break; + + case DOUBLE_QUOTE_VALUE_ESCAPE: + state = DOUBLE_QUOTE_VALUE; + if (strchr(SHELL_NEED_ESCAPE, c)) { + /* If this is a char that needs escaping, just unescape it. */ + nm_str_buf_append_c(&value, c); + } else if (c != '\n') { + /* If other char than what needs escaping, keep the "\" in place, like the + * real shell does. */ + nm_str_buf_append_c(&value, '\\', c); + } + /* Escaped newlines (aka "continuation lines") are eaten up entirely */ + break; + + case COMMENT: + if (c == '\\') + state = COMMENT_ESCAPE; + else if (nm_ascii_is_newline(c)) { + state = PRE_KEY; + line++; + } + break; + + case COMMENT_ESCAPE: + state = COMMENT; + break; + } + } + + if (NM_IN_SET(state, + PRE_VALUE, + VALUE, + VALUE_ESCAPE, + SINGLE_QUOTE_VALUE, + DOUBLE_QUOTE_VALUE, + DOUBLE_QUOTE_VALUE_ESCAPE)) { + if (state == VALUE) + if (last_value_whitespace != G_MAXSIZE) + nm_str_buf_get_str_unsafe(&value)[last_value_whitespace] = 0; + + /* strip trailing whitespace from key */ + if (last_key_whitespace != G_MAXSIZE) + nm_str_buf_get_str_unsafe(&key)[last_key_whitespace] = 0; + + r = push(line, nm_str_buf_get_str(&key), nm_str_buf_get_str(&value) ?: "", userdata); + if (r < 0) + return r; + } + + return 0; +} + +/*****************************************************************************/ + +static int +check_utf8ness_and_warn(const char *key, const char *value) +{ + /* Taken from systemd's check_utf8ness_and_warn() + * https://github.com/systemd/systemd/blob/6247128902ca71ee2ad406cf69af04ea389d3d27/src/basic/env-file.c#L273 */ + + if (!g_utf8_validate(key, -1, NULL)) + return -EINVAL; + + if (!g_utf8_validate(value, -1, NULL)) + return -EINVAL; + + return 0; +} + +static int +parse_env_file_push(unsigned line, const char *key, const char *value, void *userdata) +{ + const char *k; + va_list *ap = userdata; + va_list aq; + int r; + + r = check_utf8ness_and_warn(key, value); + if (r < 0) + return r; + + va_copy(aq, *ap); + + while ((k = va_arg(aq, const char *))) { + char **v; + + v = va_arg(aq, char **); + if (nm_streq(key, k)) { + va_end(aq); + g_free(*v); + *v = g_strdup(value); + return 1; + } + } + + va_end(aq); + return 0; +} + +int +nm_parse_env_filev(const char *contents, va_list ap) +{ + va_list aq; + int r; + + /* Copied from systemd's parse_env_filev(). + * https://github.com/systemd/systemd/blob/6247128902ca71ee2ad406cf69af04ea389d3d27/src/basic/env-file.c#L333 */ + + va_copy(aq, ap); + r = nm_parse_env_file_full(contents, parse_env_file_push, &aq); + va_end(aq); + return r; +} + +int +nm_parse_env_file_sentinel(const char *contents, ...) +{ + va_list ap; + int r; + + /* Copied from systemd's parse_env_file_sentinel(). + * https://github.com/systemd/systemd/blob/6247128902ca71ee2ad406cf69af04ea389d3d27/src/basic/env-file.c#L347 */ + + va_start(ap, contents); + r = nm_parse_env_filev(contents, ap); + va_end(ap); + return r; +} diff --git a/src/libnm-glib-aux/nm-io-utils.h b/src/libnm-glib-aux/nm-io-utils.h index a8503986..ef015153 100644 --- a/src/libnm-glib-aux/nm-io-utils.h +++ b/src/libnm-glib-aux/nm-io-utils.h @@ -77,4 +77,15 @@ int nm_io_sockaddr_un_set(struct sockaddr_un *ret, NMOptionBool is_abstract, con int nm_sd_notify(const char *state); +/*****************************************************************************/ + +int nm_parse_env_file_full( + const char *contents, + int (*push)(unsigned line, const char *key, const char *value, void *userdata), + void *userdata); + +int nm_parse_env_filev(const char *contents, va_list ap); +int nm_parse_env_file_sentinel(const char *contents, ...) G_GNUC_NULL_TERMINATED; +#define nm_parse_env_file(contents, ...) nm_parse_env_file_sentinel((contents), __VA_ARGS__, NULL) + #endif /* __NM_IO_UTILS_H__ */ diff --git a/src/libnm-glib-aux/nm-logging-fwd.h b/src/libnm-glib-aux/nm-logging-fwd.h index 0e715c50..72e5723c 100644 --- a/src/libnm-glib-aux/nm-logging-fwd.h +++ b/src/libnm-glib-aux/nm-logging-fwd.h @@ -59,7 +59,16 @@ typedef enum { LOGD_IP = LOGD_IP4 | LOGD_IP6, #define LOGD_DHCPX(is_ipv4) ((is_ipv4) ? LOGD_DHCP4 : LOGD_DHCP6) -#define LOGD_IPX(is_ipv4) ((is_ipv4) ? LOGD_IP4 : LOGD_IP6) + +#define LOGD_DHCP_af(addr_family) \ + ({ \ + const int _addr_family_1 = (addr_family); \ + \ + (_addr_family_1 == AF_UNSPEC ? LOGD_DHCP \ + : (NM_IS_IPv4(_addr_family_1) ? LOGD_DHCP4 : LOGD_DHCP6)); \ + }) + +#define LOGD_IPX(is_ipv4) ((is_ipv4) ? LOGD_IP4 : LOGD_IP6) } NMLogDomain; diff --git a/src/libnm-glib-aux/nm-macros-internal.h b/src/libnm-glib-aux/nm-macros-internal.h index 7cc8ac97..f2a4461e 100644 --- a/src/libnm-glib-aux/nm-macros-internal.h +++ b/src/libnm-glib-aux/nm-macros-internal.h @@ -1053,6 +1053,24 @@ nm_g_variant_equal(GVariant *a, GVariant *b) * the kernel command line. */ #define NM_ASCII_WHITESPACES " \n\t\r" +static inline gboolean +nm_ascii_is_whitespace(char ch) +{ + /* Checks whether @ch is in NM_ASCII_WHITESPACES. + * Similar to g_ascii_isspace(), however this one does not accept '\f'. + * This is the same as systemd's strchr(WHITESPACE, ch). */ + return NM_IN_SET(ch, ' ', '\n', '\t', '\r'); +} + +#define NM_ASCII_NEWLINE "\n\r" + +static inline gboolean +nm_ascii_is_newline(char ch) +{ + /* This is the same as systemd's (!!strchr(NEWLINE, ch)). */ + return NM_IN_SET(ch, '\n', '\t'); +} + #define nm_str_skip_leading_spaces(str) \ ({ \ typeof(*(str)) *_str_sls = (str); \ @@ -1177,7 +1195,7 @@ _NM_IN_STRSET_EVAL_op_streq_ascii_case(const char *x1, const char *x) return x && g_ascii_strcasecmp(x1, x) == 0; } -#define _NM_IN_STRSET_EVAL_OP_STREQ_ASCII_CASE(x, idx) \ +#define _NM_IN_STRSET_EVAL_OP_STREQ_ASCII_CASE(x, idx, op_arg) \ _NM_IN_STRSET_EVAL_op_streq_ascii_case(_x1, x) #define NM_IN_STRSET_ASCII_CASE(x1, ...) \ _NM_IN_STRSET_EVAL(||, _NM_IN_STRSET_EVAL_OP_STREQ_ASCII_CASE, x1, __VA_ARGS__) diff --git a/src/libnm-glib-aux/nm-random-utils.c b/src/libnm-glib-aux/nm-random-utils.c index 3a8ba64f..93eee7c4 100644 --- a/src/libnm-glib-aux/nm-random-utils.c +++ b/src/libnm-glib-aux/nm-random-utils.c @@ -10,6 +10,7 @@ #include <fcntl.h> #include <sys/auxv.h> #include <sys/syscall.h> +#include <poll.h> #if USE_SYS_RANDOM_H #include <sys/random.h> @@ -34,18 +35,121 @@ #define GRND_INSECURE 0x04 #endif -#if !HAVE_GETRANDOM && defined(SYS_getrandom) -static int +#if !HAVE_GETRANDOM +static ssize_t getrandom(void *buf, size_t buflen, unsigned flags) { +#if defined(SYS_getrandom) return syscall(SYS_getrandom, buf, buflen, flags); +#else + errno = ENOSYS; + return -1; +#endif } -#undef HAVE_GETRANDOM -#define HAVE_GETRANDOM 1 #endif /*****************************************************************************/ +static ssize_t +_getrandom(void *buf, size_t buflen, unsigned flags) +{ + static int have_getrandom = TRUE; + ssize_t l; + int errsv; + + nm_assert(buflen > 0); + + /* This calls getrandom() and either returns the positive + * success or an negative errno. ENOSYS means getrandom() + * call is not supported. That result is cached and we don't retry. */ + + if (!have_getrandom) + return -ENOSYS; + + l = getrandom(buf, buflen, flags); + if (l > 0) + return l; + if (l == 0) + return -EIO; + errsv = errno; + if (errsv == ENOSYS) + have_getrandom = FALSE; + return -errsv; +} + +static ssize_t +_getrandom_insecure(void *buf, size_t buflen) +{ + static int have_grnd_insecure = TRUE; + ssize_t l; + + /* GRND_INSECURE was added recently. We catch EINVAL + * if kernel does not support the flag (and cache it). */ + + if (!have_grnd_insecure) + return -EINVAL; + + l = _getrandom(buf, buflen, GRND_INSECURE); + + if (l == -EINVAL) + have_grnd_insecure = FALSE; + + return l; +} + +static ssize_t +_getrandom_best_effort(void *buf, size_t buflen) +{ + ssize_t l; + + /* To get best-effort bytes, we would use GRND_INSECURE (and we try that + * first). However, not all kernel versions support that, so we fallback + * to GRND_NONBLOCK. + * + * Granted, this is called from a fallback path where we have no entropy + * already, it's unlikely that GRND_NONBLOCK would succeed. Still... */ + l = _getrandom_insecure(buf, buflen); + if (l != -EINVAL) + return l; + + return _getrandom(buf, buflen, GRND_NONBLOCK); +} + +static int +_random_check_entropy(gboolean block) +{ + static gboolean seen_high_quality = FALSE; + nm_auto_close int fd = -1; + int r; + + /* We come here because getrandom() gave ENOSYS. We will fallback to /dev/urandom, + * but the caller wants to know whether we have high quality numbers. Poll + * /dev/random to find out. */ + + if (seen_high_quality) { + /* We cache the positive result. Once kernel has entropy, we will get + * good random numbers. */ + return 1; + } + + fd = open("/dev/random", O_RDONLY | O_CLOEXEC | O_NOCTTY); + if (fd < 0) + return -errno; + + r = nm_utils_fd_wait_for_event(fd, POLLIN, block ? -1 : 0); + + if (r <= 0) { + nm_assert(r < 0 || !block); + return r; + } + + nm_assert(r == 1); + seen_high_quality = TRUE; + return 1; +} + +/*****************************************************************************/ + typedef struct _nm_packed { uintptr_t heap_ptr; uintptr_t stack_ptr; @@ -72,7 +176,8 @@ typedef struct _nm_packed { guint8 u8[NM_UTILS_CHECKSUM_LENGTH_SHA256 / 2]; guint32 u32[((NM_UTILS_CHECKSUM_LENGTH_SHA256 / 2) + 3) / 4]; } rand_vals; - GRand *rand; + guint8 rand_vals_getrandom[16]; + gint64 rand_vals_timestamp; } BadRandState; static void @@ -106,18 +211,7 @@ _bad_random_init_seed(BadRandSeed *seed) memcpy(&seed->auxval, p_at_random, 16); } -#if HAVE_GETRANDOM - { - ssize_t r; - - /* This is likely to fail, because we already failed a moment earlier. Still, give - * it a try. */ - r = getrandom(seed->getrandom_buf, - sizeof(seed->getrandom_buf), - GRND_INSECURE | GRND_NONBLOCK); - (void) r; - } -#endif + _getrandom_best_effort(seed->getrandom_buf, sizeof(seed->getrandom_buf)); seed->now_bootime = nm_utils_clock_gettime_nsec(CLOCK_BOOTTIME); seed->now_real = g_get_real_time(); @@ -168,6 +262,10 @@ _bad_random_bytes(guint8 *buf, gsize n) nm_utils_checksum_get_digest(sum, gl_state.sha_digest.full); } + _getrandom_best_effort(gl_state.rand_vals_getrandom, sizeof(gl_state.rand_vals_getrandom)); + + gl_state.rand_vals_timestamp = nm_utils_clock_gettime_nsec(CLOCK_BOOTTIME); + while (TRUE) { int i; @@ -197,103 +295,155 @@ _bad_random_bytes(guint8 *buf, gsize n) } } +/*****************************************************************************/ + /** - * nm_utils_random_bytes: + * nm_random_get_bytes_full: * @p: the buffer to fill * @n: the number of bytes to write to @p. + * @out_high_quality: (allow-none) (out): whether the returned + * random bytes are of high quality. * - * Uses getrandom() or reads /dev/urandom to fill the buffer - * with random data. If all fails, as last fallback it uses - * GRand to fill the buffer with pseudo random numbers. - * The function always succeeds in writing some random numbers - * to the buffer. The return value of FALSE indicates that the - * obtained bytes are probably not of good randomness. - * - * Returns: whether the written bytes are good. If you - * don't require good randomness, you can ignore the return - * value. - * - * Note that if calling getrandom() fails because there is not enough - * entropy (at early boot), the function will read /dev/urandom. - * Which of course, still has low entropy, and cause kernel to log - * a warning. + * - will never block + * - will always produce some numbers, but they may not + * be of high quality. + * - Whether they are of high quality, you can know via @out_high_quality. + * - will always try hard to produce high quality numbers, and on success + * they are as good as nm_random_get_crypto_bytes(). */ -gboolean -nm_utils_random_bytes(void *p, size_t n) +void +nm_random_get_bytes_full(void *p, size_t n, gboolean *out_high_quality) { int fd; int r; - gboolean has_high_quality = TRUE; - guint8 *buf = p; + gboolean has_high_quality; + ssize_t l; - g_return_val_if_fail(p, FALSE); - g_return_val_if_fail(n > 0, FALSE); + if (n == 0) { + NM_SET_OUT(out_high_quality, TRUE); + return; + } -#if HAVE_GETRANDOM - { - static gboolean have_syscall = TRUE; - - if (have_syscall) { - ssize_t r2; - int errsv; - - r2 = getrandom(buf, n, GRND_NONBLOCK); - if (r2 >= 0) { - if ((size_t) r2 == n) - return TRUE; - - /* no or partial read. There is not enough entropy. - * Fill the rest reading with the fallback code and remember - * that some bits are not high quality. */ - nm_assert((size_t) r2 < n); - buf += r2; - n -= r2; - - /* At this point, we don't want to read /dev/urandom, because - * the entropy pool is low (early boot?), and asking for more - * entropy causes kernel messages to be logged. - * - * Note that we fall back to _bad_random_bytes(), which (among others) seeds - * itself with g_rand_new(). That also will read /dev/urandom, but as - * we do that only once, we don't care. But in general, we are here in - * a situation where we want to avoid reading /dev/urandom too much. */ - goto out_bad_random; - } - errsv = errno; - if (errsv == ENOSYS) { - /* no support for getrandom(). We don't know whether - * we /dev/urandom will give us good quality. Assume yes. */ - have_syscall = FALSE; - } else if (errsv == EAGAIN) { - /* No entropy. We avoid reading /dev/urandom. */ - goto out_bad_random; - } else { - /* Unknown error, likely no entropy. We'll read /dev/urandom below, but we don't - * have high-quality randomness. */ - has_high_quality = FALSE; + g_return_if_fail(p); + +again_getrandom: + l = _getrandom(p, n, GRND_NONBLOCK); + if (l > 0) { + if ((size_t) l == n) { + NM_SET_OUT(out_high_quality, TRUE); + return; + } + p = ((uint8_t *) p) + l; + n -= l; + goto again_getrandom; + } + + /* getrandom() failed. Fallback to read /dev/urandom. */ + + if (l == -ENOSYS) { + /* no support for getrandom(). */ + if (out_high_quality) { + /* The caller wants to know whether we have high quality. Poll /dev/random + * to find out. */ + has_high_quality = (_random_check_entropy(FALSE) > 0); + } else { + /* The value doesn't matter in this case. It will be unused. */ + has_high_quality = FALSE; + } + } else { + /* Any other failure of getrandom() means we don't have high quality. */ + has_high_quality = FALSE; + if (l == -EAGAIN) { + /* getrandom(GRND_NONBLOCK) failed because lack of entropy. Retry with GRND_INSECURE. */ + for (;;) { + l = _getrandom_insecure(p, n); + if (l > 0) { + if ((size_t) l == n) { + NM_SET_OUT(out_high_quality, FALSE); + return; + } + p = ((uint8_t *) p) + l; + n -= l; + continue; + } + /* Any error. Fallback to /dev/urandom. */ + break; } } } -#endif -fd_open: +again_open: fd = open("/dev/urandom", O_RDONLY | O_CLOEXEC | O_NOCTTY); if (fd < 0) { if (errno == EINTR) - goto fd_open; - goto out_bad_random; + goto again_open; + } else { + r = nm_utils_fd_read_loop_exact(fd, p, n, TRUE); + nm_close(fd); + if (r >= 0) { + NM_SET_OUT(out_high_quality, has_high_quality); + return; + } } - r = nm_utils_fd_read_loop_exact(fd, buf, n, TRUE); - nm_close(fd); - if (r >= 0) - return has_high_quality; -out_bad_random: /* we failed to fill the bytes reading from /dev/urandom. - * Fill the bits using our pseudo random numbers. - * - * We don't have good quality. + * Fill the bits using our fallback approach (which obviously + * cannot give high quality random). */ - _bad_random_bytes(buf, n); - return FALSE; + _bad_random_bytes(p, n); + NM_SET_OUT(out_high_quality, FALSE); +} + +/*****************************************************************************/ + +/** + * nm_random_get_crypto_bytes: + * @p: the buffer to fill + * @n: the number of bytes to fill + * + * - can fail (in which case a negative number is returned + * and the output buffer is undefined). + * - will block trying to get high quality random numbers. + */ +int +nm_random_get_crypto_bytes(void *p, size_t n) +{ + nm_auto_close int fd = -1; + ssize_t l; + int r; + + if (n == 0) + return 0; + + nm_assert(p); + +again_getrandom: + l = _getrandom(p, n, 0); + if (l > 0) { + if ((size_t) l == n) + return 0; + p = (uint8_t *) p + l; + n -= l; + goto again_getrandom; + } + + if (l != -ENOSYS) { + /* We got a failure, but getrandom seems to be working in principle. We + * won't get good numbers. Fail. */ + return l; + } + + /* getrandom() failed with ENOSYS. Fallback to reading /dev/urandom. */ + + r = _random_check_entropy(TRUE); + if (r < 0) + return r; + if (r == 0) + return nm_assert_unreachable_val(-EIO); + + fd = open("/dev/urandom", O_RDONLY | O_CLOEXEC | O_NOCTTY); + if (fd < 0) + return -errno; + + return nm_utils_fd_read_loop_exact(fd, p, n, FALSE); } diff --git a/src/libnm-glib-aux/nm-random-utils.h b/src/libnm-glib-aux/nm-random-utils.h index d0eae103..ab8aee1b 100644 --- a/src/libnm-glib-aux/nm-random-utils.h +++ b/src/libnm-glib-aux/nm-random-utils.h @@ -6,6 +6,14 @@ #ifndef __NM_RANDOM_UTILS_H__ #define __NM_RANDOM_UTILS_H__ -gboolean nm_utils_random_bytes(void *p, size_t n); +void nm_random_get_bytes_full(void *p, size_t n, gboolean *out_high_quality); + +static inline void +nm_random_get_bytes(void *p, size_t n) +{ + nm_random_get_bytes_full(p, n, NULL); +} + +int nm_random_get_crypto_bytes(void *p, size_t n); #endif /* __NM_RANDOM_UTILS_H__ */ diff --git a/src/libnm-glib-aux/nm-ref-string.h b/src/libnm-glib-aux/nm-ref-string.h index 3363bce0..eb3c38de 100644 --- a/src/libnm-glib-aux/nm-ref-string.h +++ b/src/libnm-glib-aux/nm-ref-string.h @@ -119,7 +119,7 @@ nm_ref_string_cmp(NMRefString *a, NMRefString *b) NM_CMP_SELF(a, b); /* It would be cheaper to first compare by length. But this - * way we get a nicer, ASCIIbethical sort order. */ + * way we get a nicer, ASCIIbetical sort order. */ NM_CMP_DIRECT_MEMCMP(a->str, b->str, NM_MIN(a->len, b->len)); NM_CMP_DIRECT(a->len, b->len); return nm_assert_unreachable_val(0); diff --git a/src/libnm-glib-aux/nm-shared-utils.c b/src/libnm-glib-aux/nm-shared-utils.c index e6ee04d7..624f9a3e 100644 --- a/src/libnm-glib-aux/nm-shared-utils.c +++ b/src/libnm-glib-aux/nm-shared-utils.c @@ -44,6 +44,15 @@ G_STATIC_ASSERT(_nm_alignof(struct in_addr) <= _nm_alignof(NMIPAddr)); G_STATIC_ASSERT(_nm_alignof(struct in6_addr) <= _nm_alignof(NMIPAddr)); G_STATIC_ASSERT(_nm_alignof(NMEtherAddr) <= _nm_alignof(NMIPAddr)); +int +nm_ip_addr_cmp_for_sort(gconstpointer a, gconstpointer b, gpointer user_data) +{ + /* This is a compare function that can be used for sorting IP addresses. + * Essentially, it calls memcmp(). @user_data must be GINT_TO_POINTER(addr_family). + * @a and @b must be either pointers to in_addr_t, struct in6_addr or NMIPAddr. */ + return nm_ip_addr_cmp(GPOINTER_TO_INT(user_data), a, b); +} + /* this initializes a struct in_addr/in6_addr and allows for untrusted * arguments (like unsuitable @addr_family or @src_len). It's almost safe * in the sense that it verifies input arguments strictly. Also, it @@ -137,6 +146,18 @@ G_STATIC_ASSERT(ETH_ALEN == sizeof(NMEtherAddr)); G_STATIC_ASSERT(_nm_alignof(struct ether_addr) <= _nm_alignof(NMEtherAddr)); +NMEtherAddr * +nm_ether_addr_from_string(NMEtherAddr *addr, const char *str) +{ + nm_assert(addr); + + if (!str || !_nm_utils_hwaddr_aton_exact(str, addr, ETH_ALEN)) { + *addr = NM_ETHER_ADDR_INIT(0x00, 0x00, 0x00, 0x00, 0x00, 0x00); + return NULL; + } + + return addr; +} /*****************************************************************************/ /** @@ -994,12 +1015,22 @@ nm_utils_ip_is_site_local(int addr_family, const void *address) return (addr4 & 0xff000000) == 0x0a000000 || (addr4 & 0xfff00000) == 0xac100000 || (addr4 & 0xffff0000) == 0xc0a80000; case AF_INET6: + /* IN6_IS_ADDR_SITELOCAL() is for deprecated fec0::/10 addresses (see rfc3879, 4.). + * Note that for unique local IPv6 addresses (ULA, fc00::/7) this returns false, + * which may or may not be a bug. */ return IN6_IS_ADDR_SITELOCAL(address); default: g_return_val_if_reached(FALSE); } } +gboolean +nm_utils_ip6_is_ula(const struct in6_addr *address) +{ + /* Unique local IPv6 address (ULA) fc00::/7 */ + return (address->s6_addr32[0] & htonl(0xfe000000u)) == htonl(0xfc000000u); +} + /*****************************************************************************/ static gboolean @@ -2288,9 +2319,11 @@ nm_utils_escaped_tokens_options_split(char *str, const char **out_key, const cha char ** nm_utils_strsplit_quoted(const char *str) { - gs_unref_ptrarray GPtrArray *arr = NULL; - gs_free char *str_out = NULL; - CharLookupTable ch_lookup; + char **arr = NULL; + gsize arr_len = 0; + gsize arr_alloc = 0; + gs_free char *str_out = NULL; + CharLookupTable ch_lookup; nm_assert(str); @@ -2347,19 +2380,32 @@ nm_utils_strsplit_quoted(const char *str) str++; } - if (!arr) - arr = g_ptr_array_new(); - g_ptr_array_add(arr, g_strndup(str_out, j)); + if (arr_len >= arr_alloc) { + if (arr_alloc == 0) + arr_alloc = 4; + else + arr_alloc *= 2; + arr = g_realloc(arr, sizeof(char *) * arr_alloc); + } + + arr[arr_len++] = g_strndup(str_out, j); } if (!arr) return g_new0(char *, 1); - g_ptr_array_add(arr, NULL); - /* We want to return an optimally sized strv array, with no excess * memory allocated. Hence, clone once more. */ - return nm_memdup(arr->pdata, sizeof(char *) * arr->len); + + if (arr_len + 1u != arr_alloc) { + gs_free char **arr_old = arr; + + arr = g_new(char *, arr_len + 1u); + memcpy(arr, arr_old, sizeof(char *) * arr_len); + } + + arr[arr_len] = NULL; + return arr; } /*****************************************************************************/ @@ -3008,7 +3054,7 @@ nm_utils_buf_utf8safe_unescape(const char *str, return str; } - nm_str_buf_init(&strbuf, len + 1u, FALSE); + strbuf = NM_STR_BUF_INIT(len + 1u, FALSE); nm_str_buf_append_len(&strbuf, str, s - str); str = s; @@ -3175,7 +3221,7 @@ nm_utils_buf_utf8safe_escape(gconstpointer buf, return str; } - nm_str_buf_init(&strbuf, buflen + 5, NM_FLAGS_HAS(flags, NM_UTILS_STR_UTF8_SAFE_FLAG_SECRET)); + strbuf = NM_STR_BUF_INIT(buflen + 5, NM_FLAGS_HAS(flags, NM_UTILS_STR_UTF8_SAFE_FLAG_SECRET)); s = str; do { @@ -3347,6 +3393,8 @@ nm_utils_fd_wait_for_event(int fd, int event, gint64 timeout_nsec) struct timespec ts, *pts; int r; + nm_assert(fd >= 0); + if (timeout_nsec < 0) pts = NULL; else { @@ -3360,6 +3408,13 @@ nm_utils_fd_wait_for_event(int fd, int event, gint64 timeout_nsec) return -NM_ERRNO_NATIVE(errno); if (r == 0) return 0; + + nm_assert(r == 1); + nm_assert(pollfd.revents > 0); + + if (pollfd.revents & POLLNVAL) + return nm_assert_unreachable_val(-EBADF); + return pollfd.revents; } @@ -3442,51 +3497,6 @@ nm_utils_named_value_clear_with_g_free(NMUtilsNamedValue *val) G_STATIC_ASSERT(G_STRUCT_OFFSET(NMUtilsNamedValue, name) == 0); -NMUtilsNamedValue * -nm_utils_named_values_from_strdict_full(GHashTable *hash, - guint *out_len, - GCompareDataFunc compare_func, - gpointer user_data, - NMUtilsNamedValue *provided_buffer, - guint provided_buffer_len, - NMUtilsNamedValue **out_allocated_buffer) -{ - GHashTableIter iter; - NMUtilsNamedValue *values; - guint i, len; - - nm_assert(provided_buffer_len == 0 || provided_buffer); - nm_assert(!out_allocated_buffer || !*out_allocated_buffer); - - if (!hash || !(len = g_hash_table_size(hash))) { - NM_SET_OUT(out_len, 0); - return NULL; - } - - if (provided_buffer_len >= len + 1) { - /* the buffer provided by the caller is large enough. Use it. */ - values = provided_buffer; - } else { - /* allocate a new buffer. */ - values = g_new(NMUtilsNamedValue, len + 1); - NM_SET_OUT(out_allocated_buffer, values); - } - - i = 0; - g_hash_table_iter_init(&iter, hash); - while (g_hash_table_iter_next(&iter, (gpointer *) &values[i].name, &values[i].value_ptr)) - i++; - nm_assert(i == len); - values[i].name = NULL; - values[i].value_ptr = NULL; - - if (compare_func) - nm_utils_named_value_list_sort(values, len, compare_func, user_data); - - NM_SET_OUT(out_len, len); - return values; -} - gssize nm_utils_named_value_list_find(const NMUtilsNamedValue *arr, gsize len, @@ -3636,6 +3646,52 @@ nm_utils_hash_values_to_array(GHashTable *hash, return arr; } +NMUtilsNamedValue * +nm_utils_hash_to_array_full(GHashTable *hash, + guint *out_len, + GCompareDataFunc compare_func, + gpointer user_data, + NMUtilsNamedValue *provided_buffer, + guint provided_buffer_len, + NMUtilsNamedValue **out_allocated_buffer) +{ + GHashTableIter iter; + NMUtilsNamedValue *values; + guint len; + guint i; + + nm_assert(provided_buffer_len == 0 || provided_buffer); + nm_assert(!out_allocated_buffer || !*out_allocated_buffer); + + if (!hash || ((len = g_hash_table_size(hash)) == 0)) { + NM_SET_OUT(out_len, 0); + return NULL; + } + + if (provided_buffer_len >= len + 1) { + /* the buffer provided by the caller is large enough. Use it. */ + values = provided_buffer; + } else { + /* allocate a new buffer. */ + values = g_new(NMUtilsNamedValue, len + 1); + NM_SET_OUT(out_allocated_buffer, values); + } + + i = 0; + g_hash_table_iter_init(&iter, hash); + while (g_hash_table_iter_next(&iter, &values[i].name_ptr, &values[i].value_ptr)) + i++; + nm_assert(i == len); + values[i].name_ptr = NULL; + values[i].value_ptr = NULL; + + if (compare_func && len > 1) + g_qsort_with_data(values, len, sizeof(NMUtilsNamedValue), compare_func, user_data); + + NM_SET_OUT(out_len, len); + return values; +} + /*****************************************************************************/ /** @@ -5850,17 +5906,29 @@ _nm_str_buf_ensure_size(NMStrBuf *strbuf, gsize new_size, gboolean reserve_exact new_size = nm_utils_get_next_realloc_size(!strbuf->_priv_do_bzero_mem, new_size); } - strbuf->_priv_str = nm_secret_mem_realloc(strbuf->_priv_str, - strbuf->_priv_do_bzero_mem, - strbuf->_priv_allocated, - new_size); + if (strbuf->_priv_malloced) { + strbuf->_priv_str = nm_secret_mem_realloc(strbuf->_priv_str, + strbuf->_priv_do_bzero_mem, + strbuf->_priv_allocated, + new_size); + } else { + char *old = strbuf->_priv_str; + + strbuf->_priv_str = g_malloc(new_size); + if (strbuf->_priv_len > 0) { + memcpy(strbuf->_priv_str, old, strbuf->_priv_len); + if (strbuf->_priv_do_bzero_mem) + nm_explicit_bzero(old, strbuf->_priv_len); + } + strbuf->_priv_malloced = TRUE; + } strbuf->_priv_allocated = new_size; } void -nm_str_buf_append_printf(NMStrBuf *strbuf, const char *format, ...) +nm_str_buf_append_printfv(NMStrBuf *strbuf, const char *format, va_list args) { - va_list args; + va_list args_copy; gsize available; int l; @@ -5870,12 +5938,12 @@ nm_str_buf_append_printf(NMStrBuf *strbuf, const char *format, ...) nm_assert(available < G_MAXULONG); - va_start(args, format); + va_copy(args_copy, args); l = g_vsnprintf(strbuf->_priv_allocated > 0 ? &strbuf->_priv_str[strbuf->_priv_len] : NULL, available, format, - args); - va_end(args); + args_copy); + va_end(args_copy); nm_assert(l >= 0); nm_assert(l < G_MAXINT); @@ -5890,9 +5958,9 @@ nm_str_buf_append_printf(NMStrBuf *strbuf, const char *format, ...) nm_str_buf_maybe_expand(strbuf, l2, FALSE); - va_start(args, format); - l = g_vsnprintf(&strbuf->_priv_str[strbuf->_priv_len], l2, format, args); - va_end(args); + va_copy(args_copy, args); + l = g_vsnprintf(&strbuf->_priv_str[strbuf->_priv_len], l2, format, args_copy); + va_end(args_copy); nm_assert(l >= 0); nm_assert((gsize) l == l2 - 1u); @@ -6594,7 +6662,7 @@ nm_utils_validate_hostname(const char *hostname) if (dot) return FALSE; - return (p - hostname <= HOST_NAME_MAX); + return (p - hostname <= NM_HOST_NAME_MAX); } /*****************************************************************************/ @@ -6723,3 +6791,540 @@ nm_g_main_context_can_acquire(GMainContext *context) g_main_context_release(context); return TRUE; } + +/*****************************************************************************/ + +int +nm_unbase64char(char c) +{ + /* copied from systemd's unbase64char(): + * https://github.com/systemd/systemd/blob/688efe7703328c5a0251fafac55757b8864a9f9a/src/basic/hexdecoct.c#L539 */ + + switch (c) { + case 'A' ... 'Z': + return c - 'A'; + case 'a' ... 'z': + return (c - 'a') + ('Z' - 'A' + 1); + case '0' ... '9': + return (c - '0') + (('Z' - 'A' + 1) + ('z' - 'a' + 1)); + case '+': + return ('Z' - 'A' + 1) + ('z' - 'a' + 1) + ('9' - '0' + 1); + case '/': + return ('Z' - 'A' + 1) + ('z' - 'a' + 1) + ('9' - '0' + 1) + 1; + case '=': + /* The padding is a different kind of base64 character. Return + * a special error code for it. */ + return -ERANGE; + default: + return -EINVAL; + } +} + +static int +unbase64_next(const char **p, size_t *l) +{ + int ret; + + nm_assert(p); + nm_assert(l); + + /* copied from systemd's unbase64_next(): + * https://github.com/systemd/systemd/blob/688efe7703328c5a0251fafac55757b8864a9f9a/src/basic/hexdecoct.c#L709 */ + + /* Find the next non-whitespace character, and decode it. If we find padding, we return it as INT_MAX. We + * greedily skip all preceding and all following whitespace. */ + + for (;;) { + if (*l == 0) + return -EPIPE; + + if (!nm_ascii_is_whitespace(**p)) + break; + + /* Skip leading whitespace */ + (*p)++; + (*l)--; + } + + ret = nm_unbase64char(**p); + if (ret < 0) { + nm_assert(NM_IN_SET(ret, -EINVAL, -ERANGE)); + if (ret != -ERANGE) + return ret; + } + + for (;;) { + (*p)++; + (*l)--; + + if (*l == 0) + break; + if (!nm_ascii_is_whitespace(**p)) + break; + + /* Skip following whitespace */ + } + + nm_assert(ret == -ERANGE || ret >= 0); + return ret; +} + +/** + * nm_unbase64mem_full: + * @p: a valid base64 string. Whitespace is ignored, but invalid encodings + * will cause the function to fail. + * @l: the length of @p. @p is not treated as NUL terminated string but + * merely as a buffer of ascii characters. + * @secure: whether the temporary memory will be cleared to avoid leaving + * secrets in memory (see also nm_explicit_bzero()). + * @mem: (transfer full): the decoded buffer on success. + * @len: the length of @mem on success. + * + * glib provides g_base64_decode(), but that does not report any errors + * from invalid encodings. Our own implementation (based on systemd code) + * rejects invalid inputs. + * + * Returns: a non-negative code on success. Invalid encoding let the + * function fail. + */ +int +nm_unbase64mem_full(const char *p, gsize l, gboolean secure, guint8 **ret, gsize *ret_size) +{ + gs_free uint8_t *buf = NULL; + const char *x; + guint8 *z; + gsize len; + int r; + + /* copied from systemd's unbase64mem_full(): + * https://github.com/systemd/systemd/blob/688efe7703328c5a0251fafac55757b8864a9f9a/src/basic/hexdecoct.c#L751 */ + + nm_assert(p || l == 0); + + if (l == G_MAXSIZE) + l = strlen(p); + + /* A group of four input bytes needs three output bytes, in case of padding we need to add two or three extra + * bytes. Note that this calculation is an upper boundary, as we ignore whitespace while decoding */ + len = (l / 4) * 3 + (l % 4 != 0 ? (l % 4) - 1 : 0); + + buf = g_malloc(len + 1); + + for (x = p, z = buf;;) { + int a; /* a == 00XXXXXX */ + int b; /* b == 00YYYYYY */ + int c; /* c == 00ZZZZZZ */ + int d; /* d == 00WWWWWW */ + + a = unbase64_next(&x, &l); + if (a < 0) { + if (a == -EPIPE) /* End of string */ + break; + if (a == -ERANGE) { /* Padding is not allowed at the beginning of a 4ch block */ + r = -EINVAL; + goto on_failure; + } + r = a; + goto on_failure; + } + + b = unbase64_next(&x, &l); + if (b < 0) { + if (b == -ERANGE) { + /* Padding is not allowed at the second character of a 4ch block either */ + r = -EINVAL; + goto on_failure; + } + r = b; + goto on_failure; + } + + c = unbase64_next(&x, &l); + if (c < 0) { + if (c != -ERANGE) { + r = c; + goto on_failure; + } + } + + d = unbase64_next(&x, &l); + if (d < 0) { + if (d != -ERANGE) { + r = d; + goto on_failure; + } + } + + if (c == -ERANGE) { /* Padding at the third character */ + + if (d != -ERANGE) { /* If the third character is padding, the fourth must be too */ + r = -EINVAL; + goto on_failure; + } + + /* b == 00YY0000 */ + if (b & 15) { + r = -EINVAL; + goto on_failure; + } + + if (l > 0) { /* Trailing rubbish? */ + r = -ENAMETOOLONG; + goto on_failure; + } + + *(z++) = (uint8_t) a << 2 | (uint8_t) (b >> 4); /* XXXXXXYY */ + break; + } + + if (d == -ERANGE) { + /* c == 00ZZZZ00 */ + if (c & 3) { + r = -EINVAL; + goto on_failure; + } + + if (l > 0) { /* Trailing rubbish? */ + r = -ENAMETOOLONG; + goto on_failure; + } + + *(z++) = (uint8_t) a << 2 | (uint8_t) b >> 4; /* XXXXXXYY */ + *(z++) = (uint8_t) b << 4 | (uint8_t) c >> 2; /* YYYYZZZZ */ + break; + } + + *(z++) = (uint8_t) a << 2 | (uint8_t) b >> 4; /* XXXXXXYY */ + *(z++) = (uint8_t) b << 4 | (uint8_t) c >> 2; /* YYYYZZZZ */ + *(z++) = (uint8_t) c << 6 | (uint8_t) d; /* ZZWWWWWW */ + } + + *z = '\0'; + + NM_SET_OUT(ret_size, (gsize) (z - buf)); + NM_SET_OUT(ret, g_steal_pointer(&buf)); + return 0; + +on_failure: + if (secure) + nm_explicit_bzero(buf, len); + return r; +} + +/*****************************************************************************/ + +static const char * +skip_slash_or_dot(const char *p) +{ + for (; !nm_str_is_empty(p);) { + if (p[0] == '/') { + p += 1; + continue; + } + if (p[0] == '.' && p[1] == '/') { + p += 2; + continue; + } + break; + } + return p; +} + +int +nm_path_find_first_component(const char **p, gboolean accept_dot_dot, const char **ret) +{ + const char *q, *first, *end_first, *next; + size_t len; + + /* Copied from systemd's path_compare() + * https://github.com/systemd/systemd/blob/bc85f8b51d962597360e982811e674c126850f56/src/basic/path-util.c#L809 */ + + nm_assert(p); + + /* When a path is input, then returns the pointer to the first component and its length, and + * move the input pointer to the next component or nul. This skips both over any '/' + * immediately *before* and *after* the first component before returning. + * + * Examples + * Input: p: "//.//aaa///bbbbb/cc" + * Output: p: "bbbbb///cc" + * ret: "aaa///bbbbb/cc" + * return value: 3 (== strlen("aaa")) + * + * Input: p: "aaa//" + * Output: p: (pointer to NUL) + * ret: "aaa//" + * return value: 3 (== strlen("aaa")) + * + * Input: p: "/", ".", "" + * Output: p: (pointer to NUL) + * ret: NULL + * return value: 0 + * + * Input: p: NULL + * Output: p: NULL + * ret: NULL + * return value: 0 + * + * Input: p: "(too long component)" + * Output: return value: -EINVAL + * + * (when accept_dot_dot is false) + * Input: p: "//..//aaa///bbbbb/cc" + * Output: return value: -EINVAL + */ + + q = *p; + + first = skip_slash_or_dot(q); + if (nm_str_is_empty(first)) { + *p = first; + if (ret) + *ret = NULL; + return 0; + } + if (nm_streq(first, ".")) { + *p = first + 1; + if (ret) + *ret = NULL; + return 0; + } + + end_first = strchrnul(first, '/'); + len = end_first - first; + + if (len > NAME_MAX) + return -EINVAL; + if (!accept_dot_dot && len == 2 && first[0] == '.' && first[1] == '.') + return -EINVAL; + + next = skip_slash_or_dot(end_first); + + *p = next + (nm_streq(next, ".") ? 1 : 0); + if (ret) + *ret = first; + return len; +} + +int +nm_path_compare(const char *a, const char *b) +{ + /* Copied from systemd's path_compare() + * https://github.com/systemd/systemd/blob/bc85f8b51d962597360e982811e674c126850f56/src/basic/path-util.c#L415 */ + + /* Order NULL before non-NULL */ + NM_CMP_SELF(a, b); + + /* A relative path and an absolute path must not compare as equal. + * Which one is sorted before the other does not really matter. + * Here a relative path is ordered before an absolute path. */ + NM_CMP_DIRECT(nm_path_is_absolute(a), nm_path_is_absolute(b)); + + for (;;) { + const char *aa, *bb; + int j, k; + + j = nm_path_find_first_component(&a, TRUE, &aa); + k = nm_path_find_first_component(&b, TRUE, &bb); + + if (j < 0 || k < 0) { + /* When one of paths is invalid, order invalid path after valid one. */ + NM_CMP_DIRECT(j < 0, k < 0); + + /* fallback to use strcmp() if both paths are invalid. */ + NM_CMP_DIRECT_STRCMP(a, b); + return 0; + } + + /* Order prefixes first: "/foo" before "/foo/bar" */ + if (j == 0) { + if (k == 0) + return 0; + return -1; + } + if (k == 0) + return 1; + + /* Alphabetical sort: "/foo/aaa" before "/foo/b" */ + NM_CMP_DIRECT_MEMCMP(aa, bb, NM_MIN(j, k)); + + /* Sort "/foo/a" before "/foo/aaa" */ + NM_CMP_DIRECT(j, k); + } +} + +char * +nm_path_startswith_full(const char *path, const char *prefix, gboolean accept_dot_dot) +{ + /* Copied from systemd's path_startswith_full() + * https://github.com/systemd/systemd/blob/bc85f8b51d962597360e982811e674c126850f56/src/basic/path-util.c#L375 */ + + nm_assert(path); + nm_assert(prefix); + + /* Returns a pointer to the start of the first component after the parts matched by + * the prefix, iff + * - both paths are absolute or both paths are relative, + * and + * - each component in prefix in turn matches a component in path at the same position. + * An empty string will be returned when the prefix and path are equivalent. + * + * Returns NULL otherwise. + */ + + if ((path[0] == '/') != (prefix[0] == '/')) + return NULL; + + for (;;) { + const char *p, *q; + int r, k; + + r = nm_path_find_first_component(&path, accept_dot_dot, &p); + if (r < 0) + return NULL; + + k = nm_path_find_first_component(&prefix, accept_dot_dot, &q); + if (k < 0) + return NULL; + + if (k == 0) + return (char *) (p ?: path); + + if (r != k) + return NULL; + + if (strncmp(p, q, r) != 0) + return NULL; + } +} + +char * +nm_path_simplify(char *path) +{ + bool add_slash = false; + char *f = path; + int r; + + /* Copied from systemd's path_simplify() + * https://github.com/systemd/systemd/blob/bc85f8b51d962597360e982811e674c126850f56/src/basic/path-util.c#L325 */ + + nm_assert(path); + + /* Removes redundant inner and trailing slashes. Also removes unnecessary dots. + * Modifies the passed string in-place. + * + * ///foo//./bar/. becomes /foo/bar + * .//./foo//./bar/. becomes foo/bar + */ + + if (path[0] == '\0') + return path; + + if (nm_path_is_absolute(path)) + f++; + + for (const char *p = f;;) { + const char *e; + + r = nm_path_find_first_component(&p, TRUE, &e); + if (r == 0) + break; + + if (add_slash) + *f++ = '/'; + + if (r < 0) { + /* if path is invalid, then refuse to simplify remaining part. */ + memmove(f, p, strlen(p) + 1); + return path; + } + + memmove(f, e, r); + f += r; + + add_slash = TRUE; + } + + /* Special rule, if we stripped everything, we need a "." for the current directory. */ + if (f == path) + *f++ = '.'; + + *f = '\0'; + return path; +} + +/*****************************************************************************/ + +static gboolean +valid_ldh_char(char c) +{ + /* "LDH" → "Letters, digits, hyphens", as per RFC 5890, Section 2.3.1 */ + + return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9') || c == '-'; +} + +/** + * nm_hostname_is_valid: + * @s: the hostname to check. + * @trailing_dot: Accept trailing dot on multi-label names. + * + * Return: %TRUE if valid. + */ +gboolean +nm_hostname_is_valid(const char *s, gboolean trailing_dot) +{ + unsigned n_dots = 0; + const char *p; + gboolean dot; + gboolean hyphen; + + /* Copied from systemd's hostname_is_valid() + * https://github.com/systemd/systemd/blob/bc85f8b51d962597360e982811e674c126850f56/src/basic/hostname-util.c#L85 */ + + /* Check if s looks like a valid hostname or FQDN. This does not do full DNS validation, but only + * checks if the name is composed of allowed characters and the length is not above the maximum + * allowed by Linux (c.f. dns_name_is_valid()). A trailing dot is allowed if + * VALID_HOSTNAME_TRAILING_DOT flag is set and at least two components are present in the name. Note + * that due to the restricted charset and length this call is substantially more conservative than + * dns_name_is_valid(). Doesn't accept empty hostnames, hostnames with leading dots, and hostnames + * with multiple dots in a sequence. Doesn't allow hyphens at the beginning or end of label. */ + + if (nm_str_is_empty(s)) + return FALSE; + + for (p = s, dot = hyphen = TRUE; *p; p++) + if (*p == '.') { + if (dot || hyphen) + return FALSE; + + dot = TRUE; + hyphen = FALSE; + n_dots++; + + } else if (*p == '-') { + if (dot) + return FALSE; + + dot = FALSE; + hyphen = TRUE; + + } else { + if (!valid_ldh_char(*p)) + return FALSE; + + dot = FALSE; + hyphen = FALSE; + } + + if (dot && (n_dots < 2 || !trailing_dot)) + return FALSE; + if (hyphen) + return FALSE; + + /* Note that HOST_NAME_MAX is 64 on Linux, but DNS allows domain names up to + * 255 characters */ + if (p - s > NM_HOST_NAME_MAX) + return FALSE; + + return TRUE; +} diff --git a/src/libnm-glib-aux/nm-shared-utils.h b/src/libnm-glib-aux/nm-shared-utils.h index 52cf1d4c..3d8eaced 100644 --- a/src/libnm-glib-aux/nm-shared-utils.h +++ b/src/libnm-glib-aux/nm-shared-utils.h @@ -243,11 +243,16 @@ extern const NMIPAddr nm_ip_addr_zero; static inline int nm_ip_addr_cmp(int addr_family, gconstpointer a, gconstpointer b) { + /* Note that @a and @b are not required to be full NMIPAddr unions. + * Depending on @addr_family, they can also be only in_addr_t or + * struct in6_addr. */ NM_CMP_SELF(a, b); NM_CMP_DIRECT_MEMCMP(a, b, nm_utils_addr_family_to_size(addr_family)); return 0; } +int nm_ip_addr_cmp_for_sort(gconstpointer a, gconstpointer b, gpointer user_data); + static inline gboolean nm_ip_addr_equal(int addr_family, gconstpointer a, gconstpointer b) { @@ -277,8 +282,15 @@ nm_ip_addr_set(int addr_family, gpointer dst, gconstpointer src) nm_assert(dst); nm_assert(src); - /* this MUST use memcpy() (or similar means) to support unaligned src/dst pointers. */ + /* this MUST use memcpy() to support unaligned src/dst pointers. */ memcpy(dst, src, nm_utils_addr_family_to_size(addr_family)); + + /* Note that @dst is not necessarily a NMIPAddr, it could also be just + * an in_addr_t/struct in6_addr. We thus can only set the bytes that + * we know are present based on the address family. + * + * Using this function to initialize an NMIPAddr union (for IPv4) leaves + * uninitalized bytes. Avoid that by using nm_ip_addr_init() instead. */ } static inline NMIPAddr @@ -291,6 +303,8 @@ nm_ip_addr_init(int addr_family, gconstpointer src) G_STATIC_ASSERT_EXPR(sizeof(NMIPAddr) == sizeof(struct in6_addr)); + /* this MUST use memcpy() to support unaligned src/dst pointers. */ + if (NM_IS_IPv4(addr_family)) { memcpy(&a, src, sizeof(in_addr_t)); @@ -312,12 +326,6 @@ gboolean nm_ip_addr_set_from_untrusted(int addr_family, gboolean nm_ip_addr_set_from_variant(int addr_family, gpointer dst, GVariant *variant, int *out_addr_family); -static inline gboolean -nm_ip4_addr_is_localhost(in_addr_t addr4) -{ - return (addr4 & htonl(0xFF000000u)) == htonl(0x7F000000u); -} - static inline gconstpointer nm_ip_addr_from_packed_array(int addr_family, gconstpointer ipaddr_arr, gsize idx) { @@ -389,6 +397,17 @@ gboolean nm_utils_get_ipv6_interface_identifier(NMLinkType link_type, /*****************************************************************************/ +static inline guint32 +_nm_utils_ip4_netmask_to_prefix(in_addr_t subnetmask) +{ + G_STATIC_ASSERT_EXPR(__SIZEOF_INT__ == 4); + G_STATIC_ASSERT_EXPR(sizeof(int) == 4); + G_STATIC_ASSERT_EXPR(sizeof(guint) == 4); + G_STATIC_ASSERT_EXPR(sizeof(subnetmask) == 4); + + return ((subnetmask != 0u) ? (guint32) (32 - __builtin_ctz(ntohl(subnetmask))) : 0u); +} + /** * _nm_utils_ip4_prefix_to_netmask: * @prefix: a CIDR prefix @@ -485,11 +504,20 @@ nm_utils_ip_address_same_prefix(int addr_family, /*****************************************************************************/ gboolean nm_utils_ip_is_site_local(int addr_family, const void *address); +gboolean nm_utils_ip6_is_ula(const struct in6_addr *address); /*****************************************************************************/ -#define NM_IPV4LL_NETWORK ((in_addr_t) (htonl(0xA9FE0000lu))) -#define NM_IPV4LL_NETMASK ((in_addr_t) (htonl(0xFFFF0000lu))) +#define NM_IPV4LL_NETWORK ((in_addr_t) htonl(0xA9FE0000lu)) +#define NM_IPV4LL_NETMASK ((in_addr_t) htonl(0xFFFF0000lu)) + +static inline gboolean +nm_utils_ip4_address_is_loopback(in_addr_t addr) +{ + /* There is also IN_LOOPBACK() in <linux/in.h>, but there the + * argument is in host order not `in_addr_t`. */ + return (addr & htonl(0xFF000000u)) == htonl(0x7F000000u); +} static inline gboolean nm_utils_ip4_address_is_link_local(in_addr_t addr) @@ -623,6 +651,16 @@ nm_utils_is_separator(const char c) /*****************************************************************************/ +static inline GBytes * +nm_g_bytes_ref(GBytes *b) +{ + if (b) + g_bytes_ref(b); + return b; +} + +/*****************************************************************************/ + GBytes *nm_g_bytes_get_empty(void); GBytes *nm_g_bytes_new_from_str(const char *str); @@ -1977,6 +2015,7 @@ typedef struct { NMUtilsNamedEntry named_entry; const char *name; char *name_mutable; + gpointer name_ptr; }; union { const char *value_str; @@ -1990,14 +2029,28 @@ typedef struct { .name = (n), .value_ptr = (v) \ } -NMUtilsNamedValue * -nm_utils_named_values_from_strdict_full(GHashTable *hash, - guint *out_len, - GCompareDataFunc compare_func, - gpointer user_data, - NMUtilsNamedValue *provided_buffer, - guint provided_buffer_len, - NMUtilsNamedValue **out_allocated_buffer); +NMUtilsNamedValue *nm_utils_hash_to_array_full(GHashTable *hash, + guint *out_len, + GCompareDataFunc compare_func, + gpointer user_data, + NMUtilsNamedValue *provided_buffer, + guint provided_buffer_len, + NMUtilsNamedValue **out_allocated_buffer); + +#define nm_utils_named_values_from_strdict_full(hash, \ + out_len, \ + compare_func, \ + user_data, \ + provided_buffer, \ + provided_buffer_len, \ + out_allocated_buffer) \ + nm_utils_hash_to_array_full((hash), \ + (out_len), \ + (compare_func), \ + (user_data), \ + (provided_buffer), \ + (provided_buffer_len), \ + (out_allocated_buffer)) #define nm_utils_named_values_from_strdict(hash, out_len, array, out_allocated_buffer) \ nm_utils_named_values_from_strdict_full((hash), \ @@ -2038,6 +2091,29 @@ gpointer *nm_utils_hash_values_to_array(GHashTable *hash, gpointer user_data, guint *out_len); +static inline NMUtilsNamedValue * +nm_utils_hash_to_array(GHashTable *hash, + GCompareDataFunc compare_func, + gpointer user_data, + guint *out_len) +{ + return nm_utils_hash_to_array_full(hash, out_len, compare_func, user_data, NULL, 0, NULL); +} + +#define nm_utils_hash_to_array_with_buffer(hash, \ + out_len, \ + compare_func, \ + user_data, \ + array, \ + out_allocated_buffer) \ + nm_utils_hash_to_array_full((hash), \ + (out_len), \ + (compare_func), \ + (user_data), \ + (array), \ + G_N_ELEMENTS(array), \ + (out_allocated_buffer)) + static inline const char ** nm_strdict_get_keys(const GHashTable *hash, gboolean sorted, guint *out_length) { @@ -2146,38 +2222,57 @@ nm_g_array_unref(GArray *arr) g_array_unref(arr); } -#define nm_g_array_first(arr, type) \ - ({ \ - GArray *const _arr = (arr); \ - guint _len; \ - \ - nm_assert(_arr); \ - _len = _arr->len; \ - nm_assert(_len > 0); \ - &g_array_index(arr, type, 0); \ +#define nm_g_array_first(arr, Type) \ + ({ \ + GArray *const _arr = (arr); \ + \ + nm_assert(_arr); \ + nm_assert(sizeof(Type) == g_array_get_element_size(_arr)); \ + nm_assert(_arr->len > 0); \ + \ + &g_array_index(arr, Type, 0); \ }) -#define nm_g_array_last(arr, type) \ - ({ \ - GArray *const _arr = (arr); \ - guint _len; \ - \ - nm_assert(_arr); \ - _len = _arr->len; \ - nm_assert(_len > 0); \ - &g_array_index(arr, type, _len - 1u); \ +#define nm_g_array_last(arr, Type) \ + ({ \ + GArray *const _arr = (arr); \ + \ + nm_assert(_arr); \ + nm_assert(sizeof(Type) == g_array_get_element_size(_arr)); \ + nm_assert(_arr->len > 0); \ + \ + &g_array_index(arr, Type, _arr->len - 1u); \ }) -#define nm_g_array_append_new(arr, type) \ - ({ \ - GArray *const _arr = (arr); \ - guint _len; \ - \ - nm_assert(_arr); \ - _len = _arr->len; \ - nm_assert(_len < G_MAXUINT); \ - g_array_set_size(_arr, _len + 1u); \ - &g_array_index(arr, type, _len); \ +/* Similar to g_array_index(). The differences are + * - this does nm_assert() checks that the arguments are valid. + * - returns a pointer to the element. */ +#define nm_g_array_index_p(arr, Type, idx) \ + ({ \ + GArray *const _arr_55 = (arr); \ + const guint _idx_55 = (idx); \ + \ + nm_assert(_arr_55); \ + nm_assert(sizeof(Type) == g_array_get_element_size(_arr_55)); \ + nm_assert(_idx_55 < _arr_55->len); \ + \ + &g_array_index(_arr_55, Type, _idx_55); \ + }) + +#define nm_g_array_append_new(arr, Type) \ + ({ \ + GArray *const _arr = (arr); \ + guint _len; \ + \ + nm_assert(_arr); \ + nm_assert(sizeof(Type) == g_array_get_element_size(_arr)); \ + \ + _len = _arr->len; \ + \ + nm_assert(_len < G_MAXUINT); \ + \ + g_array_set_size(_arr, _len + 1u); \ + &g_array_index(arr, Type, _len); \ }) /*****************************************************************************/ @@ -2311,6 +2406,25 @@ nm_g_hash_table_contains(GHashTable *hash, gconstpointer key) return hash ? g_hash_table_contains(hash, key) : FALSE; } +#define nm_g_hash_table_contains_any(hash, ...) \ + ({ \ + GHashTable *const _hash = (hash); \ + gconstpointer const _keys[] = {__VA_ARGS__}; \ + int _i_key; \ + gboolean _contains = FALSE; \ + \ + if (_hash) { \ + for (_i_key = 0; _i_key < (int) G_N_ELEMENTS(_keys); _i_key++) { \ + if (g_hash_table_contains(_hash, _keys[_i_key])) { \ + _contains = TRUE; \ + break; \ + } \ + } \ + } \ + \ + _contains; \ + }) + static inline gboolean nm_g_hash_table_remove(GHashTable *hash, gconstpointer key) { @@ -2765,6 +2879,8 @@ nm_ether_addr_to_string(const NMEtherAddr *ether_addr, char sbuf[static(sizeof(N #define nm_ether_addr_to_string_a(ether_addr) \ nm_ether_addr_to_string((ether_addr), g_alloca(sizeof(NMEtherAddr) * 3)) +NMEtherAddr *nm_ether_addr_from_string(NMEtherAddr *addr, const char *str); + guint8 *nm_utils_hexstr2bin_full(const char *hexstr, gboolean allow_0x_prefix, gboolean delimiter_required, @@ -3054,10 +3170,14 @@ gboolean nm_utils_ifname_valid(const char *name, NMUtilsIfaceType type, GError * static inline GArray * nm_strvarray_ensure(GArray **p) { + nm_assert(p); + if (!*p) { *p = g_array_new(TRUE, FALSE, sizeof(char *)); g_array_set_clear_func(*p, nm_indirect_g_free); - } + } else + nm_assert(g_array_get_element_size(*p) == sizeof(char *)); + return *p; } @@ -3066,6 +3186,9 @@ nm_strvarray_add(GArray *array, const char *str) { char *s; + nm_assert(array); + nm_assert(g_array_get_element_size(array) == sizeof(char *)); + s = g_strdup(str); g_array_append_val(array, s); } @@ -3073,15 +3196,14 @@ nm_strvarray_add(GArray *array, const char *str) static inline const char * nm_strvarray_get_idx(GArray *array, guint idx) { - nm_assert(array); - nm_assert(idx < array->len); - - return g_array_index(array, const char *, idx); + return *nm_g_array_index_p(array, const char *, idx); } static inline const char *const * nm_strvarray_get_strv_non_empty(GArray *arr, guint *length) { + nm_assert(!arr || g_array_get_element_size(arr) == sizeof(char *)); + if (!arr || arr->len == 0) { NM_SET_OUT(length, 0); return NULL; @@ -3096,6 +3218,8 @@ nm_strvarray_get_strv_non_empty_dup(GArray *arr, guint *length) { const char *const *strv; + nm_assert(!arr || g_array_get_element_size(arr) == sizeof(char *)); + if (!arr || arr->len == 0) { NM_SET_OUT(length, 0); return NULL; @@ -3114,6 +3238,8 @@ nm_strvarray_get_strv(GArray **arr, guint *length) return (const char *const *) arr; } + nm_assert(g_array_get_element_size(*arr) == sizeof(char *)); + NM_SET_OUT(length, (*arr)->len); return &g_array_index(*arr, const char *, 0); } @@ -3125,6 +3251,8 @@ nm_strvarray_set_strv(GArray **array, const char *const *strv) array_old = g_steal_pointer(array); + nm_assert(!array_old || g_array_get_element_size(array_old) == sizeof(char *)); + if (!strv || !strv[0]) return; @@ -3141,6 +3269,7 @@ nm_strvarray_find_first(GArray *strv, const char *needle) nm_assert(needle); if (strv) { + nm_assert(g_array_get_element_size(strv) == sizeof(char *)); for (i = 0; i < strv->len; i++) { if (nm_streq(needle, g_array_index(strv, const char *, i))) return i; @@ -3224,6 +3353,12 @@ char *_nm_utils_format_variant_attributes(GHashTable *a /*****************************************************************************/ +/* glibc defines HOST_NAME_MAX as 64. Also Linux' sethostname() enforces + * that (__NEW_UTS_LEN). However, musl sets this to 255. + * + * At some places, we want to follow Linux. Hardcode our own define. */ +#define NM_HOST_NAME_MAX 64 + gboolean nm_utils_is_localhost(const char *name); gboolean nm_utils_is_specific_hostname(const char *name); @@ -3276,4 +3411,46 @@ gboolean nm_utils_validate_hostname(const char *hostname); void nm_utils_thread_local_register_destroy(gpointer tls_data, GDestroyNotify destroy_notify); +/*****************************************************************************/ + +int nm_unbase64char(char c); +int nm_unbase64mem_full(const char *p, gsize l, gboolean secure, guint8 **ret, gsize *ret_size); + +/*****************************************************************************/ + +static inline gboolean +nm_path_is_absolute(const char *p) +{ + /* Copied from systemd's path_is_absolute() + * https://github.com/systemd/systemd/blob/bc85f8b51d962597360e982811e674c126850f56/src/basic/path-util.h#L50 */ + + nm_assert(p); + return p[0] == '/'; +} + +int nm_path_find_first_component(const char **p, gboolean accept_dot_dot, const char **ret); + +int nm_path_compare(const char *a, const char *b); + +static inline gboolean +nm_path_equal(const char *a, const char *b) +{ + return nm_path_compare(a, b) == 0; +} + +char *nm_path_simplify(char *path); + +char * +nm_path_startswith_full(const char *path, const char *prefix, gboolean accept_dot_dot) _nm_pure; + +static inline char * +nm_path_startswith(const char *path, const char *prefix) +{ + return nm_path_startswith_full(path, prefix, TRUE); +} + +/*****************************************************************************/ + +gboolean nm_hostname_is_valid(const char *s, gboolean trailing_dot); + #endif /* __NM_SHARED_UTILS_H__ */ diff --git a/src/libnm-glib-aux/nm-str-buf.h b/src/libnm-glib-aux/nm-str-buf.h index 47d1f055..652bc96b 100644 --- a/src/libnm-glib-aux/nm-str-buf.h +++ b/src/libnm-glib-aux/nm-str-buf.h @@ -26,6 +26,7 @@ typedef struct _NMStrBuf { }; bool _priv_do_bzero_mem; + bool _priv_malloced; } NMStrBuf; /*****************************************************************************/ @@ -36,29 +37,56 @@ _nm_str_buf_assert(const NMStrBuf *strbuf) nm_assert(strbuf); nm_assert((!!strbuf->_priv_str) == (strbuf->_priv_allocated > 0)); nm_assert(strbuf->_priv_len <= strbuf->_priv_allocated); + nm_assert(!strbuf->_priv_malloced || strbuf->_priv_str); } static inline NMStrBuf -NM_STR_BUF_INIT(gsize allocated, gboolean do_bzero_mem) +NM_STR_BUF_INIT_FULL(char *str, + gsize len, + gsize allocated, + gboolean malloced, + gboolean do_bzero_mem) { NMStrBuf strbuf = { - ._priv_str = allocated ? g_malloc(allocated) : NULL, + ._priv_str = allocated > 0 ? str : NULL, ._priv_allocated = allocated, - ._priv_len = 0, + ._priv_len = len, ._priv_do_bzero_mem = do_bzero_mem, + ._priv_malloced = allocated > 0 && malloced, }; + _nm_str_buf_assert(&strbuf); + return strbuf; } -static inline void -nm_str_buf_init(NMStrBuf *strbuf, gsize len, bool do_bzero_mem) +static inline NMStrBuf +NM_STR_BUF_INIT(gsize allocated, gboolean do_bzero_mem) { - nm_assert(strbuf); - *strbuf = NM_STR_BUF_INIT(len, do_bzero_mem); - _nm_str_buf_assert(strbuf); + return NM_STR_BUF_INIT_FULL(allocated > 0 ? g_malloc(allocated) : NULL, + 0, + allocated, + allocated > 0, + do_bzero_mem); } +#define NM_STR_BUF_INIT_A(size, do_bzero_mem) \ + NM_STR_BUF_INIT_FULL( \ + g_alloca(size), \ + 0, \ + NM_STATIC_ASSERT_EXPR_1((size) > 0 && (size) <= NM_UTILS_GET_NEXT_REALLOC_SIZE_488) \ + ? (size) \ + : 0, \ + FALSE, \ + (do_bzero_mem)); + +#define NM_STR_BUF_INIT_ARR(arr, do_bzero_mem) \ + NM_STR_BUF_INIT_FULL((arr), \ + 0, \ + NM_STATIC_ASSERT_EXPR_1(sizeof(arr) > sizeof(char *)) ? sizeof(arr) : 0, \ + FALSE, \ + (do_bzero_mem)); + void _nm_str_buf_ensure_size(NMStrBuf *strbuf, gsize new_size, gboolean reserve_exact); static inline void @@ -263,7 +291,27 @@ nm_str_buf_append0(NMStrBuf *strbuf, const char *str) return nm_str_buf_append_len0(strbuf, str, strlen(str)); } -void nm_str_buf_append_printf(NMStrBuf *strbuf, const char *format, ...) _nm_printf(2, 3); +void nm_str_buf_append_printfv(NMStrBuf *strbuf, const char *format, va_list args) _nm_printf(2, 0); + +/* Warning, this is not a function-like macro. That is, you must + * evaluate it in a place where you would otherwise call va_start(). */ +#define nm_str_buf_append_printfv_eval(strbuf, format, va_start_last) \ + ({ \ + NMStrBuf *const _strbuf = (strbuf); \ + va_list _ap; \ + \ + va_start(_ap, (va_start_last)); \ + nm_str_buf_append_printfv(_strbuf, (format), _ap); \ + va_end(_ap); \ + \ + _strbuf; \ + }) + +static inline void _nm_printf(2, 3) + nm_str_buf_append_printf(NMStrBuf *strbuf, const char *format, ...) +{ + nm_str_buf_append_printfv_eval(strbuf, format, format); +} static inline void nm_str_buf_ensure_trailing_c(NMStrBuf *strbuf, char ch) @@ -359,10 +407,10 @@ static inline gboolean nm_str_buf_is_initalized(NMStrBuf *strbuf) { nm_assert(strbuf); -#if NM_MORE_ASSERTS - if (strbuf->_priv_str) - _nm_str_buf_assert(strbuf); -#endif + if (NM_MORE_ASSERTS > 0) { + if (strbuf->_priv_str) + _nm_str_buf_assert(strbuf); + } return !!strbuf->_priv_str; } @@ -399,6 +447,29 @@ nm_str_buf_get_str(NMStrBuf *strbuf) return strbuf->_priv_str; } +/** + * nm_str_buf_get_str_unsafe: + * @strbuf: the buffer + * + * Usually, NMStrBuf is used to construct NUL terminated strings. But + * while constructing the buffer (nm_str_buf_append*()), it does + * not NUL terminate the buffer yet. Only nm_str_buf_get_str() + * and nm_str_buf_finalize() ensure that the returned string is + * actually NUL terminated. + * + * NMStrBuf can also be used for binary data, or you might not + * require the NUL termination. In that case, nm_str_buf_get_str_unsafe() + * will give you the pointer, but you must not rely on it being NUL + * terminated. This is the "unsafe" part of it. + * + * The returned string is of course initialized up to length "strbuf->len" + * and allocated with "strbuf->allocated" bytes. + * + * If currently no buffer is allocated, %NULL is returned. + * + * Returns: (transfer none): very similar to nm_str_buf_get_str(), + * except that the result is no guaranteed to be NUL terminated. + */ static inline char * nm_str_buf_get_str_unsafe(NMStrBuf *strbuf) { @@ -440,9 +511,11 @@ nm_str_buf_get_char(const NMStrBuf *strbuf, gsize index) * Returns: (transfer full): the string of the buffer * which must be freed by the caller. The @strbuf * is afterwards in undefined state, though it can be - * reused after nm_str_buf_init(). - * Note that if no string is allocated yet (after nm_str_buf_init() with - * length zero), this will return %NULL. */ + * reused after resetting with NM_STR_BUF_INIT(). + * Note that if no string is allocated yet (after NM_STR_BUF_INIT() with + * length zero), this will return %NULL. + * + * If the buffer was not malloced before, it will be malloced now. */ static inline char * nm_str_buf_finalize(NMStrBuf *strbuf, gsize *out_len) { @@ -453,6 +526,16 @@ nm_str_buf_finalize(NMStrBuf *strbuf, gsize *out_len) if (!strbuf->_priv_str) return NULL; + if (!strbuf->_priv_malloced) { + char *str = g_steal_pointer(&strbuf->_priv_str); + char *result; + + result = g_strndup(str, strbuf->_priv_len); + if (strbuf->_priv_do_bzero_mem) + nm_explicit_bzero(str, strbuf->_priv_len); + return result; + } + nm_str_buf_maybe_expand(strbuf, 1, TRUE); strbuf->_priv_str[strbuf->_priv_len] = '\0'; @@ -484,7 +567,7 @@ nm_str_buf_finalize_to_gbytes(NMStrBuf *strbuf) * * Frees the associated memory of @strbuf. The buffer * afterwards is in undefined state, but can be re-initialized - * with nm_str_buf_init(). + * with NM_STR_BUF_INIT(). */ static inline void nm_str_buf_destroy(NMStrBuf *strbuf) @@ -494,7 +577,8 @@ nm_str_buf_destroy(NMStrBuf *strbuf) _nm_str_buf_assert(strbuf); if (strbuf->_priv_do_bzero_mem) nm_explicit_bzero(strbuf->_priv_str, strbuf->_priv_len); - g_free(strbuf->_priv_str); + if (strbuf->_priv_malloced) + g_free(strbuf->_priv_str); /* the buffer is in invalid state afterwards, however, we clear it * so far, that nm_auto_str_buf is happy when calling diff --git a/src/libnm-glib-aux/nm-test-utils.h b/src/libnm-glib-aux/nm-test-utils.h index 2dfe9e32..83702070 100644 --- a/src/libnm-glib-aux/nm-test-utils.h +++ b/src/libnm-glib-aux/nm-test-utils.h @@ -185,7 +185,7 @@ #define nmtst_assert_strv(strv, ...) \ G_STMT_START \ { \ - const char *const *const _strv = (strv); \ + const char *const *const _strv = NM_CAST_STRV_CC(strv); \ const char *const _exp[] = {__VA_ARGS__, NULL}; \ const gsize _n = G_N_ELEMENTS(_exp) - 1; \ gsize _i; \ @@ -196,6 +196,7 @@ g_assert(_exp[_i]); \ g_assert_cmpstr(_strv[_i], ==, _exp[_i]); \ } \ + g_assert(!_strv[_n]); \ } \ G_STMT_END diff --git a/src/libnm-glib-aux/nm-uuid.c b/src/libnm-glib-aux/nm-uuid.c index ff0649d0..53e8b78c 100644 --- a/src/libnm-glib-aux/nm-uuid.c +++ b/src/libnm-glib-aux/nm-uuid.c @@ -115,12 +115,12 @@ nm_uuid_generate_random(NMUuid *out_uuid) /* See also, systemd's id128_make_v4_uuid() */ - /* nm_utils_random_bytes() is supposed to try hard to give good + /* nm_random_get_bytes() is supposed to try hard to give good * randomness. If it fails, it still makes an effort to fill * random data into the buffer. There is not much we can do about * that case, except making sure that it does not happen in the * first place. */ - nm_utils_random_bytes(out_uuid, sizeof(*out_uuid)); + nm_random_get_bytes(out_uuid, sizeof(*out_uuid)); /* Set the four most significant bits (bits 12 through 15) of the * time_hi_and_version field to the 4-bit version number from @@ -202,28 +202,25 @@ nm_uuid_is_valid_nm(const char *str, /* @out_normalized_str is only set, if normalization was necessary * and possible. The caller cannot request @out_normalized_str, without - * also getting @out_normalized. */ + * also requesting @out_normalized. Otherwise, they couldn't know whether + * a normalized string was returned. */ nm_assert(!out_normalized_str || out_normalized); if (!str) return FALSE; if (nm_uuid_parse_full(str, &uuid, &is_normalized)) { - /* Note that: - * @is_normalized means that "str" contains a normalized UUID - * @out_normalized: indicates whether str requires normalization - * and whether @out_normalized_str was set to contain the normalized - * UUID. - * With this, we get the slightly odd assignment: */ - NM_SET_OUT(out_normalized, !is_normalized); - - if (!is_normalized && out_normalized_str) { - /* we need to normalize the UUID */ - nm_uuid_unparse(&uuid, out_normalized_str); + if (is_normalized) { + /* @str is already normalized. No need to normalize again, so + * @out_normalized is FALSE. */ + NM_SET_OUT(out_normalized, FALSE); + } else { + NM_SET_OUT(out_normalized, TRUE); + if (out_normalized_str) { + /* we need to normalize the UUID */ + nm_uuid_unparse(&uuid, out_normalized_str); + } } - - /* regardless whether normalization was necessary, the UUID is - * essentially valid. */ return TRUE; } @@ -235,24 +232,23 @@ nm_uuid_is_valid_nm(const char *str, * are made lower case first. */ NM_SET_OUT(out_normalized, TRUE); if (out_normalized_str) { - char str_lower[40 + 1]; + char str_lower[40]; int i; - nm_assert(strlen(str) < G_N_ELEMENTS(str_lower)); + nm_assert(strlen(str) <= G_N_ELEMENTS(str_lower)); /* normalize first to lower-case. */ for (i = 0; str[i]; i++) { nm_assert(i < G_N_ELEMENTS(str_lower)); str_lower[i] = g_ascii_tolower(str[i]); } - nm_assert(i < G_N_ELEMENTS(str_lower)); - str_lower[i] = '\0'; + nm_assert(i <= G_N_ELEMENTS(str_lower)); /* The namespace UUID is chosen randomly. */ nm_uuid_generate_from_string( &uuid, str_lower, - -1, + i, NM_UUID_TYPE_VERSION5, &NM_UUID_INIT(4e, 72, f7, 09, ca, 95, 44, 05, 90, 53, 1f, 43, 29, 4a, 61, 8c)); nm_uuid_unparse(&uuid, out_normalized_str); diff --git a/src/libnm-glib-aux/tests/test-shared-general.c b/src/libnm-glib-aux/tests/test-shared-general.c index e7fd2b6b..7d36d64a 100644 --- a/src/libnm-glib-aux/tests/test-shared-general.c +++ b/src/libnm-glib-aux/tests/test-shared-general.c @@ -10,6 +10,7 @@ #include "libnm-glib-aux/nm-str-buf.h" #include "libnm-glib-aux/nm-time-utils.h" #include "libnm-glib-aux/nm-ref-string.h" +#include "libnm-glib-aux/nm-io-utils.h" #include "libnm-glib-aux/nm-test-utils.h" @@ -93,7 +94,7 @@ test_nmhash(void) { int rnd; - nm_utils_random_bytes(&rnd, sizeof(rnd)); + nm_random_get_bytes(&rnd, sizeof(rnd)); g_assert(nm_hash_val(555, 4) != 0); } @@ -116,8 +117,8 @@ test_make_strv(void) const char *const *v2a = NM_MAKE_STRV("a", "b"); const char *const *v2b = NM_MAKE_STRV("a", "b", ); const char *const v3[] = { - "a", - "b", + "a", + "b", }; const char *const *v4b = NM_MAKE_STRV("a", _make_strv_foo(), ); @@ -250,14 +251,14 @@ test_nm_strndup_a(void) /*****************************************************************************/ static void -test_nm_ip4_addr_is_localhost(void) +test_nm_utils_ip4_address_is_loopback(void) { - g_assert(nm_ip4_addr_is_localhost(nmtst_inet4_from_string("127.0.0.0"))); - g_assert(nm_ip4_addr_is_localhost(nmtst_inet4_from_string("127.0.0.1"))); - g_assert(nm_ip4_addr_is_localhost(nmtst_inet4_from_string("127.5.0.1"))); - g_assert(!nm_ip4_addr_is_localhost(nmtst_inet4_from_string("126.5.0.1"))); - g_assert(!nm_ip4_addr_is_localhost(nmtst_inet4_from_string("128.5.0.1"))); - g_assert(!nm_ip4_addr_is_localhost(nmtst_inet4_from_string("129.5.0.1"))); + g_assert(nm_utils_ip4_address_is_loopback(nmtst_inet4_from_string("127.0.0.0"))); + g_assert(nm_utils_ip4_address_is_loopback(nmtst_inet4_from_string("127.0.0.1"))); + g_assert(nm_utils_ip4_address_is_loopback(nmtst_inet4_from_string("127.5.0.1"))); + g_assert(!nm_utils_ip4_address_is_loopback(nmtst_inet4_from_string("126.5.0.1"))); + g_assert(!nm_utils_ip4_address_is_loopback(nmtst_inet4_from_string("128.5.0.1"))); + g_assert(!nm_utils_ip4_address_is_loopback(nmtst_inet4_from_string("129.5.0.1"))); } /*****************************************************************************/ @@ -781,6 +782,9 @@ test_nm_utils_get_next_realloc_size(void) {NM_UTILS_GET_NEXT_REALLOC_SIZE_232, NM_UTILS_GET_NEXT_REALLOC_SIZE_232, NM_UTILS_GET_NEXT_REALLOC_SIZE_232}, + {NM_UTILS_GET_NEXT_REALLOC_SIZE_488, + NM_UTILS_GET_NEXT_REALLOC_SIZE_488, + NM_UTILS_GET_NEXT_REALLOC_SIZE_488}, {NM_UTILS_GET_NEXT_REALLOC_SIZE_1000, NM_UTILS_GET_NEXT_REALLOC_SIZE_1000, NM_UTILS_GET_NEXT_REALLOC_SIZE_1000}, @@ -913,27 +917,58 @@ test_nm_str_buf(void) { guint i_run; - for (i_run = 0; TRUE; i_run++) { - nm_auto_str_buf NMStrBuf strbuf = {}; - nm_auto_free_gstring GString *gstr = NULL; + for (i_run = 0; i_run < 1000; i_run++) { + char stack_buf[1024]; + nm_auto_str_buf NMStrBuf strbuf; + nm_auto_free_gstring GString *gstr = NULL; int i, j, k; int c; - nm_str_buf_init(&strbuf, nmtst_get_rand_uint32() % 200u + 1u, nmtst_get_rand_bool()); + switch (nmtst_get_rand_uint32() % 10) { + case 0: + memset(&strbuf, 0, sizeof(strbuf)); + break; + case 1 ... 4: + strbuf = NM_STR_BUF_INIT_FULL(stack_buf, + 0, + nmtst_get_rand_uint32() % sizeof(stack_buf), + FALSE, + nmtst_get_rand_bool()); + break; + default: + strbuf = NM_STR_BUF_INIT(nmtst_get_rand_uint32() % 200u + 1u, nmtst_get_rand_bool()); + break; + } - if (i_run < 1000) { - c = nmtst_get_rand_word_length(NULL); - for (i = 0; i < c; i++) - nm_str_buf_append_c(&strbuf, '0' + (i % 10)); - gstr = g_string_new(nm_str_buf_get_str(&strbuf)); - j = nmtst_get_rand_uint32() % (strbuf.len + 1); - k = nmtst_get_rand_uint32() % (strbuf.len - j + 2) - 1; + c = nmtst_get_rand_word_length(NULL); + for (i = 0; i < c; i++) + nm_str_buf_append_c(&strbuf, '0' + (i % 10)); + gstr = g_string_new(nm_str_buf_get_str(&strbuf)); + j = nmtst_get_rand_uint32() % (strbuf.len + 1); + k = nmtst_get_rand_uint32() % (strbuf.len - j + 2) - 1; - nm_str_buf_erase(&strbuf, j, k, nmtst_get_rand_bool()); - g_string_erase(gstr, j, k); + nm_str_buf_erase(&strbuf, j, k, nmtst_get_rand_bool()); + g_string_erase(gstr, j, k); + if (gstr->str[0]) g_assert_cmpstr(gstr->str, ==, nm_str_buf_get_str(&strbuf)); + else + g_assert(NM_IN_STRSET(nm_str_buf_get_str(&strbuf), NULL, "")); + } + + for (i_run = 0; i_run < 50; i_run++) { + char stack_buf[20]; + nm_auto_str_buf NMStrBuf strbuf = NM_STR_BUF_INIT_ARR(stack_buf, nmtst_get_rand_bool()); + + nm_str_buf_append_c_len(&strbuf, 'a', nmtst_get_rand_uint32() % (sizeof(stack_buf) * 2)); + if (strbuf.len <= sizeof(stack_buf)) { + g_assert(stack_buf == nm_str_buf_get_str_unsafe(&strbuf)); } else - return; + g_assert(stack_buf != nm_str_buf_get_str_unsafe(&strbuf)); + + if (strbuf.len < sizeof(stack_buf)) { + g_assert(stack_buf == nm_str_buf_get_str(&strbuf)); + } else + g_assert(stack_buf != nm_str_buf_get_str(&strbuf)); } } @@ -1417,6 +1452,755 @@ test_nm_ascii(void) /*****************************************************************************/ +static int +_env_file_push_cb(unsigned line, const char *key, const char *value, void *user_data) +{ + char ***strv = user_data; + char *s_line; + gsize key_l; + gsize strv_l; + gsize i; + + g_assert(strv); + g_assert(key); + g_assert(key[0]); + g_assert(!strchr(key, '=')); + g_assert(value); + + key_l = strlen(key); + + s_line = g_strconcat(key, "=", value, NULL); + + strv_l = 0; + if (*strv) { + const char *s; + + for (i = 0; (s = (*strv)[i]); i++) { + if (g_str_has_prefix(s, key) && s[key_l] == '=') { + g_free((*strv)[i]); + (*strv)[i] = s_line; + return 0; + } + } + strv_l = i; + } + + *strv = g_realloc(*strv, sizeof(char *) * (strv_l + 2)); + (*strv)[strv_l] = s_line; + (*strv)[strv_l + 1] = NULL; + + return 0; +} + +static void +test_parse_env_file(void) +{ + gs_strfreev char **data = NULL; + gs_free char *arg1 = NULL; + gs_free char *arg2 = NULL; + int r; + +#define env_file_1 \ + "a=a\n" \ + "a=b\n" \ + "a=b\n" \ + "a=a\n" \ + "b=b\\\n" \ + "c\n" \ + "d= d\\\n" \ + "e \\\n" \ + "f \n" \ + "g=g\\ \n" \ + "h= ąęół\\ śćńźżµ \n" \ + "i=i\\" + r = nm_parse_env_file_full(env_file_1, _env_file_push_cb, &data); + g_assert_cmpint(r, ==, 0); + nmtst_assert_strv(data, "a=a", "b=bc", "d=de f", "g=g ", "h=ąęół śćńźżµ", "i=i"); + nm_clear_pointer(&data, g_strfreev); + + r = nm_parse_env_file(env_file_1, "a", &arg1); + g_assert_cmpint(r, ==, 0); + g_assert_cmpstr(arg1, ==, "a"); + nm_clear_g_free(&arg1); + + r = nm_parse_env_file(env_file_1, "a", &arg1, "d", &arg2); + g_assert_cmpint(r, ==, 0); + g_assert_cmpstr(arg1, ==, "a"); + g_assert_cmpstr(arg2, ==, "de f"); + nm_clear_g_free(&arg1); + nm_clear_g_free(&arg2); + +#define env_file_2 "a=a\\\n" + r = nm_parse_env_file_full(env_file_2, _env_file_push_cb, &data); + g_assert_cmpint(r, ==, 0); + nmtst_assert_strv(data, "a=a"); + nm_clear_pointer(&data, g_strfreev); + +#define env_file_3 \ + "#SPAMD_ARGS=\"-d --socketpath=/var/lib/bulwark/spamd \\\n" \ + "#--nouser-config \\\n" \ + "normal=line \\\n" \ + ";normal=ignored \\\n" \ + "normal_ignored \\\n" \ + "normal ignored \\\n" + r = nm_parse_env_file_full(env_file_3, _env_file_push_cb, &data); + g_assert_cmpint(r, ==, 0); + g_assert(!data); + +#define env_file_4 \ + "# Generated\n" \ + "\n" \ + "HWMON_MODULES=\"coretemp f71882fg\"\n" \ + "\n" \ + "# For compatibility reasons\n" \ + "\n" \ + "MODULE_0=coretemp\n" \ + "MODULE_1=f71882fg" + r = nm_parse_env_file_full(env_file_4, _env_file_push_cb, &data); + g_assert_cmpint(r, ==, 0); + nmtst_assert_strv(data, + "HWMON_MODULES=coretemp f71882fg", + "MODULE_0=coretemp", + "MODULE_1=f71882fg"); + nm_clear_pointer(&data, g_strfreev); + +#define env_file_5 \ + "a=\n" \ + "b=" + r = nm_parse_env_file_full(env_file_5, _env_file_push_cb, &data); + g_assert_cmpint(r, ==, 0); + nmtst_assert_strv(data, "a=", "b="); + nm_clear_pointer(&data, g_strfreev); + +#define env_file_6 \ + "a=\\ \\n \\t \\x \\y \\' \n" \ + "b= \\$' \n" \ + "c= ' \\n\\t\\$\\`\\\\\n" \ + "' \n" \ + "d= \" \\n\\t\\$\\`\\\\\n" \ + "\" \n" + r = nm_parse_env_file_full(env_file_6, _env_file_push_cb, &data); + g_assert_cmpint(r, ==, 0); + nmtst_assert_strv(data, "a= n t x y '", "b=$'", "c= \\n\\t\\$\\`\\\\\n", "d= \\n\\t$`\\\n"); + nm_clear_pointer(&data, g_strfreev); +} + +/*****************************************************************************/ + +static void +test_unbase64char(void) +{ + static const int expected[128] = { + [0] = -1, [1] = -1, [2] = -1, [3] = -1, [4] = -1, [5] = -1, [6] = -1, + [7] = -1, [8] = -1, [9] = -1, [10] = -1, [11] = -1, [12] = -1, [13] = -1, + [14] = -1, [15] = -1, [16] = -1, [17] = -1, [18] = -1, [19] = -1, [20] = -1, + [21] = -1, [22] = -1, [23] = -1, [24] = -1, [25] = -1, [26] = -1, [27] = -1, + [28] = -1, [29] = -1, [30] = -1, [31] = -1, [32] = -1, [33] = -1, [34] = -1, + [35] = -1, [36] = -1, [37] = -1, [38] = -1, [39] = -1, [40] = -1, [41] = -1, + [42] = -1, ['+'] = 62, [44] = -1, [45] = -1, [46] = -1, ['/'] = 63, ['0'] = 52, + ['1'] = 53, ['2'] = 54, ['3'] = 55, ['4'] = 56, ['5'] = 57, ['6'] = 58, ['7'] = 59, + ['8'] = 60, ['9'] = 61, [58] = -1, [59] = -1, [60] = -1, [61] = -1, [62] = -1, + [63] = -1, [64] = -1, ['A'] = 0, ['B'] = 1, ['C'] = 2, ['D'] = 3, ['E'] = 4, + ['F'] = 5, ['G'] = 6, ['H'] = 7, ['I'] = 8, ['J'] = 9, ['K'] = 10, ['L'] = 11, + ['M'] = 12, ['N'] = 13, ['O'] = 14, ['P'] = 15, ['Q'] = 16, ['R'] = 17, ['S'] = 18, + ['T'] = 19, ['U'] = 20, ['V'] = 21, ['W'] = 22, ['X'] = 23, ['Y'] = 24, ['Z'] = 25, + [91] = -1, [92] = -1, [93] = -1, [94] = -1, [95] = -1, [96] = -1, ['a'] = 26, + ['b'] = 27, ['c'] = 28, ['d'] = 29, ['e'] = 30, ['f'] = 31, ['g'] = 32, ['h'] = 33, + ['i'] = 34, ['j'] = 35, ['k'] = 36, ['l'] = 37, ['m'] = 38, ['n'] = 39, ['o'] = 40, + ['p'] = 41, ['q'] = 42, ['r'] = 43, ['s'] = 44, ['t'] = 45, ['u'] = 46, ['v'] = 47, + ['w'] = 48, ['x'] = 49, ['y'] = 50, ['z'] = 51, [123] = -1, [124] = -1, [125] = -1, + [126] = -1, [127] = -1, + }; + int i; + + /* Copied from systemd's TEST(unbase64char) + * https://github.com/systemd/systemd/blob/688efe7703328c5a0251fafac55757b8864a9f9a/src/test/test-hexdecoct.c#L44 */ + + g_assert_cmpint(nm_unbase64char('A'), ==, 0); + g_assert_cmpint(nm_unbase64char('Z'), ==, 25); + g_assert_cmpint(nm_unbase64char('a'), ==, 26); + g_assert_cmpint(nm_unbase64char('z'), ==, 51); + g_assert_cmpint(nm_unbase64char('0'), ==, 52); + g_assert_cmpint(nm_unbase64char('9'), ==, 61); + g_assert_cmpint(nm_unbase64char('+'), ==, 62); + g_assert_cmpint(nm_unbase64char('/'), ==, 63); + g_assert_cmpint(nm_unbase64char('='), ==, -ERANGE); + g_assert_cmpint(nm_unbase64char('\0'), ==, -EINVAL); + g_assert_cmpint(nm_unbase64char('\1'), ==, -EINVAL); + g_assert_cmpint(nm_unbase64char('\x7F'), ==, -EINVAL); + g_assert_cmpint(nm_unbase64char('\x80'), ==, -EINVAL); + g_assert_cmpint(nm_unbase64char('\xFF'), ==, -EINVAL); + + for (i = 0; i < 256; i++) { + int base64; + + base64 = nm_unbase64char((char) i); + + if (base64 < 0) { + if (((char) i) == '=') + g_assert_cmpint(base64, ==, -ERANGE); + else + g_assert_cmpint(base64, ==, -EINVAL); + base64 = -1; + } + + if (i >= G_N_ELEMENTS(expected)) { + g_assert_cmpint(base64, ==, -1); + continue; + } + g_assert_cmpint(base64, ==, expected[i]); + } +} + +/*****************************************************************************/ + +static void +test_unbase64mem1(void) +{ + nm_auto_str_buf NMStrBuf encoded_wrapped = NM_STR_BUF_INIT(400, FALSE); + uint8_t data[4096]; + int i_run; + + /* Copied from systemd's TEST(base64mem_linebreak) + * https://github.com/systemd/systemd/blob/688efe7703328c5a0251fafac55757b8864a9f9a/src/test/test-hexdecoct.c#L280 */ + + for (i_run = 0; i_run < 20; i_run++) { + gs_free char *encoded = NULL; + gs_free guint8 *decoded = NULL; + gsize decoded_size; + guint64 n; + guint64 m; + guint64 i; + guint64 j; + gssize l; + int r; + + /* Try a bunch of differently sized blobs */ + n = nmtst_get_rand_uint64() % sizeof(data); + nmtst_rand_buf(NULL, data, n); + + /* Break at various different columns */ + m = 1 + (nmtst_get_rand_uint64() % (n + 5)); + + encoded = g_base64_encode(data, n); + g_assert(encoded); + l = strlen(encoded); + + nm_str_buf_reset(&encoded_wrapped); + for (i = 0, j = 0; i < l; i++, j++) { + if (j == m) { + nm_str_buf_append_c(&encoded_wrapped, '\n'); + j = 0; + } + nm_str_buf_append_c(&encoded_wrapped, encoded[i]); + } + + g_assert_cmpint(strlen(nm_str_buf_get_str(&encoded_wrapped)), ==, encoded_wrapped.len); + + r = nm_unbase64mem_full(nm_str_buf_get_str(&encoded_wrapped), + nmtst_get_rand_bool() ? SIZE_MAX : encoded_wrapped.len, + nmtst_get_rand_bool(), + &decoded, + &decoded_size); + g_assert_cmpint(r, >=, 0); + g_assert_cmpmem(data, n, decoded, decoded_size); + + for (j = 0; j < encoded_wrapped.len; j++) + g_assert((nm_str_buf_get_str(&encoded_wrapped)[j] == '\n') == (j % (m + 1) == m)); + } +} + +/*****************************************************************************/ + +static void +_assert_unbase64mem(const char *input, const char *output, int ret) +{ + gs_free guint8 *buffer = NULL; + gsize size = 0; + int r; + + r = nm_unbase64mem_full(input, SIZE_MAX, nmtst_get_rand_bool(), &buffer, &size); + g_assert_cmpint(r, ==, ret); + + if (ret >= 0) { + g_assert_cmpmem(buffer, size, output, strlen(output)); + g_assert_cmpint(((const char *) buffer)[size], ==, '\0'); + } else { + g_assert(!buffer); + g_assert_cmpint(size, ==, 0); + } +} + +static void +test_unbase64mem2(void) +{ + /* Copied from systemd's TEST(unbase64mem) + * https://github.com/systemd/systemd/blob/688efe7703328c5a0251fafac55757b8864a9f9a/src/test/test-hexdecoct.c#L324 */ + + _assert_unbase64mem("", "", 0); + _assert_unbase64mem("Zg==", "f", 0); + _assert_unbase64mem("Zm8=", "fo", 0); + _assert_unbase64mem("Zm9v", "foo", 0); + _assert_unbase64mem("Zm9vYg==", "foob", 0); + _assert_unbase64mem("Zm9vYmE=", "fooba", 0); + _assert_unbase64mem("Zm9vYmFy", "foobar", 0); + + _assert_unbase64mem(" ", "", 0); + _assert_unbase64mem(" \n\r ", "", 0); + _assert_unbase64mem(" Zg\n== ", "f", 0); + _assert_unbase64mem(" Zm 8=\r", "fo", 0); + _assert_unbase64mem(" Zm9\n\r\r\nv ", "foo", 0); + _assert_unbase64mem(" Z m9vYg==\n\r", "foob", 0); + _assert_unbase64mem(" Zm 9vYmE= ", "fooba", 0); + _assert_unbase64mem(" Z m9v YmFy ", "foobar", 0); + + _assert_unbase64mem("A", NULL, -EPIPE); + _assert_unbase64mem("A====", NULL, -EINVAL); + _assert_unbase64mem("AAB==", NULL, -EINVAL); + _assert_unbase64mem(" A A A B = ", NULL, -EINVAL); + _assert_unbase64mem(" Z m 8 = q u u x ", NULL, -ENAMETOOLONG); +} + +/*****************************************************************************/ + +static void +_test_unbase64mem_mem(const char *base64, const guint8 *expected_arr, gsize expected_len) +{ + gs_free char *expected_base64 = NULL; + int r; + nm_auto_free guint8 *exp2_arr = NULL; + nm_auto_free guint8 *exp3_arr = NULL; + gsize exp2_len; + gsize exp3_len; + + expected_base64 = g_base64_encode(expected_arr, expected_len); + + r = nm_unbase64mem_full(expected_base64, strlen(expected_base64), TRUE, &exp2_arr, &exp2_len); + g_assert_cmpint(r, ==, 0); + g_assert_cmpmem(expected_arr, expected_len, exp2_arr, exp2_len); + + if (!nm_streq(base64, expected_base64)) { + r = nm_unbase64mem_full(base64, strlen(base64), TRUE, &exp3_arr, &exp3_len); + g_assert_cmpint(r, ==, 0); + g_assert_cmpmem(expected_arr, expected_len, exp3_arr, exp3_len); + } +} + +#define _test_unbase64mem(base64, expected_str) \ + _test_unbase64mem_mem(base64, (const guint8 *) "" expected_str "", NM_STRLEN(expected_str)) + +static void +_test_unbase64mem_inval(const char *base64) +{ + gs_free guint8 *exp_arr = NULL; + gsize exp_len = 0; + int r; + + r = nm_unbase64mem_full(base64, strlen(base64), TRUE, &exp_arr, &exp_len); + g_assert_cmpint(r, <, 0); + g_assert(!exp_arr); + g_assert(exp_len == 0); +} + +static void +test_unbase64mem3(void) +{ + gs_free char *rnd_base64 = NULL; + guint8 rnd_buf[30]; + guint i, rnd_len; + + _test_unbase64mem("", ""); + _test_unbase64mem(" ", ""); + _test_unbase64mem(" Y Q == ", "a"); + _test_unbase64mem(" Y WJjZGV mZ 2g = ", "abcdefgh"); + _test_unbase64mem_inval(" Y %WJjZGV mZ 2g = "); + _test_unbase64mem_inval(" Y %WJjZGV mZ 2g = a"); + _test_unbase64mem("YQ==", "a"); + _test_unbase64mem_inval("YQ==a"); + + rnd_len = nmtst_get_rand_uint32() % sizeof(rnd_buf); + for (i = 0; i < rnd_len; i++) + rnd_buf[i] = nmtst_get_rand_uint32() % 256; + rnd_base64 = g_base64_encode(rnd_buf, rnd_len); + _test_unbase64mem_mem(rnd_base64, rnd_buf, rnd_len); +} + +/*****************************************************************************/ + +static void +assert_path_compare(const char *a, const char *b, int expected) +{ + int r; + + g_assert(NM_IN_SET(expected, -1, 0, 1)); + + g_assert_cmpint(nm_path_compare(a, a), ==, 0); + g_assert_cmpint(nm_path_compare(b, b), ==, 0); + + r = nm_path_compare(a, b); + g_assert_cmpint(r, ==, expected); + r = nm_path_compare(b, a); + g_assert_cmpint(r, ==, -expected); + + g_assert(nm_path_equal(a, a) == 1); + g_assert(nm_path_equal(b, b) == 1); + g_assert(nm_path_equal(a, b) == (expected == 0)); + g_assert(nm_path_equal(b, a) == (expected == 0)); +} + +static void +test_path_compare(void) +{ + /* Copied from systemd. + * https://github.com/systemd/systemd/blob/bc85f8b51d962597360e982811e674c126850f56/src/test/test-path-util.c#L126 */ + + assert_path_compare("/goo", "/goo", 0); + assert_path_compare("/goo", "/goo", 0); + assert_path_compare("//goo", "/goo", 0); + assert_path_compare("//goo/////", "/goo", 0); + assert_path_compare("goo/////", "goo", 0); + assert_path_compare("/goo/boo", "/goo//boo", 0); + assert_path_compare("//goo/boo", "/goo/boo//", 0); + assert_path_compare("//goo/././//./boo//././//", "/goo/boo//.", 0); + assert_path_compare("/.", "//.///", 0); + assert_path_compare("/x", "x/", 1); + assert_path_compare("x/", "/", -1); + assert_path_compare("/x/./y", "x/y", 1); + assert_path_compare("/x/./y", "/x/y", 0); + assert_path_compare("/x/./././y", "/x/y/././.", 0); + assert_path_compare("./x/./././y", "./x/y/././.", 0); + assert_path_compare(".", "./.", 0); + assert_path_compare(".", "././.", 0); + assert_path_compare("./..", ".", 1); + assert_path_compare("x/.y", "x/y", -1); + assert_path_compare("foo", "/foo", -1); + assert_path_compare("/foo", "/foo/bar", -1); + assert_path_compare("/foo/aaa", "/foo/b", -1); + assert_path_compare("/foo/aaa", "/foo/b/a", -1); + assert_path_compare("/foo/a", "/foo/aaa", -1); + assert_path_compare("/foo/a/b", "/foo/aaa", -1); +} + +/*****************************************************************************/ + +static void +test_path_equal(void) +{ +#define _path_equal_check(path, expected) \ + G_STMT_START \ + { \ + const char *_path0 = (path); \ + const char *_expected = (expected); \ + gs_free char *_path = g_strdup(_path0); \ + const char *_path_result; \ + \ + _path_result = nm_path_simplify(_path); \ + g_assert(_path_result == _path); \ + g_assert_cmpstr(_path, ==, _expected); \ + } \ + G_STMT_END + + _path_equal_check("", ""); + _path_equal_check(".", "."); + _path_equal_check("..", ".."); + _path_equal_check("/..", "/.."); + _path_equal_check("//..", "/.."); + _path_equal_check("/.", "/"); + _path_equal_check("./", "."); + _path_equal_check("./.", "."); + _path_equal_check(".///.", "."); + _path_equal_check(".///./", "."); + _path_equal_check(".////", "."); + _path_equal_check("//..//foo/", "/../foo"); + _path_equal_check("///foo//./bar/.", "/foo/bar"); + _path_equal_check(".//./foo//./bar/.", "foo/bar"); +} + +/*****************************************************************************/ + +static void +assert_path_find_first_component(const char *path, + gboolean accept_dot_dot, + const char *const *expected, + int ret) +{ + const char *p; + + for (p = path;;) { + const char *e; + int r; + + r = nm_path_find_first_component(&p, accept_dot_dot, &e); + if (r <= 0) { + if (r == 0) { + if (path) + g_assert(p == path + strlen(path)); + else + g_assert(!p); + g_assert(!e); + } + g_assert(r == ret); + g_assert(!expected || !*expected); + return; + } + + g_assert(e); + g_assert(strcspn(e, "/") == (size_t) r); + g_assert(strlen(*expected) == (size_t) r); + g_assert(strncmp(e, *expected++, r) == 0); + } +} + +static void +test_path_find_first_component(void) +{ + gs_free char *hoge = NULL; + char foo[NAME_MAX * 2]; + + /* Copied from systemd. + * https://github.com/systemd/systemd/blob/bc85f8b51d962597360e982811e674c126850f56/src/test/test-path-util.c#L631 */ + + assert_path_find_first_component(NULL, false, NULL, 0); + assert_path_find_first_component("", false, NULL, 0); + assert_path_find_first_component("/", false, NULL, 0); + assert_path_find_first_component(".", false, NULL, 0); + assert_path_find_first_component("./", false, NULL, 0); + assert_path_find_first_component("./.", false, NULL, 0); + assert_path_find_first_component("..", false, NULL, -EINVAL); + assert_path_find_first_component("/..", false, NULL, -EINVAL); + assert_path_find_first_component("./..", false, NULL, -EINVAL); + assert_path_find_first_component("////./././//.", false, NULL, 0); + assert_path_find_first_component("a/b/c", false, NM_MAKE_STRV("a", "b", "c"), 0); + assert_path_find_first_component("././//.///aa/bbb//./ccc", + false, + NM_MAKE_STRV("aa", "bbb", "ccc"), + 0); + assert_path_find_first_component("././//.///aa/.../../bbb//./ccc/.", + false, + NM_MAKE_STRV("aa", "..."), + -EINVAL); + assert_path_find_first_component("//./aaa///.//./.bbb/..///c.//d.dd///..eeee/.", + false, + NM_MAKE_STRV("aaa", ".bbb"), + -EINVAL); + assert_path_find_first_component("a/foo./b", false, NM_MAKE_STRV("a", "foo.", "b"), 0); + + assert_path_find_first_component(NULL, true, NULL, 0); + assert_path_find_first_component("", true, NULL, 0); + assert_path_find_first_component("/", true, NULL, 0); + assert_path_find_first_component(".", true, NULL, 0); + assert_path_find_first_component("./", true, NULL, 0); + assert_path_find_first_component("./.", true, NULL, 0); + assert_path_find_first_component("..", true, NM_MAKE_STRV(".."), 0); + assert_path_find_first_component("/..", true, NM_MAKE_STRV(".."), 0); + assert_path_find_first_component("./..", true, NM_MAKE_STRV(".."), 0); + assert_path_find_first_component("////./././//.", true, NULL, 0); + assert_path_find_first_component("a/b/c", true, NM_MAKE_STRV("a", "b", "c"), 0); + assert_path_find_first_component("././//.///aa/bbb//./ccc", + true, + NM_MAKE_STRV("aa", "bbb", "ccc"), + 0); + assert_path_find_first_component("././//.///aa/.../../bbb//./ccc/.", + true, + NM_MAKE_STRV("aa", "...", "..", "bbb", "ccc"), + 0); + assert_path_find_first_component("//./aaa///.//./.bbb/..///c.//d.dd///..eeee/.", + true, + NM_MAKE_STRV("aaa", ".bbb", "..", "c.", "d.dd", "..eeee"), + 0); + assert_path_find_first_component("a/foo./b", true, NM_MAKE_STRV("a", "foo.", "b"), 0); + + memset(foo, 'a', sizeof(foo) - 1); + foo[sizeof(foo) - 1] = '\0'; + + assert_path_find_first_component(foo, false, NULL, -EINVAL); + assert_path_find_first_component(foo, true, NULL, -EINVAL); + + hoge = g_strjoin("", "a/b/c/", foo, "//d/e/.//f/", NULL); + g_assert(hoge); + + assert_path_find_first_component(hoge, false, NM_MAKE_STRV("a", "b", "c"), -EINVAL); + assert_path_find_first_component(hoge, true, NM_MAKE_STRV("a", "b", "c"), -EINVAL); +} + +/*****************************************************************************/ + +static void +assert_path_startswith(const char *path, + const char *prefix, + const char *skipped, + const char *expected) +{ + const char *p; + + p = nm_path_startswith(path, prefix); + g_assert_cmpstr(p, ==, expected); + if (p) { + gs_free char *q = NULL; + + g_assert(skipped); + q = g_strjoin("", skipped, p, NULL); + g_assert_cmpstr(q, ==, path); + g_assert(p == path + strlen(skipped)); + } else + g_assert(!skipped); +} + +static void +test_path_startswith(void) +{ + assert_path_startswith("/foo/bar/barfoo/", "/foo", "/foo/", "bar/barfoo/"); + assert_path_startswith("/foo/bar/barfoo/", "/foo/", "/foo/", "bar/barfoo/"); + assert_path_startswith("/foo/bar/barfoo/", "/", "/", "foo/bar/barfoo/"); + assert_path_startswith("/foo/bar/barfoo/", "////", "/", "foo/bar/barfoo/"); + assert_path_startswith("/foo/bar/barfoo/", "/foo//bar/////barfoo///", "/foo/bar/barfoo/", ""); + assert_path_startswith("/foo/bar/barfoo/", "/foo/bar/barfoo////", "/foo/bar/barfoo/", ""); + assert_path_startswith("/foo/bar/barfoo/", "/foo/bar///barfoo/", "/foo/bar/barfoo/", ""); + assert_path_startswith("/foo/bar/barfoo/", "/foo////bar/barfoo/", "/foo/bar/barfoo/", ""); + assert_path_startswith("/foo/bar/barfoo/", "////foo/bar/barfoo/", "/foo/bar/barfoo/", ""); + assert_path_startswith("/foo/bar/barfoo/", "/foo/bar/barfoo", "/foo/bar/barfoo/", ""); + + assert_path_startswith("/foo/bar/barfoo/", "/foo/bar/barfooa/", NULL, NULL); + assert_path_startswith("/foo/bar/barfoo/", "/foo/bar/barfooa", NULL, NULL); + assert_path_startswith("/foo/bar/barfoo/", "", NULL, NULL); + assert_path_startswith("/foo/bar/barfoo/", "/bar/foo", NULL, NULL); + assert_path_startswith("/foo/bar/barfoo/", "/f/b/b/", NULL, NULL); + assert_path_startswith("/foo/bar/barfoo/", "/foo/bar/barfo", NULL, NULL); + assert_path_startswith("/foo/bar/barfoo/", "/foo/bar/bar", NULL, NULL); + assert_path_startswith("/foo/bar/barfoo/", "/fo", NULL, NULL); +} + +/*****************************************************************************/ + +static void +assert_path_simplify(const char *in, const char *out) +{ + gs_free char *p = NULL; + + g_assert(in); + p = g_strdup(in); + nm_path_simplify(p); + g_assert_cmpstr(p, ==, out); +} + +static void +test_path_simplify(void) +{ + gs_free char *hoge = NULL; + gs_free char *hoge_out = NULL; + char foo[NAME_MAX * 2]; + + assert_path_simplify("", ""); + assert_path_simplify("aaa/bbb////ccc", "aaa/bbb/ccc"); + assert_path_simplify("//aaa/.////ccc", "/aaa/ccc"); + assert_path_simplify("///", "/"); + assert_path_simplify("///.//", "/"); + assert_path_simplify("///.//.///", "/"); + assert_path_simplify("////.././///../.", "/../.."); + assert_path_simplify(".", "."); + assert_path_simplify("./", "."); + assert_path_simplify(".///.//./.", "."); + assert_path_simplify(".///.//././/", "."); + assert_path_simplify("//./aaa///.//./.bbb/..///c.//d.dd///..eeee/.", + "/aaa/.bbb/../c./d.dd/..eeee"); + assert_path_simplify("//./aaa///.//./.bbb/..///c.//d.dd///..eeee/..", + "/aaa/.bbb/../c./d.dd/..eeee/.."); + assert_path_simplify(".//./aaa///.//./.bbb/..///c.//d.dd///..eeee/..", + "aaa/.bbb/../c./d.dd/..eeee/.."); + assert_path_simplify("..//./aaa///.//./.bbb/..///c.//d.dd///..eeee/..", + "../aaa/.bbb/../c./d.dd/..eeee/.."); + + memset(foo, 'a', sizeof(foo) - 1); + foo[sizeof(foo) - 1] = '\0'; + + assert_path_simplify(foo, foo); + + hoge = g_strjoin("", "/", foo, NULL); + g_assert(hoge); + assert_path_simplify(hoge, hoge); + nm_clear_g_free(&hoge); + + hoge = + g_strjoin("", "a////.//././//./b///././/./c/////././//./", foo, "//.//////d/e/.//f/", NULL); + g_assert(hoge); + + hoge_out = g_strjoin("", "a/b/c/", foo, "//.//////d/e/.//f/", NULL); + g_assert(hoge_out); + + assert_path_simplify(hoge, hoge_out); +} + +/*****************************************************************************/ + +static void +test_hostname_is_valid(void) +{ + g_assert(nm_hostname_is_valid("foobar", FALSE)); + g_assert(nm_hostname_is_valid("foobar.com", FALSE)); + g_assert(!nm_hostname_is_valid("foobar.com.", FALSE)); + g_assert(nm_hostname_is_valid("fooBAR", FALSE)); + g_assert(nm_hostname_is_valid("fooBAR.com", FALSE)); + g_assert(!nm_hostname_is_valid("fooBAR.", FALSE)); + g_assert(!nm_hostname_is_valid("fooBAR.com.", FALSE)); + g_assert(!nm_hostname_is_valid("fööbar", FALSE)); + g_assert(!nm_hostname_is_valid("", FALSE)); + g_assert(!nm_hostname_is_valid(".", FALSE)); + g_assert(!nm_hostname_is_valid("..", FALSE)); + g_assert(!nm_hostname_is_valid("foobar.", FALSE)); + g_assert(!nm_hostname_is_valid(".foobar", FALSE)); + g_assert(!nm_hostname_is_valid("foo..bar", FALSE)); + g_assert(!nm_hostname_is_valid("foo.bar..", FALSE)); + + G_STATIC_ASSERT_EXPR(NM_HOST_NAME_MAX <= HOST_NAME_MAX); + +#define _assert_hostname_length(n, valid) \ + G_STMT_START \ + { \ + const gsize _n = (n); \ + gs_free char *_h = g_strnfill(_n, 'x'); \ + gboolean _valid; \ + \ + _valid = nm_hostname_is_valid(_h, FALSE); \ + g_assert_cmpint(_valid, ==, (valid)); \ + } \ + G_STMT_END + + _assert_hostname_length(NM_HOST_NAME_MAX - 10, TRUE); + _assert_hostname_length(NM_HOST_NAME_MAX - 1, TRUE); + _assert_hostname_length(NM_HOST_NAME_MAX, TRUE); + _assert_hostname_length(NM_HOST_NAME_MAX + 1, FALSE); + _assert_hostname_length(NM_HOST_NAME_MAX + 10, FALSE); + + g_assert(!nm_hostname_is_valid( + "au-xph5-rvgrdsb5hcxc-47et3a5vvkrc-server-wyoz4elpdpe3.openstack.local", + FALSE)); + + g_assert(nm_hostname_is_valid("foobar", TRUE)); + g_assert(nm_hostname_is_valid("foobar.com", TRUE)); + g_assert(nm_hostname_is_valid("foobar.com.", TRUE)); + g_assert(nm_hostname_is_valid("fooBAR", TRUE)); + g_assert(nm_hostname_is_valid("fooBAR.com", TRUE)); + g_assert(!nm_hostname_is_valid("fooBAR.", TRUE)); + g_assert(nm_hostname_is_valid("fooBAR.com.", TRUE)); + g_assert(!nm_hostname_is_valid("fööbar", TRUE)); + g_assert(!nm_hostname_is_valid("", TRUE)); + g_assert(!nm_hostname_is_valid(".", TRUE)); + g_assert(!nm_hostname_is_valid("..", TRUE)); + g_assert(!nm_hostname_is_valid("foobar.", TRUE)); + g_assert(!nm_hostname_is_valid(".foobar", TRUE)); + g_assert(!nm_hostname_is_valid("foo..bar", TRUE)); + g_assert(!nm_hostname_is_valid("foo.bar..", TRUE)); + g_assert( + nm_hostname_is_valid("xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", + TRUE)); + g_assert( + !nm_hostname_is_valid("xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" + "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", + TRUE)); +} + +/*****************************************************************************/ + NMTST_DEFINE(); int @@ -1431,7 +2215,8 @@ main(int argc, char **argv) g_test_add_func("/general/test_nm_make_strv", test_make_strv); g_test_add_func("/general/test_nm_strdup_int", test_nm_strdup_int); g_test_add_func("/general/test_nm_strndup_a", test_nm_strndup_a); - g_test_add_func("/general/test_nm_ip4_addr_is_localhost", test_nm_ip4_addr_is_localhost); + g_test_add_func("/general/test_nm_utils_ip4_address_is_loopback", + test_nm_utils_ip4_address_is_loopback); g_test_add_func("/general/test_nm_utils_ip4_prefix_to_netmask", test_nm_utils_ip4_prefix_to_netmask); g_test_add_func("/general/test_unaligned", test_unaligned); @@ -1450,6 +2235,17 @@ main(int argc, char **argv) g_test_add_func("/general/test_utils_hashtable_cmp", test_utils_hashtable_cmp); g_test_add_func("/general/test_nm_g_source_sentinel", test_nm_g_source_sentinel); g_test_add_func("/general/test_nm_ascii", test_nm_ascii); + g_test_add_func("/general/test_parse_env_file", test_parse_env_file); + g_test_add_func("/general/test_unbase64char", test_unbase64char); + g_test_add_func("/general/test_unbase64mem1", test_unbase64mem1); + g_test_add_func("/general/test_unbase64mem2", test_unbase64mem2); + g_test_add_func("/general/test_unbase64mem3", test_unbase64mem3); + g_test_add_func("/general/test_path_compare", test_path_compare); + g_test_add_func("/general/test_path_equal", test_path_equal); + g_test_add_func("/general/test_path_find_first_component", test_path_find_first_component); + g_test_add_func("/general/test_path_startswith", test_path_startswith); + g_test_add_func("/general/test_path_simplify", test_path_simplify); + g_test_add_func("/general/test_hostname_is_valid", test_hostname_is_valid); return g_test_run(); } |