summaryrefslogtreecommitdiff
path: root/src/libnm-glib-aux/nm-enum-utils.c
diff options
context:
space:
mode:
authorThomas Haller <thaller@redhat.com>2021-02-18 17:37:47 +0100
committerThomas Haller <thaller@redhat.com>2021-02-18 19:47:16 +0100
commitfbf82f51b72a27b00c39ea37d4d359f09da4bf02 (patch)
tree035559bbae2df761c02c36f0874533f617ffb393 /src/libnm-glib-aux/nm-enum-utils.c
parent61f006caf47aaa184ff4d54a6280956acc1fbad9 (diff)
downloadNetworkManager-th/libnm-core-split2.tar.gz
build: move "shared/nm-{glib-aux,log-null,log-core}" to "src/libnm-{glib-aux,log-null,log-core}"th/libnm-core-split2
Diffstat (limited to 'src/libnm-glib-aux/nm-enum-utils.c')
-rw-r--r--src/libnm-glib-aux/nm-enum-utils.c368
1 files changed, 368 insertions, 0 deletions
diff --git a/src/libnm-glib-aux/nm-enum-utils.c b/src/libnm-glib-aux/nm-enum-utils.c
new file mode 100644
index 0000000000..f97cdfcbb5
--- /dev/null
+++ b/src/libnm-glib-aux/nm-enum-utils.c
@@ -0,0 +1,368 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+/*
+ * Copyright (C) 2017 Red Hat, Inc.
+ */
+
+#include "libnm-glib-aux/nm-default-glib-i18n-lib.h"
+
+#include "nm-enum-utils.h"
+#include "nm-str-buf.h"
+
+/*****************************************************************************/
+
+#define IS_FLAGS_SEPARATOR(ch) (NM_IN_SET((ch), ' ', '\t', ',', '\n', '\r'))
+
+static void
+_ASSERT_enum_values_info(GType type, const NMUtilsEnumValueInfo *value_infos)
+{
+#if NM_MORE_ASSERTS > 5
+ nm_auto_unref_gtypeclass GTypeClass *klass = NULL;
+ gs_unref_hashtable GHashTable *ht = NULL;
+
+ klass = g_type_class_ref(type);
+
+ g_assert(G_IS_ENUM_CLASS(klass) || G_IS_FLAGS_CLASS(klass));
+
+ if (!value_infos)
+ return;
+
+ ht = g_hash_table_new(g_str_hash, g_str_equal);
+
+ for (; value_infos->nick; value_infos++) {
+ g_assert(value_infos->nick[0]);
+
+ /* duplicate nicks make no sense!! */
+ g_assert(!g_hash_table_contains(ht, value_infos->nick));
+ g_hash_table_add(ht, (gpointer) value_infos->nick);
+
+ if (G_IS_ENUM_CLASS(klass)) {
+ GEnumValue *enum_value;
+
+ enum_value = g_enum_get_value_by_nick(G_ENUM_CLASS(klass), value_infos->nick);
+ if (enum_value) {
+ /* we do allow specifying the same name via @value_infos and @type.
+ * That might make sense, if @type comes from a library where older versions
+ * of the library don't yet support the value. In this case, the caller can
+ * provide the nick via @value_infos, to support the older library version.
+ * And then, when actually running against a newer library version where
+ * @type knows the nick, we have this situation.
+ *
+ * Another reason for specifying a nick both in @value_infos and @type,
+ * is to specify an alias which is not used with highest preference. For
+ * example, if you add an alias "disabled" for "none" (both numerically
+ * equal), then the first alias in @value_infos will be preferred over
+ * the name from @type. So, to still use "none" as preferred name, you may
+ * explicitly specify the "none" alias in @value_infos before "disabled".
+ *
+ * However, what never is allowed, is to use a name (nick) to re-number
+ * the value. That is, if both @value_infos and @type contain a particular
+ * nick, their numeric values must agree as well.
+ * Allowing this, would be very confusing, because the name would have a different
+ * value from the regular GLib GEnum API.
+ */
+ g_assert(enum_value->value == value_infos->value);
+ }
+ } else {
+ GFlagsValue *flags_value;
+
+ flags_value = g_flags_get_value_by_nick(G_FLAGS_CLASS(klass), value_infos->nick);
+ if (flags_value) {
+ /* see ENUM case above. */
+ g_assert(flags_value->value == (guint) value_infos->value);
+ }
+ }
+ }
+#endif
+}
+
+static gboolean
+_is_hex_string(const char *str, gboolean allow_sign)
+{
+ if (allow_sign && str[0] == '-')
+ str++;
+ return str[0] == '0' && str[1] == 'x' && str[2]
+ && NM_STRCHAR_ALL(&str[2], ch, g_ascii_isxdigit(ch));
+}
+
+static gboolean
+_is_dec_string(const char *str, gboolean allow_sign)
+{
+ if (allow_sign && str[0] == '-')
+ str++;
+ return str[0] && NM_STRCHAR_ALL(&str[0], ch, g_ascii_isdigit(ch));
+}
+
+static gboolean
+_enum_is_valid_enum_nick(const char *str)
+{
+ return str[0] && !NM_STRCHAR_ANY(str, ch, g_ascii_isspace(ch)) && !_is_dec_string(str, TRUE)
+ && !_is_hex_string(str, TRUE);
+}
+
+static gboolean
+_enum_is_valid_flags_nick(const char *str)
+{
+ return str[0] && !NM_STRCHAR_ANY(str, ch, IS_FLAGS_SEPARATOR(ch)) && !_is_dec_string(str, FALSE)
+ && !_is_hex_string(str, FALSE);
+}
+
+char *
+_nm_utils_enum_to_str_full(GType type,
+ int value,
+ const char * flags_separator,
+ const NMUtilsEnumValueInfo *value_infos)
+{
+ nm_auto_unref_gtypeclass GTypeClass *klass = NULL;
+
+ _ASSERT_enum_values_info(type, value_infos);
+
+ if (flags_separator
+ && (!flags_separator[0] || NM_STRCHAR_ANY(flags_separator, ch, !IS_FLAGS_SEPARATOR(ch))))
+ g_return_val_if_reached(NULL);
+
+ klass = g_type_class_ref(type);
+
+ if (G_IS_ENUM_CLASS(klass)) {
+ GEnumValue *enum_value;
+
+ for (; value_infos && value_infos->nick; value_infos++) {
+ if (value_infos->value == value)
+ return g_strdup(value_infos->nick);
+ }
+
+ enum_value = g_enum_get_value(G_ENUM_CLASS(klass), value);
+ if (!enum_value || !_enum_is_valid_enum_nick(enum_value->value_nick))
+ return g_strdup_printf("%d", value);
+ else
+ return g_strdup(enum_value->value_nick);
+ } else if (G_IS_FLAGS_CLASS(klass)) {
+ unsigned uvalue = (unsigned) value;
+ GFlagsValue *flags_value;
+ NMStrBuf strbuf;
+
+ flags_separator = flags_separator ?: " ";
+
+ nm_str_buf_init(&strbuf, 16, FALSE);
+
+ for (; value_infos && value_infos->nick; value_infos++) {
+ nm_assert(_enum_is_valid_flags_nick(value_infos->nick));
+
+ if (uvalue == 0) {
+ if (value_infos->value != 0)
+ continue;
+ } else {
+ if (!NM_FLAGS_ALL(uvalue, (unsigned) value_infos->value))
+ continue;
+ }
+
+ if (strbuf.len)
+ nm_str_buf_append(&strbuf, flags_separator);
+ nm_str_buf_append(&strbuf, value_infos->nick);
+ uvalue &= ~((unsigned) value_infos->value);
+ if (uvalue == 0) {
+ /* we printed all flags. Done. */
+ goto flags_done;
+ }
+ }
+
+ do {
+ flags_value = g_flags_get_first_value(G_FLAGS_CLASS(klass), uvalue);
+ if (strbuf.len)
+ nm_str_buf_append(&strbuf, flags_separator);
+ if (!flags_value || !_enum_is_valid_flags_nick(flags_value->value_nick)) {
+ if (uvalue)
+ nm_str_buf_append_printf(&strbuf, "0x%x", uvalue);
+ break;
+ }
+ nm_str_buf_append(&strbuf, flags_value->value_nick);
+ uvalue &= ~flags_value->value;
+ } while (uvalue);
+
+flags_done:
+ return nm_str_buf_finalize(&strbuf, NULL);
+ }
+
+ g_return_val_if_reached(NULL);
+}
+
+static const NMUtilsEnumValueInfo *
+_find_value_info(const NMUtilsEnumValueInfo *value_infos, const char *needle)
+{
+ if (value_infos) {
+ for (; value_infos->nick; value_infos++) {
+ if (nm_streq(needle, value_infos->nick))
+ return value_infos;
+ }
+ }
+ return NULL;
+}
+
+gboolean
+_nm_utils_enum_from_str_full(GType type,
+ const char * str,
+ int * out_value,
+ char ** err_token,
+ const NMUtilsEnumValueInfo *value_infos)
+{
+ nm_auto_unref_gtypeclass GTypeClass *klass = NULL;
+ gboolean ret = FALSE;
+ int value = 0;
+ gs_free char * str_clone = NULL;
+ char * s;
+ gint64 v64;
+ const NMUtilsEnumValueInfo * nick;
+
+ g_return_val_if_fail(str, FALSE);
+
+ _ASSERT_enum_values_info(type, value_infos);
+
+ s = nm_strdup_maybe_a(300, nm_str_skip_leading_spaces(str), &str_clone);
+ g_strchomp(s);
+
+ klass = g_type_class_ref(type);
+
+ if (G_IS_ENUM_CLASS(klass)) {
+ GEnumValue *enum_value;
+
+ G_STATIC_ASSERT(G_MAXINT < G_MAXINT64);
+ G_STATIC_ASSERT(G_MININT > G_MININT64);
+
+ if (s[0]) {
+ if (_is_hex_string(s, TRUE)) {
+ if (s[0] == '-') {
+ v64 = _nm_utils_ascii_str_to_int64(&s[3],
+ 16,
+ -((gint64) G_MAXINT),
+ -((gint64) G_MININT),
+ G_MAXINT64);
+ if (v64 != G_MAXINT64) {
+ value = (int) (-v64);
+ ret = TRUE;
+ }
+ } else {
+ v64 = _nm_utils_ascii_str_to_int64(&s[2], 16, G_MININT, G_MAXINT, G_MAXINT64);
+ if (v64 != G_MAXINT64) {
+ value = (int) v64;
+ ret = TRUE;
+ }
+ }
+ } else if (_is_dec_string(s, TRUE)) {
+ v64 = _nm_utils_ascii_str_to_int64(s, 10, G_MININT, G_MAXINT, G_MAXINT64);
+ if (v64 != G_MAXINT64) {
+ value = (int) v64;
+ ret = TRUE;
+ }
+ } else if ((nick = _find_value_info(value_infos, s))) {
+ value = nick->value;
+ ret = TRUE;
+ } else if ((enum_value = g_enum_get_value_by_nick(G_ENUM_CLASS(klass), s))) {
+ value = enum_value->value;
+ ret = TRUE;
+ }
+ }
+ } else if (G_IS_FLAGS_CLASS(klass)) {
+ GFlagsValue *flags_value;
+ unsigned uvalue = 0;
+
+ ret = TRUE;
+ while (s[0]) {
+ char *s_end;
+
+ for (s_end = s; s_end[0]; s_end++) {
+ if (IS_FLAGS_SEPARATOR(s_end[0])) {
+ s_end[0] = '\0';
+ s_end++;
+ break;
+ }
+ }
+
+ if (s[0]) {
+ if (_is_hex_string(s, FALSE)) {
+ v64 = _nm_utils_ascii_str_to_int64(&s[2], 16, 0, G_MAXUINT, -1);
+ if (v64 == -1) {
+ ret = FALSE;
+ break;
+ }
+ uvalue |= (unsigned) v64;
+ } else if (_is_dec_string(s, FALSE)) {
+ v64 = _nm_utils_ascii_str_to_int64(s, 10, 0, G_MAXUINT, -1);
+ if (v64 == -1) {
+ ret = FALSE;
+ break;
+ }
+ uvalue |= (unsigned) v64;
+ } else if ((nick = _find_value_info(value_infos, s)))
+ uvalue |= (unsigned) nick->value;
+ else if ((flags_value = g_flags_get_value_by_nick(G_FLAGS_CLASS(klass), s)))
+ uvalue |= flags_value->value;
+ else {
+ ret = FALSE;
+ break;
+ }
+ }
+
+ s = s_end;
+ }
+
+ value = (int) uvalue;
+ } else
+ g_return_val_if_reached(FALSE);
+
+ NM_SET_OUT(err_token, !ret && s[0] ? g_strdup(s) : NULL);
+ NM_SET_OUT(out_value, ret ? value : 0);
+ return ret;
+}
+
+const char **
+_nm_utils_enum_get_values(GType type, int from, int to)
+{
+ GTypeClass *klass;
+ GPtrArray * array;
+ int i;
+ char sbuf[64];
+
+ klass = g_type_class_ref(type);
+ array = g_ptr_array_new();
+
+ if (G_IS_ENUM_CLASS(klass)) {
+ GEnumClass *enum_class = G_ENUM_CLASS(klass);
+ GEnumValue *enum_value;
+
+ for (i = 0; i < enum_class->n_values; i++) {
+ enum_value = &enum_class->values[i];
+ if (enum_value->value >= from && enum_value->value <= to) {
+ if (_enum_is_valid_enum_nick(enum_value->value_nick))
+ g_ptr_array_add(array, (gpointer) enum_value->value_nick);
+ else
+ g_ptr_array_add(
+ array,
+ (gpointer) g_intern_string(nm_sprintf_buf(sbuf, "%d", enum_value->value)));
+ }
+ }
+ } else if (G_IS_FLAGS_CLASS(klass)) {
+ GFlagsClass *flags_class = G_FLAGS_CLASS(klass);
+ GFlagsValue *flags_value;
+
+ for (i = 0; i < flags_class->n_values; i++) {
+ flags_value = &flags_class->values[i];
+ if (flags_value->value >= (guint) from && flags_value->value <= (guint) to) {
+ if (_enum_is_valid_flags_nick(flags_value->value_nick))
+ g_ptr_array_add(array, (gpointer) flags_value->value_nick);
+ else
+ g_ptr_array_add(
+ array,
+ (gpointer) g_intern_string(
+ nm_sprintf_buf(sbuf, "0x%x", (unsigned) flags_value->value)));
+ }
+ }
+ } else {
+ g_type_class_unref(klass);
+ g_ptr_array_free(array, TRUE);
+ g_return_val_if_reached(NULL);
+ }
+
+ g_type_class_unref(klass);
+ g_ptr_array_add(array, NULL);
+
+ return (const char **) g_ptr_array_free(array, FALSE);
+}