summary refs log tree commit diff
path: root/shared
diff options
context:
space:
mode:
Diffstat (limited to 'shared')
-rw-r--r--shared/Makefile.am2
-rw-r--r--shared/Makefile.in2
-rw-r--r--shared/nm-default.h1
-rw-r--r--shared/nm-macros-internal.h63
-rw-r--r--shared/nm-shared-utils.c228
-rw-r--r--shared/nm-shared-utils.h63
-rw-r--r--shared/nm-test-utils.h109
-rw-r--r--shared/nm-version-macros.h2
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: