diff options
Diffstat (limited to 'shared')
| -rw-r--r-- | shared/Makefile.am | 2 | ||||
| -rw-r--r-- | shared/Makefile.in | 2 | ||||
| -rw-r--r-- | shared/nm-default.h | 1 | ||||
| -rw-r--r-- | shared/nm-macros-internal.h | 63 | ||||
| -rw-r--r-- | shared/nm-shared-utils.c | 228 | ||||
| -rw-r--r-- | shared/nm-shared-utils.h | 63 | ||||
| -rw-r--r-- | shared/nm-test-utils.h | 109 | ||||
| -rw-r--r-- | shared/nm-version-macros.h | 2 |
8 files changed, 450 insertions, 20 deletions
diff --git a/shared/Makefile.am b/shared/Makefile.am index 03fad123..c042fdbe 100644 --- a/shared/Makefile.am +++ b/shared/Makefile.am @@ -4,6 +4,8 @@ EXTRA_DIST = \ nm-default.h \ nm-glib.h \ nm-macros-internal.h \ + nm-shared-utils.c \ + nm-shared-utils.h \ nm-test-libnm-utils.h \ nm-test-utils.h \ nm-test-utils-impl.c \ diff --git a/shared/Makefile.in b/shared/Makefile.in index 0b3284f0..3dfa7f57 100644 --- a/shared/Makefile.in +++ b/shared/Makefile.in @@ -401,6 +401,8 @@ EXTRA_DIST = \ nm-default.h \ nm-glib.h \ nm-macros-internal.h \ + nm-shared-utils.c \ + nm-shared-utils.h \ nm-test-libnm-utils.h \ nm-test-utils.h \ nm-test-utils-impl.c \ diff --git a/shared/nm-default.h b/shared/nm-default.h index 36527772..aae4777a 100644 --- a/shared/nm-default.h +++ b/shared/nm-default.h @@ -52,6 +52,7 @@ #include "nm-version.h" #include "gsystem-local-alloc.h" #include "nm-macros-internal.h" +#include "nm-shared-utils.h" /*****************************************************************************/ diff --git a/shared/nm-macros-internal.h b/shared/nm-macros-internal.h index aa38bacf..41762318 100644 --- a/shared/nm-macros-internal.h +++ b/shared/nm-macros-internal.h @@ -22,6 +22,8 @@ #ifndef __NM_MACROS_INTERNAL_H__ #define __NM_MACROS_INTERNAL_H__ +#include <stdlib.h> + /********************************************************/ #define nm_auto(fcn) __attribute ((cleanup(fcn))) @@ -34,6 +36,13 @@ #define nm_auto_free nm_auto(_nm_auto_free_impl) GS_DEFINE_CLEANUP_FUNCTION(void*, _nm_auto_free_impl, free) +static inline void +_nm_auto_unset_gvalue_impl (GValue *v) +{ + g_value_unset (v); +} +#define nm_auto_unset_gvalue nm_auto(_nm_auto_unset_gvalue_impl) + /********************************************************/ /* http://stackoverflow.com/a/11172679 */ @@ -116,6 +125,26 @@ GS_DEFINE_CLEANUP_FUNCTION(void*, _nm_auto_free_impl, free) /********************************************************/ +/** + * NM_G_ERROR_MSG: + * @error: (allow none): the #GError instance + * + * All functions must follow the convention that when they + * return a failure, they must also set the GError to a valid + * message. For external API however, we want to be extra + * careful before accessing the error instance. Use NM_G_ERROR_MSG() + * which is safe to use on NULL. + * + * Returns: the error message. + **/ +static inline const char * +NM_G_ERROR_MSG (GError *error) +{ + return error ? (error->message ? : "(null)") : "(no-error)"; \ +} + +/********************************************************/ + /* macro to return strlen() of a compile time string. */ #define NM_STRLEN(str) ( sizeof ("" str) - 1 ) @@ -267,8 +296,10 @@ _NM_IN_STRSET_streq (const char *x, const char *s) #if NM_MORE_ASSERTS #define nm_assert(cond) G_STMT_START { g_assert (cond); } G_STMT_END +#define nm_assert_not_reached() G_STMT_START { g_assert_not_reached (); } G_STMT_END #else #define nm_assert(cond) G_STMT_START { if (FALSE) { if (cond) { } } } G_STMT_END +#define nm_assert_not_reached() G_STMT_START { ; } G_STMT_END #endif /*****************************************************************************/ @@ -293,6 +324,38 @@ _notify (obj_type *obj, _PropertyEnums prop) \ /*****************************************************************************/ +#define nm_unauto(pp) \ + ({ \ + G_STATIC_ASSERT (sizeof *(pp) == sizeof (gpointer)); \ + gpointer *_pp = (gpointer *) (pp); \ + gpointer _p = *_pp; \ + \ + *_pp = NULL; \ + _p; \ + }) + +/*****************************************************************************/ + +static inline gpointer +nm_g_object_ref (gpointer obj) +{ + /* g_object_ref() doesn't accept NULL. */ + if (obj) + g_object_ref (obj); + return obj; +} + +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 + * (for example as as destroy function for a hash table that can contain + * NULL values). */ + if (obj) + g_object_unref (obj); +} + static inline gboolean nm_clear_g_source (guint *id) { diff --git a/shared/nm-shared-utils.c b/shared/nm-shared-utils.c new file mode 100644 index 00000000..0ae54bdc --- /dev/null +++ b/shared/nm-shared-utils.c @@ -0,0 +1,228 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */ +/* 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 2016 Red Hat, Inc. + */ + +#include "nm-default.h" + +#include "nm-shared-utils.h" + +#include <errno.h> + +/*****************************************************************************/ + +/* _nm_utils_ascii_str_to_int64: + * + * A wrapper for g_ascii_strtoll, that checks whether the whole string + * can be successfully converted to a number and is within a given + * range. On any error, @fallback will be returned and %errno will be set + * to a non-zero value. On success, %errno will be set to zero, check %errno + * for errors. Any trailing or leading (ascii) white space is ignored and the + * functions is locale independent. + * + * The function is guaranteed to return a value between @min and @max + * (inclusive) or @fallback. Also, the parsing is rather strict, it does + * not allow for any unrecognized characters, except leading and trailing + * white space. + **/ +gint64 +_nm_utils_ascii_str_to_int64 (const char *str, guint base, gint64 min, gint64 max, gint64 fallback) +{ + gint64 v; + size_t len; + char buf[64], *s, *str_free = NULL; + + if (str) { + while (g_ascii_isspace (str[0])) + str++; + } + if (!str || !str[0]) { + errno = EINVAL; + return fallback; + } + + len = strlen (str); + if (g_ascii_isspace (str[--len])) { + /* backward search the first non-ws character. + * We already know that str[0] is non-ws. */ + while (g_ascii_isspace (str[--len])) + ; + + /* str[len] is now the last non-ws character... */ + len++; + + if (len >= sizeof (buf)) + s = str_free = g_malloc (len + 1); + else + s = buf; + + memcpy (s, str, len); + s[len] = 0; + + nm_assert (len > 0 && len < strlen (str) && len == strlen (s)); + nm_assert (!g_ascii_isspace (str[len-1]) && g_ascii_isspace (str[len])); + nm_assert (strncmp (str, s, len) == 0); + + str = s; + } + + errno = 0; + v = g_ascii_strtoll (str, &s, base); + + if (errno != 0) + v = fallback; + else if (s[0] != 0) { + errno = EINVAL; + v = fallback; + } else if (v > max || v < min) { + errno = ERANGE; + v = fallback; + } + + if (G_UNLIKELY (str_free)) + g_free (str_free); + return v; +} + +/*****************************************************************************/ + +G_DEFINE_QUARK (nm-utils-error-quark, nm_utils_error) + +void +nm_utils_error_set_cancelled (GError **error, + gboolean is_disposing, + const char *instance_name) +{ + if (is_disposing) { + g_set_error (error, NM_UTILS_ERROR, NM_UTILS_ERROR_CANCELLED_DISPOSING, + "Disposing %s instance", + instance_name && *instance_name ? instance_name : "source"); + } else { + g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_CANCELLED, + "Request cancelled"); + } +} + +gboolean +nm_utils_error_is_cancelled (GError *error, + gboolean consider_is_disposing) +{ + if (error) { + if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) + return TRUE; + if ( consider_is_disposing + && g_error_matches (error, NM_UTILS_ERROR, NM_UTILS_ERROR_CANCELLED_DISPOSING)) + return TRUE; + } + return FALSE; +} + +/*****************************************************************************/ + +/** + * nm_g_object_set_property: + * @object: the target object + * @property_name: the property name + * @value: the #GValue to set + * @error: (allow-none): optional error argument + * + * A reimplementation of g_object_set_property(), but instead + * returning an error instead of logging a warning. All g_object_set*() + * versions in glib require you to not pass invalid types or they will + * log a g_warning() -- without reporting an error. We don't want that, + * so we need to hack error checking around it. + * + * Returns: whether the value was successfully set. + */ +gboolean +nm_g_object_set_property (GObject *object, + const gchar *property_name, + const GValue *value, + GError **error) +{ + GParamSpec *pspec; + nm_auto_unset_gvalue GValue tmp_value = G_VALUE_INIT; + GObjectClass *klass; + + g_return_val_if_fail (G_IS_OBJECT (object), FALSE); + g_return_val_if_fail (property_name != NULL, FALSE); + g_return_val_if_fail (G_IS_VALUE (value), FALSE); + g_return_val_if_fail (!error || !*error, FALSE); + + /* g_object_class_find_property() does g_param_spec_get_redirect_target(), + * where we differ from a plain g_object_set_property(). */ + pspec = g_object_class_find_property (G_OBJECT_GET_CLASS (object), property_name); + + if (!pspec) { + g_set_error (error, NM_UTILS_ERROR, NM_UTILS_ERROR_UNKNOWN, + _("object class '%s' has no property named '%s'"), + G_OBJECT_TYPE_NAME (object), + property_name); + return FALSE; + } + if (!(pspec->flags & G_PARAM_WRITABLE)) { + g_set_error (error, NM_UTILS_ERROR, NM_UTILS_ERROR_UNKNOWN, + _("property '%s' of object class '%s' is not writable"), + pspec->name, + G_OBJECT_TYPE_NAME (object)); + return FALSE; + } + if ((pspec->flags & G_PARAM_CONSTRUCT_ONLY)) { + g_set_error (error, NM_UTILS_ERROR, NM_UTILS_ERROR_UNKNOWN, + _("construct property \"%s\" for object '%s' can't be set after construction"), + pspec->name, G_OBJECT_TYPE_NAME (object)); + return FALSE; + } + + klass = g_type_class_peek (pspec->owner_type); + if (klass == NULL) { + g_set_error (error, NM_UTILS_ERROR, NM_UTILS_ERROR_UNKNOWN, + _("'%s::%s' is not a valid property name; '%s' is not a GObject subtype"), + g_type_name (pspec->owner_type), pspec->name, g_type_name (pspec->owner_type)); + return FALSE; + } + + /* provide a copy to work from, convert (if necessary) and validate */ + g_value_init (&tmp_value, pspec->value_type); + if (!g_value_transform (value, &tmp_value)) { + g_set_error (error, NM_UTILS_ERROR, NM_UTILS_ERROR_UNKNOWN, + _("unable to set property '%s' of type '%s' from value of type '%s'"), + pspec->name, + g_type_name (pspec->value_type), + G_VALUE_TYPE_NAME (value)); + return FALSE; + } + if ( g_param_value_validate (pspec, &tmp_value) + && !(pspec->flags & G_PARAM_LAX_VALIDATION)) { + gs_free char *contents = g_strdup_value_contents (value); + + g_set_error (error, NM_UTILS_ERROR, NM_UTILS_ERROR_UNKNOWN, + _("value \"%s\" of type '%s' is invalid or out of range for property '%s' of type '%s'"), + contents, + G_VALUE_TYPE_NAME (value), + pspec->name, + g_type_name (pspec->value_type)); + return FALSE; + } + + g_object_set_property (object, property_name, &tmp_value); + return TRUE; +} + +/*****************************************************************************/ diff --git a/shared/nm-shared-utils.h b/shared/nm-shared-utils.h new file mode 100644 index 00000000..f80c850c --- /dev/null +++ b/shared/nm-shared-utils.h @@ -0,0 +1,63 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */ +/* 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 2016 Red Hat, Inc. + */ + +#ifndef __NM_SHARED_UTILS_H__ +#define __NM_SHARED_UTILS_H__ + +/******************************************************************************/ + +gint64 _nm_utils_ascii_str_to_int64 (const char *str, guint base, gint64 min, gint64 max, gint64 fallback); + +/******************************************************************************/ + +/** + * NMUtilsError: + * @NM_UTILS_ERROR_UNKNOWN: unknown or unclassified error + * @NM_UTILS_ERROR_CANCELLED_DISPOSING: when disposing an object that has + * pending aynchronous operations, the operation is cancelled with this + * error reason. Depending on the usage, this might indicate a bug because + * usually the target object should stay alive as long as there are pending + * operations. + */ +typedef enum { + NM_UTILS_ERROR_UNKNOWN = 0, /*< nick=Unknown >*/ + NM_UTILS_ERROR_CANCELLED_DISPOSING, /*< nick=CancelledDisposing >*/ +} NMUtilsError; + +#define NM_UTILS_ERROR (nm_utils_error_quark ()) +GQuark nm_utils_error_quark (void); + +void nm_utils_error_set_cancelled (GError **error, + gboolean is_disposing, + const char *instance_name); +gboolean nm_utils_error_is_cancelled (GError *error, + gboolean consider_is_disposing); + +/******************************************************************************/ + +gboolean nm_g_object_set_property (GObject *object, + const gchar *property_name, + const GValue *value, + GError **error); + +/******************************************************************************/ + +#endif /* __NM_SHARED_UTILS_H__ */ diff --git a/shared/nm-test-utils.h b/shared/nm-test-utils.h index 542081b8..25032457 100644 --- a/shared/nm-test-utils.h +++ b/shared/nm-test-utils.h @@ -71,6 +71,8 @@ * * "TRACE", this is shorthand for "log-level=TRACE". * + * "D", this is shorthand for "log-level=TRACE,no-expect-message". + * * "sudo-cmd=PATH": when running root tests as normal user, the test will execute * itself by invoking sudo at PATH. * For example @@ -110,23 +112,24 @@ /* general purpose functions that have no dependency on other nmtst functions */ -inline static void -nmtst_assert_error (GError *error, - GQuark expect_error_domain, - gint expect_error_code, - const char *expect_error_pattern) -{ - if (expect_error_domain) - g_assert_error (error, expect_error_domain, expect_error_code); - else - g_assert (error); - g_assert (error->message); - if ( expect_error_pattern - && !g_pattern_match_simple (expect_error_pattern, error->message)) { - g_error ("error message does not have expected pattern '%s'. Instead it is '%s' (%s, %d)", - expect_error_pattern, error->message, g_quark_to_string (error->domain), error->code); - } -} +#define nmtst_assert_error(error, expect_error_domain, expect_error_code, expect_error_pattern) \ + G_STMT_START { \ + GError *_error = (error); \ + GQuark _expect_error_domain = (expect_error_domain); \ + const char *_expect_error_pattern = (expect_error_pattern); \ + \ + if (_expect_error_domain) \ + g_assert_error (_error, _expect_error_domain, (expect_error_code)); \ + else \ + g_assert (_error); \ + g_assert (_error->message); \ + if ( _expect_error_pattern \ + && !g_pattern_match_simple (_expect_error_pattern, _error->message)) { \ + g_error ("%s:%d: error message does not have expected pattern '%s'. Instead it is '%s' (%s, %d)", \ + __FILE__, __LINE__, \ + _expect_error_pattern, _error->message, g_quark_to_string (_error->domain), _error->code); \ + } \ + } G_STMT_END #define NMTST_WAIT(max_wait_ms, wait) \ ({ \ @@ -154,7 +157,7 @@ inline static void _nmtst_assert_success (gboolean success, GError *error, const char *file, int line) { if (!success || error) - g_error ("(%s:%d) FAILURE success=%d, error=%s", file, line, success, error && error->message ? error->message : "(no error)"); + g_error ("(%s:%d) FAILURE success=%d, error=%s", file, line, success, error ? error->message : "(no error)"); } #define nmtst_assert_success(success, error) _nmtst_assert_success ((success), (error), __FILE__, __LINE__) @@ -340,9 +343,14 @@ __nmtst_init (int *argc, char ***argv, gboolean assert_logging, const char *log_ } else if (!g_ascii_strncasecmp (debug, "log-level=", strlen ("log-level="))) { g_free (c_log_level); log_level = c_log_level = g_strdup (&debug[strlen ("log-level=")]); + } else if (!g_ascii_strcasecmp (debug, "D")) { + /* shorthand for "log-level=TRACE,no-expect-message" */ + g_free (c_log_level); + log_level = c_log_level = g_strdup ("TRACE"); + no_expect_message = TRUE; } else if (!g_ascii_strcasecmp (debug, "TRACE")) { g_free (c_log_level); - log_level = c_log_level = g_strdup (debug); + log_level = c_log_level = g_strdup ("TRACE"); } else if (!g_ascii_strncasecmp (debug, "log-domains=", strlen ("log-domains="))) { g_free (c_log_domains); log_domains = c_log_domains = g_strdup (&debug[strlen ("log-domains=")]); @@ -852,6 +860,18 @@ nmtst_main_loop_run (GMainLoop *loop, int timeout_ms) return loopx != NULL; } +inline static void +_nmtst_main_loop_quit_on_notify (GObject *object, GParamSpec *pspec, gpointer user_data) +{ + GMainLoop *loop = user_data; + + g_assert (G_IS_OBJECT (object)); + g_assert (loop); + + g_main_loop_quit (loop); +} +#define nmtst_main_loop_quit_on_notify ((GCallback) _nmtst_main_loop_quit_on_notify) + /*****************************************************************************/ inline static const char * @@ -1773,6 +1793,55 @@ nmtst_create_connection_from_keyfile (const char *keyfile_str, const char *keyfi #ifdef __NM_CONNECTION_H__ +inline static GVariant * +_nmtst_variant_new_vardict (int dummy, ...) +{ + GVariantBuilder builder; + va_list ap; + const char *name; + GVariant *variant; + + g_variant_builder_init (&builder, G_VARIANT_TYPE_VARDICT); + + va_start (ap, dummy); + while ((name = va_arg (ap, const char *))) { + variant = va_arg (ap, GVariant *); + g_variant_builder_add (&builder, "{sv}", name, variant); + } + va_end (ap); + + return g_variant_builder_end (&builder); +} +#define nmtst_variant_new_vardict(...) _nmtst_variant_new_vardict (0, __VA_ARGS__, NULL) + +#define nmtst_assert_variant_is_of_type(variant, type) \ + G_STMT_START { \ + GVariant *_variantx = (variant); \ + \ + g_assert (_variantx); \ + g_assert (g_variant_is_of_type (_variantx, (type))); \ + } G_STMT_END + +#define nmtst_assert_variant_uint32(variant, val) \ + G_STMT_START { \ + GVariant *_variant = (variant); \ + \ + nmtst_assert_variant_is_of_type (_variant, G_VARIANT_TYPE_UINT32); \ + g_assert_cmpint (g_variant_get_uint32 (_variant), ==, (val)); \ + } G_STMT_END + +#define nmtst_assert_variant_string(variant, str) \ + G_STMT_START { \ + gsize _l; \ + GVariant *_variant = (variant); \ + const char *_str = (str); \ + \ + nmtst_assert_variant_is_of_type (_variant, G_VARIANT_TYPE_STRING); \ + g_assert (_str); \ + g_assert_cmpstr (g_variant_get_string (_variant, &_l), ==, _str); \ + g_assert_cmpint (_l, ==, strlen (_str)); \ + } G_STMT_END + typedef enum { NMTST_VARIANT_EDITOR_CONNECTION, NMTST_VARIANT_EDITOR_SETTING, @@ -1815,6 +1884,8 @@ typedef enum { \ if (__cur_setting_name) \ g_variant_builder_add (&__connection_builder, "{sa{sv}}", __cur_setting_name, &__setting_builder); \ + else \ + g_variant_builder_clear (&__setting_builder); \ g_variant_iter_free (__setting_iter); \ } \ \ diff --git a/shared/nm-version-macros.h b/shared/nm-version-macros.h index e113b035..fa27b4c2 100644 --- a/shared/nm-version-macros.h +++ b/shared/nm-version-macros.h @@ -45,7 +45,7 @@ * Evaluates to the micro version number of NetworkManager which this source * compiled against. */ -#define NM_MICRO_VERSION (91) +#define NM_MICRO_VERSION (92) /** * NM_CHECK_VERSION: |