summary refs log tree commit diff
path: root/libnm-core/nm-keyfile/nm-keyfile-utils.c
diff options
context:
space:
mode:
Diffstat (limited to 'libnm-core/nm-keyfile/nm-keyfile-utils.c')
-rw-r--r--libnm-core/nm-keyfile/nm-keyfile-utils.c701
1 files changed, 701 insertions, 0 deletions
diff --git a/libnm-core/nm-keyfile/nm-keyfile-utils.c b/libnm-core/nm-keyfile/nm-keyfile-utils.c
new file mode 100644
index 00000000..0ffce40b
--- /dev/null
+++ b/libnm-core/nm-keyfile/nm-keyfile-utils.c
@@ -0,0 +1,701 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Copyright (C) 2010 Red Hat, Inc.
+ */
+
+#include "nm-default.h"
+
+#include "nm-keyfile-utils.h"
+
+#include <stdlib.h>
+
+#include "nm-glib-aux/nm-str-buf.h"
+
+#include "nm-keyfile-internal.h"
+#include "nm-setting-wired.h"
+#include "nm-setting-wireless.h"
+#include "nm-setting-wireless-security.h"
+
+/*****************************************************************************/
+
+/**
+ * nm_key_file_get_boolean:
+ * @kf: the #GKeyFile
+ * @group: the group
+ * @key: the key
+ * @default_value: the default value if the value is set or not parsable as a boolean.
+ *
+ * Replacement for g_key_file_get_boolean() (which uses g_key_file_parse_value_as_boolean()).
+ * g_key_file_get_boolean() seems odd to me, because it accepts trailing ASCII whitespace,
+ * but not leading.
+ * This uses _nm_utils_ascii_str_to_bool(), which accepts trailing and leading whitespace,
+ * case-insensitive words, and also strings like "on" and "off".
+ * _nm_utils_ascii_str_to_bool() is our way to parse booleans from string, and we should
+ * use that one consistently.
+ *
+ * Also, it doesn't have g_key_file_get_boolean()'s odd API to require an error argument
+ * to detect parsing failures.
+ *
+ * Returns: either %TRUE or %FALSE if the key exists and is parsable as a boolean.
+ *   Otherwise, @default_value. Sets errno to ENODATA, EINVAL or 0, depending on whether
+ *   the key exists, whether the value is invalid, or success.
+ */
+int
+nm_key_file_get_boolean (GKeyFile *kf, const char *group, const char *key, int default_value)
+{
+	int v;
+	gs_free char *value = NULL;
+
+	value = g_key_file_get_value (kf, group, key, NULL);
+
+	if (!value) {
+		errno = ENODATA;
+		return default_value;
+	}
+	v = _nm_utils_ascii_str_to_bool (value, -1);
+	if (v != -1) {
+		errno = 0;
+		return v;
+	}
+	errno = EINVAL;
+	return default_value;
+}
+
+/*****************************************************************************/
+
+typedef struct {
+	const char *setting;
+	const char *alias;
+} SettingAlias;
+
+static const SettingAlias alias_list[] = {
+	{ NM_SETTING_WIRED_SETTING_NAME, "ethernet" },
+	{ NM_SETTING_WIRELESS_SETTING_NAME, "wifi" },
+	{ NM_SETTING_WIRELESS_SECURITY_SETTING_NAME, "wifi-security" },
+};
+
+const char *
+nm_keyfile_plugin_get_alias_for_setting_name (const char *setting_name)
+{
+	guint i;
+
+	g_return_val_if_fail (setting_name != NULL, NULL);
+
+	for (i = 0; i < G_N_ELEMENTS (alias_list); i++) {
+		if (nm_streq (setting_name, alias_list[i].setting))
+			return alias_list[i].alias;
+	}
+	return NULL;
+}
+
+const char *
+nm_keyfile_plugin_get_setting_name_for_alias (const char *alias)
+{
+	guint i;
+
+	g_return_val_if_fail (alias != NULL, NULL);
+
+	for (i = 0; i < G_N_ELEMENTS (alias_list); i++) {
+		if (nm_streq (alias, alias_list[i].alias))
+			return alias_list[i].setting;
+	}
+	return NULL;
+}
+
+/*****************************************************************************/
+
+char **
+nm_keyfile_plugin_kf_get_string_list (GKeyFile *kf,
+                                      const char *group,
+                                      const char *key,
+                                      gsize *out_length,
+                                      GError **error)
+{
+	char **list;
+	const char *alias;
+	GError *local = NULL;
+	gsize l;
+
+	list = g_key_file_get_string_list (kf, group, key, &l, &local);
+	if (nm_g_error_matches (local, G_KEY_FILE_ERROR, G_KEY_FILE_ERROR_GROUP_NOT_FOUND)) {
+		alias = nm_keyfile_plugin_get_alias_for_setting_name (group);
+		if (alias) {
+			g_clear_error (&local);
+			list = g_key_file_get_string_list (kf, alias, key, &l, &local);
+		}
+	}
+	if (local)
+		g_propagate_error (error, local);
+	if (!list)
+		l = 0;
+	NM_SET_OUT (out_length, l);
+	return list;
+}
+
+guint *
+nm_keyfile_plugin_kf_get_integer_list_uint (GKeyFile *key_file,
+                                            const char *group_name,
+                                            const char *key,
+                                            gsize *out_length,
+                                            GError **error)
+{
+	GError *key_file_error = NULL;
+	gs_strfreev char **values = NULL;
+	gs_free guint *int_values = NULL;
+	gsize i, num_ints;
+
+	g_return_val_if_fail (key_file != NULL, NULL);
+	g_return_val_if_fail (group_name != NULL, NULL);
+	g_return_val_if_fail (key != NULL, NULL);
+
+	NM_SET_OUT (out_length, 0);
+
+	values = nm_keyfile_plugin_kf_get_string_list (key_file, group_name, key, &num_ints, &key_file_error);
+
+	if (key_file_error)
+		g_propagate_error (error, key_file_error);
+	if (!values)
+		return NULL;
+
+	int_values = g_new (guint, num_ints);
+
+	for (i = 0; i < num_ints; i++) {
+		gint64 v;
+
+		G_STATIC_ASSERT_EXPR (sizeof (v) > sizeof (guint));
+		v = _nm_utils_ascii_str_to_int64 (values[i], 10, 0, G_MAXUINT, -1);
+		if (v == -1) {
+			g_set_error (error,
+			             G_KEY_FILE_ERROR,
+			             G_KEY_FILE_ERROR_INVALID_VALUE,
+			             _("Value cannot be interpreted as a list of numbers."));
+			return NULL;
+		}
+
+		int_values[i] = v;
+	}
+
+	NM_SET_OUT (out_length, num_ints);
+	return g_steal_pointer (&int_values);
+}
+
+void
+nm_keyfile_plugin_kf_set_string_list (GKeyFile *kf,
+                                      const char *group,
+                                      const char *key,
+                                      const char *const*list,
+                                      gsize length)
+{
+	const char *alias;
+
+	alias = nm_keyfile_plugin_get_alias_for_setting_name (group);
+	g_key_file_set_string_list (kf, alias ?: group, key, list, length);
+}
+
+void
+nm_keyfile_plugin_kf_set_integer_list_uint (GKeyFile *kf,
+                                            const char *group,
+                                            const char *key,
+                                            const guint *data,
+                                            gsize length)
+{
+	nm_auto_str_buf NMStrBuf strbuf = { };
+	gsize i;
+
+	g_return_if_fail (kf);
+	g_return_if_fail (!length || data);
+	g_return_if_fail (group && group[0]);
+	g_return_if_fail (key && key[0]);
+
+	nm_str_buf_init (&strbuf, length * 4u + 2u, FALSE);
+	for (i = 0; i < length; i++)
+		nm_str_buf_append_printf (&strbuf, "%u;", data[i]);
+	nm_keyfile_plugin_kf_set_value (kf, group, key, nm_str_buf_get_str (&strbuf));
+}
+
+void
+nm_keyfile_plugin_kf_set_integer_list_uint8 (GKeyFile *kf,
+                                             const char *group,
+                                             const char *key,
+                                             const guint8 *data,
+                                             gsize length)
+{
+	nm_auto_str_buf NMStrBuf strbuf = { };
+	gsize i;
+
+	g_return_if_fail (kf);
+	g_return_if_fail (!length || data);
+	g_return_if_fail (group && group[0]);
+	g_return_if_fail (key && key[0]);
+
+	nm_str_buf_init (&strbuf, length * 4u + 2u, FALSE);
+	for (i = 0; i < length; i++)
+		nm_str_buf_append_printf (&strbuf, "%u;", (guint) data[i]);
+	nm_keyfile_plugin_kf_set_value (kf, group, key, nm_str_buf_get_str (&strbuf));
+}
+
+#define DEFINE_KF_WRAPPER_GET(fcn_name, get_ctype, key_file_get_fcn) \
+get_ctype \
+fcn_name (GKeyFile *kf, \
+          const char *group, \
+          const char *key, \
+          GError **error) \
+{ \
+	get_ctype val; \
+	const char *alias; \
+	GError *local = NULL; \
+\
+	val = key_file_get_fcn (kf, group, key, &local); \
+	if (nm_g_error_matches (local, G_KEY_FILE_ERROR, G_KEY_FILE_ERROR_GROUP_NOT_FOUND)) { \
+		alias = nm_keyfile_plugin_get_alias_for_setting_name (group); \
+		if (alias) { \
+			g_clear_error (&local); \
+			val = key_file_get_fcn (kf, alias, key, &local); \
+		} \
+	} \
+	if (local) \
+		g_propagate_error (error, local); \
+	return val; \
+}
+
+DEFINE_KF_WRAPPER_GET (nm_keyfile_plugin_kf_get_string,  char *,   g_key_file_get_string);
+DEFINE_KF_WRAPPER_GET (nm_keyfile_plugin_kf_get_boolean, gboolean, g_key_file_get_boolean);
+DEFINE_KF_WRAPPER_GET (nm_keyfile_plugin_kf_get_value,   char *,   g_key_file_get_value);
+
+#define DEFINE_KF_WRAPPER_SET(fcn_name, set_ctype, key_file_set_fcn) \
+void \
+fcn_name (GKeyFile *kf, \
+          const char *group, \
+          const char *key, \
+          set_ctype value) \
+{ \
+	const char *alias; \
+\
+	alias = nm_keyfile_plugin_get_alias_for_setting_name (group); \
+	key_file_set_fcn (kf, alias ?: group, key, value); \
+}
+
+DEFINE_KF_WRAPPER_SET (nm_keyfile_plugin_kf_set_string,  const char *, g_key_file_set_string);
+DEFINE_KF_WRAPPER_SET (nm_keyfile_plugin_kf_set_boolean, gboolean,     g_key_file_set_boolean);
+DEFINE_KF_WRAPPER_SET (nm_keyfile_plugin_kf_set_value,   const char *, g_key_file_set_value);
+
+gint64
+nm_keyfile_plugin_kf_get_int64 (GKeyFile *kf,
+                                const char *group,
+                                const char *key,
+                                guint base,
+                                gint64 min,
+                                gint64 max,
+                                gint64 fallback,
+                                GError **error)
+{
+	gs_free char *s = NULL;
+	int errsv;
+	gint64 v;
+
+	s = nm_keyfile_plugin_kf_get_value (kf, group, key, error);
+	if (!s) {
+		errno = ENODATA;
+		return fallback;
+	}
+
+	v = _nm_utils_ascii_str_to_int64 (s, base, min, max, fallback);
+	errsv = errno;
+	if (   errsv != 0
+	    && error) {
+		g_set_error (error, G_KEY_FILE_ERROR, G_KEY_FILE_ERROR_INVALID_VALUE,
+		             _("value is not an integer in range [%lld, %lld]"),
+		             (long long) min, (long long) max);
+		errno = errsv;
+	}
+	return v;
+}
+
+char **
+nm_keyfile_plugin_kf_get_keys (GKeyFile *kf,
+                               const char *group,
+                               gsize *out_length,
+                               GError **error)
+{
+	char **keys;
+	const char *alias;
+	GError *local = NULL;
+	gsize l;
+
+	keys = g_key_file_get_keys (kf, group, &l, &local);
+	if (nm_g_error_matches (local, G_KEY_FILE_ERROR, G_KEY_FILE_ERROR_GROUP_NOT_FOUND)) {
+		alias = nm_keyfile_plugin_get_alias_for_setting_name (group);
+		if (alias) {
+			g_clear_error (&local);
+			keys = g_key_file_get_keys (kf, alias, &l, error ? &local : NULL);
+		}
+	}
+	nm_assert ((!local) != (!keys));
+	if (!keys)
+		l = 0;
+	nm_assert (l == NM_PTRARRAY_LEN (keys));
+	NM_SET_OUT (out_length, l);
+	if (local)
+		g_propagate_error (error, local);
+	return keys;
+}
+
+gboolean
+nm_keyfile_plugin_kf_has_key (GKeyFile *kf,
+                              const char *group,
+                              const char *key,
+                              GError **error)
+{
+	gboolean has;
+	const char *alias;
+	GError *local = NULL;
+
+	has = g_key_file_has_key (kf, group, key, &local);
+	if (nm_g_error_matches (local, G_KEY_FILE_ERROR, G_KEY_FILE_ERROR_GROUP_NOT_FOUND)) {
+		alias = nm_keyfile_plugin_get_alias_for_setting_name (group);
+		if (alias) {
+			g_clear_error (&local);
+			has = g_key_file_has_key (kf, alias, key, &local);
+		}
+	}
+	if (local)
+		g_propagate_error (error, local);
+	return has;
+}
+
+/*****************************************************************************/
+
+void
+_nm_keyfile_copy (GKeyFile *dst, GKeyFile *src)
+{
+	gs_strfreev char **groups = NULL;
+	guint g, k;
+
+	groups = g_key_file_get_groups (src, NULL);
+	for (g = 0; groups && groups[g]; g++) {
+		const char *group = groups[g];
+		gs_strfreev char **keys = NULL;
+
+		keys = g_key_file_get_keys (src, group, NULL, NULL);
+		if (!keys)
+			continue;
+
+		for (k = 0; keys[k]; k++) {
+			const char *key = keys[k];
+			gs_free char *value = NULL;
+
+			value = g_key_file_get_value (src, group, key, NULL);
+			if (value)
+				g_key_file_set_value (dst, group, key, value);
+			else
+				g_key_file_remove_key (dst, group, key, NULL);
+		}
+	}
+}
+
+/*****************************************************************************/
+
+gboolean
+_nm_keyfile_a_contains_all_in_b (GKeyFile *kf_a, GKeyFile *kf_b)
+{
+	gs_strfreev char **groups = NULL;
+	guint i, j;
+
+	if (kf_a == kf_b)
+		return TRUE;
+	if (!kf_a || !kf_b)
+		return FALSE;
+
+	groups = g_key_file_get_groups (kf_a, NULL);
+	for (i = 0; groups && groups[i]; i++) {
+		gs_strfreev char **keys = NULL;
+
+		keys = g_key_file_get_keys (kf_a, groups[i], NULL, NULL);
+		if (!keys)
+			continue;
+
+		for (j = 0; keys[j]; j++) {
+			gs_free char *key_a = g_key_file_get_value (kf_a, groups[i], keys[j], NULL);
+			gs_free char *key_b = g_key_file_get_value (kf_b, groups[i], keys[j], NULL);
+
+			if (g_strcmp0 (key_a, key_b) != 0)
+				return FALSE;
+		}
+	}
+	return TRUE;
+}
+
+static gboolean
+_nm_keyfile_equals_ordered (GKeyFile *kf_a, GKeyFile *kf_b)
+{
+	gs_strfreev char **groups = NULL;
+	gs_strfreev char **groups_b = NULL;
+	guint i, j;
+
+	if (kf_a == kf_b)
+		return TRUE;
+	if (!kf_a || !kf_b)
+		return FALSE;
+
+	groups = g_key_file_get_groups (kf_a, NULL);
+	groups_b = g_key_file_get_groups (kf_b, NULL);
+	if (!groups && !groups_b)
+		return TRUE;
+	if (!groups || !groups_b)
+		return FALSE;
+	for (i = 0; groups[i] && groups_b[i] && !strcmp (groups[i], groups_b[i]); i++)
+		;
+	if (groups[i] || groups_b[i])
+		return FALSE;
+
+	for (i = 0; groups[i]; i++) {
+		gs_strfreev char **keys = NULL;
+		gs_strfreev char **keys_b = NULL;
+
+		keys = g_key_file_get_keys (kf_a, groups[i], NULL, NULL);
+		keys_b = g_key_file_get_keys (kf_b, groups[i], NULL, NULL);
+
+		if ((!keys) != (!keys_b))
+			return FALSE;
+		if (!keys)
+			continue;
+
+		for (j = 0; keys[j] && keys_b[j] && !strcmp (keys[j], keys_b[j]); j++)
+			;
+		if (keys[j] || keys_b[j])
+			return FALSE;
+
+		for (j = 0; keys[j]; j++) {
+			gs_free char *key_a = g_key_file_get_value (kf_a, groups[i], keys[j], NULL);
+			gs_free char *key_b = g_key_file_get_value (kf_b, groups[i], keys[j], NULL);
+
+			if (g_strcmp0 (key_a, key_b) != 0)
+				return FALSE;
+		}
+	}
+	return TRUE;
+}
+
+gboolean
+_nm_keyfile_equals (GKeyFile *kf_a, GKeyFile *kf_b, gboolean consider_order)
+{
+	if (!consider_order) {
+		return    _nm_keyfile_a_contains_all_in_b (kf_a, kf_b)
+		       && _nm_keyfile_a_contains_all_in_b (kf_b, kf_a);
+	} else {
+		return _nm_keyfile_equals_ordered (kf_a, kf_b);
+	}
+}
+
+gboolean
+_nm_keyfile_has_values (GKeyFile *keyfile)
+{
+	gs_strfreev char **groups = NULL;
+
+	g_return_val_if_fail (keyfile, FALSE);
+
+	groups = g_key_file_get_groups (keyfile, NULL);
+	return groups && groups[0];
+}
+
+/*****************************************************************************/
+
+static const char *
+_keyfile_key_encode (const char *name,
+                     char **out_to_free)
+{
+	gsize len, i;
+	GString *str;
+
+	nm_assert (name);
+	nm_assert (out_to_free && !*out_to_free);
+
+	/* See g_key_file_is_key_name().
+	 *
+	 * GKeyFile allows all UTF-8 characters (even non-well formed sequences),
+	 * except:
+	 *  - no empty keys
+	 *  - no leading/trailing ' '
+	 *  - no '=', '[', ']'
+	 *
+	 * We do something more strict here. All non-ASCII characters, all non-printable
+	 * characters, and all invalid characters are escaped with "\\XX".
+	 *
+	 * We don't escape \\, unless it is followed by two hex digits.
+	 */
+
+	if (!name[0]) {
+		/* empty keys are are backslash encoded. Note that usually
+		 * \\00 is not a valid encode, the only exception is the empty
+		 * word. */
+		return "\\00";
+	}
+
+	/* find the first character that needs escaping. */
+	i = 0;
+	if (name[0] != ' ') {
+		for (;; i++) {
+			const guchar ch = (guchar) name[i];
+
+			if (ch == '\0')
+				return name;
+
+			if (   ch < 0x20
+			    || ch >= 127
+			    || NM_IN_SET (ch, '=', '[', ']')
+			    || (   ch == '\\'
+			        && g_ascii_isxdigit (name[i + 1])
+			        && g_ascii_isxdigit (name[i + 2]))
+			    || (   ch == ' '
+			        && name[i + 1] == '\0'))
+				break;
+		}
+	} else if (name[1] == '\0')
+		return "\\20";
+
+	len = i + strlen (&name[i]);
+	nm_assert (len == strlen (name));
+	str = g_string_sized_new (len + 15);
+
+	if (name[0] == ' ') {
+		nm_assert (i == 0);
+		g_string_append (str, "\\20");
+		i = 1;
+	} else
+		g_string_append_len (str, name, i);
+
+	for (;; i++) {
+		const guchar ch = (guchar) name[i];
+
+		if (ch == '\0')
+			break;
+
+		if (   ch < 0x20
+		    || ch >= 127
+		    || NM_IN_SET (ch, '=', '[', ']')
+		    || (   ch == '\\'
+		        && g_ascii_isxdigit (name[i + 1])
+		        && g_ascii_isxdigit (name[i + 2]))
+		    || (   ch == ' '
+		        && name[i + 1] == '\0'))
+			g_string_append_printf (str, "\\%02X", ch);
+		else
+			g_string_append_c (str, (char) ch);
+	}
+
+	return (*out_to_free = g_string_free (str, FALSE));
+}
+
+static const char *
+_keyfile_key_decode (const char *key,
+                     char **out_to_free)
+{
+	gsize i, len;
+	GString *str;
+
+	nm_assert (key);
+	nm_assert (out_to_free && !*out_to_free);
+
+	if (!key[0])
+		return "";
+
+	for (i = 0; TRUE; i++) {
+		const char ch = key[i];
+
+		if (ch == '\0')
+			return key;
+		if (   ch == '\\'
+		    && g_ascii_isxdigit (key[i + 1])
+		    && g_ascii_isxdigit (key[i + 2]))
+			break;
+	}
+
+	len = i + strlen (&key[i]);
+
+	if (   len == 3
+	    && nm_streq (key, "\\00"))
+		return "";
+
+	nm_assert (len == strlen (key));
+	str = g_string_sized_new (len + 3);
+
+	g_string_append_len (str, key, i);
+	for (;;) {
+		const char ch = key[i];
+		char ch1, ch2;
+		unsigned v;
+
+		if (ch == '\0')
+			break;
+
+		if (   ch == '\\'
+		    && g_ascii_isxdigit ((ch1 = key[i + 1]))
+		    && g_ascii_isxdigit ((ch2 = key[i + 2]))) {
+			v = (g_ascii_xdigit_value (ch1) << 4) + g_ascii_xdigit_value (ch2);
+			if (v != 0) {
+				g_string_append_c (str, (char) v);
+				i += 3;
+				continue;
+			}
+		}
+		g_string_append_c (str, ch);
+		i++;
+	}
+
+	return (*out_to_free = g_string_free (str, FALSE));
+}
+
+/*****************************************************************************/
+
+const char *
+nm_keyfile_key_encode (const char *name,
+                       char **out_to_free)
+{
+	const char *key;
+
+	key = _keyfile_key_encode (name, out_to_free);
+#if NM_MORE_ASSERTS > 5
+	nm_assert (key);
+	nm_assert (!*out_to_free || key == *out_to_free);
+	nm_assert (!*out_to_free || !nm_streq0 (name, key));
+	{
+		gs_free char *to_free2 = NULL;
+		const char *name2;
+
+		name2 = _keyfile_key_decode (key, &to_free2);
+		/* name2, the result of encode()+decode() is identical to name.
+		 * That is because
+		 *   - encode() is a injective function.
+		 *   - decode() is a surjective function, however for output
+		 *     values of encode() is behaves injective too. */
+		nm_assert (nm_streq0 (name2, name));
+	}
+#endif
+	return key;
+}
+
+const char *
+nm_keyfile_key_decode (const char *key,
+                       char **out_to_free)
+{
+	const char *name;
+
+	name = _keyfile_key_decode (key, out_to_free);
+#if NM_MORE_ASSERTS > 5
+	nm_assert (name);
+	nm_assert (!*out_to_free || name == *out_to_free);
+	{
+		gs_free char *to_free2 = NULL;
+		const char *key2;
+
+		key2 = _keyfile_key_encode (name, &to_free2);
+		/* key2, the result of decode+encode may not be idential
+		 * to the original key. That is, decode() is a surjective
+		 * function mapping different keys to the same name.
+		 * However, decode() behaves injective for input that
+		 * are valid output of encode(). */
+		nm_assert (key2);
+	}
+#endif
+	return name;
+}