diff options
Diffstat (limited to 'shared/nm-utils')
23 files changed, 2546 insertions, 265 deletions
diff --git a/shared/nm-utils/nm-c-list.h b/shared/nm-utils/nm-c-list.h index b43d1441..5c73f574 100644 --- a/shared/nm-utils/nm-c-list.h +++ b/shared/nm-utils/nm-c-list.h @@ -78,4 +78,40 @@ nm_c_list_elem_free_all (CList *head, GDestroyNotify free_fcn) } } +/*****************************************************************************/ + +static inline gboolean +nm_c_list_move_before (CList *lst, CList *elem) +{ + nm_assert (lst); + nm_assert (elem); + nm_assert (c_list_contains (lst, elem)); + + if ( lst != elem + && lst->prev != elem) { + c_list_unlink_stale (elem); + c_list_link_before (lst, elem); + return TRUE; + } + return FALSE; +} +#define nm_c_list_move_tail(lst, elem) nm_c_list_move_before (lst, elem) + +static inline gboolean +nm_c_list_move_after (CList *lst, CList *elem) +{ + nm_assert (lst); + nm_assert (elem); + nm_assert (c_list_contains (lst, elem)); + + if ( lst != elem + && lst->next != elem) { + c_list_unlink_stale (elem); + c_list_link_after (lst, elem); + return TRUE; + } + return FALSE; +} +#define nm_c_list_move_front(lst, elem) nm_c_list_move_after (lst, elem) + #endif /* __NM_C_LIST_H__ */ diff --git a/shared/nm-utils/nm-dedup-multi.c b/shared/nm-utils/nm-dedup-multi.c index fc134e25..5bdc3e3c 100644 --- a/shared/nm-utils/nm-dedup-multi.c +++ b/shared/nm-utils/nm-dedup-multi.c @@ -24,6 +24,7 @@ #include "nm-dedup-multi.h" #include "nm-hash-utils.h" +#include "nm-c-list.h" /*****************************************************************************/ @@ -159,7 +160,7 @@ _entry_unpack (const NMDedupMultiEntry *entry, ASSERT_idx_type (*out_idx_type); /* for lookup of the head, we allow to omit object, but only - * if the idx_type does not parition the objects. Otherwise, we + * if the idx_type does not partition the objects. Otherwise, we * require a obj to compare. */ nm_assert ( !*out_lookup_head || ( *out_obj @@ -260,44 +261,27 @@ _add (NMDedupMultiIndex *self, nm_dedup_multi_entry_set_dirty (entry, FALSE); nm_assert (!head_existing || entry->head == head_existing); - - if (entry_order) { - nm_assert (entry_order->head == entry->head); - nm_assert (c_list_contains (&entry->lst_entries, &entry_order->lst_entries)); - nm_assert (c_list_contains (&entry_order->lst_entries, &entry->lst_entries)); - } + nm_assert (!entry_order || entry_order->head == entry->head); + nm_assert (!entry_order || c_list_contains (&entry->lst_entries, &entry_order->lst_entries)); + nm_assert (!entry_order || c_list_contains (&entry_order->lst_entries, &entry->lst_entries)); switch (mode) { case NM_DEDUP_MULTI_IDX_MODE_PREPEND_FORCE: if (entry_order) { - if ( entry_order != entry - && entry->lst_entries.next != &entry_order->lst_entries) { - c_list_unlink_stale (&entry->lst_entries); - c_list_link_before ((CList *) &entry_order->lst_entries, &entry->lst_entries); + if (nm_c_list_move_before ((CList *) &entry_order->lst_entries, &entry->lst_entries)) changed = TRUE; - } } else { - if (entry->lst_entries.prev != &entry->head->lst_entries_head) { - c_list_unlink_stale (&entry->lst_entries); - c_list_link_front ((CList *) &entry->head->lst_entries_head, &entry->lst_entries); + if (nm_c_list_move_front ((CList *) &entry->head->lst_entries_head, &entry->lst_entries)) changed = TRUE; - } } break; case NM_DEDUP_MULTI_IDX_MODE_APPEND_FORCE: if (entry_order) { - if ( entry_order != entry - && entry->lst_entries.prev != &entry_order->lst_entries) { - c_list_unlink_stale (&entry->lst_entries); - c_list_link_after ((CList *) &entry_order->lst_entries, &entry->lst_entries); + if (nm_c_list_move_after ((CList *) &entry_order->lst_entries, &entry->lst_entries)) changed = TRUE; - } } else { - if (entry->lst_entries.next != &entry->head->lst_entries_head) { - c_list_unlink_stale (&entry->lst_entries); - c_list_link_tail ((CList *) &entry->head->lst_entries_head, &entry->lst_entries); + if (nm_c_list_move_tail ((CList *) &entry->head->lst_entries_head, &entry->lst_entries)) changed = TRUE; - } } break; case NM_DEDUP_MULTI_IDX_MODE_PREPEND: @@ -441,10 +425,10 @@ nm_dedup_multi_index_add (NMDedupMultiIndex *self, * object will be placed in. You can omit this, and it will be automatically * detected (at the expense of an additional hash lookup). * Basically, this is the result of nm_dedup_multi_index_lookup_obj(), - * with the pecularity that if you know that @obj is not yet tracked, + * with the peculiarity that if you know that @obj is not yet tracked, * you may specify %NM_DEDUP_MULTI_ENTRY_MISSING. * @head_existing: an optional argument to safe a lookup for the head. If specified, - * it must be identical to nm_dedup_multi_index_lookup_head(), with the pecularity + * it must be identical to nm_dedup_multi_index_lookup_head(), with the peculiarity * that if the head is not yet tracked, you may specify %NM_DEDUP_MULTI_HEAD_ENTRY_MISSING * @out_entry: if give, return the added entry. This entry may have already exists (update) * or be newly created. If @obj is not partitionable according to @idx_type, @obj @@ -1022,33 +1006,20 @@ nm_dedup_multi_entry_reorder (const NMDedupMultiEntry *entry, if (!entry_order) { const NMDedupMultiHeadEntry *head_entry = entry->head; - nm_assert (c_list_contains (&head_entry->lst_entries_head, &entry->lst_entries)); if (order_after) { - if (head_entry->lst_entries_head.prev != &entry->lst_entries) { - c_list_unlink_stale ((CList *) &entry->lst_entries); - c_list_link_tail ((CList *) &head_entry->lst_entries_head, (CList *) &entry->lst_entries); + if (nm_c_list_move_tail ((CList *) &head_entry->lst_entries_head, (CList *) &entry->lst_entries)) return TRUE; - } } else { - if (head_entry->lst_entries_head.next != &entry->lst_entries) { - c_list_unlink_stale ((CList *) &entry->lst_entries); - c_list_link_front ((CList *) &head_entry->lst_entries_head, (CList *) &entry->lst_entries); + if (nm_c_list_move_front ((CList *) &head_entry->lst_entries_head, (CList *) &entry->lst_entries)) return TRUE; - } } - } else if (entry != entry_order) { + } else { if (order_after) { - if (entry_order->lst_entries.next != &entry->lst_entries) { - c_list_unlink_stale ((CList *) &entry->lst_entries); - c_list_link_after ((CList *) &entry_order->lst_entries, (CList *) &entry->lst_entries); + if (nm_c_list_move_after ((CList *) &entry_order->lst_entries, (CList *) &entry->lst_entries)) return TRUE; - } } else { - if (entry_order->lst_entries.prev != &entry->lst_entries) { - c_list_unlink_stale ((CList *) &entry->lst_entries); - c_list_link_before ((CList *) &entry_order->lst_entries, (CList *) &entry->lst_entries); + if (nm_c_list_move_before ((CList *) &entry_order->lst_entries, (CList *) &entry->lst_entries)) return TRUE; - } } } diff --git a/shared/nm-utils/nm-dedup-multi.h b/shared/nm-utils/nm-dedup-multi.h index 8d482de9..845b4c3e 100644 --- a/shared/nm-utils/nm-dedup-multi.h +++ b/shared/nm-utils/nm-dedup-multi.h @@ -47,7 +47,7 @@ typedef enum _NMDedupMultiIdxMode { NM_DEDUP_MULTI_IDX_MODE_APPEND, /* like NM_DEDUP_MULTI_IDX_MODE_APPEND, but if the object - * is already in teh cache, move it to the end. */ + * is already in the cache, move it to the end. */ NM_DEDUP_MULTI_IDX_MODE_APPEND_FORCE, } NMDedupMultiIdxMode; @@ -85,7 +85,7 @@ static inline const NMDedupMultiObj * nm_dedup_multi_obj_ref (const NMDedupMultiObj *obj) { /* ref and unref accept const pointers. Objects is supposed to be shared - * and kept immutable. Disallowing to take/retrun a reference to a const + * and kept immutable. Disallowing to take/return a reference to a const * NMPObject is cumbersome, because callers are precisely expected to * keep a ref on the otherwise immutable object. */ @@ -131,12 +131,12 @@ void nm_dedup_multi_index_obj_release (NMDedupMultiIndex *self, * routes by ifindex. As the ifindex is dynamic, it does not create an * idx-type instance for each ifindex. Instead, it has one idx-type for * all routes. But whenever accessing NMDedupMultiIndex with an NMDedupMultiObj, - * the partitioning NMDedupMultiIdxType takes into accound the NMDedupMultiObj + * the partitioning NMDedupMultiIdxType takes into account the NMDedupMultiObj * instance to associate it with the right list. * * Hence, a NMDedupMultiIdxEntry has a list of possibly multiple NMDedupMultiHeadEntry * instances, which each is the head for a list of NMDedupMultiEntry instances. - * In the platform example, the NMDedupMultiHeadEntry parition the indexed objects + * In the platform example, the NMDedupMultiHeadEntry partition the indexed objects * by their ifindex. */ struct _NMDedupMultiIdxType { union { diff --git a/shared/nm-utils/nm-errno.c b/shared/nm-utils/nm-errno.c new file mode 100644 index 00000000..30eb9a8e --- /dev/null +++ b/shared/nm-utils/nm-errno.c @@ -0,0 +1,198 @@ +/* NetworkManager -- Network link manager + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301 USA. + * + * Copyright 2018 Red Hat, Inc. + */ + +#include "nm-default.h" + +#include "nm-errno.h" + +#include <pthread.h> + +/*****************************************************************************/ + +NM_UTILS_LOOKUP_STR_DEFINE_STATIC (_geterror, +#if 0 + enum _NMErrno, +#else + int, +#endif + NM_UTILS_LOOKUP_DEFAULT (NULL), + + NM_UTILS_LOOKUP_STR_ITEM (NME_ERRNO_SUCCESS, "NME_ERRNO_SUCCESS"), + NM_UTILS_LOOKUP_STR_ITEM (NME_ERRNO_OUT_OF_RANGE, "NME_ERRNO_OUT_OF_RANGE"), + + NM_UTILS_LOOKUP_STR_ITEM (NME_UNSPEC, "NME_UNSPEC"), + NM_UTILS_LOOKUP_STR_ITEM (NME_BUG, "NME_BUG"), + NM_UTILS_LOOKUP_STR_ITEM (NME_NATIVE_ERRNO, "NME_NATIVE_ERRNO"), + + 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_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"), + NM_UTILS_LOOKUP_STR_ITEM (NME_NL_SEQ_MISMATCH, "NME_NL_SEQ_MISMATCH"), + NM_UTILS_LOOKUP_STR_ITEM (NME_NL_NOADDR, "NME_NL_NOADDR"), + + NM_UTILS_LOOKUP_STR_ITEM (NME_PL_NOT_FOUND, "not-found"), + NM_UTILS_LOOKUP_STR_ITEM (NME_PL_EXISTS, "exists"), + NM_UTILS_LOOKUP_STR_ITEM (NME_PL_WRONG_TYPE, "wrong-type"), + NM_UTILS_LOOKUP_STR_ITEM (NME_PL_NOT_SLAVE, "not-slave"), + NM_UTILS_LOOKUP_STR_ITEM (NME_PL_NO_FIRMWARE, "no-firmware"), + NM_UTILS_LOOKUP_STR_ITEM (NME_PL_OPNOTSUPP, "not-supported"), + NM_UTILS_LOOKUP_STR_ITEM (NME_PL_NETLINK, "netlink"), + NM_UTILS_LOOKUP_STR_ITEM (NME_PL_CANT_SET_MTU, "cant-set-mtu"), + + NM_UTILS_LOOKUP_ITEM_IGNORE (_NM_ERRNO_MININT), + NM_UTILS_LOOKUP_ITEM_IGNORE (_NM_ERRNO_RESERVED_LAST_PLUS_1), +); + +/** + * nm_strerror(): + * @nmerr: the NetworkManager specific errno to be converted + * to string. + * + * NetworkManager specific error numbers reserve a range in "errno.h" with + * our own defines. For numbers that don't fall into this range, the numbers + * are identical to the common error numbers. + * + * Idential to strerror(), g_strerror(), nm_strerror_native() for error numbers + * that are not in the reserved range of NetworkManager specific errors. + * + * Returns: (transfer none): the string representation of the error number. + */ +const char * +nm_strerror (int nmerr) +{ + const char *s; + + nmerr = nm_errno (nmerr); + + if (nmerr >= _NM_ERRNO_RESERVED_FIRST) { + s = _geterror (nmerr); + if (s) + return s; + } + return nm_strerror_native (nmerr); +} + +/*****************************************************************************/ + +/** + * nm_strerror_native_r: + * @errsv: the errno to convert to string. + * @buf: the output buffer where to write the string to. + * @buf_size: the length of buffer. + * + * This is like strerror_r(), with one difference: depending on the + * locale, the returned string is guaranteed to be valid UTF-8. + * Also, there is some confusion as to whether to use glibc's + * strerror_r() or the POXIX/XSI variant. This is abstracted + * by the function. + * + * Note that the returned buffer may also be a statically allocated + * buffer, and not the input buffer @buf. Consequently, the returned + * string may be longer than @buf_size. + * + * Returns: (transfer none): a NUL terminated error message. This is either a static + * string (that is never freed), or the provided @buf argumnt. + */ +const char * +nm_strerror_native_r (int errsv, char *buf, gsize buf_size) +{ + char *buf2; + + nm_assert (buf); + nm_assert (buf_size > 0); + +#if (_POSIX_C_SOURCE >= 200112L) && ! _GNU_SOURCE + /* XSI-compliant */ + { + int errno_saved = errno; + + if (strerror_r (errsv, buf, buf_size) != 0) { + g_snprintf (buf, buf_size, "Unspecified errno %d", errsv); + errno = errno_saved; + } + buf2 = buf; + } +#else + /* GNU-specific */ + buf2 = strerror_r (errsv, buf, buf_size); +#endif + + /* like g_strerror(), ensure that the error message is UTF-8. */ + if ( !g_get_charset (NULL) + && !g_utf8_validate (buf2, -1, NULL)) { + gs_free char *msg = NULL; + + msg = g_locale_to_utf8 (buf2, -1, NULL, NULL, NULL); + if (msg) { + g_strlcpy (buf, msg, buf_size); + buf2 = buf; + } + } + + return buf2; +} + +/** + * nm_strerror_native: + * @errsv: the errno integer from <errno.h> + * + * Like strerror(), but strerror() is not thread-safe and not guaranteed + * to be UTF-8. + * + * g_strerror() is a thread-safe variant of strerror(), however it caches + * all returned strings in a dictionary. That means, using this on untrusted + * error numbers can result in this cache to grow without limits. + * + * Instead, return a tread-local buffer. This way, it's thread-safe. + * + * There is a downside to this: subsequent calls of nm_strerror_native() + * overwrite the error message. + * + * Returns: (transfer none): the text representation of the error number. + */ +const char * +nm_strerror_native (int errsv) +{ + static _nm_thread_local char *buf_static = NULL; + char *buf; + + buf = buf_static; + if (G_UNLIKELY (!buf)) { + int errno_saved = errno; + pthread_key_t key; + + buf = g_malloc (NM_STRERROR_BUFSIZE); + buf_static = buf; + + if ( pthread_key_create (&key, g_free) != 0 + || pthread_setspecific (key, buf) != 0) { + /* Failure. We will leak the buffer when the thread exits. + * + * Nothing we can do about it really. For Debug builds we fail with an assertion. */ + nm_assert_not_reached (); + } + errno = errno_saved; + } + + return nm_strerror_native_r (errsv, buf, NM_STRERROR_BUFSIZE); +} diff --git a/shared/nm-utils/nm-errno.h b/shared/nm-utils/nm-errno.h new file mode 100644 index 00000000..d77735a7 --- /dev/null +++ b/shared/nm-utils/nm-errno.h @@ -0,0 +1,185 @@ +/* NetworkManager -- Network link manager + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301 USA. + * + * Copyright 2018 Red Hat, Inc. + */ + +#ifndef __NM_ERRNO_H__ +#define __NM_ERRNO_H__ + +#include <errno.h> + +/*****************************************************************************/ + +enum _NMErrno { + _NM_ERRNO_MININT = G_MININT, + _NM_ERRNO_MAXINT = G_MAXINT, + _NM_ERRNO_RESERVED_FIRST = 100000, + + + /* when we cannot represent a number as positive number, we resort to this + * number. Basically, the values G_MININT, -NME_ERRNO_SUCCESS, NME_ERRNO_SUCCESS + * and G_MAXINT all map to the same value. */ + NME_ERRNO_OUT_OF_RANGE = G_MAXINT, + + /* Indicate that the original errno was zero. Zero denotes *no error*, but we know something + * went wrong and we want to report some error. This is a placeholder to mean, something + * was wrong, but errno was zero. */ + NME_ERRNO_SUCCESS = G_MAXINT - 1, + + + /* an unspecified error. */ + NME_UNSPEC = _NM_ERRNO_RESERVED_FIRST, + + /* A bug, for example when an assertion failed. + * Should never happen. */ + NME_BUG, + + /* a native error number (from <errno.h>) cannot be mapped as + * an nm-error, because it is in the range [_NM_ERRNO_RESERVED_FIRST, + * _NM_ERRNO_RESERVED_LAST]. */ + NME_NATIVE_ERRNO, + + /* netlink errors. */ + NME_NL_SEQ_MISMATCH, + NME_NL_MSG_TRUNC, + NME_NL_MSG_TOOSHORT, + NME_NL_DUMP_INTR, + NME_NL_ATTRSIZE, + NME_NL_BAD_SOCK, + NME_NL_NOADDR, + NME_NL_MSG_OVERFLOW, + + /* platform errors. */ + NME_PL_NOT_FOUND, + NME_PL_EXISTS, + NME_PL_WRONG_TYPE, + NME_PL_NOT_SLAVE, + NME_PL_NO_FIRMWARE, + NME_PL_OPNOTSUPP, + NME_PL_NETLINK, + NME_PL_CANT_SET_MTU, + + _NM_ERRNO_RESERVED_LAST_PLUS_1, + _NM_ERRNO_RESERVED_LAST = _NM_ERRNO_RESERVED_LAST_PLUS_1 - 1, +}; + +/*****************************************************************************/ + +/* When we receive an errno from a system function, we can safely assume + * that the error number is not negative. We rely on that, and possibly just + * "return -errsv;" to signal an error. We also rely on that, because libc + * is our trusted base: meaning, if it cannot even succeed at setting errno + * according to specification, all bets are off. + * + * This macro returns the input argument, and asserts that the error variable + * is positive. + * + * In a sense, the macro is related to nm_errno_native() function, but the difference + * is that this macro asserts that @errsv is positive, while nm_errno_native() coerces + * negative values to be non-negative. */ +#define NM_ERRNO_NATIVE(errsv) \ + ({ \ + const int _errsv_x = (errsv); \ + \ + nm_assert (_errsv_x > 0); \ + _errsv_x; \ + }) + +/* Normalize native errno. + * + * Our API may return native error codes (<errno.h>) as negative values. This function + * takes such an errno, and normalizes it to their positive value. + * + * The special values G_MININT and zero are coerced to NME_ERRNO_OUT_OF_RANGE and NME_ERRNO_SUCCESS + * respectively. + * Other values are coerced to their inverse. + * Other positive values are returned unchanged. + * + * Basically, this normalizes errsv to be positive (taking care of two pathological cases). + */ +static inline int +nm_errno_native (int errsv) +{ + switch (errsv) { + case 0: return NME_ERRNO_SUCCESS; + case G_MININT: return NME_ERRNO_OUT_OF_RANGE; + default: + return errsv >= 0 ? errsv : -errsv; + } +} + +/* Normalizes an nm-error to be positive. + * + * Various API returns negative error codes, and this function converts the negative + * value to its positive. + * + * Note that @nmerr is on the domain of NetworkManager specific error numbers, + * which is not the same as the native error numbers (errsv from <errno.h>). But + * as far as normalizing goes, nm_errno() does exactly the same remapping as + * nm_errno_native(). */ +static inline int +nm_errno (int nmerr) +{ + return nm_errno_native (nmerr); +} + +/* this maps a native errno to a (always non-negative) nm-error number. + * + * Note that nm-error numbers are embedded into the range of regular + * errno. The only difference is, that nm-error numbers reserve a + * range (_NM_ERRNO_RESERVED_FIRST, _NM_ERRNO_RESERVED_LAST) for their + * own purpose. + * + * That means, converting an errno to nm-error number means in + * most cases just returning itself. + * Only pathological cases need special handling: + * + * - 0 is mapped to NME_ERRNO_SUCCESS; + * - G_MININT is mapped to NME_ERRNO_OUT_OF_RANGE; + * - values in the range of (+/-) [_NM_ERRNO_RESERVED_FIRST, _NM_ERRNO_RESERVED_LAST] + * are mapped to NME_NATIVE_ERRNO + * - all other values are their (positive) absolute value. + */ +static inline int +nm_errno_from_native (int errsv) +{ + switch (errsv) { + case 0: return NME_ERRNO_SUCCESS; + case G_MININT: return NME_ERRNO_OUT_OF_RANGE; + default: + if (errsv < 0) + errsv = -errsv; + return G_UNLIKELY ( errsv >= _NM_ERRNO_RESERVED_FIRST + && errsv <= _NM_ERRNO_RESERVED_LAST) + ? NME_NATIVE_ERRNO + : errsv; + } +} + +const char *nm_strerror (int nmerr); + +/*****************************************************************************/ + +#define NM_STRERROR_BUFSIZE 1024 + +const char *nm_strerror_native_r (int errsv, char *buf, gsize buf_size); +const char *nm_strerror_native (int errsv); + +/*****************************************************************************/ + +#endif /* __NM_ERRNO_H__ */ diff --git a/shared/nm-utils/nm-glib.h b/shared/nm-utils/nm-glib.h index 770cf0fe..e941e067 100644 --- a/shared/nm-utils/nm-glib.h +++ b/shared/nm-utils/nm-glib.h @@ -424,11 +424,13 @@ g_steal_pointer (gpointer pp) return ref; } +#endif -/* type safety */ -#define g_steal_pointer(pp) \ - (0 ? (*(pp)) : (g_steal_pointer) (pp)) +#ifdef g_steal_pointer +#undef g_steal_pointer #endif +#define g_steal_pointer(pp) \ + ((typeof (*(pp))) g_steal_pointer (pp)) /*****************************************************************************/ @@ -538,4 +540,28 @@ _nm_g_variant_new_printf (const char *format_string, ...) /*****************************************************************************/ +#if !GLIB_CHECK_VERSION (2, 47, 1) +/* Older versions of g_value_unset() only allowed to unset a GValue which + * was initialized previously. This was relaxed ([1], [2], [3]). + * + * Our nm_auto_unset_gvalue macro requires to be able to call g_value_unset(). + * Also, it is our general practice to allow for that. Add a compat implementation. + * + * [1] https://gitlab.gnome.org/GNOME/glib/commit/4b2d92a864f1505f1b08eb639d74293fa32681da + * [2] commit "Allow passing unset GValues to g_value_unset()" + * [3] https://bugzilla.gnome.org/show_bug.cgi?id=755766 + */ +static inline void +_nm_g_value_unset (GValue *value) +{ + g_return_if_fail (value); + + if (value->g_type != 0) + g_value_unset (value); +} +#define g_value_unset _nm_g_value_unset +#endif + +/*****************************************************************************/ + #endif /* __NM_GLIB_H__ */ diff --git a/shared/nm-utils/nm-hash-utils.c b/shared/nm-utils/nm-hash-utils.c index 80387c71..6e728e6b 100644 --- a/shared/nm-utils/nm-hash-utils.c +++ b/shared/nm-utils/nm-hash-utils.c @@ -71,7 +71,7 @@ again: * the first guint has only the entropy that nm_utils_random_bytes() * generated for the first 4 bytes and relies on a good random generator. * - * The first int is especially intersting for nm_hash_static() below, and we + * The first int is especially interesting for nm_hash_static() below, and we * want to have it all the entropy of t_arr. */ c_siphash_init (&siph_state, t_arr.v8); c_siphash_append (&siph_state, (const guint8 *) &t_arr, sizeof (t_arr)); diff --git a/shared/nm-utils/nm-hash-utils.h b/shared/nm-utils/nm-hash-utils.h index cf71a7e9..1a1e44f5 100644 --- a/shared/nm-utils/nm-hash-utils.h +++ b/shared/nm-utils/nm-hash-utils.h @@ -122,6 +122,9 @@ nm_hash_update (NMHashState *state, const void *ptr, gsize n) nm_hash_update ((state), &_val, sizeof (_val)); \ } G_STMT_END +#define nm_hash_update_valp(state, val) \ + nm_hash_update ((state), (val), sizeof (*(val))) \ + static inline void nm_hash_update_bool (NMHashState *state, bool val) { diff --git a/shared/nm-utils/nm-io-utils.c b/shared/nm-utils/nm-io-utils.c index 88cb13ff..51312748 100644 --- a/shared/nm-utils/nm-io-utils.c +++ b/shared/nm-utils/nm-io-utils.c @@ -29,6 +29,7 @@ #include "nm-shared-utils.h" #include "nm-secret-utils.h" +#include "nm-errno.h" /*****************************************************************************/ @@ -36,14 +37,12 @@ _nm_printf (3, 4) static int _get_contents_error (GError **error, int errsv, const char *format, ...) { - if (errsv < 0) - errsv = -errsv; - else if (!errsv) - errsv = errno; + nm_assert (NM_ERRNO_NATIVE (errsv)); if (error) { - char *msg; + gs_free char *msg = NULL; va_list args; + char bstrerr[NM_STRERROR_BUFSIZE]; va_start (args, format); msg = g_strdup_vprintf (format, args); @@ -52,11 +51,17 @@ _get_contents_error (GError **error, int errsv, const char *format, ...) G_FILE_ERROR, g_file_error_from_errno (errsv), "%s: %s", - msg, g_strerror (errsv)); - g_free (msg); + msg, + nm_strerror_native_r (errsv, bstrerr, sizeof (bstrerr))); } return -errsv; } +#define _get_contents_error_errno(error, ...) \ + ({ \ + int _errsv = (errno); \ + \ + _get_contents_error (error, _errsv, __VA_ARGS__); \ + }) static char * _mem_realloc (char *old, gboolean do_bzero_mem, gsize cur_len, gsize new_len) @@ -110,7 +115,7 @@ _mem_realloc (char *old, gboolean do_bzero_mem, gsize cur_len, gsize new_len) * A reimplementation of g_file_get_contents() with a few differences: * - accepts an open fd, instead of a path name. This allows you to * use openat(). - * - limits the maxium filesize to max_length. + * - limits the maximum filesize to max_length. * * Returns: a negative error code on failure. */ @@ -127,13 +132,14 @@ nm_utils_fd_get_contents (int fd, struct stat stat_buf; gs_free char *str = NULL; const bool do_bzero_mem = NM_FLAGS_HAS (flags, NM_UTILS_FILE_GET_CONTENTS_FLAG_SECRET); + int errsv; g_return_val_if_fail (fd >= 0, -EINVAL); g_return_val_if_fail (contents, -EINVAL); g_return_val_if_fail (!error || !*error, -EINVAL); if (fstat (fd, &stat_buf) < 0) - return _get_contents_error (error, 0, "failure during fstat"); + return _get_contents_error_errno (error, "failure during fstat"); if (!max_length) { /* default to a very large size, but not extreme */ @@ -156,7 +162,7 @@ nm_utils_fd_get_contents (int fd, if (n_read < 0) { if (do_bzero_mem) nm_explicit_bzero (str, n_stat); - return _get_contents_error (error, n_read, "error reading %zu bytes from file descriptor", n_stat); + return _get_contents_error (error, -n_read, "error reading %zu bytes from file descriptor", n_stat); } str[n_read] = '\0'; @@ -176,19 +182,19 @@ nm_utils_fd_get_contents (int fd, else { fd2 = fcntl (fd, F_DUPFD_CLOEXEC, 0); if (fd2 < 0) - return _get_contents_error (error, 0, "error during dup"); + return _get_contents_error_errno (error, "error during dup"); } if (!(f = fdopen (fd2, "r"))) { + errsv = errno; nm_close (fd2); - return _get_contents_error (error, 0, "failure during fdopen"); + return _get_contents_error (error, errsv, "failure during fdopen"); } n_have = 0; n_alloc = 0; while (!feof (f)) { - int errsv; gsize n_read; n_read = fread (buf, 1, sizeof (buf), f); @@ -262,13 +268,13 @@ nm_utils_fd_get_contents (int fd, * @flags: %NMUtilsFileGetContentsFlags for reading the file. * @contents: the output buffer with the file read. It is always * NUL terminated. The buffer is at most @max_length long, including - * the NUL byte. That is, it reads only files up to a length of - * @max_length - 1 bytes. + * the NUL byte. That is, it reads only files up to a length of + * @max_length - 1 bytes. * @length: optional output argument of the read file size. * * A reimplementation of g_file_get_contents() with a few differences: * - accepts an @dirfd to open @filename relative to that path via openat(). - * - limits the maxium filesize to max_length. + * - limits the maximum filesize to max_length. * - uses O_CLOEXEC on internal file descriptor * * Returns: a negative error code on failure. @@ -284,6 +290,7 @@ nm_utils_file_get_contents (int dirfd, { int fd; int errsv; + char bstrerr[NM_STRERROR_BUFSIZE]; g_return_val_if_fail (filename && filename[0], -EINVAL); @@ -297,8 +304,8 @@ nm_utils_file_get_contents (int dirfd, g_file_error_from_errno (errsv), "Failed to open file \"%s\" with openat: %s", filename, - g_strerror (errsv)); - return -errsv; + nm_strerror_native_r (errsv, bstrerr, sizeof (bstrerr))); + return -NM_ERRNO_NATIVE (errsv); } } else { fd = open (filename, O_RDONLY | O_CLOEXEC); @@ -310,8 +317,8 @@ nm_utils_file_get_contents (int dirfd, g_file_error_from_errno (errsv), "Failed to open file \"%s\": %s", filename, - g_strerror (errsv)); - return -errsv; + nm_strerror_native_r (errsv, bstrerr, sizeof (bstrerr))); + return -NM_ERRNO_NATIVE (errsv); } } return nm_utils_fd_get_contents (fd, @@ -341,6 +348,7 @@ nm_utils_file_set_contents (const char *filename, int errsv; gssize s; int fd; + char bstrerr[NM_STRERROR_BUFSIZE]; g_return_val_if_fail (filename, FALSE); g_return_val_if_fail (contents || !length, FALSE); @@ -351,7 +359,7 @@ nm_utils_file_set_contents (const char *filename, length = strlen (contents); tmp_name = g_strdup_printf ("%s.XXXXXX", filename); - fd = g_mkstemp_full (tmp_name, O_RDWR, mode); + fd = g_mkstemp_full (tmp_name, O_RDWR | O_CLOEXEC, mode); if (fd < 0) { errsv = errno; g_set_error (error, @@ -359,7 +367,7 @@ nm_utils_file_set_contents (const char *filename, g_file_error_from_errno (errsv), "failed to create file %s: %s", tmp_name, - g_strerror (errsv)); + nm_strerror_native_r (errsv, bstrerr, sizeof (bstrerr))); return FALSE; } @@ -378,7 +386,7 @@ nm_utils_file_set_contents (const char *filename, g_file_error_from_errno (errsv), "failed to write to file %s: %s", tmp_name, - g_strerror (errsv)); + nm_strerror_native_r (errsv, bstrerr, sizeof (bstrerr))); return FALSE; } @@ -395,20 +403,21 @@ nm_utils_file_set_contents (const char *filename, * guarantee the data is written to the disk before the metadata.) */ if ( lstat (filename, &statbuf) == 0 - && statbuf.st_size > 0 - && fsync (fd) != 0) { - errsv = errno; + && statbuf.st_size > 0) { + if (fsync (fd) != 0) { + errsv = errno; - nm_close (fd); - unlink (tmp_name); + nm_close (fd); + unlink (tmp_name); - g_set_error (error, - G_FILE_ERROR, - g_file_error_from_errno (errsv), - "failed to fsync %s: %s", - tmp_name, - g_strerror (errsv)); - return FALSE; + g_set_error (error, + G_FILE_ERROR, + g_file_error_from_errno (errsv), + "failed to fsync %s: %s", + tmp_name, + nm_strerror_native_r (errsv, bstrerr, sizeof (bstrerr))); + return FALSE; + } } nm_close (fd); @@ -422,7 +431,7 @@ nm_utils_file_set_contents (const char *filename, "failed to rename %s to %s: %s", tmp_name, filename, - g_strerror (errsv)); + nm_strerror_native_r (errsv, bstrerr, sizeof (bstrerr))); return FALSE; } diff --git a/shared/nm-utils/nm-jansson.h b/shared/nm-utils/nm-jansson.h index cacf87a6..5a73231f 100644 --- a/shared/nm-utils/nm-jansson.h +++ b/shared/nm-utils/nm-jansson.h @@ -34,11 +34,11 @@ /* Added in Jansson v2.8 */ #ifndef json_object_foreach_safe #define json_object_foreach_safe(object, n, key, value) \ - for(key = json_object_iter_key(json_object_iter(object)), \ - n = json_object_iter_next(object, json_object_key_to_iter(key)); \ - key && (value = json_object_iter_value(json_object_key_to_iter(key))); \ - key = json_object_iter_key(n), \ - n = json_object_iter_next(object, json_object_key_to_iter(key))) + for (key = json_object_iter_key(json_object_iter(object)), \ + n = json_object_iter_next(object, json_object_key_to_iter(key)); \ + key && (value = json_object_iter_value(json_object_key_to_iter(key))); \ + key = json_object_iter_key(n), \ + n = json_object_iter_next(object, json_object_key_to_iter(key))) #endif NM_AUTO_DEFINE_FCN0 (json_t *, _nm_auto_decref_json, json_decref) diff --git a/shared/nm-utils/nm-logging-fwd.h b/shared/nm-utils/nm-logging-fwd.h new file mode 100644 index 00000000..900dfff8 --- /dev/null +++ b/shared/nm-utils/nm-logging-fwd.h @@ -0,0 +1,113 @@ +/* NetworkManager -- Network link manager + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301 USA. + * + * Copyright (C) 2006 - 2018 Red Hat, Inc. + * Copyright (C) 2006 - 2008 Novell, Inc. + */ + +#ifndef __NM_LOGGING_DEFINES_H__ +#define __NM_LOGGING_DEFINES_H__ + +/* Log domains */ + +typedef enum { /*< skip >*/ + LOGD_NONE = 0LL, + LOGD_PLATFORM = (1LL << 0), /* Platform services */ + LOGD_RFKILL = (1LL << 1), + LOGD_ETHER = (1LL << 2), + LOGD_WIFI = (1LL << 3), + LOGD_BT = (1LL << 4), + LOGD_MB = (1LL << 5), /* mobile broadband */ + LOGD_DHCP4 = (1LL << 6), + LOGD_DHCP6 = (1LL << 7), + LOGD_PPP = (1LL << 8), + LOGD_WIFI_SCAN = (1LL << 9), + LOGD_IP4 = (1LL << 10), + LOGD_IP6 = (1LL << 11), + LOGD_AUTOIP4 = (1LL << 12), + LOGD_DNS = (1LL << 13), + LOGD_VPN = (1LL << 14), + LOGD_SHARING = (1LL << 15), /* Connection sharing/dnsmasq */ + LOGD_SUPPLICANT = (1LL << 16), /* Wi-Fi and 802.1x */ + LOGD_AGENTS = (1LL << 17), /* Secret agents */ + LOGD_SETTINGS = (1LL << 18), /* Settings */ + LOGD_SUSPEND = (1LL << 19), /* Suspend/Resume */ + LOGD_CORE = (1LL << 20), /* Core daemon and policy stuff */ + LOGD_DEVICE = (1LL << 21), /* Device state and activation */ + LOGD_OLPC = (1LL << 22), + LOGD_INFINIBAND = (1LL << 23), + LOGD_FIREWALL = (1LL << 24), + LOGD_ADSL = (1LL << 25), + LOGD_BOND = (1LL << 26), + LOGD_VLAN = (1LL << 27), + LOGD_BRIDGE = (1LL << 28), + LOGD_DBUS_PROPS = (1LL << 29), + LOGD_TEAM = (1LL << 30), + LOGD_CONCHECK = (1LL << 31), + LOGD_DCB = (1LL << 32), /* Data Center Bridging */ + LOGD_DISPATCH = (1LL << 33), + LOGD_AUDIT = (1LL << 34), + LOGD_SYSTEMD = (1LL << 35), + LOGD_VPN_PLUGIN = (1LL << 36), + LOGD_PROXY = (1LL << 37), + + __LOGD_MAX, + LOGD_ALL = (((__LOGD_MAX - 1LL) << 1) - 1LL), + LOGD_DEFAULT = LOGD_ALL & ~( + LOGD_DBUS_PROPS | + LOGD_WIFI_SCAN | + LOGD_VPN_PLUGIN | + 0), + + /* aliases: */ + LOGD_DHCP = LOGD_DHCP4 | LOGD_DHCP6, + LOGD_IP = LOGD_IP4 | LOGD_IP6, +} NMLogDomain; + +/* Log levels */ +typedef enum { /*< skip >*/ + LOGL_TRACE, + LOGL_DEBUG, + LOGL_INFO, + LOGL_WARN, + LOGL_ERR, + + _LOGL_N_REAL, /* the number of actual logging levels */ + + _LOGL_OFF = _LOGL_N_REAL, /* special logging level that is always disabled. */ + _LOGL_KEEP, /* special logging level to indicate that the logging level should not be changed. */ + + _LOGL_N, /* the number of logging levels including "OFF" */ +} NMLogLevel; + +gboolean _nm_log_enabled_impl (gboolean mt_require_locking, + NMLogLevel level, + NMLogDomain domain); + +void _nm_log_impl (const char *file, + guint line, + const char *func, + gboolean mt_require_locking, + NMLogLevel level, + NMLogDomain domain, + int error, + const char *ifname, + const char *con_uuid, + const char *fmt, + ...) _nm_printf (10, 11); + +#endif /* __NM_LOGGING_DEFINES_H__ */ diff --git a/shared/nm-utils/nm-macros-internal.h b/shared/nm-utils/nm-macros-internal.h index 9059783f..42299c96 100644 --- a/shared/nm-utils/nm-macros-internal.h +++ b/shared/nm-utils/nm-macros-internal.h @@ -32,19 +32,33 @@ /*****************************************************************************/ -#define _nm_packed __attribute__ ((packed)) -#define _nm_unused __attribute__ ((unused)) -#define _nm_pure __attribute__ ((pure)) -#define _nm_const __attribute__ ((const)) +#define _nm_packed __attribute__ ((__packed__)) +#define _nm_unused __attribute__ ((__unused__)) +#define _nm_used __attribute__ ((__used__)) +#define _nm_pure __attribute__ ((__pure__)) +#define _nm_const __attribute__ ((__const__)) #define _nm_printf(a,b) __attribute__ ((__format__ (__printf__, a, b))) -#define _nm_align(s) __attribute__ ((aligned (s))) +#define _nm_align(s) __attribute__ ((__aligned__ (s))) +#define _nm_section(s) __attribute__ ((__section__ (s))) #define _nm_alignof(type) __alignof (type) #define _nm_alignas(type) _nm_align (_nm_alignof (type)) -#define nm_auto(fcn) __attribute__ ((cleanup(fcn))) +#define nm_auto(fcn) __attribute__ ((__cleanup__(fcn))) + + +/* This is required to make LTO working. + * + * See https://gitlab.freedesktop.org/NetworkManager/NetworkManager/merge_requests/76#note_112694 + * https://gcc.gnu.org/bugzilla/show_bug.cgi?id=48200#c28 + */ +#ifndef __clang__ +#define _nm_externally_visible __attribute__ ((__externally_visible__)) +#else +#define _nm_externally_visible +#endif #if __GNUC__ >= 7 -#define _nm_fallthrough __attribute__ ((fallthrough)) +#define _nm_fallthrough __attribute__ ((__fallthrough__)) #else #define _nm_fallthrough #endif @@ -65,6 +79,28 @@ /*****************************************************************************/ +/* most of our code is single-threaded with a mainloop. Hence, we usually don't need + * any thread-safety. Sometimes, we do need thread-safety (nm-logging), but we can + * avoid locking if we are on the main-thread by: + * + * - modifications of shared data is done infrequently and only from the + * main-thread (nm_logging_setup()) + * - read-only access is done frequently (nm_logging_enabled()) + * - from the main-thread, we can do that without locking (because + * all modifications are also done on the main thread. + * - from other threads, we need locking. But this is expected to be + * done infrequently too. Important is the lock-free fast-path on the + * main-thread. + * + * By defining NM_THREAD_SAFE_ON_MAIN_THREAD you indicate that this code runs + * on the main-thread. It is by default defined to "1". If you have code that + * is also used on another thread, redefine the define to 0 (to opt in into + * the slow-path). + */ +#define NM_THREAD_SAFE_ON_MAIN_THREAD 1 + +/*****************************************************************************/ + #define NM_AUTO_DEFINE_FCN_VOID(CastType, name, func) \ static inline void name (void *v) \ { \ @@ -99,7 +135,7 @@ static inline void name (Type *v) \ * Call g_free() on a variable location when it goes out of scope. */ #define gs_free nm_auto(gs_local_free) -NM_AUTO_DEFINE_FCN_VOID (void *, gs_local_free, g_free) +NM_AUTO_DEFINE_FCN_VOID0 (void *, gs_local_free, g_free) /** * gs_unref_object: @@ -160,7 +196,7 @@ NM_AUTO_DEFINE_FCN0 (GHashTable *, gs_local_hashtable_unref, g_hash_table_unref) * of scope. */ #define gs_free_slist nm_auto(gs_local_free_slist) -NM_AUTO_DEFINE_FCN (GSList *, gs_local_free_slist, g_slist_free) +NM_AUTO_DEFINE_FCN0 (GSList *, gs_local_free_slist, g_slist_free) /** * gs_unref_bytes: @@ -178,7 +214,7 @@ NM_AUTO_DEFINE_FCN0 (GBytes *, gs_local_bytes_unref, g_bytes_unref) * Call g_strfreev() on a variable location when it goes out of scope. */ #define gs_strfreev nm_auto(gs_local_strfreev) -NM_AUTO_DEFINE_FCN (char **, gs_local_strfreev, g_strfreev) +NM_AUTO_DEFINE_FCN0 (char **, gs_local_strfreev, g_strfreev) /** * gs_free_error: @@ -222,7 +258,7 @@ static inline int nm_close (int fd); * However, let's never mix them. To free malloc'ed memory, always use * free() or nm_auto_free. */ -NM_AUTO_DEFINE_FCN_VOID (void *, _nm_auto_free_impl, free) +NM_AUTO_DEFINE_FCN_VOID0 (void *, _nm_auto_free_impl, free) #define nm_auto_free nm_auto(_nm_auto_free_impl) NM_AUTO_DEFINE_FCN0 (GVariantIter *, _nm_auto_free_variant_iter, g_variant_iter_free) @@ -231,7 +267,7 @@ NM_AUTO_DEFINE_FCN0 (GVariantIter *, _nm_auto_free_variant_iter, g_variant_iter_ NM_AUTO_DEFINE_FCN0 (GVariantBuilder *, _nm_auto_unref_variant_builder, g_variant_builder_unref) #define nm_auto_unref_variant_builder nm_auto(_nm_auto_unref_variant_builder) -NM_AUTO_DEFINE_FCN (GList *, _nm_auto_free_list, g_list_free) +NM_AUTO_DEFINE_FCN0 (GList *, _nm_auto_free_list, g_list_free) #define nm_auto_free_list nm_auto(_nm_auto_free_list) NM_AUTO_DEFINE_FCN0 (GChecksum *, _nm_auto_checksum_free, g_checksum_free) @@ -413,7 +449,7 @@ NM_G_ERROR_MSG (GError *error) /*****************************************************************************/ /* macro to return strlen() of a compile time string. */ -#define NM_STRLEN(str) ( sizeof ("" str) - 1 ) +#define NM_STRLEN(str) ( sizeof (""str"") - 1 ) /* returns the length of a NULL terminated array of pointers, * like g_strv_length() does. The difference is: @@ -458,6 +494,10 @@ NM_G_ERROR_MSG (GError *error) #endif #ifndef _NM_CC_SUPPORT_GENERIC +/* In the meantime, NetworkManager requires C11 and _Generic() should always be available. + * However, shared/nm-utils may also be used in VPN/applet, which possibly did not yet + * bump the C standard requirement. Leave this for the moment, but eventually we can + * drop it. */ #if (defined (__GNUC__) && (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 9 ))) || (defined (__clang__)) #define _NM_CC_SUPPORT_GENERIC 1 #else @@ -605,7 +645,7 @@ NM_G_ERROR_MSG (GError *error) * argument is not modified (CC), but you want to make it work also * for "char **". C doesn't allow this form of casting (for good reasons), * so the function makes a choice like g_strdupv(char**). That means, - * every time you want to call ith with a const argument, you need to + * every time you want to call it with a const argument, you need to * explicitly cast it. * * These macros do the cast, but they only accept a compatible input @@ -639,8 +679,11 @@ NM_G_ERROR_MSG (GError *error) #define NM_PROPAGATE_CONST(test_expr, ptr) (ptr) #endif +/* with the way it is implemented, the caller may or may not pass a trailing + * ',' and it will work. However, this makes the macro unsuitable for initializing + * an array. */ #define NM_MAKE_STRV(...) \ - ((const char *const[]) { __VA_ARGS__, NULL }) + ((const char *const[(sizeof (((const char *const[]) { __VA_ARGS__ })) / sizeof (const char *)) + 1]) { __VA_ARGS__ }) /*****************************************************************************/ @@ -676,7 +719,7 @@ NM_G_ERROR_MSG (GError *error) /* Beware that this does short-circuit evaluation (use "||" instead of "|") * which has a possibly unexpected non-function-like behavior. - * Use NM_IN_SET_SE if you need all arguments to be evaluted. */ + * Use NM_IN_SET_SE if you need all arguments to be evaluated. */ #define NM_IN_SET(x, ...) _NM_IN_SET(||, typeof (x), x, __VA_ARGS__) /* "SE" stands for "side-effect". Contrary to NM_IN_SET(), this does not do @@ -727,7 +770,7 @@ _NM_IN_STRSET_streq (const char *x, const char *s) /* Beware that this does short-circuit evaluation (use "||" instead of "|") * which has a possibly unexpected non-function-like behavior. - * Use NM_IN_STRSET_SE if you need all arguments to be evaluted. */ + * Use NM_IN_STRSET_SE if you need all arguments to be evaluated. */ #define NM_IN_STRSET(x, ...) _NM_IN_STRSET_EVAL_N(||, x, NM_NARG (__VA_ARGS__), __VA_ARGS__) /* "SE" stands for "side-effect". Contrary to NM_IN_STRSET(), this does not do @@ -817,8 +860,32 @@ fcn (void) \ /*****************************************************************************/ -#define nm_streq(s1, s2) (strcmp (s1, s2) == 0) -#define nm_streq0(s1, s2) (g_strcmp0 (s1, s2) == 0) +static inline gboolean +nm_streq (const char *s1, const char *s2) +{ + return strcmp (s1, s2) == 0; +} + +static inline gboolean +nm_streq0 (const char *s1, const char *s2) +{ + return (s1 == s2) + || (s1 && s2 && strcmp (s1, s2) == 0); +} + +#define NM_STR_HAS_PREFIX(str, prefix) \ + (strncmp ((str), ""prefix"", NM_STRLEN (prefix)) == 0) + +#define NM_STR_HAS_SUFFIX(str, suffix) \ + ({ \ + const char *_str = (str); \ + gsize _l = strlen (_str); \ + \ + ( (_l >= NM_STRLEN (suffix)) \ + && (memcmp (&_str[_l - NM_STRLEN (suffix)], \ + ""suffix"", \ + NM_STRLEN (suffix)) == 0)); \ + }) /*****************************************************************************/ @@ -832,6 +899,14 @@ nm_gstring_prepare (GString **l) return *l; } +static inline GString * +nm_gstring_add_space_delimiter (GString *str) +{ + if (str->len > 0) + g_string_append_c (str, ' '); + return str; +} + static inline const char * nm_str_not_empty (const char *str) { @@ -976,7 +1051,7 @@ static inline void nm_g_object_unref (gpointer obj) { /* g_object_unref() doesn't accept NULL. Usully, we workaround that - * by using g_clear_object(), but sometimes that is not convinient + * by using g_clear_object(), but sometimes that is not convenient * (for example as as destroy function for a hash table that can contain * NULL values). */ if (obj) @@ -1107,6 +1182,45 @@ nm_clear_g_cancellable (GCancellable **cancellable) return FALSE; } +/* If @cancellable_id is not 0, clear it and call g_cancellable_disconnect(). + * @cancellable may be %NULL, if there is nothing to disconnect. + * + * It's like nm_clear_g_signal_handler(), except that it uses g_cancellable_disconnect() + * instead of g_signal_handler_disconnect(). + * + * Note the warning in glib documentation about dead-lock and what g_cancellable_disconnect() + * actually does. */ +static inline gboolean +nm_clear_g_cancellable_disconnect (GCancellable *cancellable, gulong *cancellable_id) +{ + gulong id; + + if ( cancellable_id + && (id = *cancellable_id) != 0) { + *cancellable_id = 0; + g_cancellable_disconnect (cancellable, id); + return TRUE; + } + return FALSE; +} + +/*****************************************************************************/ + +static inline GVariant * +nm_g_variant_ref (GVariant *v) +{ + if (v) + g_variant_ref (v); + return v; +} + +static inline void +nm_g_variant_unref (GVariant *v) +{ + if (v) + g_variant_unref (v); +} + /*****************************************************************************/ /* Determine whether @x is a power of two (@x being an integer type). @@ -1157,7 +1271,7 @@ fcn_name (lookup_type val) \ /* Call the string-lookup-table function @fcn_name. If the function returns * %NULL, the numeric index is converted to string using a alloca() buffer. * Beware: this macro uses alloca(). */ -#define NM_UTILS_LOOKUP_STR(fcn_name, idx) \ +#define NM_UTILS_LOOKUP_STR_A(fcn_name, idx) \ ({ \ typeof (idx) _idx = (idx); \ const char *_s; \ @@ -1206,17 +1320,17 @@ fcn_name (lookup_type val) \ /*****************************************************************************/ -#define _NM_BACKPORT_SYMBOL_IMPL(VERSION, RETURN_TYPE, ORIG_FUNC, VERSIONED_FUNC, ARGS_TYPED, ARGS) \ -RETURN_TYPE VERSIONED_FUNC ARGS_TYPED; \ -RETURN_TYPE VERSIONED_FUNC ARGS_TYPED \ +#define _NM_BACKPORT_SYMBOL_IMPL(version, return_type, orig_func, versioned_func, args_typed, args) \ +return_type versioned_func args_typed; \ +_nm_externally_visible return_type versioned_func args_typed \ { \ - return ORIG_FUNC ARGS; \ + return orig_func args; \ } \ -RETURN_TYPE ORIG_FUNC ARGS_TYPED; \ -__asm__(".symver "G_STRINGIFY(VERSIONED_FUNC)", "G_STRINGIFY(ORIG_FUNC)"@"G_STRINGIFY(VERSION)) +return_type orig_func args_typed; \ +__asm__(".symver "G_STRINGIFY(versioned_func)", "G_STRINGIFY(orig_func)"@"G_STRINGIFY(version)) -#define NM_BACKPORT_SYMBOL(VERSION, RETURN_TYPE, FUNC, ARGS_TYPED, ARGS) \ -_NM_BACKPORT_SYMBOL_IMPL(VERSION, RETURN_TYPE, FUNC, _##FUNC##_##VERSION, ARGS_TYPED, ARGS) +#define NM_BACKPORT_SYMBOL(version, return_type, func, args_typed, args) \ +_NM_BACKPORT_SYMBOL_IMPL(version, return_type, func, _##func##_##version, args_typed, args) /*****************************************************************************/ @@ -1334,6 +1448,14 @@ nm_strcmp_p (gconstpointer a, gconstpointer b) : _b); \ }) +/* evaluates to (void) if _A or _B are not constant or of different types */ +#define NM_CONST_MAX(_A, _B) \ + (__builtin_choose_expr (( __builtin_constant_p (_A) \ + && __builtin_constant_p (_B) \ + && __builtin_types_compatible_p (typeof (_A), typeof (_B))), \ + ((_A) > (_B)) ? (_A) : (_B), \ + ((void) 0))) + /*****************************************************************************/ static inline guint @@ -1372,7 +1494,8 @@ nm_decode_version (guint version, guint *major, guint *minor, guint *micro) * If @str is longer then @trunc_at, the string is truncated and the closing * quote is instead '^' to indicate truncation. * - * Thus, the maximum stack allocated buffer will be @trunc_at+3. */ + * Thus, the maximum stack allocated buffer will be @trunc_at+3. The maximum + * buffer size must be a constant and not larger than 300. */ #define nm_strquote_a(trunc_at, str) \ ({ \ const char *const _str = (str); \ @@ -1383,6 +1506,8 @@ nm_decode_version (guint version, guint *major, guint *minor, guint *micro) const gsize _strlen_trunc = NM_MIN (strlen (_str), _trunc_at); \ char *_buf; \ \ + G_STATIC_ASSERT_EXPR ((trunc_at) <= 300); \ + \ _buf = g_alloca (_strlen_trunc + 3); \ _buf[0] = '"'; \ memcpy (&_buf[1], _str, _strlen_trunc); \ @@ -1407,19 +1532,30 @@ nm_decode_version (guint version, guint *major, guint *minor, guint *micro) _buf; \ }) -#define nm_sprintf_bufa(n_elements, format, ...) \ +/* it is "unsafe" because @bufsize must not be a constant expression and + * there is no check at compiletime. Regardless of that, the buffer size + * must not be larger than 300 bytes, as this gets stack allocated. */ +#define nm_sprintf_buf_unsafe_a(bufsize, format, ...) \ ({ \ char *_buf; \ int _buf_len; \ - typeof (n_elements) _n_elements = (n_elements); \ + typeof (bufsize) _bufsize = (bufsize); \ + \ + nm_assert (_bufsize <= 300); \ \ - _buf = g_alloca (_n_elements); \ - _buf_len = g_snprintf (_buf, _n_elements, \ + _buf = g_alloca (_bufsize); \ + _buf_len = g_snprintf (_buf, _bufsize, \ ""format"", ##__VA_ARGS__); \ - nm_assert (_buf_len < _n_elements); \ + nm_assert (_buf_len >= 0 && _buf_len < _bufsize); \ _buf; \ }) +#define nm_sprintf_bufa(bufsize, format, ...) \ + ({ \ + G_STATIC_ASSERT_EXPR ((bufsize) <= 300); \ + nm_sprintf_buf_unsafe_a ((bufsize), format, ##__VA_ARGS__); \ + }) + /* aims to alloca() a buffer and fill it with printf(format, name). * Note that format must not contain any format specifier except * "%s". @@ -1433,8 +1569,9 @@ nm_decode_version (guint version, guint *major, guint *minor, guint *micro) char *_buf2; \ \ nm_assert (_p_val_to_free && !*_p_val_to_free); \ - if (NM_STRLEN (format) + _name_len < 200) \ - _buf2 = nm_sprintf_bufa (NM_STRLEN (format) + _name_len, format, _name); \ + if ( NM_STRLEN (format) <= 290 \ + && _name_len < (gsize) (290 - NM_STRLEN (format))) \ + _buf2 = nm_sprintf_buf_unsafe_a (NM_STRLEN (format) + _name_len, format, _name); \ else { \ _buf2 = g_strdup_printf (format, _name); \ *_p_val_to_free = _buf2; \ @@ -1446,7 +1583,7 @@ nm_decode_version (guint version, guint *major, guint *minor, guint *micro) /** * The boolean type _Bool is C99 while we mostly stick to C89. However, _Bool is too - * convinient to miss and is effectively available in gcc and clang. So, just use it. + * convenient to miss and is effectively available in gcc and clang. So, just use it. * * Usually, one would include "stdbool.h" to get the "bool" define which aliases * _Bool. We provide this define here, because we want to make use of it anywhere. @@ -1458,7 +1595,7 @@ nm_decode_version (guint version, guint *major, guint *minor, guint *micro) * is a typedef for int). Especially when having boolean fields in a struct, we can * thereby easily save some space. * - * - _Bool type guarantees that two "true" expressions compare equal. E.g. the follwing + * - _Bool type guarantees that two "true" expressions compare equal. E.g. the following * will not work: * gboolean v1 = 1; * gboolean v2 = 2; diff --git a/shared/nm-utils/nm-random-utils.c b/shared/nm-utils/nm-random-utils.c index 3e968a8e..d7c7da42 100644 --- a/shared/nm-utils/nm-random-utils.c +++ b/shared/nm-utils/nm-random-utils.c @@ -81,7 +81,7 @@ nm_utils_random_bytes (void *p, size_t n) /* no or partial read. There is not enough entropy. * Fill the rest reading from urandom, and remember that - * some bits are not hight quality. */ + * some bits are not high quality. */ nm_assert (r < n); buf += r; n -= r; diff --git a/shared/nm-utils/nm-secret-utils.c b/shared/nm-utils/nm-secret-utils.c index 65f99c65..ec5cc6b1 100644 --- a/shared/nm-utils/nm-secret-utils.c +++ b/shared/nm-utils/nm-secret-utils.c @@ -17,6 +17,7 @@ * Boston, MA 02110-1301 USA. * * (C) Copyright 2018 Red Hat, Inc. + * (C) Copyright 2015 - 2019 Jason A. Donenfeld <Jason@zx2c4.com>. All Rights Reserved. */ #include "nm-default.h" @@ -132,3 +133,29 @@ nm_secret_buf_to_gbytes_take (NMSecretBuf *secret, gssize actual_len) _secret_buf_free, secret); } + +/*****************************************************************************/ + +/** + * nm_utils_memeqzero_secret: + * @data: the data pointer to check (may be %NULL if @length is zero). + * @length: the number of bytes to check. + * + * Checks that all bytes are zero. This always takes the same amount + * of time to prevent timing attacks. + * + * Returns: whether all bytes are zero. + */ +gboolean +nm_utils_memeqzero_secret (gconstpointer data, gsize length) +{ + const guint8 *const key = data; + volatile guint8 acc = 0; + gsize i; + + for (i = 0; i < length; i++) { + acc |= key[i]; + asm volatile("" : "=r"(acc) : "0"(acc)); + } + return 1 & ((acc - 1) >> 8); +} diff --git a/shared/nm-utils/nm-secret-utils.h b/shared/nm-utils/nm-secret-utils.h index 21a3c1ba..034ef7bd 100644 --- a/shared/nm-utils/nm-secret-utils.h +++ b/shared/nm-utils/nm-secret-utils.h @@ -43,7 +43,7 @@ nm_free_secret (char *secret) } } -NM_AUTO_DEFINE_FCN (char *, _nm_auto_free_secret, nm_free_secret) +NM_AUTO_DEFINE_FCN0 (char *, _nm_auto_free_secret, nm_free_secret) /** * nm_auto_free_secret: * @@ -76,6 +76,19 @@ typedef struct { } NMSecretPtr; static inline void +nm_secret_ptr_bzero (NMSecretPtr *secret) +{ + if (secret) { + if (secret->len > 0) { + if (secret->ptr) + nm_explicit_bzero (secret->ptr, secret->len); + } + } +} + +#define nm_auto_bzero_secret_ptr nm_auto(nm_secret_ptr_bzero) + +static inline void nm_secret_ptr_clear (NMSecretPtr *secret) { if (secret) { @@ -90,12 +103,24 @@ nm_secret_ptr_clear (NMSecretPtr *secret) #define nm_auto_clear_secret_ptr nm_auto(nm_secret_ptr_clear) +#define NM_SECRET_PTR_INIT() \ + ((const NMSecretPtr) { \ + .len = 0, \ + .ptr = NULL, \ + }) + #define NM_SECRET_PTR_STATIC(_len) \ ((const NMSecretPtr) { \ .len = _len, \ .ptr = ((guint8 [_len]) { }), \ }) +#define NM_SECRET_PTR_ARRAY(_arr) \ + ((const NMSecretPtr) { \ + .len = G_N_ELEMENTS (_arr) * sizeof ((_arr)[0]), \ + .ptr = &((_arr)[0]), \ + }) + static inline void nm_secret_ptr_clear_static (const NMSecretPtr *secret) { @@ -148,4 +173,6 @@ GBytes *nm_secret_buf_to_gbytes_take (NMSecretBuf *secret, gssize actual_len); /*****************************************************************************/ +gboolean nm_utils_memeqzero_secret (gconstpointer data, gsize length); + #endif /* __NM_SECRET_UTILS_H__ */ diff --git a/shared/nm-utils/nm-shared-utils.c b/shared/nm-utils/nm-shared-utils.c index d399ce3f..6a43c670 100644 --- a/shared/nm-utils/nm-shared-utils.c +++ b/shared/nm-utils/nm-shared-utils.c @@ -23,10 +23,12 @@ #include "nm-shared-utils.h" -#include <errno.h> #include <arpa/inet.h> #include <poll.h> #include <fcntl.h> +#include <sys/syscall.h> + +#include "nm-errno.h" /*****************************************************************************/ @@ -34,7 +36,116 @@ const void *const _NM_PTRARRAY_EMPTY[1] = { NULL }; /*****************************************************************************/ -const NMIPAddr nm_ip_addr_zero = { 0 }; +const NMIPAddr nm_ip_addr_zero = { }; + +/* 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 + * uses memcpy() to access @src, so alignment is not an issue. + * + * Only potential pitfalls: + * + * - it allows for @addr_family to be AF_UNSPEC. If that is the case (and the + * caller allows for that), the caller MUST provide @out_addr_family. + * - when setting @dst to an IPv4 address, the trailing bytes are not touched. + * Meaning, if @dst is an NMIPAddr union, only the first bytes will be set. + * If that matter to you, clear @dst before. */ +gboolean +nm_ip_addr_set_from_untrusted (int addr_family, + gpointer dst, + gconstpointer src, + gsize src_len, + int *out_addr_family) +{ + nm_assert (dst); + + switch (addr_family) { + case AF_UNSPEC: + if (!out_addr_family) { + /* when the callers allow undefined @addr_family, they must provide + * an @out_addr_family argument. */ + nm_assert_not_reached (); + return FALSE; + } + switch (src_len) { + case sizeof (struct in_addr): addr_family = AF_INET; break; + case sizeof (struct in6_addr): addr_family = AF_INET6; break; + default: + return FALSE; + } + break; + case AF_INET: + if (src_len != sizeof (struct in_addr)) + return FALSE; + break; + case AF_INET6: + if (src_len != sizeof (struct in6_addr)) + return FALSE; + break; + default: + /* when the callers allow undefined @addr_family, they must provide + * an @out_addr_family argument. */ + nm_assert (out_addr_family); + return FALSE; + } + + nm_assert (src); + + memcpy (dst, src, src_len); + NM_SET_OUT (out_addr_family, addr_family); + return TRUE; +} + +/*****************************************************************************/ + +pid_t +nm_utils_gettid (void) +{ + return (pid_t) syscall (SYS_gettid); +} + +/* Used for asserting that this function is called on the main-thread. + * The main-thread is determined by remembering the thread-id + * of when the function was called the first time. + * + * When forking, the thread-id is again reset upon first call. */ +gboolean +_nm_assert_on_main_thread (void) +{ + G_LOCK_DEFINE_STATIC (lock); + static pid_t seen_tid; + static pid_t seen_pid; + pid_t tid; + pid_t pid; + gboolean success = FALSE; + + tid = nm_utils_gettid (); + nm_assert (tid != 0); + + G_LOCK (lock); + + if (G_LIKELY (tid == seen_tid)) { + /* we don't care about false positives (when the process forked, and the thread-id + * is accidentally re-used) . It's for assertions only. */ + success = TRUE; + } else { + pid = getpid (); + nm_assert (pid != 0); + + if ( seen_tid == 0 + || seen_pid != pid) { + /* either this is the first time we call the function, or the process + * forked. In both cases, remember the thread-id. */ + seen_tid = tid; + seen_pid = pid; + success = TRUE; + } + } + + G_UNLOCK (lock); + + return success; +} /*****************************************************************************/ @@ -59,6 +170,41 @@ nm_utils_strbuf_append_c (char **buf, gsize *len, char c) } void +nm_utils_strbuf_append_bin (char **buf, gsize *len, gconstpointer str, gsize str_len) +{ + switch (*len) { + case 0: + return; + case 1: + if (str_len == 0) { + (*buf)[0] = '\0'; + return; + } + (*buf)[0] = '\0'; + *len = 0; + (*buf)++; + return; + default: + if (str_len == 0) { + (*buf)[0] = '\0'; + return; + } + if (str_len >= *len) { + memcpy (*buf, str, *len - 1); + (*buf)[*len - 1] = '\0'; + *buf = &(*buf)[*len]; + *len = 0; + } else { + memcpy (*buf, str, str_len); + *buf = &(*buf)[str_len]; + (*buf)[0] = '\0'; + *len -= str_len; + } + return; + } +} + +void nm_utils_strbuf_append_str (char **buf, gsize *len, const char *str) { gsize src_len; @@ -118,7 +264,7 @@ nm_utils_strbuf_append (char **buf, gsize *len, const char *format, ...) /** * nm_utils_strbuf_seek_end: * @buf: the input/output buffer - * @len: the input/output lenght of the buffer. + * @len: the input/output length of the buffer. * * Commonly, one uses nm_utils_strbuf_append*(), to incrementally * append strings to the buffer. However, sometimes we need to use @@ -459,34 +605,25 @@ nm_utils_ip_is_site_local (int addr_family, gboolean nm_utils_parse_inaddr_bin (int addr_family, const char *text, + int *out_addr_family, gpointer out_addr) { NMIPAddr addrbin; g_return_val_if_fail (text, FALSE); - if (addr_family == AF_UNSPEC) + if (addr_family == AF_UNSPEC) { + g_return_val_if_fail (!out_addr || out_addr_family, FALSE); addr_family = strchr (text, ':') ? AF_INET6 : AF_INET; - else + } else g_return_val_if_fail (NM_IN_SET (addr_family, AF_INET, AF_INET6), FALSE); - /* use a temporary variable @addrbin, to guarantee that @out_addr - * is only modified on success. */ if (inet_pton (addr_family, text, &addrbin) != 1) return FALSE; - if (out_addr) { - switch (addr_family) { - case AF_INET: - *((in_addr_t *) out_addr) = addrbin.addr4; - break; - case AF_INET6: - *((struct in6_addr *) out_addr) = addrbin.addr6; - break; - default: - nm_assert_not_reached (); - } - } + NM_SET_OUT (out_addr_family, addr_family); + if (out_addr) + nm_ip_addr_set (addr_family, out_addr, &addrbin); return TRUE; } @@ -498,9 +635,7 @@ nm_utils_parse_inaddr (int addr_family, NMIPAddr addrbin; char addrstr_buf[MAX (INET_ADDRSTRLEN, INET6_ADDRSTRLEN)]; - nm_assert (!out_addr || !*out_addr); - - if (!nm_utils_parse_inaddr_bin (addr_family, text, &addrbin)) + if (!nm_utils_parse_inaddr_bin (addr_family, text, &addr_family, &addrbin)) return FALSE; NM_SET_OUT (out_addr, g_strdup (inet_ntop (addr_family, &addrbin, addrstr_buf, sizeof (addrstr_buf)))); return TRUE; @@ -509,6 +644,7 @@ nm_utils_parse_inaddr (int addr_family, gboolean nm_utils_parse_inaddr_prefix_bin (int addr_family, const char *text, + int *out_addr_family, gpointer out_addr, int *out_prefix) { @@ -517,19 +653,14 @@ nm_utils_parse_inaddr_prefix_bin (int addr_family, const char *slash; const char *addrstr; NMIPAddr addrbin; - int addr_len; g_return_val_if_fail (text, FALSE); - if (addr_family == AF_UNSPEC) + if (addr_family == AF_UNSPEC) { + g_return_val_if_fail (!out_addr || out_addr_family, FALSE); addr_family = strchr (text, ':') ? AF_INET6 : AF_INET; - - if (addr_family == AF_INET) - addr_len = sizeof (in_addr_t); - else if (addr_family == AF_INET6) - addr_len = sizeof (struct in6_addr); - else - g_return_val_if_reached (FALSE); + } else + g_return_val_if_fail (NM_IN_SET (addr_family, AF_INET, AF_INET6), FALSE); slash = strchr (text, '/'); if (slash) @@ -541,6 +672,8 @@ nm_utils_parse_inaddr_prefix_bin (int addr_family, return FALSE; if (slash) { + /* For IPv4, `ip addr add` supports the prefix-length as a netmask. We don't + * do that. */ prefix = _nm_utils_ascii_str_to_int64 (slash + 1, 10, 0, addr_family == AF_INET ? 32 : 128, @@ -549,8 +682,9 @@ nm_utils_parse_inaddr_prefix_bin (int addr_family, return FALSE; } + NM_SET_OUT (out_addr_family, addr_family); if (out_addr) - memcpy (out_addr, &addrbin, addr_len); + nm_ip_addr_set (addr_family, out_addr, &addrbin); NM_SET_OUT (out_prefix, prefix); return TRUE; } @@ -564,7 +698,7 @@ nm_utils_parse_inaddr_prefix (int addr_family, NMIPAddr addrbin; char addrstr_buf[MAX (INET_ADDRSTRLEN, INET6_ADDRSTRLEN)]; - if (!nm_utils_parse_inaddr_prefix_bin (addr_family, text, &addrbin, out_prefix)) + if (!nm_utils_parse_inaddr_prefix_bin (addr_family, text, &addr_family, &addrbin, out_prefix)) return FALSE; NM_SET_OUT (out_addr, g_strdup (inet_ntop (addr_family, &addrbin, addrstr_buf, sizeof (addrstr_buf)))); return TRUE; @@ -1122,7 +1256,7 @@ nm_utils_error_is_notfound (GError *error) */ gboolean nm_g_object_set_property (GObject *object, - const char *property_name, + const char *property_name, const GValue *value, GError **error) { @@ -1195,30 +1329,136 @@ nm_g_object_set_property (GObject *object, return TRUE; } +#define _set_property(object, property_name, gtype, gtype_set, value, error) \ + G_STMT_START { \ + nm_auto_unset_gvalue GValue gvalue = { 0 }; \ + \ + g_value_init (&gvalue, gtype); \ + gtype_set (&gvalue, (value)); \ + return nm_g_object_set_property ((object), (property_name), &gvalue, (error)); \ + } G_STMT_END + +gboolean +nm_g_object_set_property_string (GObject *object, + const char *property_name, + const char *value, + GError **error) +{ + _set_property (object, property_name, G_TYPE_STRING, g_value_set_string, value, error); +} + +gboolean +nm_g_object_set_property_string_static (GObject *object, + const char *property_name, + const char *value, + GError **error) +{ + _set_property (object, property_name, G_TYPE_STRING, g_value_set_static_string, value, error); +} + +gboolean +nm_g_object_set_property_string_take (GObject *object, + const char *property_name, + char *value, + GError **error) +{ + _set_property (object, property_name, G_TYPE_STRING, g_value_take_string, value, error); +} + gboolean nm_g_object_set_property_boolean (GObject *object, - const char *property_name, + const char *property_name, gboolean value, GError **error) { - nm_auto_unset_gvalue GValue gvalue = { 0 }; + _set_property (object, property_name, G_TYPE_BOOLEAN, g_value_set_boolean, !!value, error); +} - g_value_init (&gvalue, G_TYPE_BOOLEAN); - g_value_set_boolean (&gvalue, !!value); - return nm_g_object_set_property (object, property_name, &gvalue, error); +gboolean +nm_g_object_set_property_char (GObject *object, + const char *property_name, + gint8 value, + GError **error) +{ + /* glib says about G_TYPE_CHAR: + * + * The type designated by G_TYPE_CHAR is unconditionally an 8-bit signed integer. + * + * This is always a (signed!) char. */ + _set_property (object, property_name, G_TYPE_CHAR, g_value_set_schar, value, error); +} + +gboolean +nm_g_object_set_property_uchar (GObject *object, + const char *property_name, + guint8 value, + GError **error) +{ + _set_property (object, property_name, G_TYPE_UCHAR, g_value_set_uchar, value, error); +} + +gboolean +nm_g_object_set_property_int (GObject *object, + const char *property_name, + int value, + GError **error) +{ + _set_property (object, property_name, G_TYPE_INT, g_value_set_int, value, error); +} + +gboolean +nm_g_object_set_property_int64 (GObject *object, + const char *property_name, + gint64 value, + GError **error) +{ + _set_property (object, property_name, G_TYPE_INT64, g_value_set_int64, value, error); } gboolean nm_g_object_set_property_uint (GObject *object, - const char *property_name, + const char *property_name, guint value, GError **error) { - nm_auto_unset_gvalue GValue gvalue = { 0 }; + _set_property (object, property_name, G_TYPE_UINT, g_value_set_uint, value, error); +} + +gboolean +nm_g_object_set_property_uint64 (GObject *object, + const char *property_name, + guint64 value, + GError **error) +{ + _set_property (object, property_name, G_TYPE_UINT64, g_value_set_uint64, value, error); +} - g_value_init (&gvalue, G_TYPE_UINT); - g_value_set_uint (&gvalue, value); - return nm_g_object_set_property (object, property_name, &gvalue, error); +gboolean +nm_g_object_set_property_flags (GObject *object, + const char *property_name, + GType gtype, + guint value, + GError **error) +{ + nm_assert (({ + nm_auto_unref_gtypeclass GTypeClass *gtypeclass = g_type_class_ref (gtype); + G_IS_FLAGS_CLASS (gtypeclass); + })); + _set_property (object, property_name, gtype, g_value_set_flags, value, error); +} + +gboolean +nm_g_object_set_property_enum (GObject *object, + const char *property_name, + GType gtype, + int value, + GError **error) +{ + nm_assert (({ + nm_auto_unref_gtypeclass GTypeClass *gtypeclass = g_type_class_ref (gtype); + G_IS_ENUM_CLASS (gtypeclass); + })); + _set_property (object, property_name, gtype, g_value_set_enum, value, error); } GParamSpec * @@ -1233,6 +1473,53 @@ nm_g_object_class_find_property_from_gtype (GType gtype, /*****************************************************************************/ +/** + * nm_g_type_find_implementing_class_for_property: + * @gtype: the GObject type which has a property @pname + * @pname: the name of the property to look up + * + * This is only a helper function for printf debugging. It's not + * used in actual code. Hence, the function just asserts that + * @pname and @gtype arguments are suitable. It cannot fail. + * + * Returns: the most ancestor type of @gtype, that + * implements the property @pname. It means, it + * searches the type hierarchy to find the type + * that added @pname. + */ +GType +nm_g_type_find_implementing_class_for_property (GType gtype, + const char *pname) +{ + nm_auto_unref_gtypeclass GObjectClass *klass = NULL; + GParamSpec *pspec; + + g_return_val_if_fail (pname, G_TYPE_INVALID); + + klass = g_type_class_ref (gtype); + g_return_val_if_fail (G_IS_OBJECT_CLASS (klass), G_TYPE_INVALID); + + pspec = g_object_class_find_property (klass, pname); + g_return_val_if_fail (pspec, G_TYPE_INVALID); + + gtype = G_TYPE_FROM_CLASS (klass); + + while (TRUE) { + nm_auto_unref_gtypeclass GObjectClass *k = NULL; + + k = g_type_class_ref (g_type_parent (gtype)); + + g_return_val_if_fail (G_IS_OBJECT_CLASS (k), G_TYPE_INVALID); + + if (g_object_class_find_property (k, pname) != pspec) + return gtype; + + gtype = G_TYPE_FROM_CLASS (k); + } +} + +/*****************************************************************************/ + static void _str_append_escape (GString *s, char ch) { @@ -1564,7 +1851,7 @@ nm_utils_fd_wait_for_event (int fd, int event, gint64 timeout_ns) r = ppoll (&pollfd, 1, pts, NULL); if (r < 0) - return -errno; + return -NM_ERRNO_NATIVE (errno); if (r == 0) return 0; return pollfd.revents; @@ -1591,10 +1878,12 @@ nm_utils_fd_read_loop (int fd, void *buf, size_t nbytes, bool do_poll) k = read (fd, p, nbytes); if (k < 0) { - if (errno == EINTR) + int errsv = errno; + + if (errsv == EINTR) continue; - if (errno == EAGAIN && do_poll) { + if (errsv == EAGAIN && do_poll) { /* We knowingly ignore any return value here, * and expect that any error/EOF is reported @@ -1604,7 +1893,7 @@ nm_utils_fd_read_loop (int fd, void *buf, size_t nbytes, bool do_poll) continue; } - return n > 0 ? n : -errno; + return n > 0 ? n : -NM_ERRNO_NATIVE (errsv); } if (k == 0) @@ -2126,3 +2415,327 @@ _nm_utils_unescape_spaces (char *str) } #undef IS_SPACE + +/*****************************************************************************/ + +typedef struct { + gpointer callback_user_data; + GCancellable *cancellable; + NMUtilsInvokeOnIdleCallback callback; + gulong cancelled_id; + guint idle_id; +} InvokeOnIdleData; + +static gboolean +_nm_utils_invoke_on_idle_cb_idle (gpointer user_data) +{ + InvokeOnIdleData *data = user_data; + + data->idle_id = 0; + nm_clear_g_signal_handler (data->cancellable, &data->cancelled_id); + + data->callback (data->callback_user_data, data->cancellable); + nm_g_object_unref (data->cancellable); + g_slice_free (InvokeOnIdleData, data); + return G_SOURCE_REMOVE; +} + +static void +_nm_utils_invoke_on_idle_cb_cancelled (GCancellable *cancellable, + InvokeOnIdleData *data) +{ + /* on cancellation, we invoke the callback synchronously. */ + nm_clear_g_signal_handler (data->cancellable, &data->cancelled_id); + nm_clear_g_source (&data->idle_id); + data->callback (data->callback_user_data, data->cancellable); + nm_g_object_unref (data->cancellable); + g_slice_free (InvokeOnIdleData, data); +} + +void +nm_utils_invoke_on_idle (NMUtilsInvokeOnIdleCallback callback, + gpointer callback_user_data, + GCancellable *cancellable) +{ + InvokeOnIdleData *data; + + g_return_if_fail (callback); + + data = g_slice_new (InvokeOnIdleData); + data->callback = callback; + data->callback_user_data = callback_user_data; + data->cancellable = nm_g_object_ref (cancellable); + if ( cancellable + && !g_cancellable_is_cancelled (cancellable)) { + /* if we are passed a non-cancelled cancellable, we register to the "cancelled" + * signal an invoke the callback synchronously (from the signal handler). + * + * We don't do that, + * - if the cancellable is already cancelled (because we don't want to invoke + * the callback synchronously from the caller). + * - if we have no cancellable at hand. */ + data->cancelled_id = g_signal_connect (cancellable, + "cancelled", + G_CALLBACK (_nm_utils_invoke_on_idle_cb_cancelled), + data); + } else + data->cancelled_id = 0; + data->idle_id = g_idle_add (_nm_utils_invoke_on_idle_cb_idle, data); +} + +/*****************************************************************************/ + +int +nm_utils_getpagesize (void) +{ + static volatile int val = 0; + long l; + int v; + + v = g_atomic_int_get (&val); + + if (G_UNLIKELY (v == 0)) { + l = sysconf (_SC_PAGESIZE); + + g_return_val_if_fail (l > 0 && l < G_MAXINT, 4*1024); + + v = (int) l; + if (!g_atomic_int_compare_and_exchange (&val, 0, v)) { + v = g_atomic_int_get (&val); + g_return_val_if_fail (v > 0, 4*1024); + } + } + + nm_assert (v > 0); +#if NM_MORE_ASSERTS > 5 + nm_assert (v == getpagesize ()); + nm_assert (v == sysconf (_SC_PAGESIZE)); +#endif + + return v; +} + +gboolean +nm_utils_memeqzero (gconstpointer data, gsize length) +{ + const unsigned char *p = data; + int len; + + /* Taken from https://github.com/rustyrussell/ccan/blob/9d2d2c49f053018724bcc6e37029da10b7c3d60d/ccan/mem/mem.c#L92, + * CC-0 licensed. */ + + /* Check first 16 bytes manually */ + for (len = 0; len < 16; len++) { + if (!length) + return TRUE; + if (*p) + return FALSE; + p++; + length--; + } + + /* Now we know that's zero, memcmp with self. */ + return memcmp (data, p, length) == 0; +} + +/** + * nm_utils_bin2hexstr_full: + * @addr: pointer of @length bytes. If @length is zero, this may + * also be %NULL. + * @length: number of bytes in @addr. May also be zero, in which + * case this will return an empty string. + * @delimiter: either '\0', otherwise the output string will have the + * given delimiter character between each two hex numbers. + * @upper_case: if TRUE, use upper case ASCII characters for hex. + * @out: if %NULL, the function will allocate a new buffer of + * either (@length*2+1) or (@length*3) bytes, depending on whether + * a @delimiter is specified. In that case, the allocated buffer will + * be returned and must be freed by the caller. + * If not %NULL, the buffer must already be preallocated and contain + * at least (@length*2+1) or (@length*3) bytes, depending on the delimiter. + * + * Returns: the binary value converted to a hex string. If @out is given, + * this always returns @out. If @out is %NULL, a newly allocated string + * is returned. + */ +char * +nm_utils_bin2hexstr_full (gconstpointer addr, + gsize length, + char delimiter, + gboolean upper_case, + char *out) +{ + const guint8 *in = addr; + const char *LOOKUP = upper_case ? "0123456789ABCDEF" : "0123456789abcdef"; + char *out0; + + if (out) + out0 = out; + else { + out0 = out = g_new (char, delimiter == '\0' + ? length * 2 + 1 + : length * 3); + } + + /* @out must contain at least @length*3 bytes if @delimiter is set, + * otherwise, @length*2+1. */ + + if (length > 0) { + nm_assert (in); + for (;;) { + const guint8 v = *in++; + + *out++ = LOOKUP[v >> 4]; + *out++ = LOOKUP[v & 0x0F]; + length--; + if (!length) + break; + if (delimiter) + *out++ = delimiter; + } + } + + *out = '\0'; + return out0; +} + +guint8 * +nm_utils_hexstr2bin_full (const char *hexstr, + gboolean allow_0x_prefix, + gboolean delimiter_required, + const char *delimiter_candidates, + gsize required_len, + guint8 *buffer, + gsize buffer_len, + gsize *out_len) +{ + const char *in = hexstr; + guint8 *out = buffer; + gboolean delimiter_has = TRUE; + guint8 delimiter = '\0'; + gsize len; + + nm_assert (hexstr); + nm_assert (buffer); + nm_assert (required_len > 0 || out_len); + + if ( allow_0x_prefix + && in[0] == '0' + && in[1] == 'x') + in += 2; + + while (TRUE) { + const guint8 d1 = in[0]; + guint8 d2; + int i1, i2; + + i1 = nm_utils_hexchar_to_int (d1); + if (i1 < 0) + goto fail; + + /* If there's no leading zero (ie "aa:b:cc") then fake it */ + d2 = in[1]; + if ( d2 + && (i2 = nm_utils_hexchar_to_int (d2)) >= 0) { + *out++ = (i1 << 4) + i2; + d2 = in[2]; + if (!d2) + break; + in += 2; + } else { + /* Fake leading zero */ + *out++ = i1; + if (!d2) { + if (!delimiter_has) { + /* when using no delimiter, there must be pairs of hex chars */ + goto fail; + } + break; + } + in += 1; + } + + if (--buffer_len == 0) + goto fail; + + if (delimiter_has) { + if (d2 != delimiter) { + if (delimiter) + goto fail; + if (delimiter_candidates) { + while (delimiter_candidates[0]) { + if (delimiter_candidates++[0] == d2) + delimiter = d2; + } + } + if (!delimiter) { + if (delimiter_required) + goto fail; + delimiter_has = FALSE; + continue; + } + } + in++; + } + } + + len = out - buffer; + if ( required_len == 0 + || len == required_len) { + NM_SET_OUT (out_len, len); + return buffer; + } + +fail: + NM_SET_OUT (out_len, 0); + return NULL; +} + +guint8 * +nm_utils_hexstr2bin_alloc (const char *hexstr, + gboolean allow_0x_prefix, + gboolean delimiter_required, + const char *delimiter_candidates, + gsize required_len, + gsize *out_len) +{ + guint8 *buffer; + gsize buffer_len, len; + + g_return_val_if_fail (hexstr, NULL); + + nm_assert (required_len > 0 || out_len); + + if ( allow_0x_prefix + && hexstr[0] == '0' + && hexstr[1] == 'x') + hexstr += 2; + + if (!hexstr[0]) + goto fail; + + if (required_len > 0) + buffer_len = required_len; + else + buffer_len = strlen (hexstr) / 2 + 3; + + buffer = g_malloc (buffer_len); + + if (nm_utils_hexstr2bin_full (hexstr, + FALSE, + delimiter_required, + delimiter_candidates, + required_len, + buffer, + buffer_len, + &len)) { + NM_SET_OUT (out_len, len); + return buffer; + } + + g_free (buffer); + +fail: + NM_SET_OUT (out_len, 0); + return NULL; +} diff --git a/shared/nm-utils/nm-shared-utils.h b/shared/nm-utils/nm-shared-utils.h index 82eebc9d..65e34959 100644 --- a/shared/nm-utils/nm-shared-utils.h +++ b/shared/nm-utils/nm-shared-utils.h @@ -26,6 +26,18 @@ /*****************************************************************************/ +pid_t nm_utils_gettid (void); + +gboolean _nm_assert_on_main_thread (void); + +#if NM_MORE_ASSERTS > 5 +#define NM_ASSERT_ON_MAIN_THREAD() G_STMT_START { nm_assert (_nm_assert_on_main_thread ()); } G_STMT_END +#else +#define NM_ASSERT_ON_MAIN_THREAD() G_STMT_START { ; } G_STMT_END +#endif + +/*****************************************************************************/ + static inline gboolean _NM_INT_NOT_NEGATIVE (gssize val) { @@ -76,8 +88,9 @@ static inline char nm_utils_addr_family_to_char (int addr_family) { switch (addr_family) { - case AF_INET: return '4'; - case AF_INET6: return '6'; + case AF_UNSPEC: return 'X'; + case AF_INET: return '4'; + case AF_INET6: return '6'; } g_return_val_if_reached ('?'); } @@ -101,6 +114,7 @@ typedef struct { union { guint8 addr_ptr[1]; in_addr_t addr4; + struct in_addr addr4_struct; struct in6_addr addr6; /* NMIPAddr is really a union for IP addresses. @@ -113,16 +127,29 @@ typedef struct { extern const NMIPAddr nm_ip_addr_zero; static inline void -nm_ip_addr_set (int addr_family, gpointer dst, const NMIPAddr *src) +nm_ip_addr_set (int addr_family, gpointer dst, gconstpointer src) { nm_assert_addr_family (addr_family); nm_assert (dst); nm_assert (src); - if (addr_family != AF_INET6) - *((in_addr_t *) dst) = src->addr4; - else - *((struct in6_addr *) dst) = src->addr6; + memcpy (dst, + src, + (addr_family != AF_INET6) + ? sizeof (in_addr_t) + : sizeof (struct in6_addr)); +} + +gboolean nm_ip_addr_set_from_untrusted (int addr_family, + gpointer dst, + gconstpointer src, + gsize src_len, + int *out_addr_family); + +static inline gboolean +nm_ip4_addr_is_localhost (in_addr_t addr4) +{ + return (addr4 & htonl (0xFF000000u)) == htonl (0x7F000000u); } /*****************************************************************************/ @@ -217,19 +244,7 @@ nm_ip_addr_set (int addr_family, gpointer dst, const NMIPAddr *src) /*****************************************************************************/ -static inline gboolean -nm_utils_mem_all_zero (gconstpointer mem, gsize len) -{ - const guint8 *p; - - for (p = mem; len-- > 0; p++) { - if (*p != 0) - return FALSE; - } - - /* incidentally, a buffer with len==0, is also *all-zero*. */ - return TRUE; -} +gboolean nm_utils_memeqzero (gconstpointer data, gsize length); /*****************************************************************************/ @@ -262,6 +277,17 @@ nm_memdup (gconstpointer data, gsize size) return p; } +static inline char * +_nm_strndup_a_step (char *s, const char *str, gsize len) +{ + NM_PRAGMA_WARNING_DISABLE ("-Wstringop-truncation"); + if (len > 0) + strncpy (s, str, len); + s[len] = '\0'; + return s; + NM_PRAGMA_WARNING_REENABLE; +} + /* Similar to g_strndup(), however, if the string (including the terminating * NUL char) fits into alloca_maxlen, this will alloca() the memory. * @@ -270,7 +296,12 @@ nm_memdup (gconstpointer data, gsize size) * * In case malloc() is necessary, @out_str_free will be set (this string * must be freed afterwards). It is permissible to pass %NULL as @out_str_free, - * if you ensure that len < alloca_maxlen. */ + * if you ensure that len < alloca_maxlen. + * + * Note that just like g_strndup(), this always returns a buffer with @len + 1 + * bytes, even if strlen(@str) is shorter than that (NUL terminated early). We fill + * the buffer with strncpy(), which means, that @str is copied up to the first + * NUL character and then filled with NUL characters. */ #define nm_strndup_a(alloca_maxlen, str, len, out_str_free) \ ({ \ const gsize _alloca_maxlen = (alloca_maxlen); \ @@ -279,6 +310,8 @@ nm_memdup (gconstpointer data, gsize size) char **const _out_str_free = (out_str_free); \ char *_s; \ \ + G_STATIC_ASSERT_EXPR ((alloca_maxlen) <= 300); \ + \ if ( _out_str_free \ && _len >= _alloca_maxlen) { \ _s = g_malloc (_len + 1); \ @@ -287,14 +320,46 @@ nm_memdup (gconstpointer data, gsize size) g_assert (_len < _alloca_maxlen); \ _s = g_alloca (_len + 1); \ } \ - if (_len > 0) \ - strncpy (_s, _str, _len); \ - _s[_len] = '\0'; \ - _s; \ + _nm_strndup_a_step (_s, _str, _len); \ }) /*****************************************************************************/ +/* generic macro to convert an int to a (heap allocated) string. + * + * Usually, an inline function nm_strdup_int64() would be enough. However, + * that cannot be used for guint64. So, we would also need nm_strdup_uint64(). + * This causes subtle error potential, because the caller needs to ensure to + * use the right one (and compiler isn't going to help as it silently casts). + * + * Instead, this generic macro is supposed to handle all integers correctly. */ +#if _NM_CC_SUPPORT_GENERIC +#define nm_strdup_int(val) \ + _Generic ((val), \ + char: g_strdup_printf ("%d", (int) (val)), \ + \ + signed char: g_strdup_printf ("%d", (signed) (val)), \ + signed short: g_strdup_printf ("%d", (signed) (val)), \ + signed: g_strdup_printf ("%d", (signed) (val)), \ + signed long: g_strdup_printf ("%ld", (signed long) (val)), \ + signed long long: g_strdup_printf ("%lld", (signed long long) (val)), \ + \ + unsigned char: g_strdup_printf ("%u", (unsigned) (val)), \ + unsigned short: g_strdup_printf ("%u", (unsigned) (val)), \ + unsigned: g_strdup_printf ("%u", (unsigned) (val)), \ + unsigned long: g_strdup_printf ("%lu", (unsigned long) (val)), \ + unsigned long long: g_strdup_printf ("%llu", (unsigned long long) (val)) \ + ) +#else +#define nm_strdup_int(val) \ + ( ( sizeof (val) == sizeof (guint64) \ + && ((typeof (val)) -1) > 0) \ + ? g_strdup_printf ("%"G_GUINT64_FORMAT, (guint64) (val)) \ + : g_strdup_printf ("%"G_GINT64_FORMAT, (gint64) (val))) +#endif + +/*****************************************************************************/ + extern const void *const _NM_PTRARRAY_EMPTY[1]; #define NM_PTRARRAY_EMPTY(type) ((type const*) _NM_PTRARRAY_EMPTY) @@ -315,6 +380,7 @@ _nm_utils_strbuf_init (char *buf, gsize len, char **p_buf_ptr, gsize *p_buf_len) void nm_utils_strbuf_append (char **buf, gsize *len, const char *format, ...) _nm_printf (3, 4); void nm_utils_strbuf_append_c (char **buf, gsize *len, char c); void nm_utils_strbuf_append_str (char **buf, gsize *len, const char *str); +void nm_utils_strbuf_append_bin (char **buf, gsize *len, gconstpointer str, gsize str_len); void nm_utils_strbuf_seek_end (char **buf, gsize *len); const char *nm_strquote (char *buf, gsize buf_len, const char *str); @@ -429,6 +495,7 @@ gboolean nm_utils_ip_is_site_local (int addr_family, gboolean nm_utils_parse_inaddr_bin (int addr_family, const char *text, + int *out_addr_family, gpointer out_addr); gboolean nm_utils_parse_inaddr (int addr_family, @@ -437,6 +504,7 @@ gboolean nm_utils_parse_inaddr (int addr_family, gboolean nm_utils_parse_inaddr_prefix_bin (int addr_family, const char *text, + int *out_addr_family, gpointer out_addr, int *out_prefix); @@ -579,19 +647,6 @@ _nm_g_slice_free_fcn_define (16) /*****************************************************************************/ -static inline int -nm_errno (int errsv) -{ - /* several API returns negative errno values as errors. Normalize - * negative values to positive values. - * - * As a special case, map G_MININT to G_MAXINT. If you care about the - * distinction, then check for G_MININT before. */ - return errsv >= 0 - ? errsv - : ((errsv == G_MININT) ? G_MAXINT : -errsv); -} - /** * NMUtilsError: * @NM_UTILS_ERROR_UNKNOWN: unknown or unclassified error @@ -657,35 +712,106 @@ nm_utils_error_set_literal (GError **error, int error_code, const char *literal) g_set_error ((error), NM_UTILS_ERROR, error_code, __VA_ARGS__) #define nm_utils_error_set_errno(error, errsv, fmt, ...) \ - g_set_error ((error), \ - NM_UTILS_ERROR, \ - NM_UTILS_ERROR_UNKNOWN, \ - fmt, \ - ##__VA_ARGS__, \ - g_strerror (nm_errno (errsv))) + G_STMT_START { \ + char _bstrerr[NM_STRERROR_BUFSIZE]; \ + \ + g_set_error ((error), \ + NM_UTILS_ERROR, \ + NM_UTILS_ERROR_UNKNOWN, \ + fmt, \ + ##__VA_ARGS__, \ + nm_strerror_native_r (({ \ + const int _errsv = (errsv); \ + \ + ( _errsv >= 0 \ + ? _errsv \ + : ( G_UNLIKELY (_errsv == G_MININT) \ + ? G_MAXINT \ + : -errsv)); \ + }), \ + _bstrerr, \ + sizeof (_bstrerr))); \ + } G_STMT_END /*****************************************************************************/ gboolean nm_g_object_set_property (GObject *object, - const char *property_name, + const char *property_name, const GValue *value, GError **error); +gboolean nm_g_object_set_property_string (GObject *object, + const char *property_name, + const char *value, + GError **error); + +gboolean nm_g_object_set_property_string_static (GObject *object, + const char *property_name, + const char *value, + GError **error); + +gboolean nm_g_object_set_property_string_take (GObject *object, + const char *property_name, + char *value, + GError **error); + gboolean nm_g_object_set_property_boolean (GObject *object, - const char *property_name, + const char *property_name, gboolean value, GError **error); +gboolean nm_g_object_set_property_char (GObject *object, + const char *property_name, + gint8 value, + GError **error); + +gboolean nm_g_object_set_property_uchar (GObject *object, + const char *property_name, + guint8 value, + GError **error); + +gboolean nm_g_object_set_property_int (GObject *object, + const char *property_name, + int value, + GError **error); + +gboolean nm_g_object_set_property_int64 (GObject *object, + const char *property_name, + gint64 value, + GError **error); + gboolean nm_g_object_set_property_uint (GObject *object, - const char *property_name, + const char *property_name, guint value, GError **error); +gboolean nm_g_object_set_property_uint64 (GObject *object, + const char *property_name, + guint64 value, + GError **error); + +gboolean nm_g_object_set_property_flags (GObject *object, + const char *property_name, + GType gtype, + guint value, + GError **error); + +gboolean nm_g_object_set_property_enum (GObject *object, + const char *property_name, + GType gtype, + int value, + GError **error); + GParamSpec *nm_g_object_class_find_property_from_gtype (GType gtype, const char *property_name); /*****************************************************************************/ +GType nm_g_type_find_implementing_class_for_property (GType gtype, + const char *pname); + +/*****************************************************************************/ + typedef enum { NM_UTILS_STR_UTF8_SAFE_FLAG_NONE = 0, NM_UTILS_STR_UTF8_SAFE_FLAG_ESCAPE_CTRL = 0x0001, @@ -949,4 +1075,84 @@ void _nm_utils_user_data_unpack (gpointer user_data, int nargs, ...); const char *_nm_utils_escape_spaces (const char *str, char **to_free); char *_nm_utils_unescape_spaces (char *str); +/*****************************************************************************/ + +typedef void (*NMUtilsInvokeOnIdleCallback) (gpointer callback_user_data, + GCancellable *cancellable); + +void nm_utils_invoke_on_idle (NMUtilsInvokeOnIdleCallback callback, + gpointer callback_user_data, + GCancellable *cancellable); + +/*****************************************************************************/ + +static inline void +nm_strv_ptrarray_add_string_take (GPtrArray *cmd, + char *str) +{ + nm_assert (cmd); + nm_assert (str); + + g_ptr_array_add (cmd, str); +} + +static inline void +nm_strv_ptrarray_add_string_dup (GPtrArray *cmd, + const char *str) +{ + nm_strv_ptrarray_add_string_take (cmd, + g_strdup (str)); +} + +#define nm_strv_ptrarray_add_string_concat(cmd, ...) \ + nm_strv_ptrarray_add_string_take ((cmd), g_strconcat (__VA_ARGS__, NULL)) + +#define nm_strv_ptrarray_add_string_printf(cmd, ...) \ + nm_strv_ptrarray_add_string_take ((cmd), g_strdup_printf (__VA_ARGS__)) + +#define nm_strv_ptrarray_add_int(cmd, val) \ + nm_strv_ptrarray_add_string_take ((cmd), nm_strdup_int (val)) + +static inline void +nm_strv_ptrarray_take_gstring (GPtrArray *cmd, + GString **gstr) +{ + nm_assert (gstr && *gstr); + + nm_strv_ptrarray_add_string_take (cmd, + g_string_free (g_steal_pointer (gstr), + FALSE)); +} + +/*****************************************************************************/ + +int nm_utils_getpagesize (void); + +/*****************************************************************************/ + +char *nm_utils_bin2hexstr_full (gconstpointer addr, + gsize length, + char delimiter, + gboolean upper_case, + char *out); + +guint8 *nm_utils_hexstr2bin_full (const char *hexstr, + gboolean allow_0x_prefix, + gboolean delimiter_required, + const char *delimiter_candidates, + gsize required_len, + guint8 *buffer, + gsize buffer_len, + gsize *out_len); + +#define nm_utils_hexstr2bin_buf(hexstr, allow_0x_prefix, delimiter_required, delimiter_candidates, buffer) \ + nm_utils_hexstr2bin_full ((hexstr), (allow_0x_prefix), (delimiter_required), (delimiter_candidates), G_N_ELEMENTS (buffer), (buffer), G_N_ELEMENTS (buffer), NULL) + +guint8 *nm_utils_hexstr2bin_alloc (const char *hexstr, + gboolean allow_0x_prefix, + gboolean delimiter_required, + const char *delimiter_candidates, + gsize required_len, + gsize *out_len); + #endif /* __NM_SHARED_UTILS_H__ */ diff --git a/shared/nm-utils/nm-test-utils.h b/shared/nm-utils/nm-test-utils.h index 7decd363..c5ea5e3f 100644 --- a/shared/nm-utils/nm-test-utils.h +++ b/shared/nm-utils/nm-test-utils.h @@ -30,6 +30,9 @@ * * Our tests (make check) include this header-only file nm-test-utils.h. * + * You should always include this header *as last*. Reason is, that depending on + * previous includes, functionality will be enabled. + * * Logging: * In tests, nm-logging redirects to glib logging. By default, glib suppresses all debug * messages unless you set G_MESSAGES_DEBUG. To enable debug logging, you can explicitly set @@ -110,7 +113,9 @@ #include <string.h> #include <errno.h> +#ifndef NM_TEST_UTILS_NO_LIBNM #include "nm-utils.h" +#endif /*****************************************************************************/ @@ -191,6 +196,25 @@ /*****************************************************************************/ +/* Our nm-error error numbers use negative values to signal failure. + * A non-negative value signals success. Hence, the correct way for checking + * is always (r < 0) vs. (r >= 0). Never (r == 0). + * + * For assertions in tests, we also want to assert that no positive values + * are returned. For a lot of functions, positive return values are unexpected + * and a bug. This macro evaluates @r to success or failure, while asserting + * that @r is not positive. */ +#define NMTST_NM_ERR_SUCCESS(r) \ + ({ \ + const int _r = (r); \ + \ + if (_r >= 0) \ + g_assert_cmpint (_r, ==, 0); \ + (_r >= 0); \ + }) + +/*****************************************************************************/ + struct __nmtst_internal { GRand *rand0; @@ -695,6 +719,7 @@ nmtst_test_quick (void) #else #define NMTST_EXPECT_LIBNM(level, msg) NMTST_EXPECT ("libnm", level, msg) +#define NMTST_EXPECT_LIBNM_WARNING(msg) NMTST_EXPECT_LIBNM (G_LOG_LEVEL_WARNING, msg) #define NMTST_EXPECT_LIBNM_CRITICAL(msg) NMTST_EXPECT_LIBNM (G_LOG_LEVEL_CRITICAL, msg) #endif @@ -880,6 +905,16 @@ nmtst_rand_buf (GRand *rand, gpointer buffer, gsize buffer_length) return buffer; } +#define _nmtst_rand_select(uniq, v0, ...) \ + ({ \ + typeof (v0) NM_UNIQ_T (UNIQ, uniq)[1 + NM_NARG (__VA_ARGS__)] = { (v0), __VA_ARGS__ }; \ + \ + NM_UNIQ_T (UNIQ, uniq)[nmtst_get_rand_int () % G_N_ELEMENTS (NM_UNIQ_T (UNIQ, uniq))]; \ + }) + +#define nmtst_rand_select(...) \ + _nmtst_rand_select (NM_UNIQ, __VA_ARGS__) + static inline void * nmtst_rand_perm (GRand *rand, void *dst, const void *src, gsize elmt_size, gsize n_elmt) { @@ -1025,7 +1060,7 @@ nmtst_reexec_sudo (void) execvp (__nmtst_internal.sudo_cmd, argv); errsv = errno; - g_error (">> exec %s failed: %d - %s", __nmtst_internal.sudo_cmd, errsv, strerror (errsv)); + g_error (">> exec %s failed: %d - %s", __nmtst_internal.sudo_cmd, errsv, nm_strerror_native (errsv)); } /*****************************************************************************/ @@ -1089,6 +1124,8 @@ __define_nmtst_static(02, 1024) __define_nmtst_static(03, 1024) #undef __define_nmtst_static +#if defined (__NM_UTILS_H__) || defined (NM_UTILS_H) + #define NMTST_UUID_INIT(uuid) \ gs_free char *_nmtst_hidden_##uuid = nm_utils_uuid_generate (); \ const char *const uuid = _nmtst_hidden_##uuid @@ -1105,6 +1142,8 @@ nmtst_uuid_generate (void) return u; } +#endif + #define NMTST_SWAP(x,y) \ G_STMT_START { \ char __nmtst_swap_temp[sizeof(x) == sizeof(y) ? (signed) sizeof(x) : -1]; \ @@ -1156,6 +1195,48 @@ nmtst_inet6_from_string (const char *str) return &addr; } +static inline gconstpointer +nmtst_inet_from_string (int addr_family, const char *str) +{ + if (addr_family == AF_INET) { + static in_addr_t a; + + a = nmtst_inet4_from_string (str); + return &a; + } + if (addr_family == AF_INET6) + return nmtst_inet6_from_string (str); + + g_assert_not_reached (); + return NULL; +} + +static inline const char * +nmtst_inet_to_string (int addr_family, gconstpointer addr) +{ + static char buf[NM_CONST_MAX (INET6_ADDRSTRLEN, INET_ADDRSTRLEN)]; + + g_assert (NM_IN_SET (addr_family, AF_INET, AF_INET6)); + g_assert (addr); + + if (inet_ntop (addr_family, addr, buf, sizeof (buf)) != buf) + g_assert_not_reached (); + + return buf; +} + +static inline const char * +nmtst_inet4_to_string (in_addr_t addr) +{ + return nmtst_inet_to_string (AF_INET, &addr); +} + +static inline const char * +nmtst_inet6_to_string (const struct in6_addr *addr) +{ + return nmtst_inet_to_string (AF_INET6, addr); +} + static inline void _nmtst_assert_ip4_address (const char *file, int line, in_addr_t addr, const char *str_expected) { @@ -1289,7 +1370,7 @@ nmtst_file_unlink_if_exists (const char *name) if (unlink (name) != 0) { errsv = errno; if (errsv != ENOENT) - g_error ("nmtst_file_unlink_if_exists(%s): failed with %s", name, strerror (errsv)); + g_error ("nmtst_file_unlink_if_exists(%s): failed with %s", name, nm_strerror_native (errsv)); } } @@ -1302,7 +1383,7 @@ nmtst_file_unlink (const char *name) if (unlink (name) != 0) { errsv = errno; - g_error ("nmtst_file_unlink(%s): failed with %s", name, strerror (errsv)); + g_error ("nmtst_file_unlink(%s): failed with %s", name, nm_strerror_native (errsv)); } } @@ -1895,8 +1976,8 @@ nmtst_assert_hwaddr_equals (gconstpointer hwaddr1, gssize hwaddr1_len, const cha static inline NMConnection * nmtst_create_connection_from_keyfile (const char *keyfile_str, const char *full_filename) { - GKeyFile *keyfile; - GError *error = NULL; + gs_unref_keyfile GKeyFile *keyfile = NULL; + gs_free_error GError *error = NULL; gboolean success; NMConnection *con; gs_free char *filename = g_path_get_basename (full_filename); @@ -1907,14 +1988,10 @@ nmtst_create_connection_from_keyfile (const char *keyfile_str, const char *full_ keyfile = g_key_file_new (); success = g_key_file_load_from_data (keyfile, keyfile_str, strlen (keyfile_str), G_KEY_FILE_NONE, &error); - g_assert_no_error (error); - g_assert (success); + nmtst_assert_success (success, error); con = nm_keyfile_read (keyfile, base_dir, NULL, NULL, &error); - g_assert_no_error (error); - g_assert (NM_IS_CONNECTION (con)); - - g_key_file_unref (keyfile); + nmtst_assert_success (NM_IS_CONNECTION (con), error); nm_keyfile_read_ensure_id (con, filename); nm_keyfile_read_ensure_uuid (con, full_filename); @@ -2070,4 +2147,50 @@ typedef enum { #endif /* __NM_CONNECTION_H__ */ +/*****************************************************************************/ + +static inline void +nmtst_keyfile_assert_data (GKeyFile *kf, const char *data, gssize data_len) +{ + gs_unref_keyfile GKeyFile *kf2 = NULL; + gs_free_error GError *error = NULL; + gs_free char *d1 = NULL; + gs_free char *d2 = NULL; + gboolean success; + gsize d1_len; + gsize d2_len; + + g_assert (kf); + g_assert (data || data_len == 0); + g_assert (data_len >= -1); + + d1 = g_key_file_to_data (kf, &d1_len, &error); + nmtst_assert_success (d1, error); + + if (data_len == -1) { + g_assert_cmpint (strlen (d1), ==, d1_len); + data_len = strlen (data); + g_assert_cmpstr (d1, ==, data); + } + + g_assert_cmpmem (d1, d1_len, data, (gsize) data_len); + + /* also check that we can re-generate the same keyfile from the data. */ + + kf2 = g_key_file_new (); + success = g_key_file_load_from_data (kf2, + d1, + d1_len, + G_KEY_FILE_NONE, + &error); + nmtst_assert_success (success, error); + + d2 = g_key_file_to_data (kf2, &d2_len, &error); + nmtst_assert_success (d2, error); + + g_assert_cmpmem (d2, d2_len, d1, d1_len); +} + +/*****************************************************************************/ + #endif /* __NM_TEST_UTILS_H__ */ diff --git a/shared/nm-utils/nm-time-utils.c b/shared/nm-utils/nm-time-utils.c new file mode 100644 index 00000000..ae526c34 --- /dev/null +++ b/shared/nm-utils/nm-time-utils.c @@ -0,0 +1,273 @@ +/* NetworkManager -- Network link manager + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301 USA. + * + * (C) Copyright 2018 Red Hat, Inc. + */ + +#include "nm-default.h" + +#include "nm-time-utils.h" + +/*****************************************************************************/ + +typedef struct { + /* the offset to the native clock, in seconds. */ + gint64 offset_sec; + clockid_t clk_id; +} GlobalState; + +static const GlobalState *volatile p_global_state; + +static const GlobalState * +_t_init_global_state (void) +{ + static GlobalState global_state = { }; + static gsize init_once = 0; + const GlobalState *p; + clockid_t clk_id; + struct timespec tp; + gint64 offset_sec; + int r; + + clk_id = CLOCK_BOOTTIME; + r = clock_gettime (clk_id, &tp); + if (r == -1 && errno == EINVAL) { + clk_id = CLOCK_MONOTONIC; + r = clock_gettime (clk_id, &tp); + } + + /* The only failure we tolerate is that CLOCK_BOOTTIME is not supported. + * Other than that, we rely on kernel to not fail on this. */ + g_assert (r == 0); + g_assert (tp.tv_nsec >= 0 && tp.tv_nsec < NM_UTILS_NS_PER_SECOND); + + /* Calculate an offset for the time stamp. + * + * We always want positive values, because then we can initialize + * a timestamp with 0 and be sure, that it will be less then any + * value nm_utils_get_monotonic_timestamp_*() might return. + * For this to be true also for nm_utils_get_monotonic_timestamp_s() at + * early boot, we have to shift the timestamp to start counting at + * least from 1 second onward. + * + * Another advantage of shifting is, that this way we make use of the whole 31 bit + * range of signed int, before the time stamp for nm_utils_get_monotonic_timestamp_s() + * wraps (~68 years). + **/ + offset_sec = (- ((gint64) tp.tv_sec)) + 1; + + if (!g_once_init_enter (&init_once)) { + /* there was a race. We expect the pointer to be fully initialized now. */ + p = g_atomic_pointer_get (&p_global_state); + g_assert (p); + return p; + } + + global_state.offset_sec = offset_sec; + global_state.clk_id = clk_id; + p = &global_state; + g_atomic_pointer_set (&p_global_state, p); + g_once_init_leave (&init_once, 1); + + _nm_utils_monotonic_timestamp_initialized (&tp, + p->offset_sec, + p->clk_id == CLOCK_BOOTTIME); + + return p; +} + +#define _t_get_global_state() \ + ({ \ + const GlobalState *_p; \ + \ + _p = g_atomic_pointer_get (&p_global_state); \ + (G_LIKELY (_p) ? _p : _t_init_global_state ()); \ + }) + +#define _t_clock_gettime_eval(p, tp) \ + ({ \ + struct timespec *const _tp = (tp); \ + const GlobalState *const _p2 = (p); \ + int _r; \ + \ + nm_assert (_tp); \ + \ + _r = clock_gettime (_p2->clk_id, _tp); \ + \ + nm_assert (_r == 0); \ + nm_assert (_tp->tv_nsec >= 0 && _tp->tv_nsec < NM_UTILS_NS_PER_SECOND); \ + \ + _p2; \ + }) + +#define _t_clock_gettime(tp) \ + _t_clock_gettime_eval (_t_get_global_state (), tp); + +/*****************************************************************************/ + +/** + * nm_utils_get_monotonic_timestamp_ns: + * + * Returns: a monotonically increasing time stamp in nanoseconds, + * starting at an unspecified offset. See clock_gettime(), %CLOCK_BOOTTIME. + * + * The returned value will start counting at an undefined point + * in the past and will always be positive. + * + * All the nm_utils_get_monotonic_timestamp_*s functions return the same + * timestamp but in different scales (nsec, usec, msec, sec). + **/ +gint64 +nm_utils_get_monotonic_timestamp_ns (void) +{ + const GlobalState *p; + struct timespec tp; + + p = _t_clock_gettime (&tp); + + /* Although the result will always be positive, we return a signed + * integer, which makes it easier to calculate time differences (when + * you want to subtract signed values). + **/ + return (((gint64) tp.tv_sec) + p->offset_sec) * NM_UTILS_NS_PER_SECOND + + tp.tv_nsec; +} + +/** + * nm_utils_get_monotonic_timestamp_us: + * + * Returns: a monotonically increasing time stamp in microseconds, + * starting at an unspecified offset. See clock_gettime(), %CLOCK_BOOTTIME. + * + * The returned value will start counting at an undefined point + * in the past and will always be positive. + * + * All the nm_utils_get_monotonic_timestamp_*s functions return the same + * timestamp but in different scales (nsec, usec, msec, sec). + **/ +gint64 +nm_utils_get_monotonic_timestamp_us (void) +{ + const GlobalState *p; + struct timespec tp; + + p = _t_clock_gettime (&tp); + + /* Although the result will always be positive, we return a signed + * integer, which makes it easier to calculate time differences (when + * you want to subtract signed values). + **/ + return (((gint64) tp.tv_sec) + p->offset_sec) * ((gint64) G_USEC_PER_SEC) + + (tp.tv_nsec / (NM_UTILS_NS_PER_SECOND/G_USEC_PER_SEC)); +} + +/** + * nm_utils_get_monotonic_timestamp_ms: + * + * Returns: a monotonically increasing time stamp in milliseconds, + * starting at an unspecified offset. See clock_gettime(), %CLOCK_BOOTTIME. + * + * The returned value will start counting at an undefined point + * in the past and will always be positive. + * + * All the nm_utils_get_monotonic_timestamp_*s functions return the same + * timestamp but in different scales (nsec, usec, msec, sec). + **/ +gint64 +nm_utils_get_monotonic_timestamp_ms (void) +{ + const GlobalState *p; + struct timespec tp; + + p = _t_clock_gettime (&tp); + + /* Although the result will always be positive, we return a signed + * integer, which makes it easier to calculate time differences (when + * you want to subtract signed values). + **/ + return (((gint64) tp.tv_sec) + p->offset_sec) * ((gint64) 1000) + + (tp.tv_nsec / (NM_UTILS_NS_PER_SECOND/1000)); +} + +/** + * nm_utils_get_monotonic_timestamp_s: + * + * Returns: nm_utils_get_monotonic_timestamp_ms() in seconds (throwing + * away sub second parts). The returned value will always be positive. + * + * This value wraps after roughly 68 years which should be fine for any + * practical purpose. + * + * All the nm_utils_get_monotonic_timestamp_*s functions return the same + * timestamp but in different scales (nsec, usec, msec, sec). + **/ +gint32 +nm_utils_get_monotonic_timestamp_s (void) +{ + const GlobalState *p; + struct timespec tp; + + p = _t_clock_gettime (&tp); + + return (((gint64) tp.tv_sec) + p->offset_sec); +} + +/** + * nm_utils_monotonic_timestamp_as_boottime: + * @timestamp: the monotonic-timestamp that should be converted into CLOCK_BOOTTIME. + * @timestamp_ns_per_tick: How many nano seconds make one unit of @timestamp? E.g. if + * @timestamp is in unit seconds, pass %NM_UTILS_NS_PER_SECOND; @timestamp in nano + * seconds, pass 1; @timestamp in milli seconds, pass %NM_UTILS_NS_PER_SECOND/1000; etc. + * + * Returns: the monotonic-timestamp as CLOCK_BOOTTIME, as returned by clock_gettime(). + * The unit is the same as the passed in @timestamp basd on @timestamp_ns_per_tick. + * E.g. if you passed @timestamp in as seconds, it will return boottime in seconds. + * If @timestamp is a non-positive, it returns -1. Note that a (valid) monotonic-timestamp + * is always positive. + * + * On older kernels that don't support CLOCK_BOOTTIME, the returned time is instead CLOCK_MONOTONIC. + **/ +gint64 +nm_utils_monotonic_timestamp_as_boottime (gint64 timestamp, gint64 timestamp_ns_per_tick) +{ + const GlobalState *p; + gint64 offset; + + /* only support ns-per-tick being a multiple of 10. */ + g_return_val_if_fail (timestamp_ns_per_tick == 1 + || (timestamp_ns_per_tick > 0 && + timestamp_ns_per_tick <= NM_UTILS_NS_PER_SECOND && + timestamp_ns_per_tick % 10 == 0), + -1); + + /* Check that the timestamp is in a valid range. */ + g_return_val_if_fail (timestamp >= 0, -1); + + /* if the caller didn't yet ever fetch a monotonic-timestamp, he cannot pass any meaningful + * value (because he has no idea what these timestamps would be). That would be a bug. */ + nm_assert (g_atomic_pointer_get (&p_global_state)); + + p = _t_get_global_state (); + + /* calculate the offset of monotonic-timestamp to boottime. offset_s is <= 1. */ + offset = p->offset_sec * (NM_UTILS_NS_PER_SECOND / timestamp_ns_per_tick); + + /* check for overflow. */ + g_return_val_if_fail (offset > 0 || timestamp < G_MAXINT64 + offset, G_MAXINT64); + + return timestamp - offset; +} diff --git a/shared/nm-utils/nm-time-utils.h b/shared/nm-utils/nm-time-utils.h new file mode 100644 index 00000000..7e4f4f25 --- /dev/null +++ b/shared/nm-utils/nm-time-utils.h @@ -0,0 +1,45 @@ +/* NetworkManager -- Network link manager + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301 USA. + * + * (C) Copyright 2018 Red Hat, Inc. + */ + +#ifndef __NM_TIME_UTILS_H__ +#define __NM_TIME_UTILS_H__ + +gint64 nm_utils_get_monotonic_timestamp_ns (void); +gint64 nm_utils_get_monotonic_timestamp_us (void); +gint64 nm_utils_get_monotonic_timestamp_ms (void); +gint32 nm_utils_get_monotonic_timestamp_s (void); +gint64 nm_utils_monotonic_timestamp_as_boottime (gint64 timestamp, gint64 timestamp_ticks_per_ns); + +static inline gint64 +nm_utils_get_monotonic_timestamp_ns_cached (gint64 *cache_now) +{ + return (*cache_now) + ?: (*cache_now = nm_utils_get_monotonic_timestamp_ns ()); +} + +struct timespec; + +/* this function must be implemented to handle the notification when + * the first monotonic-timestamp is fetched. */ +extern void _nm_utils_monotonic_timestamp_initialized (const struct timespec *tp, + gint64 offset_sec, + gboolean is_boottime); + +#endif /* __NM_TIME_UTILS_H__ */ diff --git a/shared/nm-utils/nm-vpn-plugin-utils.c b/shared/nm-utils/nm-vpn-plugin-utils.c index 772aa39a..353a2817 100644 --- a/shared/nm-utils/nm-vpn-plugin-utils.c +++ b/shared/nm-utils/nm-vpn-plugin-utils.c @@ -16,7 +16,7 @@ * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301 USA. * - * Copyright 2016 Red Hat, Inc. + * Copyright 2016,2018 Red Hat, Inc. */ #include "nm-default.h" @@ -44,14 +44,37 @@ nm_vpn_plugin_utils_load_editor (const char *module_name, char *factory_name; } cached = { 0 }; NMVpnEditor *editor; + gs_free char *module_path = NULL; + gs_free char *dirname = NULL; + Dl_info plugin_info; - g_return_val_if_fail (module_name && g_path_is_absolute (module_name), NULL); + g_return_val_if_fail (module_name, NULL); g_return_val_if_fail (factory_name && factory_name[0], NULL); g_return_val_if_fail (editor_factory, NULL); g_return_val_if_fail (NM_IS_VPN_EDITOR_PLUGIN (editor_plugin), NULL); g_return_val_if_fail (NM_IS_CONNECTION (connection), NULL); g_return_val_if_fail (!error || !*error, NULL); + if (!g_path_is_absolute (module_name)) { + /* + * Load an editor from the same directory this plugin is in. + * Ideally, we'd get our .so name from the NMVpnEditorPlugin if it + * would just have a property with it... + */ + if (!dladdr(nm_vpn_plugin_utils_load_editor, &plugin_info)) { + /* Really a "can not happen" scenario. */ + g_set_error (error, + NM_VPN_PLUGIN_ERROR, + NM_VPN_PLUGIN_ERROR_FAILED, + _("unable to get editor plugin name: %s"), dlerror ()); + } + + dirname = g_path_get_dirname (plugin_info.dli_fname); + module_path = g_build_filename (dirname, module_name, NULL); + } else { + module_path = g_strdup (module_name); + } + /* we really expect this function to be called with unchanging @module_name * and @factory_name. And we only want to load the module once, hence it would * be more complicated to accept changing @module_name/@factory_name arguments. @@ -71,18 +94,18 @@ nm_vpn_plugin_utils_load_editor (const char *module_name, gpointer factory; void *dl_module; - dl_module = dlopen (module_name, RTLD_LAZY | RTLD_LOCAL); + dl_module = dlopen (module_path, RTLD_LAZY | RTLD_LOCAL); if (!dl_module) { - if (!g_file_test (module_name, G_FILE_TEST_EXISTS)) { + if (!g_file_test (module_path, G_FILE_TEST_EXISTS)) { g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_NOENT, - _("missing plugin file \"%s\""), module_name); + _("missing plugin file \"%s\""), module_path); return NULL; } g_set_error (error, - NM_CONNECTION_ERROR, - NM_CONNECTION_ERROR_FAILED, + NM_VPN_PLUGIN_ERROR, + NM_VPN_PLUGIN_ERROR_FAILED, _("cannot load editor plugin: %s"), dlerror ()); return NULL; } @@ -90,8 +113,8 @@ nm_vpn_plugin_utils_load_editor (const char *module_name, factory = dlsym (dl_module, factory_name); if (!factory) { g_set_error (error, - NM_CONNECTION_ERROR, - NM_CONNECTION_ERROR_FAILED, + NM_VPN_PLUGIN_ERROR, + NM_VPN_PLUGIN_ERROR_FAILED, _("cannot load factory %s from plugin: %s"), factory_name, dlerror ()); dlclose (dl_module); @@ -116,8 +139,8 @@ nm_vpn_plugin_utils_load_editor (const char *module_name, if (!editor) { if (error && !*error ) { g_set_error_literal (error, - NM_CONNECTION_ERROR, - NM_CONNECTION_ERROR_FAILED, + NM_VPN_PLUGIN_ERROR, + NM_VPN_PLUGIN_ERROR_FAILED, _("unknown error creating editor instance")); g_return_val_if_reached (NULL); } @@ -127,4 +150,3 @@ nm_vpn_plugin_utils_load_editor (const char *module_name, g_return_val_if_fail (NM_IS_VPN_EDITOR (editor), NULL); return editor; } - diff --git a/shared/nm-utils/tests/test-shared-general.c b/shared/nm-utils/tests/test-shared-general.c new file mode 100644 index 00000000..d53b21d9 --- /dev/null +++ b/shared/nm-utils/tests/test-shared-general.c @@ -0,0 +1,267 @@ +/* + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301 USA. + * + * Copyright 2018 Red Hat, Inc. + */ + +#define NM_TEST_UTILS_NO_LIBNM 1 + +#include "nm-default.h" + +#include "nm-utils/nm-time-utils.h" +#include "nm-utils/nm-random-utils.h" +#include "nm-utils/unaligned.h" + +#include "nm-utils/nm-test-utils.h" + +/*****************************************************************************/ + +static int _monotonic_timestamp_initialized; + +void +_nm_utils_monotonic_timestamp_initialized (const struct timespec *tp, + gint64 offset_sec, + gboolean is_boottime) +{ + g_assert (!_monotonic_timestamp_initialized); + _monotonic_timestamp_initialized = 1; +} + +/*****************************************************************************/ + +static void +test_monotonic_timestamp (void) +{ + g_assert (nm_utils_get_monotonic_timestamp_s () > 0); + g_assert (_monotonic_timestamp_initialized); +} + +/*****************************************************************************/ + +static void +test_nmhash (void) +{ + int rnd; + + nm_utils_random_bytes (&rnd, sizeof (rnd)); + + g_assert (nm_hash_val (555, 4) != 0); +} + +/*****************************************************************************/ + +static const char * +_make_strv_foo (void) +{ + return "foo"; +} + +static const char *const*const _tst_make_strv_1 = NM_MAKE_STRV ("1", "2"); + +static void +test_make_strv (void) +{ + const char *const*v1a = NM_MAKE_STRV ("a"); + const char *const*v1b = NM_MAKE_STRV ("a", ); + const char *const*v2a = NM_MAKE_STRV ("a", "b"); + const char *const*v2b = NM_MAKE_STRV ("a", "b", ); + const char *const v3[] = { "a", "b", }; + const char *const*v4b = NM_MAKE_STRV ("a", _make_strv_foo (), ); + + g_assert (NM_PTRARRAY_LEN (v1a) == 1); + g_assert (NM_PTRARRAY_LEN (v1b) == 1); + g_assert (NM_PTRARRAY_LEN (v2a) == 2); + g_assert (NM_PTRARRAY_LEN (v2b) == 2); + + g_assert (NM_PTRARRAY_LEN (_tst_make_strv_1) == 2); + g_assert_cmpstr (_tst_make_strv_1[0], ==, "1"); + g_assert_cmpstr (_tst_make_strv_1[1], ==, "2"); + /* writing the static read-only variable leads to crash .*/ + //((char **) _tst_make_strv_1)[0] = NULL; + //((char **) _tst_make_strv_1)[2] = "c"; + + G_STATIC_ASSERT_EXPR (G_N_ELEMENTS (v3) == 2); + + g_assert (NM_PTRARRAY_LEN (v4b) == 2); + + G_STATIC_ASSERT_EXPR (G_N_ELEMENTS (NM_MAKE_STRV ("a", "b" )) == 3); + G_STATIC_ASSERT_EXPR (G_N_ELEMENTS (NM_MAKE_STRV ("a", "b", )) == 3); + + nm_strquote_a (300, ""); +} + +/*****************************************************************************/ + +typedef enum { + TEST_NM_STRDUP_ENUM_m1 = -1, + TEST_NM_STRDUP_ENUM_3 = 3, +} TestNMStrdupIntEnum; + +static void +test_nm_strdup_int (void) +{ +#define _NM_STRDUP_INT_TEST(num, str) \ + G_STMT_START { \ + gs_free char *_s1 = NULL; \ + \ + _s1 = nm_strdup_int ((num)); \ + \ + g_assert (_s1); \ + g_assert_cmpstr (_s1, ==, str); \ + } G_STMT_END + +#define _NM_STRDUP_INT_TEST_TYPED(type, num) \ + G_STMT_START { \ + type _num = ((type) num); \ + \ + _NM_STRDUP_INT_TEST (_num, G_STRINGIFY (num)); \ + } G_STMT_END + + _NM_STRDUP_INT_TEST_TYPED (char, 0); + _NM_STRDUP_INT_TEST_TYPED (char, 1); + _NM_STRDUP_INT_TEST_TYPED (guint8, 0); + _NM_STRDUP_INT_TEST_TYPED (gint8, 25); + _NM_STRDUP_INT_TEST_TYPED (char, 47); + _NM_STRDUP_INT_TEST_TYPED (short, 47); + _NM_STRDUP_INT_TEST_TYPED (int, 47); + _NM_STRDUP_INT_TEST_TYPED (long, 47); + _NM_STRDUP_INT_TEST_TYPED (unsigned char, 47); + _NM_STRDUP_INT_TEST_TYPED (unsigned short, 47); + _NM_STRDUP_INT_TEST_TYPED (unsigned, 47); + _NM_STRDUP_INT_TEST_TYPED (unsigned long, 47); + _NM_STRDUP_INT_TEST_TYPED (gint64, 9223372036854775807); + _NM_STRDUP_INT_TEST_TYPED (gint64, -9223372036854775807); + _NM_STRDUP_INT_TEST_TYPED (guint64, 0); + _NM_STRDUP_INT_TEST_TYPED (guint64, 9223372036854775807); + + _NM_STRDUP_INT_TEST (TEST_NM_STRDUP_ENUM_m1, "-1"); + _NM_STRDUP_INT_TEST (TEST_NM_STRDUP_ENUM_3, "3"); +} + +/*****************************************************************************/ + +static void +test_nm_strndup_a (void) +{ + int run; + + for (run = 0; run < 20; run++) { + gs_free char *input = NULL; + char ch; + gsize i, l; + + input = g_strnfill (nmtst_get_rand_int () % 20, 'x'); + + for (i = 0; input[i]; i++) { + while ((ch = ((char) nmtst_get_rand_int ())) == '\0') { + /* repeat. */ + } + input[i] = ch; + } + + { + gs_free char *dup_free = NULL; + const char *dup; + + l = strlen (input) + 1; + dup = nm_strndup_a (10, input, l - 1, &dup_free); + g_assert_cmpstr (dup, ==, input); + if (strlen (dup) < 10) + g_assert (!dup_free); + else + g_assert (dup == dup_free); + } + + { + gs_free char *dup_free = NULL; + const char *dup; + + l = nmtst_get_rand_int () % 23; + dup = nm_strndup_a (10, input, l, &dup_free); + g_assert (strncmp (dup, input, l) == 0); + g_assert (strlen (dup) <= l); + if (l < 10) + g_assert (!dup_free); + else + g_assert (dup == dup_free); + if (strlen (input) < l) + g_assert (nm_utils_memeqzero (&dup[strlen (input)], l - strlen (input))); + } + } +} + +/*****************************************************************************/ + +static void +test_nm_ip4_addr_is_localhost (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"))); +} + +/*****************************************************************************/ + +static void +test_unaligned (void) +{ + int shift; + + for (shift = 0; shift <= 32; shift++) { + guint8 buf[100] = { }; + guint8 val = 0; + + while (val == 0) + val = nmtst_get_rand_int () % 256; + + buf[shift] = val; + + g_assert_cmpint (unaligned_read_le64 (&buf[shift]), ==, (guint64) val); + g_assert_cmpint (unaligned_read_be64 (&buf[shift]), ==, ((guint64) val) << 56); + g_assert_cmpint (unaligned_read_ne64 (&buf[shift]), !=, 0); + + g_assert_cmpint (unaligned_read_le32 (&buf[shift]), ==, (guint32) val); + g_assert_cmpint (unaligned_read_be32 (&buf[shift]), ==, ((guint32) val) << 24); + g_assert_cmpint (unaligned_read_ne32 (&buf[shift]), !=, 0); + + g_assert_cmpint (unaligned_read_le16 (&buf[shift]), ==, (guint16) val); + g_assert_cmpint (unaligned_read_be16 (&buf[shift]), ==, ((guint16) val) << 8); + g_assert_cmpint (unaligned_read_ne16 (&buf[shift]), !=, 0); + } +} + +/*****************************************************************************/ + +NMTST_DEFINE (); + +int main (int argc, char **argv) +{ + nmtst_init (&argc, &argv, TRUE); + + g_test_add_func ("/general/test_monotonic_timestamp", test_monotonic_timestamp); + g_test_add_func ("/general/test_nmhash", test_nmhash); + 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_unaligned", test_unaligned); + + return g_test_run (); +} + diff --git a/shared/nm-utils/unaligned.h b/shared/nm-utils/unaligned.h index e62188d1..00c17f87 100644 --- a/shared/nm-utils/unaligned.h +++ b/shared/nm-utils/unaligned.h @@ -7,37 +7,37 @@ /* BE */ static inline uint16_t unaligned_read_be16(const void *_u) { - const struct __attribute__((packed, may_alias)) { uint16_t x; } *u = _u; + const struct __attribute__((__packed__, __may_alias__)) { uint16_t x; } *u = _u; return be16toh(u->x); } static inline uint32_t unaligned_read_be32(const void *_u) { - const struct __attribute__((packed, may_alias)) { uint32_t x; } *u = _u; + const struct __attribute__((__packed__, __may_alias__)) { uint32_t x; } *u = _u; return be32toh(u->x); } static inline uint64_t unaligned_read_be64(const void *_u) { - const struct __attribute__((packed, may_alias)) { uint64_t x; } *u = _u; + const struct __attribute__((__packed__, __may_alias__)) { uint64_t x; } *u = _u; return be64toh(u->x); } static inline void unaligned_write_be16(void *_u, uint16_t a) { - struct __attribute__((packed, may_alias)) { uint16_t x; } *u = _u; + struct __attribute__((__packed__, __may_alias__)) { uint16_t x; } *u = _u; u->x = be16toh(a); } static inline void unaligned_write_be32(void *_u, uint32_t a) { - struct __attribute__((packed, may_alias)) { uint32_t x; } *u = _u; + struct __attribute__((__packed__, __may_alias__)) { uint32_t x; } *u = _u; u->x = be32toh(a); } static inline void unaligned_write_be64(void *_u, uint64_t a) { - struct __attribute__((packed, may_alias)) { uint64_t x; } *u = _u; + struct __attribute__((__packed__, __may_alias__)) { uint64_t x; } *u = _u; u->x = be64toh(a); } @@ -45,37 +45,37 @@ static inline void unaligned_write_be64(void *_u, uint64_t a) { /* LE */ static inline uint16_t unaligned_read_le16(const void *_u) { - const struct __attribute__((packed, may_alias)) { uint16_t x; } *u = _u; + const struct __attribute__((__packed__, __may_alias__)) { uint16_t x; } *u = _u; return le16toh(u->x); } static inline uint32_t unaligned_read_le32(const void *_u) { - const struct __attribute__((packed, may_alias)) { uint32_t x; } *u = _u; + const struct __attribute__((__packed__, __may_alias__)) { uint32_t x; } *u = _u; return le32toh(u->x); } static inline uint64_t unaligned_read_le64(const void *_u) { - const struct __attribute__((packed, may_alias)) { uint64_t x; } *u = _u; + const struct __attribute__((__packed__, __may_alias__)) { uint64_t x; } *u = _u; return le64toh(u->x); } static inline void unaligned_write_le16(void *_u, uint16_t a) { - struct __attribute__((packed, may_alias)) { uint16_t x; } *u = _u; + struct __attribute__((__packed__, __may_alias__)) { uint16_t x; } *u = _u; u->x = le16toh(a); } static inline void unaligned_write_le32(void *_u, uint32_t a) { - struct __attribute__((packed, may_alias)) { uint32_t x; } *u = _u; + struct __attribute__((__packed__, __may_alias__)) { uint32_t x; } *u = _u; u->x = le32toh(a); } static inline void unaligned_write_le64(void *_u, uint64_t a) { - struct __attribute__((packed, may_alias)) { uint64_t x; } *u = _u; + struct __attribute__((__packed__, __may_alias__)) { uint64_t x; } *u = _u; u->x = le64toh(a); } |