summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorThomas Haller <thaller@redhat.com>2017-05-04 16:26:02 +0200
committerThomas Haller <thaller@redhat.com>2017-05-06 14:12:19 +0200
commit79be44d99020bbaf23b8226b7e459f321348c0b1 (patch)
tree03b0e95f01fa8088e2bcde8729fad1a4ecf22437
parent22fd7d2e39327c9f288dd8f17529c1898bc20e47 (diff)
downloadNetworkManager-79be44d99020bbaf23b8226b7e459f321348c0b1.tar.gz
ifcfg: add read/write support for user-data
The user data values are encoded in shell variables named prefix "NM_USER_". The variable name is an encoded form of the data key, consisting only of upper-case letters, digits, and underscore. The alternative would be something like NM_USER_1_KEY=my.keys.1 NM_USER_1_VAL='some value' NM_USER_2_KEY=my.other.KEY.42 NM_USER_2_VAL='other value' contary to NM_USER_MY__KEYS__1='some value' NM_USER_MY__OTHER___K_E_Y__42='other value' The advantage of the former, numbered scheme is that it may be easier to find the key of a user-data entry. With the current implementation, the shell script would have to decode the key, like the ifcfg-rh plugin does. However, user data keys are opaque identifers for values. Usually, you are not concerned with a certain name of the key, you already know it. Hence, you don't need to write a shell script to decode the key name, instead, you can use it directly: if [ -z ${NM_USER_MY__OTHER___K_E_Y__42+x} ]; then do_something_with_key "$NM_USER_MY__OTHER___K_E_Y__42" fi Otherwise, you'd first have to search write a shell script to search for the interesting key -- in this example "$NM_USER_2_KEY", before being able to access the value "$NM_USER_2_VAL".
-rw-r--r--Makefile.am3
-rw-r--r--src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-reader.c55
-rw-r--r--src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-utils.c114
-rw-r--r--src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-utils.h4
-rw-r--r--src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-writer.c37
-rw-r--r--src/settings/plugins/ifcfg-rh/shvar.c51
-rw-r--r--src/settings/plugins/ifcfg-rh/shvar.h4
-rw-r--r--src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_User_1.cexpected34
-rw-r--r--src/settings/plugins/ifcfg-rh/tests/test-ifcfg-rh.c59
9 files changed, 356 insertions, 5 deletions
diff --git a/Makefile.am b/Makefile.am
index 9191b1c015..335239bdbd 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -2157,7 +2157,8 @@ EXTRA_DIST += \
src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-random_wifi_connection.cexpected \
src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-random_wifi_connection_2.cexpected \
src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-team-slave-enp31s0f1-142.cexpected \
- src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-static-routes-legacy.cexpected
+ src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-static-routes-legacy.cexpected \
+ src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_User_1.cexpected
# make target dependencies can't have colons in their names, which ends up
# meaning that we can't add the alias files to EXTRA_DIST. They are instead
diff --git a/src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-reader.c b/src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-reader.c
index 2df922313b..5baa9c792b 100644
--- a/src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-reader.c
+++ b/src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-reader.c
@@ -48,6 +48,7 @@
#include "nm-setting-bridge.h"
#include "nm-setting-bridge-port.h"
#include "nm-setting-dcb.h"
+#include "nm-setting-user.h"
#include "nm-setting-proxy.h"
#include "nm-setting-generic.h"
#include "nm-core-internal.h"
@@ -1023,6 +1024,54 @@ error:
}
static NMSetting *
+make_user_setting (shvarFile *ifcfg, GError **error)
+{
+ gboolean has_user_data = FALSE;
+ gs_unref_object NMSettingUser *s_user = NULL;
+ gs_unref_hashtable GHashTable *keys = NULL;
+ GHashTableIter iter;
+ const char *key;
+ nm_auto_free_gstring GString *str = NULL;
+
+ keys = svGetKeys (ifcfg);
+ if (!keys)
+ return NULL;
+
+ g_hash_table_iter_init (&iter, keys);
+ while (g_hash_table_iter_next (&iter, (gpointer *) &key, NULL)) {
+ const char *value;
+ gs_free char *value_to_free = NULL;
+
+ if (!g_str_has_prefix (key, "NM_USER_"))
+ continue;
+
+ value = svGetValue (ifcfg, key, &value_to_free);
+
+ if (!value)
+ continue;
+
+ if (!str)
+ str = g_string_sized_new (100);
+ else
+ g_string_set_size (str, 0);
+
+ if (!nms_ifcfg_rh_utils_user_key_decode (key + NM_STRLEN ("NM_USER_"), str))
+ continue;
+
+ if (!s_user)
+ s_user = NM_SETTING_USER (nm_setting_user_new ());
+
+ if (nm_setting_user_set_data (s_user, str->str,
+ value, NULL))
+ has_user_data = TRUE;
+ }
+
+ return has_user_data
+ ? g_steal_pointer (&s_user)
+ : NULL;
+}
+
+static NMSetting *
make_proxy_setting (shvarFile *ifcfg, GError **error)
{
NMSettingProxy *s_proxy = NULL;
@@ -5121,7 +5170,7 @@ connection_from_file_full (const char *filename,
gs_unref_object NMConnection *connection = NULL;
gs_free char *type = NULL;
char *devtype, *bootproto;
- NMSetting *s_ip4, *s_ip6, *s_proxy, *s_port, *s_dcb = NULL;
+ NMSetting *s_ip4, *s_ip6, *s_proxy, *s_port, *s_dcb = NULL, *s_user;
const char *ifcfg_name = NULL;
gboolean has_ip4_defroute = FALSE;
@@ -5361,6 +5410,10 @@ connection_from_file_full (const char *filename,
if (s_proxy)
nm_connection_add_setting (connection, s_proxy);
+ s_user = make_user_setting (parsed, error);
+ if (s_user)
+ nm_connection_add_setting (connection, s_user);
+
/* Bridge port? */
s_port = make_bridge_port_setting (parsed);
if (s_port)
diff --git a/src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-utils.c b/src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-utils.c
index 0dd49fb4a7..e82ef60c63 100644
--- a/src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-utils.c
+++ b/src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-utils.c
@@ -365,3 +365,117 @@ utils_detect_ifcfg_path (const char *path, gboolean only_ifcfg)
return NULL;
return utils_get_ifcfg_path (path);
}
+
+void
+nms_ifcfg_rh_utils_user_key_encode (const char *key, GString *str_buffer)
+{
+ gsize i;
+
+ nm_assert (key);
+ nm_assert (str_buffer);
+
+ for (i = 0; key[i]; i++) {
+ char ch = key[i];
+
+ /* we encode the key in only upper case letters, digits, and underscore.
+ * As we expect lower-case letters to be more common, we encode lower-case
+ * letters as upper case, and upper-case letters with a leading underscore. */
+
+ if (ch >= '0' && ch <= '9') {
+ g_string_append_c (str_buffer, ch);
+ continue;
+ }
+ if (ch >= 'a' && ch <= 'z') {
+ g_string_append_c (str_buffer, ch - 'a' + 'A');
+ continue;
+ }
+ if (ch == '.') {
+ g_string_append (str_buffer, "__");
+ continue;
+ }
+ if (ch >= 'A' && ch <= 'Z') {
+ g_string_append_c (str_buffer, '_');
+ g_string_append_c (str_buffer, ch);
+ continue;
+ }
+ g_string_append_printf (str_buffer, "_%03o", (unsigned) ch);
+ }
+}
+
+gboolean
+nms_ifcfg_rh_utils_user_key_decode (const char *name, GString *str_buffer)
+{
+ gsize i;
+
+ nm_assert (name);
+ nm_assert (str_buffer);
+
+ if (!name[0])
+ return FALSE;
+
+ for (i = 0; name[i]; ) {
+ char ch = name[i];
+
+ if (ch >= '0' && ch <= '9') {
+ g_string_append_c (str_buffer, ch);
+ i++;
+ continue;
+ }
+ if (ch >= 'A' && ch <= 'Z') {
+ g_string_append_c (str_buffer, ch - 'A' + 'a');
+ i++;
+ continue;
+ }
+
+ if (ch == '_') {
+ ch = name[i + 1];
+ if (ch == '_') {
+ g_string_append_c (str_buffer, '.');
+ i += 2;
+ continue;
+ }
+ if (ch >= 'A' && ch <= 'Z') {
+ g_string_append_c (str_buffer, ch);
+ i += 2;
+ continue;
+ }
+ if (ch >= '0' && ch <= '7') {
+ char ch2, ch3;
+ unsigned v;
+
+ ch2 = name[i + 2];
+ if (!(ch2 >= '0' && ch2 <= '7'))
+ return FALSE;
+
+ ch3 = name[i + 3];
+ if (!(ch3 >= '0' && ch3 <= '7'))
+ return FALSE;
+
+#define OCTAL_VALUE(ch) ((unsigned) ((ch) - '0'))
+ v = (OCTAL_VALUE (ch) << 6) +
+ (OCTAL_VALUE (ch2) << 3) +
+ OCTAL_VALUE (ch3);
+ if ( v > 0xFF
+ || v == 0)
+ return FALSE;
+ ch = (char) v;
+ if ( (ch >= 'A' && ch <= 'Z')
+ || (ch >= '0' && ch <= '9')
+ || (ch == '.')
+ || (ch >= 'a' && ch <= 'z')) {
+ /* such characters are not expected to be encoded via
+ * octal representation. The encoding is invalid. */
+ return FALSE;
+ }
+ g_string_append_c (str_buffer, ch);
+ i += 4;
+ continue;
+ }
+ return FALSE;
+ }
+
+ return FALSE;
+ }
+
+ return TRUE;
+}
diff --git a/src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-utils.h b/src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-utils.h
index d209a0673c..2b2c27558f 100644
--- a/src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-utils.h
+++ b/src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-utils.h
@@ -54,5 +54,7 @@ gboolean utils_is_ifcfg_alias_file (const char *alias, const char *ifcfg);
char *utils_detect_ifcfg_path (const char *path, gboolean only_ifcfg);
-#endif /* _UTILS_H_ */
+void nms_ifcfg_rh_utils_user_key_encode (const char *key, GString *str_buffer);
+gboolean nms_ifcfg_rh_utils_user_key_decode (const char *name, GString *str_buffer);
+#endif /* _UTILS_H_ */
diff --git a/src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-writer.c b/src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-writer.c
index 8dd8737ad9..28bea262ed 100644
--- a/src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-writer.c
+++ b/src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-writer.c
@@ -43,6 +43,7 @@
#include "nm-setting-ip6-config.h"
#include "nm-setting-pppoe.h"
#include "nm-setting-vlan.h"
+#include "nm-setting-user.h"
#include "nm-setting-team.h"
#include "nm-setting-team-port.h"
#include "nm-utils.h"
@@ -2003,6 +2004,39 @@ write_proxy_setting (NMConnection *connection, shvarFile *ifcfg, GError **error)
}
static gboolean
+write_user_setting (NMConnection *connection, shvarFile *ifcfg, GError **error)
+{
+ NMSettingUser *s_user;
+ guint i, len;
+ const char *const*keys;
+
+ s_user = NM_SETTING_USER (nm_connection_get_setting (connection, NM_TYPE_SETTING_USER));
+
+ svUnsetValuesWithPrefix (ifcfg, "NM_USER_");
+
+ if (!s_user)
+ return TRUE;
+
+ keys = nm_setting_user_get_keys (s_user, &len);
+ if (len) {
+ nm_auto_free_gstring GString *str = g_string_sized_new (100);
+
+ for (i = 0; i < len; i++) {
+ const char *key = keys[i];
+
+ g_string_set_size (str, 0);
+ g_string_append (str, "NM_USER_");
+ nms_ifcfg_rh_utils_user_key_encode (key, str);
+ svSetValue (ifcfg,
+ str->str,
+ nm_setting_user_get_data (s_user, key));
+ }
+ }
+
+ return TRUE;
+}
+
+static gboolean
write_ip4_setting (NMConnection *connection, shvarFile *ifcfg, GError **error)
{
NMSettingIPConfig *s_ip4;
@@ -2882,6 +2916,9 @@ write_connection (NMConnection *connection,
if (!write_proxy_setting (connection, ifcfg, error))
return FALSE;
+ if (!write_user_setting (connection, ifcfg, error))
+ return FALSE;
+
svUnsetValue (ifcfg, "DHCP_HOSTNAME");
svUnsetValue (ifcfg, "DHCP_FQDN");
diff --git a/src/settings/plugins/ifcfg-rh/shvar.c b/src/settings/plugins/ifcfg-rh/shvar.c
index cddf3daa72..ca6aa72e0f 100644
--- a/src/settings/plugins/ifcfg-rh/shvar.c
+++ b/src/settings/plugins/ifcfg-rh/shvar.c
@@ -42,7 +42,7 @@
/*****************************************************************************/
-typedef struct {
+struct _shvarLine {
/* There are three cases:
*
* 1) the line is not a valid variable assignment (that is, it doesn't
@@ -62,7 +62,9 @@ typedef struct {
char *line;
const char *key;
char *key_with_prefix;
-} shvarLine;
+};
+
+typedef struct _shvarLine shvarLine;
struct _shvarFile {
char *fileName;
@@ -880,6 +882,30 @@ shlist_find (const GList *current, const char *key)
/*****************************************************************************/
+GHashTable *
+svGetKeys (shvarFile *s)
+{
+ GHashTable *keys = NULL;
+ const GList *current;
+ const shvarLine *line;
+
+ nm_assert (s);
+
+ for (current = s->lineList; current; current = current->next) {
+ line = current->data;
+ if (line->key && line->line) {
+ /* we don't clone the keys. The keys are only valid
+ * until @s gets modified. */
+ if (!keys)
+ keys = g_hash_table_new_full (g_str_hash, g_str_equal, NULL, NULL);
+ g_hash_table_add (keys, (gpointer) line->key);
+ }
+ }
+ return keys;
+}
+
+/*****************************************************************************/
+
static const char *
_svGetValue (shvarFile *s, const char *key, char **to_free)
{
@@ -1171,6 +1197,27 @@ svUnsetValue (shvarFile *s, const char *key)
svSetValue (s, key, NULL);
}
+void
+svUnsetValuesWithPrefix (shvarFile *s, const char *prefix)
+{
+ GList *current;
+
+ g_return_if_fail (s);
+ g_return_if_fail (prefix);
+
+ for (current = s->lineList; current; current = current->next) {
+ shvarLine *line = current->data;
+
+ ASSERT_shvarLine (line);
+ if ( line->key
+ && g_str_has_prefix (line->key, prefix)) {
+ if (nm_clear_g_free (&line->line))
+ s->modified = TRUE;
+ }
+ ASSERT_shvarLine (line);
+ }
+}
+
/*****************************************************************************/
/* Write the current contents iff modified. Returns FALSE on error
diff --git a/src/settings/plugins/ifcfg-rh/shvar.h b/src/settings/plugins/ifcfg-rh/shvar.h
index 8654d73c75..62b07c2f57 100644
--- a/src/settings/plugins/ifcfg-rh/shvar.h
+++ b/src/settings/plugins/ifcfg-rh/shvar.h
@@ -56,6 +56,8 @@ char *svGetValueStr_cp (shvarFile *s, const char *key);
gint svParseBoolean (const char *value, gint def);
+GHashTable *svGetKeys (shvarFile *s);
+
/* return TRUE if <key> resolves to any truth value (e.g. "yes", "y", "true")
* return FALSE if <key> resolves to any non-truth value (e.g. "no", "n", "false")
* return <def> otherwise
@@ -81,6 +83,8 @@ void svSetValueEnum (shvarFile *s, const char *key, GType gtype, int value);
void svUnsetValue (shvarFile *s, const char *key);
+void svUnsetValuesWithPrefix (shvarFile *s, const char *prefix);
+
/* Write the current contents iff modified. Returns FALSE on error
* and TRUE on success. Do not write if no values have been modified.
* The mode argument is only used if creating the file, not if
diff --git a/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_User_1.cexpected b/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_User_1.cexpected
new file mode 100644
index 0000000000..a48be78a23
--- /dev/null
+++ b/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_User_1.cexpected
@@ -0,0 +1,34 @@
+TYPE=Ethernet
+PROXY_METHOD=none
+BROWSER_ONLY=no
+NM_USER__M_Y___053=val=MY.+
+NM_USER__M_Y___055=val=MY.-
+NM_USER__M_Y___057=val=MY./
+NM_USER__M_Y__8_053_V=val=MY.8+V
+NM_USER__M_Y__8_055_V=val=MY.8-V
+NM_USER__M_Y__8_057_V=val=MY.8/V
+NM_USER__M_Y__8_075_V=val=MY.8=V
+NM_USER__M_Y__8_V=val=MY.8V
+NM_USER__M_Y__8_137_V=val=MY.8_V
+NM_USER__M_Y___075=val=MY.=
+NM_USER__M_Y___A_V=val=MY.AV
+NM_USER__M_Y___137=val=MY._
+NM_USER_MY___AV=val=my.Av
+NM_USER_MY___137V=val=my._v
+NM_USER_MY__KEYS__1=val=my.keys.1
+NM_USER_MY__OTHER___K_E_Y__42=val=my.other.KEY.42
+NM_USER_MY__V_053=val=my.v+
+NM_USER_MY__V_137_137AL3=val=my.v__al3
+NM_USER_MY__VAL1=
+NM_USER_MY__VAL2=val=my.val2
+BOOTPROTO=dhcp
+DEFROUTE=yes
+IPV4_FAILURE_FATAL=no
+IPV6INIT=yes
+IPV6_AUTOCONF=yes
+IPV6_DEFROUTE=yes
+IPV6_FAILURE_FATAL=no
+IPV6_ADDR_GEN_MODE=stable-privacy
+NAME="Test User 1"
+UUID=${UUID}
+ONBOOT=yes
diff --git a/src/settings/plugins/ifcfg-rh/tests/test-ifcfg-rh.c b/src/settings/plugins/ifcfg-rh/tests/test-ifcfg-rh.c
index b588cf1979..b7ae5ad511 100644
--- a/src/settings/plugins/ifcfg-rh/tests/test-ifcfg-rh.c
+++ b/src/settings/plugins/ifcfg-rh/tests/test-ifcfg-rh.c
@@ -33,6 +33,7 @@
#include "nm-utils.h"
#include "nm-setting-connection.h"
#include "nm-setting-wired.h"
+#include "nm-setting-user.h"
#include "nm-setting-wireless.h"
#include "nm-setting-wireless-security.h"
#include "nm-setting-ip4-config.h"
@@ -1097,6 +1098,62 @@ test_read_wired_obsolete_gateway_n (void)
}
static void
+test_user_1 (void)
+{
+ nmtst_auto_unlinkfile char *testfile = NULL;
+ gs_unref_object NMConnection *connection = NULL;
+ gs_unref_object NMConnection *reread = NULL;
+ NMSettingUser *s_user;
+
+ connection = nmtst_create_minimal_connection ("Test User 1", NULL, NM_SETTING_WIRED_SETTING_NAME, NULL);
+ s_user = NM_SETTING_USER (nm_setting_user_new ());
+
+#define _USER_SET_DATA(s_user, key, val) \
+ G_STMT_START { \
+ GError *_error = NULL; \
+ gboolean _success; \
+ \
+ _success = nm_setting_user_set_data ((s_user), (key), (val), &_error); \
+ nmtst_assert_success (_success, _error); \
+ } G_STMT_END
+
+#define _USER_SET_DATA_X(s_user, key) \
+ _USER_SET_DATA (s_user, key, "val="key"")
+
+ _USER_SET_DATA (s_user, "my.val1", "");
+ _USER_SET_DATA_X (s_user, "my.val2");
+ _USER_SET_DATA_X (s_user, "my.v__al3");
+ _USER_SET_DATA_X (s_user, "my._v");
+ _USER_SET_DATA_X (s_user, "my.v+");
+ _USER_SET_DATA_X (s_user, "my.Av");
+ _USER_SET_DATA_X (s_user, "MY.AV");
+ _USER_SET_DATA_X (s_user, "MY.8V");
+ _USER_SET_DATA_X (s_user, "MY.8-V");
+ _USER_SET_DATA_X (s_user, "MY.8_V");
+ _USER_SET_DATA_X (s_user, "MY.8+V");
+ _USER_SET_DATA_X (s_user, "MY.8/V");
+ _USER_SET_DATA_X (s_user, "MY.8=V");
+ _USER_SET_DATA_X (s_user, "MY.-");
+ _USER_SET_DATA_X (s_user, "MY._");
+ _USER_SET_DATA_X (s_user, "MY.+");
+ _USER_SET_DATA_X (s_user, "MY./");
+ _USER_SET_DATA_X (s_user, "MY.=");
+ _USER_SET_DATA_X (s_user, "my.keys.1");
+ _USER_SET_DATA_X (s_user, "my.other.KEY.42");
+
+ nm_connection_add_setting (connection, NM_SETTING (s_user));
+
+ _writer_new_connec_exp (connection,
+ TEST_SCRATCH_DIR "/network-scripts/",
+ TEST_IFCFG_DIR "/network-scripts/ifcfg-Test_User_1.cexpected",
+ &testfile);
+
+ reread = _connection_from_file (testfile, NULL, TYPE_ETHERNET, NULL);
+
+ nmtst_assert_connection_equals (connection, TRUE, reread, FALSE);
+}
+
+static void
test_read_wired_never_default (void)
{
NMConnection *connection;
@@ -9311,6 +9368,8 @@ int main (int argc, char **argv)
nmtst_add_test_func (TPATH "wired/read/manual/3", test_read_wired_ipv4_manual, TEST_IFCFG_DIR "/network-scripts/ifcfg-test-wired-ipv4-manual-3", "System test-wired-ipv4-manual-3");
nmtst_add_test_func (TPATH "wired/read/manual/4", test_read_wired_ipv4_manual, TEST_IFCFG_DIR "/network-scripts/ifcfg-test-wired-ipv4-manual-4", "System test-wired-ipv4-manual-4");
+ g_test_add_func (TPATH "user/1", test_user_1);
+
g_test_add_func (TPATH "wired/ipv6-manual", test_read_wired_ipv6_manual);
nmtst_add_test_func (TPATH "wired-ipv6-only/0", test_read_wired_ipv6_only, TEST_IFCFG_DIR"/network-scripts/ifcfg-test-wired-ipv6-only", "System test-wired-ipv6-only");