diff options
author | Thomas Haller <thaller@redhat.com> | 2015-07-02 16:02:36 +0200 |
---|---|---|
committer | Thomas Haller <thaller@redhat.com> | 2015-07-02 16:04:31 +0200 |
commit | 05db3ee08a30edc503e2b9033993ee3d2cbf019f (patch) | |
tree | ed9e54026c180f097ac8e61c495d2705d4c5847c | |
parent | 65753dbc133f77ccd5531288aab9a9e80da3b786 (diff) | |
parent | 25b23f931ec0d26b3f68720e8d9ab299d4c43cdb (diff) |
config: merge branch 'th/nm-config-intern-bgo750558'
Add write support to NMConfig.
Also, add a new configuration directory /usr/lib/NetworkManager/conf.d/
which allows us to install configuration snippets under /usr, instead
of /etc.
https://bugzilla.gnome.org/show_bug.cgi?id=738853
https://bugzilla.gnome.org/show_bug.cgi?id=750558
-rw-r--r-- | configure.ac | 7 | ||||
-rw-r--r-- | contrib/fedora/rpm/NetworkManager.conf | 20 | ||||
-rw-r--r-- | contrib/fedora/rpm/NetworkManager.spec | 25 | ||||
-rwxr-xr-x | contrib/fedora/rpm/build.sh | 3 | ||||
-rw-r--r-- | man/NetworkManager.conf.xml.in | 37 | ||||
-rw-r--r-- | src/Makefile.am | 1 | ||||
-rw-r--r-- | src/devices/nm-device.c | 2 | ||||
-rw-r--r-- | src/main.c | 2 | ||||
-rw-r--r-- | src/nm-config-data.c | 232 | ||||
-rw-r--r-- | src/nm-config-data.h | 25 | ||||
-rw-r--r-- | src/nm-config.c | 799 | ||||
-rw-r--r-- | src/nm-config.h | 17 | ||||
-rw-r--r-- | src/settings/nm-settings.c | 141 | ||||
-rw-r--r-- | src/tests/config/test-config.c | 376 |
14 files changed, 1501 insertions, 186 deletions
diff --git a/configure.ac b/configure.ac index e1a1917db6..e9b39d01f7 100644 --- a/configure.ac +++ b/configure.ac @@ -78,6 +78,7 @@ AC_SUBST(runstatedir) # NetworkManager paths AC_SUBST(nmbinary, "$sbindir/$PACKAGE", [NetworkManager binary executable]) AC_SUBST(nmconfdir, "$sysconfdir/$PACKAGE", [NetworkManager configuration directory]) +AC_SUBST(nmlibdir, "$libdir/$PACKAGE", [NetworkManager library directory]) AC_SUBST(nmdatadir, "$datadir/$PACKAGE", [NetworkManager shared data directory]) AC_SUBST(nmstatedir, "$localstatedir/lib/$PACKAGE", [NetworkManager persistent state directory]) AC_SUBST(nmrundir, "$runstatedir/$PACKAGE", [NetworkManager runtime state directory]) @@ -125,6 +126,11 @@ test "$enable_ifnet" = "yes" && distro_plugins="$distro_plugins,ifn distro_plugins="${distro_plugins#,}" AC_DEFINE_UNQUOTED(CONFIG_PLUGINS_DEFAULT, "$config_plugins_default", [Default configuration option for main.plugins setting]) +if test "${enable_config_plugin_ibft}" = yes; then + AC_DEFINE(WITH_SETTINGS_PLUGIN_IBFT, 1, [Whether compilation of ibft setting plugin is enabled]) +else + AC_DEFINE(WITH_SETTINGS_PLUGIN_IBFT, 0, [Whether compilation of ibft setting plugin is enabled]) +fi if test "$enable_ifcfg_rh" = "yes"; then DISTRO_NETWORK_SERVICE=network.service @@ -1065,6 +1071,7 @@ echo " exec_prefix: $exec_prefix" echo " systemdunitdir: $with_systemdsystemunitdir" echo " nmbinary: $nmbinary" echo " nmconfdir: $nmconfdir" +echo " nmlibdir: $nmlibdir" echo " nmdatadir: $nmdatadir" echo " nmstatedir: $nmstatedir" echo " nmrundir: $nmrundir" diff --git a/contrib/fedora/rpm/NetworkManager.conf b/contrib/fedora/rpm/NetworkManager.conf index 900ca9b23b..0352aa1087 100644 --- a/contrib/fedora/rpm/NetworkManager.conf +++ b/contrib/fedora/rpm/NetworkManager.conf @@ -2,14 +2,24 @@ # # See "man 5 NetworkManager.conf" for details. # +# The directory /usr/lib/NetworkManager/conf.d/ can contain additional configuration +# snippets installed by packages. These files are read before NetworkManager.conf +# and have thus lowest priority. # The directory /etc/NetworkManager/conf.d/ can contain additional configuration -# snippets that are installed by some packages. Those snippets override the -# settings from this main file. -# To override a configuration from a conf.d/ snippet, add another configuration -# with a name sorted lastly (such as 99-my.conf). +# snippets. Those snippets override the settings from this main file. +# +# The files within one conf.d/ directory are read in asciibetical order. +# +# If /etc/NetworkManager/conf.d/ contains a file with the same name as +# /usr/lib/NetworkManager/conf.d/, the latter file is shadowed and thus ignored. +# Hence, to disable loading a file from /usr/lib/NetworkManager/conf.d/ you can +# put an empty file with the same name. +# +# If two files define the same key, the one that is read afterwards will overwrite +# the previous one. [main] -plugins=ifcfg-rh,ibft +#plugins=ifcfg-rh,ibft [logging] #level=DEBUG diff --git a/contrib/fedora/rpm/NetworkManager.spec b/contrib/fedora/rpm/NetworkManager.spec index 0bdabe8dec..0786b305a8 100644 --- a/contrib/fedora/rpm/NetworkManager.spec +++ b/contrib/fedora/rpm/NetworkManager.spec @@ -34,6 +34,7 @@ %define systemd_dir %{_prefix}/lib/systemd/system %define udev_dir %{_prefix}/lib/udev +%define nmlibdir %{_prefix}/lib/%{name} %global with_adsl 1 %global with_bluetooth 1 @@ -83,8 +84,7 @@ URL: http://www.gnome.org/projects/NetworkManager/ Source: __SOURCE1__ Source1: NetworkManager.conf Source2: 00-server.conf -Source3: 10-ibft-plugin.conf -Source4: 20-connectivity-fedora.conf +Source3: 20-connectivity-fedora.conf #Patch1: 0001-some.patch @@ -443,9 +443,9 @@ make install DESTDIR=$RPM_BUILD_ROOT %{__cp} %{SOURCE1} $RPM_BUILD_ROOT%{_sysconfdir}/%{name}/ mkdir -p $RPM_BUILD_ROOT%{_sysconfdir}/%{name}/conf.d -%{__cp} %{SOURCE2} $RPM_BUILD_ROOT%{_sysconfdir}/%{name}/conf.d -%{__cp} %{SOURCE3} $RPM_BUILD_ROOT%{_sysconfdir}/%{name}/conf.d -%{__cp} %{SOURCE4} $RPM_BUILD_ROOT%{_sysconfdir}/%{name}/conf.d +mkdir -p $RPM_BUILD_ROOT%{nmlibdir}/conf.d +%{__cp} %{SOURCE2} $RPM_BUILD_ROOT%{nmlibdir}/conf.d/ +%{__cp} %{SOURCE3} $RPM_BUILD_ROOT%{nmlibdir}/conf.d/ # create a VPN directory %{__mkdir_p} $RPM_BUILD_ROOT%{_sysconfdir}/NetworkManager/VPN @@ -543,7 +543,8 @@ fi %endif %dir %{_sysconfdir}/%{name} %dir %{_sysconfdir}/%{name}/conf.d -%config %{_sysconfdir}/%{name}/conf.d/10-ibft-plugin.conf +%dir %{nmlibdir} +%dir %{nmlibdir}/conf.d %{_mandir}/man1/* %{_mandir}/man5/* %{_mandir}/man8/* @@ -660,15 +661,15 @@ fi %files config-connectivity-fedora %defattr(-,root,root,0755) -%dir %{_sysconfdir}/%{name} -%dir %{_sysconfdir}/%{name}/conf.d -%config(noreplace) %{_sysconfdir}/%{name}/conf.d/20-connectivity-fedora.conf +%dir %{nmlibdir} +%dir %{nmlibdir}/conf.d +%{nmlibdir}/conf.d/20-connectivity-fedora.conf %files config-server %defattr(-,root,root,0755) -%dir %{_sysconfdir}/%{name} -%dir %{_sysconfdir}/%{name}/conf.d -%config(noreplace) %{_sysconfdir}/%{name}/conf.d/00-server.conf +%dir %{nmlibdir} +%dir %{nmlibdir}/conf.d +%{nmlibdir}/conf.d/00-server.conf %if 0%{?with_nmtui} %files tui diff --git a/contrib/fedora/rpm/build.sh b/contrib/fedora/rpm/build.sh index c1ededb554..8e6539dcb1 100755 --- a/contrib/fedora/rpm/build.sh +++ b/contrib/fedora/rpm/build.sh @@ -73,7 +73,6 @@ SOURCE="$(abs_path "$SOURCE" "$(ls -1 "$GITDIR/NetworkManager-$VERSION"*.tar* 2> [[ -f "$SOURCE" ]] || die "could not find source ${_SOURCE:-$GITDIR/NetworkManager-$VERSION*.tar*} . Did you execute \`make dist\`? Otherwise set \$SOURCE variable" SOURCE_NETWORKMANAGER_CONF="$(abs_path "$SOURCE_NETWORKMANAGER_CONF" "$SCRIPTDIR/NetworkManager.conf")" SOURCE_CONFIG_SERVER="$(abs_path "$SOURCE_CONFIG_SERVER" "$SCRIPTDIR/00-server.conf")" -SOURCE_CONFIG_IBFT_PLUGIN="$(abs_path "$SOURCE_CONFIG_IBFT_PLUGIN" "$SCRIPTDIR/10-ibft-plugin.conf")" SOURCE_CONFIG_CONNECTIVITY_FEDORA="$(abs_path "$SOURCE_CONFIG_CONNECTIVITY_FEDORA" "$SCRIPTDIR/20-connectivity-fedora.conf")" TEMP="$(mktemp -d "$SCRIPTDIR/NetworkManager.$DATE.XXXXXX")" @@ -88,7 +87,6 @@ LOG "SPECFILE=$SPECFILE" LOG "SOURCE=$SOURCE" LOG "SOURCE_NETWORKMANAGER_CONF=$SOURCE_NETWORKMANAGER_CONF" LOG "SOURCE_CONFIG_SERVER=$SOURCE_CONFIG_SERVER" -LOG "SOURCE_CONFIG_IBFT_PLUGIN=$SOURCE_CONFIG_IBFT_PLUGIN" LOG "SOURCE_CONFIG_CONNECTIVITY_FEDORA=$SOURCE_CONFIG_CONNECTIVITY_FEDORA" LOG "BASEDIR=$TEMP" @@ -102,7 +100,6 @@ mkdir -p "$TEMP/SOURCES/" "$TEMP/SPECS/" || die "error creating SPECS directoy" cp "$SOURCE" "$TEMP/SOURCES/" || die "Could not copy source $SOURCE to $TEMP/SOURCES" cp "$SOURCE_NETWORKMANAGER_CONF" "$TEMP/SOURCES/NetworkManager.conf" || die "Could not copy source $SOURCE_NETWORKMANAGER_CONF to $TEMP/SOURCES" cp "$SOURCE_CONFIG_SERVER" "$TEMP/SOURCES/00-server.conf" || die "Could not copy source $SOURCE_CONFIG_SERVER to $TEMP/SOURCES" -cp "$SOURCE_CONFIG_IBFT_PLUGIN" "$TEMP/SOURCES/10-ibft-plugin.conf" || die "Could not copy source $SOURCE_CONFIG_IBFT_PLUGIN to $TEMP/SOURCES" cp "$SOURCE_CONFIG_CONNECTIVITY_FEDORA" "$TEMP/SOURCES/20-connectivity-fedora.conf" || die "Could not copy source $SOURCE_CONFIG_CONNECTIVITY_FEDORA to $TEMP/SOURCES" write_changelog diff --git a/man/NetworkManager.conf.xml.in b/man/NetworkManager.conf.xml.in index e2501c8464..bf15c9e8b5 100644 --- a/man/NetworkManager.conf.xml.in +++ b/man/NetworkManager.conf.xml.in @@ -27,23 +27,39 @@ Copyright 2010 - 2014 Red Hat, Inc. <refsynopsisdiv> <para><filename>/etc/NetworkManager/NetworkManager.conf</filename>, - <filename>/etc/NetworkManager/conf.d/<replaceable>name</replaceable>.conf</filename> + <filename>/etc/NetworkManager/conf.d/<replaceable>name</replaceable>.conf</filename>, + <filename>/usr/lib/NetworkManager/conf.d/<replaceable>name</replaceable>.conf</filename>, + <filename>/var/lib/NetworkManager/NetworkManager-intern.conf</filename> </para> </refsynopsisdiv> <refsect1> <title>Description</title> - <para>This is a configuration file for NetworkManager. It is used + <para><literal>NetworkManager.conf</literal> is the configuration file for NetworkManager. It is used to set up various aspects of NetworkManager's behavior. The - location of the file may be changed through use of the - <option>--config</option> argument for NetworkManager. + location of the main file and configuration directories may be changed + through use of the <option>--config</option>, <option>--config-dir</option>, + <option>--system-config-dir</option>, and <option>--intern-config</option> + argument for NetworkManager, respectively. </para> <para>If a default <literal>NetworkManager.conf</literal> is provided by your distribution's packages, you should not modify it, since your changes may get overwritten by package updates. Instead, you can add additional <literal>.conf</literal> - files to the <literal>conf.d</literal> directory. These will be read in order, - with later files overriding earlier ones. + files to the <literal>/etc/NetworkManager/conf.d</literal> directory. + These will be read in order, with later files overriding earlier ones. + Packages might install further configuration snippets to <literal>/usr/lib/NetworkManager/conf.d</literal>. + This directory is parsed first, even before <literal>NetworkManager.conf</literal>. + The loading of a file <literal>/usr/lib/NetworkManager/conf.d/<replaceable>name</replaceable>.conf</literal> + can be prevented by adding a file <literal>/etc/NetworkManager/conf.d/<replaceable>name</replaceable>.conf</literal>. + In this case, the file from the etc configuration shadows the file from the + system configuration directory. + </para> + <para> + NetworkManager can overwrite certain user configuration options via D-Bus or other internal + operations. In this case it writes those changes to <literal>/var/lib/NetworkManager/NetworkManager-intern.conf</literal>. + This file is not intended to be modified by the user, but it is read last and can shadow + user configuration from <literal>NetworkManager.conf</literal>. </para> </refsect1> @@ -625,6 +641,9 @@ ipv6.ip6-privacy=1 <filename>/etc/sysconfig/network-scripts/ifcfg-*</filename> files. It currently supports reading Ethernet, Wi-Fi, InfiniBand, VLAN, Bond, Bridge, and Team connections. + Enabling <literal>ifcfg-rh</literal> implicitly enables + <literal>ibft</literal> plugin, if it is available. + This can be disabled by adding <literal>no-ibft</literal>. </para> </listitem> </varlistentry> @@ -658,12 +677,16 @@ ipv6.ip6-privacy=1 </varlistentry> <varlistentry> - <term><varname>ibft</varname></term> + <term><varname>ibft</varname>, <varname>no-ibft</varname></term> <listitem> <para> This plugin allows to read iBFT configuration (iSCSI Boot Firmware Table). The configuration is read using /sbin/iscsiadm. Users are expected to configure iBFT connections via the firmware interfaces. + If ibft support is available, it is automatically enabled after + <literal>ifcfg-rh</literal>. This can be disabled by <literal>no-ibft</literal>. + You can also explicitly specify <literal>ibft</literal> to load the + plugin without <literal>ifcfg-rh</literal> or to change the plugin order. </para> </listitem> </varlistentry> diff --git a/src/Makefile.am b/src/Makefile.am index 3aa8b5f4f9..e994b19cad 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -428,6 +428,7 @@ AM_CPPFLAGS += \ -DNMPLUGINDIR=\"$(pkglibdir)\" \ -DNMRUNDIR=\"$(nmrundir)\" \ -DNMSTATEDIR=\"$(nmstatedir)\" \ + -DNMLIBDIR=\"$(nmlibdir)\" \ \ -DDHCLIENT_PATH=\"$(DHCLIENT_PATH)\" \ -DDHCPCD_PATH=\"$(DHCPCD_PATH)\" \ diff --git a/src/devices/nm-device.c b/src/devices/nm-device.c index dcdd29e440..16d4c48c69 100644 --- a/src/devices/nm-device.c +++ b/src/devices/nm-device.c @@ -3323,7 +3323,7 @@ ip4_config_merge_and_apply (NMDevice *self, if ( !priv->default_route.v4_configure_first_time && !nm_device_uses_assumed_connection (self) && connection_is_never_default) { - /* If the connection is explicitly configured as never-default, we enforce the (absense of the) + /* If the connection is explicitly configured as never-default, we enforce the (absence of the) * default-route only once. That allows the user to configure a connection as never-default, * but he can add default routes externally (via a dispatcher script) and NM will not interfere. */ goto END_ADD_DEFAULT_ROUTE; diff --git a/src/main.c b/src/main.c index dc6973446f..4f906f5574 100644 --- a/src/main.c +++ b/src/main.c @@ -343,7 +343,7 @@ main (int argc, char *argv[]) } /* Read the config file and CLI overrides */ - config = nm_config_setup (config_cli, &error); + config = nm_config_setup (config_cli, NULL, &error); nm_config_cmd_line_options_free (config_cli); config_cli = NULL; if (config == NULL) { diff --git a/src/nm-config-data.c b/src/nm-config-data.c index 82c1deab11..03cae638c7 100644 --- a/src/nm-config-data.c +++ b/src/nm-config-data.c @@ -48,6 +48,8 @@ typedef struct { char *config_description; GKeyFile *keyfile; + GKeyFile *keyfile_user; + GKeyFile *keyfile_intern; /* A zero-terminated list of pre-processed information from the * [connection] sections. This is to speed up lookup. */ @@ -77,7 +79,8 @@ enum { PROP_0, PROP_CONFIG_MAIN_FILE, PROP_CONFIG_DESCRIPTION, - PROP_KEYFILE, + PROP_KEYFILE_USER, + PROP_KEYFILE_INTERN, PROP_CONNECTIVITY_URI, PROP_CONNECTIVITY_INTERVAL, PROP_CONNECTIVITY_RESPONSE, @@ -92,6 +95,14 @@ G_DEFINE_TYPE (NMConfigData, nm_config_data, G_TYPE_OBJECT) /************************************************************************/ +#define _HAS_PREFIX(str, prefix) \ + ({ \ + const char *_str = (str); \ + g_str_has_prefix ( _str, ""prefix"") && _str[STRLEN(prefix)] != '\0'; \ + }) + +/************************************************************************/ + const char * nm_config_data_get_config_main_file (const NMConfigData *self) { @@ -238,6 +249,40 @@ nm_config_data_get_assume_ipv6ll_only (const NMConfigData *self, NMDevice *devic return nm_device_spec_match_list (device, NM_CONFIG_DATA_GET_PRIVATE (self)->assume_ipv6ll_only); } +GKeyFile * +nm_config_data_clone_keyfile_intern (const NMConfigData *self) +{ + NMConfigDataPrivate *priv; + GKeyFile *keyfile; + + g_return_val_if_fail (NM_IS_CONFIG_DATA (self), FALSE); + + priv = NM_CONFIG_DATA_GET_PRIVATE (self); + + keyfile = nm_config_create_keyfile (); + if (priv->keyfile_intern) + _nm_keyfile_copy (keyfile, priv->keyfile_intern); + return keyfile; +} + +GKeyFile * +_nm_config_data_get_keyfile (const NMConfigData *self) +{ + return NM_CONFIG_DATA_GET_PRIVATE (self)->keyfile; +} + +GKeyFile * +_nm_config_data_get_keyfile_intern (const NMConfigData *self) +{ + return NM_CONFIG_DATA_GET_PRIVATE (self)->keyfile_intern; +} + +GKeyFile * +_nm_config_data_get_keyfile_user (const NMConfigData *self) +{ + return NM_CONFIG_DATA_GET_PRIVATE (self)->keyfile_user; +} + /************************************************************************/ /** @@ -265,15 +310,128 @@ nm_config_data_get_keys (const NMConfigData *self, const char *group) return g_key_file_get_keys (NM_CONFIG_DATA_GET_PRIVATE (self)->keyfile, group, NULL, NULL); } +/** + * nm_config_data_is_intern_atomic_group: + * @self: + * @group: name of the group to check. + * + * whether a configuration group @group exists and is entirely overwritten + * by internal configuration, i.e. whether it is an atomic group that is + * overwritten. + * + * It doesn't say, that there actually is a user setting that was overwritten. That + * means there could be no corresponding section defined in user configuration + * that required overwriting. + * + * Returns: %TRUE if @group exists and is an atomic group set via internal configuration. + */ +gboolean +nm_config_data_is_intern_atomic_group (const NMConfigData *self, const char *group) +{ + NMConfigDataPrivate *priv; + + g_return_val_if_fail (NM_IS_CONFIG_DATA (self), FALSE); + g_return_val_if_fail (group && *group, FALSE); + + priv = NM_CONFIG_DATA_GET_PRIVATE (self); + + if ( !priv->keyfile_intern + || !g_key_file_has_key (priv->keyfile_intern, group, NM_CONFIG_KEYFILE_KEY_ATOMIC_SECTION_WAS, NULL)) + return FALSE; + + /* we have a .was entry for the section. That means that the section would be overwritten + * from user configuration. But it doesn't mean that the merged configuration contains this + * groups, because the internal setting could hide the user section. + * Only return TRUE, if we actually have such a group in the merged configuration.*/ + return g_key_file_has_group (priv->keyfile, group); +} + +/************************************************************************/ + +static GKeyFile * +_merge_keyfiles (GKeyFile *keyfile_user, GKeyFile *keyfile_intern) +{ + gs_strfreev char **groups = NULL; + guint g, k; + GKeyFile *keyfile; + gsize ngroups; + + keyfile = nm_config_create_keyfile (); + if (keyfile_user) + _nm_keyfile_copy (keyfile, keyfile_user); + if (!keyfile_intern) + return keyfile; + + groups = g_key_file_get_groups (keyfile_intern, &ngroups); + if (!groups) + return keyfile; + + /* we must reverse the order of the connection settings so that we + * have lowest priority last. */ + _nm_config_sort_groups (groups, ngroups); + for (g = 0; groups[g]; g++) { + const char *group = groups[g]; + gs_strfreev char **keys = NULL; + gboolean is_intern, is_atomic = FALSE; + + keys = g_key_file_get_keys (keyfile_intern, group, NULL, NULL); + if (!keys) + continue; + + is_intern = g_str_has_prefix (group, NM_CONFIG_KEYFILE_GROUPPREFIX_INTERN); + if ( !is_intern + && g_key_file_has_key (keyfile_intern, group, NM_CONFIG_KEYFILE_KEY_ATOMIC_SECTION_WAS, NULL)) { + /* the entire section is atomically overwritten by @keyfile_intern. */ + g_key_file_remove_group (keyfile, group, NULL); + is_atomic = TRUE; + } + + for (k = 0; keys[k]; k++) { + const char *key = keys[k]; + gs_free char *value = NULL; + + if (is_atomic && strcmp (key, NM_CONFIG_KEYFILE_KEY_ATOMIC_SECTION_WAS) == 0) + continue; + + if ( !is_intern && !is_atomic + && _HAS_PREFIX (key, NM_CONFIG_KEYFILE_KEYPREFIX_WAS)) { + const char *key_base = &key[STRLEN (NM_CONFIG_KEYFILE_KEYPREFIX_WAS)]; + + if (!g_key_file_has_key (keyfile_intern, group, key_base, NULL)) + g_key_file_remove_key (keyfile, group, key_base, NULL); + continue; + } + if (!is_intern && !is_atomic && _HAS_PREFIX (key, NM_CONFIG_KEYFILE_KEYPREFIX_SET)) + continue; + + value = g_key_file_get_value (keyfile_intern, group, key, NULL); + g_key_file_set_value (keyfile, group, key, value); + } + } + return keyfile; +} + /************************************************************************/ static int _nm_config_data_log_sort (const char **pa, const char **pb, gpointer dummy) { gboolean a_is_connection, b_is_connection; + gboolean a_is_intern, b_is_intern; const char *a = *pa; const char *b = *pb; + /* we sort intern groups to the end. */ + a_is_intern = g_str_has_prefix (a, NM_CONFIG_KEYFILE_GROUPPREFIX_INTERN); + b_is_intern = g_str_has_prefix (b, NM_CONFIG_KEYFILE_GROUPPREFIX_INTERN); + + if (a_is_intern && b_is_intern) + return 0; + if (a_is_intern) + return 1; + if (b_is_intern) + return -1; + /* we sort connection groups before intern groups (to the end). */ a_is_connection = a && g_str_has_prefix (a, NM_CONFIG_KEYFILE_GROUPPREFIX_CONNECTION); b_is_connection = b && g_str_has_prefix (b, NM_CONFIG_KEYFILE_GROUPPREFIX_CONNECTION); @@ -336,9 +494,12 @@ nm_config_data_log (const NMConfigData *self, const char *prefix) for (g = 0; g < ngroups; g++) { const char *group = groups[g]; gs_strfreev char **keys = NULL; + gboolean is_atomic; + + is_atomic = nm_config_data_is_intern_atomic_group (self, group); _LOG (""); - _LOG ("[%s]", group); + _LOG ("[%s]%s", group, is_atomic ? "*" : ""); keys = g_key_file_get_keys (priv->keyfile, group, NULL, NULL); for (k = 0; keys && keys[k]; k++) { @@ -479,8 +640,11 @@ nm_config_data_diff (NMConfigData *old_data, NMConfigData *new_data) priv_old = NM_CONFIG_DATA_GET_PRIVATE (old_data); priv_new = NM_CONFIG_DATA_GET_PRIVATE (new_data); - if (!_nm_keyfile_equals (priv_old->keyfile, priv_new->keyfile, TRUE)) - changes |= NM_CONFIG_CHANGE_VALUES; + if (!_nm_keyfile_equals (priv_old->keyfile_user, priv_new->keyfile_user, TRUE)) + changes |= NM_CONFIG_CHANGE_VALUES | NM_CONFIG_CHANGE_VALUES_USER; + + if (!_nm_keyfile_equals (priv_old->keyfile_intern, priv_new->keyfile_intern, TRUE)) + changes |= NM_CONFIG_CHANGE_VALUES | NM_CONFIG_CHANGE_VALUES_INTERN; if ( g_strcmp0 (nm_config_data_get_config_main_file (old_data), nm_config_data_get_config_main_file (new_data)) != 0 || g_strcmp0 (nm_config_data_get_config_description (old_data), nm_config_data_get_config_description (new_data)) != 0) @@ -553,10 +717,21 @@ set_property (GObject *object, case PROP_CONFIG_DESCRIPTION: priv->config_description = g_value_dup_string (value); break; - case PROP_KEYFILE: - priv->keyfile = g_value_dup_boxed (value); - if (!priv->keyfile) - priv->keyfile = nm_config_create_keyfile (); + case PROP_KEYFILE_USER: + priv->keyfile_user = g_value_dup_boxed (value); + if ( priv->keyfile_user + && !_nm_keyfile_has_values (priv->keyfile_user)) { + g_key_file_unref (priv->keyfile_user); + priv->keyfile_user = NULL; + } + break; + case PROP_KEYFILE_INTERN: + priv->keyfile_intern = g_value_dup_boxed (value); + if ( priv->keyfile_intern + && !_nm_keyfile_has_values (priv->keyfile_intern)) { + g_key_file_unref (priv->keyfile_intern); + priv->keyfile_intern = NULL; + } break; case PROP_NO_AUTO_DEFAULT: { @@ -620,6 +795,10 @@ finalize (GObject *gobject) } g_key_file_unref (priv->keyfile); + if (priv->keyfile_user) + g_key_file_unref (priv->keyfile_user); + if (priv->keyfile_intern) + g_key_file_unref (priv->keyfile_intern); G_OBJECT_CLASS (nm_config_data_parent_class)->finalize (gobject); } @@ -636,6 +815,8 @@ constructed (GObject *object) NMConfigDataPrivate *priv = NM_CONFIG_DATA_GET_PRIVATE (self); char *interval; + priv->keyfile = _merge_keyfiles (priv->keyfile_user, priv->keyfile_intern); + priv->connection_infos = _get_connection_infos (priv->keyfile); priv->connectivity.uri = nm_strstrip (g_key_file_get_string (priv->keyfile, NM_CONFIG_KEYFILE_GROUP_CONNECTIVITY, "uri", NULL)); @@ -664,17 +845,33 @@ NMConfigData * nm_config_data_new (const char *config_main_file, const char *config_description, const char *const*no_auto_default, - GKeyFile *keyfile) + GKeyFile *keyfile_user, + GKeyFile *keyfile_intern) { return g_object_new (NM_TYPE_CONFIG_DATA, NM_CONFIG_DATA_CONFIG_MAIN_FILE, config_main_file, NM_CONFIG_DATA_CONFIG_DESCRIPTION, config_description, - NM_CONFIG_DATA_KEYFILE, keyfile, + NM_CONFIG_DATA_KEYFILE_USER, keyfile_user, + NM_CONFIG_DATA_KEYFILE_INTERN, keyfile_intern, NM_CONFIG_DATA_NO_AUTO_DEFAULT, no_auto_default, NULL); } NMConfigData * +nm_config_data_new_update_keyfile_intern (const NMConfigData *base, GKeyFile *keyfile_intern) +{ + NMConfigDataPrivate *priv = NM_CONFIG_DATA_GET_PRIVATE (base); + + return g_object_new (NM_TYPE_CONFIG_DATA, + NM_CONFIG_DATA_CONFIG_MAIN_FILE, priv->config_main_file, + NM_CONFIG_DATA_CONFIG_DESCRIPTION, priv->config_description, + NM_CONFIG_DATA_KEYFILE_USER, priv->keyfile_user, /* the keyfile is unchanged. It's safe to share it. */ + NM_CONFIG_DATA_KEYFILE_INTERN, keyfile_intern, + NM_CONFIG_DATA_NO_AUTO_DEFAULT, priv->no_auto_default.arr, + NULL); +} + +NMConfigData * nm_config_data_new_update_no_auto_default (const NMConfigData *base, const char *const*no_auto_default) { @@ -683,7 +880,8 @@ nm_config_data_new_update_no_auto_default (const NMConfigData *base, return g_object_new (NM_TYPE_CONFIG_DATA, NM_CONFIG_DATA_CONFIG_MAIN_FILE, priv->config_main_file, NM_CONFIG_DATA_CONFIG_DESCRIPTION, priv->config_description, - NM_CONFIG_DATA_KEYFILE, priv->keyfile, /* the keyfile is unchanged. It's safe to share it. */ + NM_CONFIG_DATA_KEYFILE_USER, priv->keyfile_user, /* the keyfile is unchanged. It's safe to share it. */ + NM_CONFIG_DATA_KEYFILE_INTERN, priv->keyfile_intern, NM_CONFIG_DATA_NO_AUTO_DEFAULT, no_auto_default, NULL); } @@ -718,8 +916,16 @@ nm_config_data_class_init (NMConfigDataClass *config_class) G_PARAM_STATIC_STRINGS)); g_object_class_install_property - (object_class, PROP_KEYFILE, - g_param_spec_boxed (NM_CONFIG_DATA_KEYFILE, "", "", + (object_class, PROP_KEYFILE_USER, + g_param_spec_boxed (NM_CONFIG_DATA_KEYFILE_USER, "", "", + G_TYPE_KEY_FILE, + G_PARAM_WRITABLE | + G_PARAM_CONSTRUCT_ONLY | + G_PARAM_STATIC_STRINGS)); + + g_object_class_install_property + (object_class, PROP_KEYFILE_INTERN, + g_param_spec_boxed (NM_CONFIG_DATA_KEYFILE_INTERN, "", "", G_TYPE_KEY_FILE, G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY | diff --git a/src/nm-config-data.h b/src/nm-config-data.h index bd26b9dfbb..0dfdf531ec 100644 --- a/src/nm-config-data.h +++ b/src/nm-config-data.h @@ -38,7 +38,8 @@ G_BEGIN_DECLS #define NM_CONFIG_DATA_CONFIG_MAIN_FILE "config-main-file" #define NM_CONFIG_DATA_CONFIG_DESCRIPTION "config-description" -#define NM_CONFIG_DATA_KEYFILE "keyfile" +#define NM_CONFIG_DATA_KEYFILE_USER "keyfile-user" +#define NM_CONFIG_DATA_KEYFILE_INTERN "keyfile-intern" #define NM_CONFIG_DATA_CONNECTIVITY_URI "connectivity-uri" #define NM_CONFIG_DATA_CONNECTIVITY_INTERVAL "connectivity-interval" #define NM_CONFIG_DATA_CONNECTIVITY_RESPONSE "connectivity-response" @@ -71,10 +72,12 @@ typedef enum { /*< flags >*/ NM_CONFIG_CHANGE_CONFIG_FILES = (1L << 3), NM_CONFIG_CHANGE_VALUES = (1L << 4), - NM_CONFIG_CHANGE_CONNECTIVITY = (1L << 5), - NM_CONFIG_CHANGE_NO_AUTO_DEFAULT = (1L << 6), - NM_CONFIG_CHANGE_DNS_MODE = (1L << 7), - NM_CONFIG_CHANGE_RC_MANAGER = (1L << 8), + NM_CONFIG_CHANGE_VALUES_USER = (1L << 5), + NM_CONFIG_CHANGE_VALUES_INTERN = (1L << 6), + NM_CONFIG_CHANGE_CONNECTIVITY = (1L << 7), + NM_CONFIG_CHANGE_NO_AUTO_DEFAULT = (1L << 8), + NM_CONFIG_CHANGE_DNS_MODE = (1L << 9), + NM_CONFIG_CHANGE_RC_MANAGER = (1L << 10), _NM_CONFIG_CHANGE_LAST, NM_CONFIG_CHANGE_ALL = ((_NM_CONFIG_CHANGE_LAST - 1) << 1) - 1, @@ -93,7 +96,9 @@ GType nm_config_data_get_type (void); NMConfigData *nm_config_data_new (const char *config_main_file, const char *config_description, const char *const*no_auto_default, - GKeyFile *keyfile); + GKeyFile *keyfile_user, + GKeyFile *keyfile_intern); +NMConfigData *nm_config_data_new_update_keyfile_intern (const NMConfigData *base, GKeyFile *keyfile_intern); NMConfigData *nm_config_data_new_update_no_auto_default (const NMConfigData *base, const char *const*no_auto_default); NMConfigChangeFlags nm_config_data_diff (NMConfigData *old_data, NMConfigData *new_data); @@ -127,6 +132,14 @@ char *nm_config_data_get_connection_default (const NMConfigData *self, char **nm_config_data_get_groups (const NMConfigData *self); char **nm_config_data_get_keys (const NMConfigData *self, const char *group); +gboolean nm_config_data_is_intern_atomic_group (const NMConfigData *self, const char *group); + +GKeyFile *nm_config_data_clone_keyfile_intern (const NMConfigData *self); + +/* private accessors */ +GKeyFile *_nm_config_data_get_keyfile (const NMConfigData *self); +GKeyFile *_nm_config_data_get_keyfile_user (const NMConfigData *self); +GKeyFile *_nm_config_data_get_keyfile_intern (const NMConfigData *self); G_END_DECLS diff --git a/src/nm-config.c b/src/nm-config.c index add9b4d696..215595c208 100644 --- a/src/nm-config.c +++ b/src/nm-config.c @@ -33,6 +33,7 @@ #include "gsystem-local-alloc.h" #include "nm-enum-types.h" #include "nm-core-internal.h" +#include "nm-keyfile-internal.h" #include <gio/gio.h> #include <glib/gi18n.h> @@ -40,11 +41,15 @@ #define DEFAULT_CONFIG_MAIN_FILE NMCONFDIR "/NetworkManager.conf" #define DEFAULT_CONFIG_DIR NMCONFDIR "/conf.d" #define DEFAULT_CONFIG_MAIN_FILE_OLD NMCONFDIR "/nm-system-settings.conf" +#define DEFAULT_SYSTEM_CONFIG_DIR NMLIBDIR "/conf.d" #define DEFAULT_NO_AUTO_DEFAULT_FILE NMSTATEDIR "/no-auto-default.state" +#define DEFAULT_INTERN_CONFIG_FILE NMSTATEDIR "/NetworkManager-intern.conf" struct NMConfigCmdLineOptions { char *config_main_file; + char *intern_config_file; char *config_dir; + char *system_config_dir; char *no_auto_default_file; char *plugins; gboolean configure_and_quit; @@ -64,7 +69,9 @@ typedef struct { NMConfigData *config_data_orig; char *config_dir; + char *system_config_dir; char *no_auto_default_file; + char *intern_config_file; char **plugins; gboolean monitor_connection_files; @@ -77,11 +84,14 @@ typedef struct { char *debug; gboolean configure_and_quit; + + char **atomic_section_prefixes; } NMConfigPrivate; enum { PROP_0, PROP_CMD_LINE_OPTIONS, + PROP_ATOMIC_SECTION_PREFIXES, LAST_PROP, }; @@ -108,6 +118,14 @@ static void _set_config_data (NMConfig *self, NMConfigData *new_data, int signal /************************************************************************/ +#define _HAS_PREFIX(str, prefix) \ + ({ \ + const char *_str = (str); \ + g_str_has_prefix ( _str, ""prefix"") && _str[STRLEN(prefix)] != '\0'; \ + }) + +/************************************************************************/ + gint nm_config_parse_boolean (const char *str, gint default_value) @@ -359,7 +377,7 @@ nm_config_get_no_auto_default_for_device (NMConfig *self, NMDevice *device) void nm_config_set_no_auto_default_for_device (NMConfig *self, NMDevice *device) { - NMConfigPrivate *priv = NM_CONFIG_GET_PRIVATE (self); + NMConfigPrivate *priv; GError *error = NULL; NMConfigData *new_data = NULL; const char *hw_address; @@ -370,6 +388,8 @@ nm_config_set_no_auto_default_for_device (NMConfig *self, NMDevice *device) g_return_if_fail (NM_IS_CONFIG (self)); g_return_if_fail (NM_IS_DEVICE (device)); + priv = NM_CONFIG_GET_PRIVATE (self); + hw_address = nm_device_get_hw_address (device); no_auto_default_current = nm_config_data_get_no_auto_default (priv->config_data); @@ -407,7 +427,9 @@ _nm_config_cmd_line_options_clear (NMConfigCmdLineOptions *cli) { g_clear_pointer (&cli->config_main_file, g_free); g_clear_pointer (&cli->config_dir, g_free); + g_clear_pointer (&cli->system_config_dir, g_free); g_clear_pointer (&cli->no_auto_default_file, g_free); + g_clear_pointer (&cli->intern_config_file, g_free); g_clear_pointer (&cli->plugins, g_free); cli->configure_and_quit = FALSE; g_clear_pointer (&cli->connectivity_uri, g_free); @@ -424,8 +446,10 @@ _nm_config_cmd_line_options_copy (const NMConfigCmdLineOptions *cli, NMConfigCmd _nm_config_cmd_line_options_clear (dst); dst->config_dir = g_strdup (cli->config_dir); + dst->system_config_dir = g_strdup (cli->system_config_dir); dst->config_main_file = g_strdup (cli->config_main_file); dst->no_auto_default_file = g_strdup (cli->no_auto_default_file); + dst->intern_config_file = g_strdup (cli->intern_config_file); dst->plugins = g_strdup (cli->plugins); dst->configure_and_quit = cli->configure_and_quit; dst->connectivity_uri = g_strdup (cli->connectivity_uri); @@ -462,6 +486,8 @@ nm_config_cmd_line_options_add_to_entries (NMConfigCmdLineOptions *cli, GOptionEntry config_options[] = { { "config", 0, 0, G_OPTION_ARG_FILENAME, &cli->config_main_file, N_("Config file location"), N_(DEFAULT_CONFIG_MAIN_FILE) }, { "config-dir", 0, 0, G_OPTION_ARG_FILENAME, &cli->config_dir, N_("Config directory location"), N_(DEFAULT_CONFIG_DIR) }, + { "system-config-dir", 0, 0, G_OPTION_ARG_FILENAME, &cli->system_config_dir, N_("System config directory location"), N_(DEFAULT_SYSTEM_CONFIG_DIR) }, + { "intern-config", 0, 0, G_OPTION_ARG_FILENAME, &cli->intern_config_file, N_("Internal config file location"), N_(DEFAULT_INTERN_CONFIG_FILE) }, { "no-auto-default", 0, G_OPTION_FLAG_HIDDEN, G_OPTION_ARG_FILENAME, &cli->no_auto_default_file, N_("State file for no-auto-default devices"), N_(DEFAULT_NO_AUTO_DEFAULT_FILE) }, { "plugins", 0, 0, G_OPTION_ARG_STRING, &cli->plugins, N_("List of plugins separated by ','"), N_(CONFIG_PLUGINS_DEFAULT) }, { "configure-and-quit", 0, 0, G_OPTION_ARG_NONE, &cli->configure_and_quit, N_("Quit after initial configuration"), NULL }, @@ -528,6 +554,18 @@ _sort_groups_cmp (const char **pa, const char **pb, gpointer dummy) return pa > pb ? -1 : 1; } +void +_nm_config_sort_groups (char **groups, gsize ngroups) +{ + if (ngroups > 1) { + g_qsort_with_data (groups, + ngroups, + sizeof (char *), + (GCompareDataFunc) _sort_groups_cmp, + NULL); + } +} + static gboolean _setting_is_device_spec (const char *group, const char *key) { @@ -550,17 +588,23 @@ _setting_is_string_list (const char *group, const char *key) } static gboolean -read_config (GKeyFile *keyfile, const char *path, GError **error) +read_config (GKeyFile *keyfile, const char *dirname, const char *path, GError **error) { GKeyFile *kf; char **groups, **keys; gsize ngroups, nkeys; int g, k; + gs_free char *path_free = NULL; g_return_val_if_fail (keyfile, FALSE); g_return_val_if_fail (path, FALSE); g_return_val_if_fail (!error || !*error, FALSE); + if (dirname) { + path_free = g_build_filename (dirname, path, NULL); + path = path_free; + } + if (g_file_test (path, G_FILE_TEST_EXISTS) == FALSE) { g_set_error (error, G_KEY_FILE_ERROR, G_KEY_FILE_ERROR_NOT_FOUND, "file %s not found", path); return FALSE; @@ -587,17 +631,15 @@ read_config (GKeyFile *keyfile, const char *path, GError **error) * At the very end, we will revert the order of all sections again and * get thus the right behavior. This final reversing is done in * NMConfigData:_get_connection_infos(). */ - if (ngroups > 1) { - g_qsort_with_data (groups, - ngroups, - sizeof (char *), - (GCompareDataFunc) _sort_groups_cmp, - NULL); - } + _nm_config_sort_groups (groups, ngroups); for (g = 0; groups[g]; g++) { const char *group = groups[g]; + if (g_str_has_prefix (group, NM_CONFIG_KEYFILE_GROUPPREFIX_INTERN)) { + /* internal groups cannot be set by user configuration. */ + continue; + } keys = g_key_file_get_keys (kf, group, &nkeys, NULL); if (!keys) continue; @@ -609,6 +651,18 @@ read_config (GKeyFile *keyfile, const char *path, GError **error) key = keys[k]; g_assert (key && *key); + + if ( _HAS_PREFIX (key, NM_CONFIG_KEYFILE_KEYPREFIX_WAS) + || _HAS_PREFIX (key, NM_CONFIG_KEYFILE_KEYPREFIX_SET)) { + /* these keys are protected. We ignore them if the user sets them. */ + continue; + } + + if (!strcmp (key, NM_CONFIG_KEYFILE_KEY_ATOMIC_SECTION_WAS)) { + /* the "was" key is protected and it cannot be set by user configuration. */ + continue; + } + key_len = strlen (key); last_char = key[key_len - 1]; if ( key_len > 1 @@ -710,7 +764,7 @@ read_base_config (GKeyFile *keyfile, /* Try a user-specified config file first */ if (cli_config_main_file) { /* Bad user-specific config file path is a hard error */ - if (read_config (keyfile, cli_config_main_file, error)) { + if (read_config (keyfile, NULL, cli_config_main_file, error)) { *out_config_main_file = g_strdup (cli_config_main_file); return TRUE; } else @@ -725,7 +779,7 @@ read_base_config (GKeyFile *keyfile, */ /* Try deprecated nm-system-settings.conf first */ - if (read_config (keyfile, DEFAULT_CONFIG_MAIN_FILE_OLD, &my_error)) { + if (read_config (keyfile, NULL, DEFAULT_CONFIG_MAIN_FILE_OLD, &my_error)) { *out_config_main_file = g_strdup (DEFAULT_CONFIG_MAIN_FILE_OLD); return TRUE; } @@ -738,7 +792,7 @@ read_base_config (GKeyFile *keyfile, g_clear_error (&my_error); /* Try the standard config file location next */ - if (read_config (keyfile, DEFAULT_CONFIG_MAIN_FILE, &my_error)) { + if (read_config (keyfile, NULL, DEFAULT_CONFIG_MAIN_FILE, &my_error)) { *out_config_main_file = g_strdup (DEFAULT_CONFIG_MAIN_FILE); return TRUE; } @@ -771,24 +825,20 @@ sort_asciibetically (gconstpointer a, gconstpointer b) } static GPtrArray * -_get_config_dir_files (const char *config_main_file, - const char *config_dir, - char **out_config_description) +_get_config_dir_files (const char *config_dir) { GFile *dir; GFileEnumerator *direnum; GFileInfo *info; GPtrArray *confs; - GString *config_description; const char *name; - guint i; - g_return_val_if_fail (config_main_file, NULL); g_return_val_if_fail (config_dir, NULL); - g_return_val_if_fail (out_config_description && !*out_config_description, NULL); confs = g_ptr_array_new_with_free_func (g_free); - config_description = g_string_new (config_main_file); + if (!*config_dir) + return confs; + dir = g_file_new_for_path (config_dir); direnum = g_file_enumerate_children (dir, G_FILE_ATTRIBUTE_STANDARD_NAME, 0, NULL, NULL); if (direnum) { @@ -802,43 +852,59 @@ _get_config_dir_files (const char *config_main_file, } g_object_unref (dir); - if (confs->len > 0) { - g_ptr_array_sort (confs, sort_asciibetically); - g_string_append (config_description, " and conf.d: "); - for (i = 0; i < confs->len; i++) { - char *n = confs->pdata[i]; - - if (i > 0) - g_string_append (config_description, ", "); - g_string_append (config_description, n); - confs->pdata[i] = g_build_filename (config_dir, n, NULL); - g_free (n); - } - } - - *out_config_description = g_string_free (config_description, FALSE); + g_ptr_array_sort (confs, sort_asciibetically); return confs; } static GKeyFile * read_entire_config (const NMConfigCmdLineOptions *cli, const char *config_dir, + const char *system_config_dir, char **out_config_main_file, char **out_config_description, GError **error) { - GKeyFile *keyfile = nm_config_create_keyfile (); - GPtrArray *confs; + GKeyFile *keyfile; + gs_unref_ptrarray GPtrArray *system_confs = NULL; + gs_unref_ptrarray GPtrArray *confs = NULL; guint i; - char *o_config_main_file = NULL; - char *o_config_description = NULL; - char **plugins_tmp; + gs_free char *o_config_main_file = NULL; + GString *str; + char **plugins_default; g_return_val_if_fail (config_dir, NULL); - g_return_val_if_fail (out_config_main_file && !*out_config_main_file, FALSE); - g_return_val_if_fail (out_config_description && !*out_config_description, NULL); + g_return_val_if_fail (system_config_dir, NULL); + g_return_val_if_fail (!out_config_main_file || !*out_config_main_file, FALSE); + g_return_val_if_fail (!out_config_description || !*out_config_description, NULL); g_return_val_if_fail (!error || !*error, FALSE); + /* create a default configuration file. */ + keyfile = nm_config_create_keyfile (); + + plugins_default = g_strsplit (CONFIG_PLUGINS_DEFAULT, ",", -1); + if (plugins_default && plugins_default[0]) + nm_config_keyfile_set_string_list (keyfile, NM_CONFIG_KEYFILE_GROUP_MAIN, "plugins", (const char *const*) plugins_default, -1); + g_strfreev (plugins_default); + + system_confs = _get_config_dir_files (system_config_dir); + confs = _get_config_dir_files (config_dir); + + for (i = 0; i < system_confs->len; ) { + const char *filename = system_confs->pdata[i]; + + /* if a same named file exists in config_dir, skip it. */ + if (_nm_utils_strv_find_first ((char **) confs->pdata, confs->len, filename) >= 0) { + g_ptr_array_remove_index (system_confs, i); + continue; + } + + if (!read_config (keyfile, system_config_dir, filename, error)) { + g_key_file_free (keyfile); + return NULL; + } + i++; + } + /* First read the base config file */ if (!read_base_config (keyfile, cli ? cli->config_main_file : NULL, &o_config_main_file, error)) { g_key_file_free (keyfile); @@ -847,33 +913,22 @@ read_entire_config (const NMConfigCmdLineOptions *cli, g_assert (o_config_main_file); - confs = _get_config_dir_files (o_config_main_file, config_dir, &o_config_description); for (i = 0; i < confs->len; i++) { - if (!read_config (keyfile, confs->pdata[i], error)) { + if (!read_config (keyfile, config_dir, confs->pdata[i], error)) { g_key_file_free (keyfile); - g_free (o_config_main_file); - g_free (o_config_description); - g_ptr_array_unref (confs); return NULL; } } - g_ptr_array_unref (confs); /* Merge settings from command line. They overwrite everything read from * config files. */ - - if (cli && cli->plugins && cli->plugins[0]) + if (cli && cli->plugins) { + /* plugins is a string list. Set the value directly, so the user has to do proper escaping + * on the command line. */ g_key_file_set_value (keyfile, NM_CONFIG_KEYFILE_GROUP_MAIN, "plugins", cli->plugins); - plugins_tmp = g_key_file_get_string_list (keyfile, NM_CONFIG_KEYFILE_GROUP_MAIN, "plugins", NULL, NULL); - if (!plugins_tmp) { - if (STRLEN (CONFIG_PLUGINS_DEFAULT) > 0) - g_key_file_set_value (keyfile, NM_CONFIG_KEYFILE_GROUP_MAIN, "plugins", CONFIG_PLUGINS_DEFAULT); - } else - g_strfreev (plugins_tmp); - + } if (cli && cli->configure_and_quit) g_key_file_set_boolean (keyfile, NM_CONFIG_KEYFILE_GROUP_MAIN, "configure-and-quit", TRUE); - if (cli && cli->connectivity_uri && cli->connectivity_uri[0]) g_key_file_set_string (keyfile, NM_CONFIG_KEYFILE_GROUP_CONNECTIVITY, "uri", cli->connectivity_uri); if (cli && cli->connectivity_interval >= 0) @@ -881,11 +936,462 @@ read_entire_config (const NMConfigCmdLineOptions *cli, if (cli && cli->connectivity_response && cli->connectivity_response[0]) g_key_file_set_string (keyfile, NM_CONFIG_KEYFILE_GROUP_CONNECTIVITY, "response", cli->connectivity_response); - *out_config_main_file = o_config_main_file; - *out_config_description = o_config_description; + str = g_string_new (o_config_main_file); + if (system_confs->len > 0) { + for (i = 0; i < system_confs->len; i++) { + if (i == 0) + g_string_append (str, " (lib: "); + else + g_string_append (str, ", "); + g_string_append (str, system_confs->pdata[i]); + } + g_string_append (str, ")"); + } + if (confs->len > 0) { + for (i = 0; i < confs->len; i++) { + if (i == 0) + g_string_append (str, " (etc: "); + else + g_string_append (str, ", "); + g_string_append (str, confs->pdata[i]); + } + g_string_append (str, ")"); + } + + if (out_config_main_file) + *out_config_main_file = o_config_main_file; + else + g_free (o_config_main_file); + if (out_config_description) + *out_config_description = g_string_free (str, FALSE); + else + g_string_free (str, TRUE); + + o_config_main_file = NULL; return keyfile; } +static gboolean +_is_atomic_section (const char *const*atomic_section_prefixes, const char *group) +{ + if (atomic_section_prefixes) { + for (; *atomic_section_prefixes; atomic_section_prefixes++) { + if ( **atomic_section_prefixes + && g_str_has_prefix (group, *atomic_section_prefixes)) + return TRUE; + } + } + return FALSE; +} + +static void +_string_append_val (GString *str, const char *value) +{ + if (!value) + return; + g_string_append_c (str, '+'); + while (TRUE) { + switch (*value) { + case '\0': + return; + case '\\': + case '+': + case '#': + case ':': + g_string_append_c (str, '+'); + default: + g_string_append_c (str, *value); + } + value++; + } +} + +static char * +_keyfile_serialize_section (GKeyFile *keyfile, const char *group) +{ + gs_strfreev char **keys = NULL; + GString *str; + guint k; + + if (keyfile) + keys = g_key_file_get_keys (keyfile, group, NULL, NULL); + if (!keys) + return g_strdup ("0#"); + + /* prepend a version. */ + str = g_string_new ("1#"); + + for (k = 0; keys[k]; k++) { + const char *key = keys[k]; + gs_free char *value = NULL; + + _string_append_val (str, key); + g_string_append_c (str, ':'); + + value = g_key_file_get_value (keyfile, group, key, NULL); + _string_append_val (str, value); + g_string_append_c (str, '#'); + } + return g_string_free (str, FALSE); +} + +/** + * intern_config_read: + * @filename: the filename where to store the internal config + * @keyfile_conf: the merged configuration from user (/etc/NM/NetworkManager.conf). + * @out_needs_rewrite: (allow-none): whether the read keyfile contains inconsistent + * data (compared to @keyfile_conf). If %TRUE, you might want to rewrite + * the file. + * + * Does the opposite of intern_config_write(). It reads the internal configuration. + * Note that the actual format of how the configuration is saved in @filename + * is different then what we return here. NMConfig manages what is written internally + * by having it inside a keyfile_intern. But we don't write that to disk as is. + * Especially, we also store parts of @keyfile_conf as ".was" and on read we compare + * what we have, with what ".was". + * + * Returns: a #GKeyFile instance with the internal configuration. + */ +static GKeyFile * +intern_config_read (const char *filename, + GKeyFile *keyfile_conf, + const char *const*atomic_section_prefixes, + gboolean *out_needs_rewrite) +{ + GKeyFile *keyfile_intern; + GKeyFile *keyfile; + gboolean needs_rewrite = FALSE; + gs_strfreev char **groups = NULL; + guint g, k; + gboolean has_intern = FALSE; + + g_return_val_if_fail (filename, NULL); + + if (!*filename) { + if (out_needs_rewrite) + *out_needs_rewrite = FALSE; + return NULL; + } + + keyfile_intern = nm_config_create_keyfile (); + + keyfile = nm_config_create_keyfile (); + if (!g_key_file_load_from_file (keyfile, filename, G_KEY_FILE_NONE, NULL)) { + needs_rewrite = TRUE; + goto out; + } + + groups = g_key_file_get_groups (keyfile, NULL); + for (g = 0; groups && groups[g]; g++) { + gs_strfreev char **keys = NULL; + const char *group = groups[g]; + gboolean is_intern, is_atomic; + + keys = g_key_file_get_keys (keyfile, group, NULL, NULL); + if (!keys) + continue; + + is_intern = g_str_has_prefix (group, NM_CONFIG_KEYFILE_GROUPPREFIX_INTERN); + is_atomic = !is_intern && _is_atomic_section (atomic_section_prefixes, group); + + if (is_atomic) { + gs_free char *conf_section_was = NULL; + gs_free char *conf_section_is = NULL; + + conf_section_is = _keyfile_serialize_section (keyfile_conf, group); + conf_section_was = g_key_file_get_string (keyfile, group, NM_CONFIG_KEYFILE_KEY_ATOMIC_SECTION_WAS, NULL); + + if (g_strcmp0 (conf_section_was, conf_section_is) != 0) { + /* the section no longer matches. Skip it entirely. */ + needs_rewrite = TRUE; + continue; + } + /* we must set the "was" marker in our keyfile, so that we know that the section + * from user config is overwritten. The value doesn't matter, it's just a marker + * that this section is present. */ + g_key_file_set_value (keyfile_intern, group, NM_CONFIG_KEYFILE_KEY_ATOMIC_SECTION_WAS, ""); + } + + for (k = 0; keys[k]; k++) { + gs_free char *value_set = NULL; + const char *key = keys[k]; + + value_set = g_key_file_get_value (keyfile, group, key, NULL); + + if (is_intern) { + has_intern = TRUE; + g_key_file_set_value (keyfile_intern, group, key, value_set); + } else if (is_atomic) { + if (strcmp (key, NM_CONFIG_KEYFILE_KEY_ATOMIC_SECTION_WAS) == 0) + continue; + g_key_file_set_value (keyfile_intern, group, key, value_set); + } else if (_HAS_PREFIX (key, NM_CONFIG_KEYFILE_KEYPREFIX_SET)) { + const char *key_base = &key[STRLEN (NM_CONFIG_KEYFILE_KEYPREFIX_SET)]; + gs_free char *value_was = NULL; + gs_free char *value_conf = NULL; + gs_free char *key_was = g_strdup_printf (NM_CONFIG_KEYFILE_KEYPREFIX_WAS"%s", key_base); + + if (keyfile_conf) + value_conf = g_key_file_get_value (keyfile_conf, group, key_base, NULL); + value_was = g_key_file_get_value (keyfile, group, key_was, NULL); + + if (g_strcmp0 (value_conf, value_was) != 0) { + /* if value_was is no longer the same as @value_conf, it means the user + * changed the configuration since the last write. In this case, we + * drop the value. It also means our file is out-of-date, and we should + * rewrite it. */ + needs_rewrite = TRUE; + continue; + } + has_intern = TRUE; + g_key_file_set_value (keyfile_intern, group, key_base, value_set); + } else if (_HAS_PREFIX (key, NM_CONFIG_KEYFILE_KEYPREFIX_WAS)) { + const char *key_base = &key[STRLEN (NM_CONFIG_KEYFILE_KEYPREFIX_WAS)]; + gs_free char *key_set = g_strdup_printf (NM_CONFIG_KEYFILE_KEYPREFIX_SET"%s", key_base); + gs_free char *value_was = NULL; + gs_free char *value_conf = NULL; + + if (g_key_file_has_key (keyfile, group, key_set, NULL)) { + /* we have a matching "set" key too. Handle the "was" key there. */ + continue; + } + + if (keyfile_conf) + value_conf = g_key_file_get_value (keyfile_conf, group, key_base, NULL); + value_was = g_key_file_get_value (keyfile, group, key, NULL); + + if (g_strcmp0 (value_conf, value_was) != 0) { + /* if value_was is no longer the same as @value_conf, it means the user + * changed the configuration since the last write. In this case, we + * don't overwrite the user-provided value. It also means our file is + * out-of-date, and we should rewrite it. */ + needs_rewrite = TRUE; + continue; + } + has_intern = TRUE; + /* signal the absence of the value. That means, we must propagate the + * "was" key to NMConfigData, so that it knows to hide the corresponding + * user key. */ + g_key_file_set_value (keyfile_intern, group, key, ""); + } else + needs_rewrite = TRUE; + } + } + +out: + g_key_file_unref (keyfile); + + if (out_needs_rewrite) + *out_needs_rewrite = needs_rewrite; + + nm_log_dbg (LOGD_CORE, "intern config file \"%s\"", filename); + + if (!has_intern) { + g_key_file_unref (keyfile_intern); + return NULL; + } + return keyfile_intern; +} + +static int +_intern_config_write_sort_fcn (const char **a, const char **b, const char *const*atomic_section_prefixes) +{ + const char *g_a = (a ? *a : NULL); + const char *g_b = (b ? *b : NULL); + gboolean a_is, b_is; + + a_is = g_str_has_prefix (g_a, NM_CONFIG_KEYFILE_GROUPPREFIX_INTERN); + b_is = g_str_has_prefix (g_b, NM_CONFIG_KEYFILE_GROUPPREFIX_INTERN); + + if (a_is != b_is) { + if (a_is) + return 1; + return -1; + } + if (!a_is) { + a_is = _is_atomic_section (atomic_section_prefixes, g_a); + b_is = _is_atomic_section (atomic_section_prefixes, g_b); + + if (a_is != b_is) { + if (a_is) + return 1; + return -1; + } + } + return g_strcmp0 (g_a, g_b); +} + +static gboolean +intern_config_write (const char *filename, + GKeyFile *keyfile_intern, + GKeyFile *keyfile_conf, + const char *const*atomic_section_prefixes, + GError **error) +{ + GKeyFile *keyfile; + gs_strfreev char **groups = NULL; + guint g, k; + gboolean has_intern = FALSE; + gboolean success = FALSE; + GError *local = NULL; + + g_return_val_if_fail (filename, FALSE); + + if (!*filename) { + g_set_error (error, G_KEY_FILE_ERROR, G_KEY_FILE_ERROR_NOT_FOUND, "no filename to write (use --intern-config?)"); + return FALSE; + } + + keyfile = nm_config_create_keyfile (); + + if (keyfile_intern) { + groups = g_key_file_get_groups (keyfile_intern, NULL); + if (groups && groups[0]) { + g_qsort_with_data (groups, + g_strv_length (groups), + sizeof (char *), + (GCompareDataFunc) _intern_config_write_sort_fcn, + (gpointer) atomic_section_prefixes); + } + } + for (g = 0; groups && groups[g]; g++) { + gs_strfreev char **keys = NULL; + const char *group = groups[g]; + gboolean is_intern, is_atomic; + + keys = g_key_file_get_keys (keyfile_intern, group, NULL, NULL); + if (!keys) + continue; + + is_intern = g_str_has_prefix (group, NM_CONFIG_KEYFILE_GROUPPREFIX_INTERN); + is_atomic = !is_intern && _is_atomic_section (atomic_section_prefixes, group); + + if (is_atomic) { + if ( (!keys[0] || (!keys[1] && strcmp (keys[0], NM_CONFIG_KEYFILE_KEY_ATOMIC_SECTION_WAS) == 0)) + && !g_key_file_has_group (keyfile_conf, group)) { + /* we are about to save an atomic section. However, we don't have any additional + * keys on our own and there is no user-provided (overlapping) section either. + * We don't have to write an empty section (i.e. skip the useless ".was=0#"). */ + continue; + } else { + gs_free char *conf_section_is = NULL; + + conf_section_is = _keyfile_serialize_section (keyfile_conf, group); + g_key_file_set_string (keyfile, group, NM_CONFIG_KEYFILE_KEY_ATOMIC_SECTION_WAS, conf_section_is); + g_key_file_set_comment (keyfile, group, NULL, + " Overwrites entire section from 'NetworkManager.conf'", + NULL); + } + } + + for (k = 0; keys[k]; k++) { + const char *key = keys[k]; + gs_free char *value_set = NULL; + gs_free char *key_set = NULL; + + if ( !is_intern + && strcmp (key, NM_CONFIG_KEYFILE_KEY_ATOMIC_SECTION_WAS) == 0) { + g_warn_if_fail (is_atomic); + continue; + } + + value_set = g_key_file_get_value (keyfile_intern, group, key, NULL); + + if (is_intern) { + has_intern = TRUE; + g_key_file_set_value (keyfile, group, key, value_set); + } else if (is_atomic) + g_key_file_set_value (keyfile, group, key, value_set); + else { + gs_free char *value_was = NULL; + + if (_HAS_PREFIX (key, NM_CONFIG_KEYFILE_KEYPREFIX_SET)) { + /* Setting a key with .set prefix has no meaning, as these keys + * are protected. Just set the value you want to set instead. + * Why did this happen?? */ + g_warn_if_reached (); + } else if (_HAS_PREFIX (key, NM_CONFIG_KEYFILE_KEYPREFIX_WAS)) { + const char *key_base = &key[STRLEN (NM_CONFIG_KEYFILE_KEYPREFIX_WAS)]; + + if ( _HAS_PREFIX (key_base, NM_CONFIG_KEYFILE_KEYPREFIX_SET) + || _HAS_PREFIX (key_base, NM_CONFIG_KEYFILE_KEYPREFIX_WAS)) { + g_warn_if_reached (); + continue; + } + + if (g_key_file_has_key (keyfile_intern, group, key_base, NULL)) { + /* There is also a matching key_base entry. Skip processing + * the .was. key ad handle the key_base in the other else branch. */ + continue; + } + + if (keyfile_conf) { + value_was = g_key_file_get_value (keyfile_conf, group, key_base, NULL); + if (value_was) + g_key_file_set_value (keyfile, group, key, value_was); + } + } else { + if (keyfile_conf) { + value_was = g_key_file_get_value (keyfile_conf, group, key, NULL); + if (g_strcmp0 (value_set, value_was) == 0) { + /* there is no point in storing the identical value as we have via + * user configuration. Skip it. */ + continue; + } + if (value_was) { + gs_free char *key_was = NULL; + + key_was = g_strdup_printf (NM_CONFIG_KEYFILE_KEYPREFIX_WAS"%s", key); + g_key_file_set_value (keyfile, group, key_was, value_was); + } + } + key = key_set = g_strdup_printf (NM_CONFIG_KEYFILE_KEYPREFIX_SET"%s", key); + g_key_file_set_value (keyfile, group, key, value_set); + } + } + } + if ( is_intern + && g_key_file_has_group (keyfile, group)) { + g_key_file_set_comment (keyfile, group, NULL, + " Internal section. Not overwritable via user configuration in 'NetworkManager.conf'", + NULL); + } + } + + g_key_file_set_comment (keyfile, NULL, NULL, + " Internal configuration file. This file is written and read\n" + " by NetworkManager and its configuration values are merged\n" + " with the configuration from 'NetworkManager.conf'.\n" + "\n" + " Keys with a \""NM_CONFIG_KEYFILE_KEYPREFIX_SET"\" prefix specify the value to set.\n" + " A corresponding key with a \""NM_CONFIG_KEYFILE_KEYPREFIX_WAS"\" prefix records the value\n" + " of the user configuration at the time of storing the file.\n" + " The value from internal configuration is rejected if the corresponding\n" + " \""NM_CONFIG_KEYFILE_KEYPREFIX_WAS"\" key no longer matches the configuration from 'NetworkManager.conf'.\n" + " That means, if you modify a value in 'NetworkManager.conf', the internal\n" + " overwrite no longer matches and is ignored.\n" + "\n" + " Certain sections can only be overwritten whole, not on a per key basis.\n" + " Such sections are marked with a \""NM_CONFIG_KEYFILE_KEY_ATOMIC_SECTION_WAS"\" key that records the user configuration\n" + " at the time of writing.\n" + "\n" + " Internal sections of the form [" NM_CONFIG_KEYFILE_GROUPPREFIX_INTERN "*] cannot\n" + " be set by user configuration.\n" + "\n" + " CHANGES TO THIS FILE WILL BE OVERWRITTEN", + NULL); + + success = g_key_file_save_to_file (keyfile, filename, &local); + + nm_log_dbg (LOGD_CORE, "write intern config file \"%s\"%s%s", filename, success ? "" : ": ", success ? "" : local->message); + g_key_file_unref (keyfile); + if (!success) + g_propagate_error (error, local); + return success; +} + +/************************************************************************/ + GSList * nm_config_get_device_match_spec (const GKeyFile *keyfile, const char *group, const char *key, gboolean *out_has_key) { @@ -902,16 +1408,112 @@ nm_config_get_device_match_spec (const GKeyFile *keyfile, const char *group, con /************************************************************************/ +/** + * nm_config_set_values: + * @self: the NMConfig instance + * @keyfile_intern_new: (allow-none): the new internal settings to set. + * If %NULL, it is equal to an empty keyfile. + * @allow_write: only if %TRUE, allow writing the changes to file. Otherwise, + * do the changes in-memory only. + * @force_rewrite: if @allow_write is %FALSE, this has no effect. If %FALSE, + * only write the configuration to file, if there are any actual changes. + * If %TRUE, always write the configuration to file, even if tere are seemingly + * no changes. + * + * This is the most flexible function to set values. It all depends on the + * keys and values you set in @keyfile_intern_new. You basically reset all + * internal configuration values to what is in @keyfile_intern_new. + * + * There are 3 types of settings: + * - all groups/sections with a prefix [.intern.*] are taken as is. As these + * groups are separate from user configuration, there is no conflict. You set + * them, that's it. + * - there are atomic sections, i.e. sections whose name start with one of + * NM_CONFIG_ATOMIC_SECTION_PREFIXES. If you put values in these sections, + * it means you completely replace the section from user configuration. + * You can also hide a user provided section by only putting the special + * key NM_CONFIG_KEYFILE_KEY_ATOMIC_SECTION_WAS into that section. + * - otherwise you can overwrite individual values from user-configuration. + * Just set the value. Keys with a prefix NM_CONFIG_KEYFILE_KEYPREFIX_* + * are protected -- as they are not value user keys. + * You can also hide a certain user setting by putting only a key + * NM_CONFIG_KEYFILE_KEYPREFIX_WAS"keyname" into the keyfile. + */ +void +nm_config_set_values (NMConfig *self, + GKeyFile *keyfile_intern_new, + gboolean allow_write, + gboolean force_rewrite) +{ + NMConfigPrivate *priv; + GKeyFile *keyfile_intern_current; + GKeyFile *keyfile_user; + GKeyFile *keyfile_new; + GError *local = NULL; + NMConfigData *new_data = NULL; + gs_strfreev char **groups = NULL; + gint g; + + g_return_if_fail (NM_IS_CONFIG (self)); + + priv = NM_CONFIG_GET_PRIVATE (self); + + keyfile_intern_current = _nm_config_data_get_keyfile_intern (priv->config_data); + + keyfile_new = nm_config_create_keyfile (); + if (keyfile_intern_new) + _nm_keyfile_copy (keyfile_new, keyfile_intern_new); + + /* ensure that every atomic section has a .was entry. */ + groups = g_key_file_get_groups (keyfile_new, NULL); + for (g = 0; groups && groups[g]; g++) { + if (_is_atomic_section ((const char *const*) priv->atomic_section_prefixes, groups[g])) + g_key_file_set_value (keyfile_new, groups[g], NM_CONFIG_KEYFILE_KEY_ATOMIC_SECTION_WAS, ""); + } + + if (!_nm_keyfile_equals (keyfile_intern_current, keyfile_new, TRUE)) + new_data = nm_config_data_new_update_keyfile_intern (priv->config_data, keyfile_new); + + nm_log_dbg (LOGD_CORE, "set values(): %s", new_data ? "has changes" : "no changes"); + + if (allow_write + && (new_data || force_rewrite)) { + /* We write the internal config file based on the user configuration from + * the last load/reload. That is correct, because the intern properties might + * be in accordance to what NM thinks is currently configured. Even if the files + * on disk changed in the meantime. + * But if they changed, on the next reload with might throw away our just + * written data. That is correct, because from NM's point of view, those + * changes on disk happened in any case *after* now. */ + if (*priv->intern_config_file) { + keyfile_user = _nm_config_data_get_keyfile_user (priv->config_data); + if (!intern_config_write (priv->intern_config_file, keyfile_new, keyfile_user, + (const char *const*) priv->atomic_section_prefixes, &local)) { + nm_log_warn (LOGD_CORE, "error saving internal configuration \"%s\": %s", priv->intern_config_file, local->message); + g_clear_error (&local); + } + } else + nm_log_dbg (LOGD_CORE, "don't persistate internal configuration (no file set, use --intern-config?)"); + } + if (new_data) + _set_config_data (self, new_data, 0); + + g_key_file_unref (keyfile_new); +} + +/************************************************************************/ + void nm_config_reload (NMConfig *self, int signal) { NMConfigPrivate *priv; GError *error = NULL; - GKeyFile *keyfile; + GKeyFile *keyfile, *keyfile_intern; NMConfigData *new_data = NULL; char *config_main_file = NULL; char *config_description = NULL; gs_strfreev char **no_auto_default = NULL; + gboolean intern_config_needs_rewrite; g_return_if_fail (NM_IS_CONFIG (self)); @@ -928,6 +1530,7 @@ nm_config_reload (NMConfig *self, int signal) */ keyfile = read_entire_config (&priv->cli, priv->config_dir, + priv->system_config_dir, &config_main_file, &config_description, &error); @@ -937,12 +1540,24 @@ nm_config_reload (NMConfig *self, int signal) _set_config_data (self, NULL, signal); return; } + no_auto_default = no_auto_default_from_file (priv->no_auto_default_file); - new_data = nm_config_data_new (config_main_file, config_description, (const char *const*) no_auto_default, keyfile); + keyfile_intern = intern_config_read (priv->intern_config_file, + keyfile, + (const char *const*) priv->atomic_section_prefixes, + &intern_config_needs_rewrite); + if (intern_config_needs_rewrite) { + intern_config_write (priv->intern_config_file, keyfile_intern, keyfile, + (const char *const*) priv->atomic_section_prefixes, NULL); + } + + new_data = nm_config_data_new (config_main_file, config_description, (const char *const*) no_auto_default, keyfile, keyfile_intern); g_free (config_main_file); g_free (config_description); g_key_file_unref (keyfile); + if (keyfile_intern) + g_key_file_unref (keyfile_intern); _set_config_data (self, new_data, signal); } @@ -961,6 +1576,10 @@ _change_flags_one_to_string (NMConfigChangeFlags flag) return "config-files"; case NM_CONFIG_CHANGE_VALUES: return "values"; + case NM_CONFIG_CHANGE_VALUES_USER: + return "values-user"; + case NM_CONFIG_CHANGE_VALUES_INTERN: + return "values-intern"; case NM_CONFIG_CHANGE_CONNECTIVITY: return "connectivity"; case NM_CONFIG_CHANGE_NO_AUTO_DEFAULT: @@ -1054,11 +1673,11 @@ nm_config_get (void) } NMConfig * -nm_config_setup (const NMConfigCmdLineOptions *cli, GError **error) +nm_config_setup (const NMConfigCmdLineOptions *cli, char **atomic_section_prefixes, GError **error) { g_assert (!singleton_instance); - singleton_instance = nm_config_new (cli, error); + singleton_instance = nm_config_new (cli, atomic_section_prefixes, error); if (singleton_instance) nm_singleton_instance_weak_ref_register (); return singleton_instance; @@ -1069,10 +1688,11 @@ init_sync (GInitable *initable, GCancellable *cancellable, GError **error) { NMConfig *self = NM_CONFIG (initable); NMConfigPrivate *priv = NM_CONFIG_GET_PRIVATE (self); - GKeyFile *keyfile; + GKeyFile *keyfile, *keyfile_intern; char *config_main_file = NULL; char *config_description = NULL; gs_strfreev char **no_auto_default = NULL; + gboolean intern_config_needs_rewrite; if (priv->config_dir) { /* Object is already initialized. */ @@ -1087,8 +1707,26 @@ init_sync (GInitable *initable, GCancellable *cancellable, GError **error) else priv->config_dir = g_strdup (DEFAULT_CONFIG_DIR); + if (priv->cli.system_config_dir) + priv->system_config_dir = g_strdup (priv->cli.system_config_dir); + else + priv->system_config_dir = g_strdup (DEFAULT_SYSTEM_CONFIG_DIR); + + if (strcmp (priv->config_dir, priv->system_config_dir) == 0) { + /* having the same directory twice makes no sense. In that case, clear + * @system_config_dir. */ + g_free (priv->system_config_dir); + priv->system_config_dir = g_strdup (""); + } + + if (priv->cli.intern_config_file) + priv->intern_config_file = g_strdup (priv->cli.intern_config_file); + else + priv->intern_config_file = g_strdup (DEFAULT_INTERN_CONFIG_FILE); + keyfile = read_entire_config (&priv->cli, priv->config_dir, + priv->system_config_dir, &config_main_file, &config_description, error); @@ -1122,23 +1760,35 @@ init_sync (GInitable *initable, GCancellable *cancellable, GError **error) no_auto_default = no_auto_default_from_file (priv->no_auto_default_file); - priv->config_data_orig = nm_config_data_new (config_main_file, config_description, (const char *const*) no_auto_default, keyfile); + keyfile_intern = intern_config_read (priv->intern_config_file, + keyfile, + (const char *const*) priv->atomic_section_prefixes, + &intern_config_needs_rewrite); + if (intern_config_needs_rewrite) { + intern_config_write (priv->intern_config_file, keyfile_intern, keyfile, + (const char *const*) priv->atomic_section_prefixes, NULL); + } + + priv->config_data_orig = nm_config_data_new (config_main_file, config_description, (const char *const*) no_auto_default, keyfile, keyfile_intern); priv->config_data = g_object_ref (priv->config_data_orig); g_free (config_main_file); g_free (config_description); g_key_file_unref (keyfile); + if (keyfile_intern) + g_key_file_unref (keyfile_intern); return TRUE; } NMConfig * -nm_config_new (const NMConfigCmdLineOptions *cli, GError **error) +nm_config_new (const NMConfigCmdLineOptions *cli, char **atomic_section_prefixes, GError **error) { return NM_CONFIG (g_initable_new (NM_TYPE_CONFIG, NULL, error, NM_CONFIG_CMD_LINE_OPTIONS, cli, + NM_CONFIG_ATOMIC_SECTION_PREFIXES, atomic_section_prefixes, NULL)); } @@ -1156,12 +1806,15 @@ finalize (GObject *gobject) NMConfigPrivate *priv = NM_CONFIG_GET_PRIVATE (gobject); g_free (priv->config_dir); + g_free (priv->system_config_dir); g_free (priv->no_auto_default_file); + g_free (priv->intern_config_file); g_strfreev (priv->plugins); g_free (priv->dhcp_client); g_free (priv->log_level); g_free (priv->log_domains); g_free (priv->debug); + g_strfreev (priv->atomic_section_prefixes); _nm_config_cmd_line_options_clear (&priv->cli); @@ -1188,6 +1841,10 @@ set_property (GObject *object, guint prop_id, else _nm_config_cmd_line_options_copy (cli, &priv->cli); break; + case PROP_ATOMIC_SECTION_PREFIXES: + /* construct only */ + priv->atomic_section_prefixes = g_strdupv (g_value_get_boxed (value)); + break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; @@ -1210,6 +1867,14 @@ nm_config_class_init (NMConfigClass *config_class) G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS)); + g_object_class_install_property + (object_class, PROP_ATOMIC_SECTION_PREFIXES, + g_param_spec_boxed (NM_CONFIG_ATOMIC_SECTION_PREFIXES, "", "", + G_TYPE_STRV, + G_PARAM_WRITABLE | + G_PARAM_CONSTRUCT_ONLY | + G_PARAM_STATIC_STRINGS)); + signals[SIGNAL_CONFIG_CHANGED] = g_signal_new (NM_CONFIG_SIGNAL_CONFIG_CHANGED, G_OBJECT_CLASS_TYPE (object_class), diff --git a/src/nm-config.h b/src/nm-config.h index 5b096f165d..3caf0c5c31 100644 --- a/src/nm-config.h +++ b/src/nm-config.h @@ -39,6 +39,7 @@ G_BEGIN_DECLS /* Properties */ #define NM_CONFIG_CMD_LINE_OPTIONS "cmd-line-options" +#define NM_CONFIG_ATOMIC_SECTION_PREFIXES "atomic-section-prefixes" /* Signals */ #define NM_CONFIG_SIGNAL_CONFIG_CHANGED "config-changed" @@ -48,6 +49,7 @@ G_BEGIN_DECLS #define NM_CONFIG_KEYFILE_LIST_SEPARATOR ',' +#define NM_CONFIG_KEYFILE_GROUPPREFIX_INTERN ".intern." #define NM_CONFIG_KEYFILE_GROUPPREFIX_CONNECTION "connection" #define NM_CONFIG_KEYFILE_GROUPPREFIX_TEST_APPEND_STRINGLIST ".test-append-stringlist" @@ -59,10 +61,14 @@ G_BEGIN_DECLS #define NM_CONFIG_KEYFILE_GROUP_IFUPDOWN "ifupdown" #define NM_CONFIG_KEYFILE_GROUP_IFNET "ifnet" +#define NM_CONFIG_KEYFILE_KEY_ATOMIC_SECTION_WAS ".was" #define NM_CONFIG_KEYFILE_KEY_IFNET_AUTO_REFRESH "auto_refresh" #define NM_CONFIG_KEYFILE_KEY_IFNET_MANAGED "managed" #define NM_CONFIG_KEYFILE_KEY_IFUPDOWN_MANAGED "managed" +#define NM_CONFIG_KEYFILE_KEYPREFIX_WAS ".was." +#define NM_CONFIG_KEYFILE_KEYPREFIX_SET ".set." + typedef struct NMConfigCmdLineOptions NMConfigCmdLineOptions; struct _NMConfig { @@ -97,6 +103,11 @@ const char *nm_config_get_log_domains (NMConfig *config); const char *nm_config_get_debug (NMConfig *config); gboolean nm_config_get_configure_and_quit (NMConfig *config); +void nm_config_set_values (NMConfig *self, + GKeyFile *keyfile_intern_new, + gboolean allow_write, + gboolean force_rewrite); + /* for main.c only */ NMConfigCmdLineOptions *nm_config_cmd_line_options_new (void); void nm_config_cmd_line_options_free (NMConfigCmdLineOptions *cli); @@ -106,8 +117,8 @@ void nm_config_cmd_line_options_add_to_entries (NMConfigCmdLi gboolean nm_config_get_no_auto_default_for_device (NMConfig *config, NMDevice *device); void nm_config_set_no_auto_default_for_device (NMConfig *config, NMDevice *device); -NMConfig *nm_config_new (const NMConfigCmdLineOptions *cli, GError **error); -NMConfig *nm_config_setup (const NMConfigCmdLineOptions *cli, GError **error); +NMConfig *nm_config_new (const NMConfigCmdLineOptions *cli, char **atomic_section_prefixes, GError **error); +NMConfig *nm_config_setup (const NMConfigCmdLineOptions *cli, char **atomic_section_prefixes, GError **error); void nm_config_reload (NMConfig *config, int signal); gint nm_config_parse_boolean (const char *str, gint default_value); @@ -128,6 +139,8 @@ void nm_config_keyfile_set_string_list (GKeyFile *keyfile, gssize len); GSList *nm_config_get_device_match_spec (const GKeyFile *keyfile, const char *group, const char *key, gboolean *out_has_key); +void _nm_config_sort_groups (char **groups, gsize ngroups); + G_END_DECLS #endif /* __NETWORKMANAGER_CONFIG_H__ */ diff --git a/src/settings/nm-settings.c b/src/settings/nm-settings.c index 9f7b47d9bc..befebc1591 100644 --- a/src/settings/nm-settings.c +++ b/src/settings/nm-settings.c @@ -764,18 +764,20 @@ load_plugins (NMSettings *self, const char **plugins, GError **error) const char **iter; gboolean keyfile_added = FALSE; gboolean success = TRUE; + gboolean add_ibft = FALSE; + gboolean has_no_ibft; + gssize idx_no_ibft, idx_ibft; + + idx_ibft = _nm_utils_strv_find_first ((char **) plugins, -1, "ibft"); + idx_no_ibft = _nm_utils_strv_find_first ((char **) plugins, -1, "no-ibft"); + has_no_ibft = idx_no_ibft >= 0 && idx_no_ibft > idx_ibft; +#if WITH_SETTINGS_PLUGIN_IBFT + add_ibft = idx_no_ibft < 0 && idx_ibft < 0; +#endif for (iter = plugins; iter && *iter; iter++) { - GModule *plugin; - gs_free char *full_name = NULL; - gs_free char *path = NULL; - const char *pname; + const char *pname = *iter; GObject *obj; - GObject * (*factory_func) (void); - struct stat st; - int errsv; - - pname = *iter; if (!*pname || strchr (pname, '/')) { LOG (LOGL_WARN, "ignore invalid plugin \"%s\"", pname); @@ -787,6 +789,11 @@ load_plugins (NMSettings *self, const char **plugins, GError **error) continue; } + if (!strcmp (pname, "no-ibft")) + continue; + if (has_no_ibft && !strcmp (pname, "ibft")) + continue; + obj = find_plugin (list, pname); if (obj) continue; @@ -800,61 +807,79 @@ load_plugins (NMSettings *self, const char **plugins, GError **error) continue; } - full_name = g_strdup_printf ("nm-settings-plugin-%s", pname); - path = g_module_build_path (NMPLUGINDIR, full_name); +load_plugin: + { + GModule *plugin; + gs_free char *full_name = NULL; + gs_free char *path = NULL; + GObject * (*factory_func) (void); + struct stat st; + int errsv; + + full_name = g_strdup_printf ("nm-settings-plugin-%s", pname); + path = g_module_build_path (NMPLUGINDIR, full_name); + + if (stat (path, &st) != 0) { + errsv = errno; + LOG (LOGL_WARN, "Could not load plugin '%s' from file '%s': %s", pname, path, strerror (errsv)); + goto next; + } + if (!S_ISREG (st.st_mode)) { + LOG (LOGL_WARN, "Could not load plugin '%s' from file '%s': not a file", pname, path); + goto next; + } + if (st.st_uid != 0) { + LOG (LOGL_WARN, "Could not load plugin '%s' from file '%s': file must be owned by root", pname, path); + goto next; + } + if (st.st_mode & (S_IWGRP | S_IWOTH | S_ISUID)) { + LOG (LOGL_WARN, "Could not load plugin '%s' from file '%s': invalid file permissions", pname, path); + goto next; + } - if (stat (path, &st) != 0) { - errsv = errno; - LOG (LOGL_WARN, "Could not load plugin '%s' from file '%s': %s", pname, path, strerror (errsv)); - continue; - } - if (!S_ISREG (st.st_mode)) { - LOG (LOGL_WARN, "Could not load plugin '%s' from file '%s': not a file", pname, path); - continue; - } - if (st.st_uid != 0) { - LOG (LOGL_WARN, "Could not load plugin '%s' from file '%s': file must be owned by root", pname, path); - continue; - } - if (st.st_mode & (S_IWGRP | S_IWOTH | S_ISUID)) { - LOG (LOGL_WARN, "Could not load plugin '%s' from file '%s': invalid file permissions", pname, path); - continue; - } + plugin = g_module_open (path, G_MODULE_BIND_LOCAL); + if (!plugin) { + LOG (LOGL_WARN, "Could not load plugin '%s' from file '%s': %s", + pname, path, g_module_error ()); + goto next; + } - plugin = g_module_open (path, G_MODULE_BIND_LOCAL); - if (!plugin) { - LOG (LOGL_WARN, "Could not load plugin '%s' from file '%s': %s", - pname, path, g_module_error ()); - continue; - } + /* errors after this point are fatal, because we loaded the shared library already. */ + + if (!g_module_symbol (plugin, "nm_system_config_factory", (gpointer) (&factory_func))) { + g_set_error (error, NM_SETTINGS_ERROR, NM_SETTINGS_ERROR_FAILED, + "Could not find plugin '%s' factory function.", + pname); + success = FALSE; + g_module_close (plugin); + break; + } - /* errors after this point are fatal, because we loaded the shared library already. */ + obj = (*factory_func) (); + if (!obj || !NM_IS_SYSTEM_CONFIG_INTERFACE (obj)) { + g_set_error (error, NM_SETTINGS_ERROR, NM_SETTINGS_ERROR_FAILED, + "Plugin '%s' returned invalid system config object.", + pname); + success = FALSE; + g_module_close (plugin); + break; + } - if (!g_module_symbol (plugin, "nm_system_config_factory", (gpointer) (&factory_func))) { - g_set_error (error, NM_SETTINGS_ERROR, NM_SETTINGS_ERROR_FAILED, - "Could not find plugin '%s' factory function.", - pname); - success = FALSE; - g_module_close (plugin); - break; + g_module_make_resident (plugin); + g_object_weak_ref (obj, (GWeakNotify) g_module_close, plugin); + g_object_set_data_full (obj, PLUGIN_MODULE_PATH, path, g_free); + path = NULL; + add_plugin (self, NM_SYSTEM_CONFIG_INTERFACE (obj)); + list = g_slist_append (list, obj); } - - obj = (*factory_func) (); - if (!obj || !NM_IS_SYSTEM_CONFIG_INTERFACE (obj)) { - g_set_error (error, NM_SETTINGS_ERROR, NM_SETTINGS_ERROR_FAILED, - "Plugin '%s' returned invalid system config object.", - pname); - success = FALSE; - g_module_close (plugin); - break; +next: + if (add_ibft && !strcmp (pname, "ifcfg-rh")) { + /* The plugin ibft is not explicitly mentioned but we just enabled "ifcfg-rh". + * Enable "ibft" by default after "ifcfg-rh". */ + pname = "ibft"; + add_ibft = FALSE; + goto load_plugin; } - - g_module_make_resident (plugin); - g_object_weak_ref (obj, (GWeakNotify) g_module_close, plugin); - g_object_set_data_full (obj, PLUGIN_MODULE_PATH, path, g_free); - path = NULL; - add_plugin (self, NM_SYSTEM_CONFIG_INTERFACE (obj)); - list = g_slist_append (list, obj); } /* If keyfile plugin was not among configured plugins, add it as the last one */ diff --git a/src/tests/config/test-config.c b/src/tests/config/test-config.c index ca115fd1ab..33b66d614f 100644 --- a/src/tests/config/test-config.c +++ b/src/tests/config/test-config.c @@ -32,8 +32,27 @@ #include "nm-test-utils.h" +/********************************************************************************/ + +static void +_assert_config_value (const NMConfigData *config_data, const char *group, const char *key, const char *expected_value, const char *file, int line) +{ + gs_free char *value = NULL; + + value = nm_config_data_get_value (config_data, group, key, NM_CONFIG_GET_VALUE_NONE); + if (g_strcmp0 (value, expected_value)) { + g_error ("(%s:%d) invalid value in config-data %s.%s = %s%s%s (instead of %s%s%s)", + file, line, group, key, + NM_PRINT_FMT_QUOTED (value, "\"", value, "\"", "(null)"), + NM_PRINT_FMT_QUOTED (expected_value, "\"", expected_value, "\"", "(null)")); + } +} +#define assert_config_value(config_data, group, key, expected_value) _assert_config_value (config_data, group, key, expected_value, __FILE__, __LINE__) + +/********************************************************************************/ + static NMConfig * -setup_config (GError **error, const char *config_file, const char *config_dir, ...) +setup_config (GError **error, const char *config_file, const char *intern_config, const char *const* atomic_section_prefixes, const char *config_dir, const char *system_config_dir, ...) { va_list ap; GPtrArray *args; @@ -51,10 +70,18 @@ setup_config (GError **error, const char *config_file, const char *config_dir, . g_ptr_array_add (args, "test-config"); g_ptr_array_add (args, "--config"); g_ptr_array_add (args, (char *)config_file); + if (intern_config) { + g_ptr_array_add (args, "--intern-config"); + g_ptr_array_add (args, (char *)intern_config); + } g_ptr_array_add (args, "--config-dir"); g_ptr_array_add (args, (char *)config_dir); + if (system_config_dir) { + g_ptr_array_add (args, "--system-config-dir"); + g_ptr_array_add (args, (char *) system_config_dir); + } - va_start (ap, config_dir); + va_start (ap, system_config_dir); while ((arg = va_arg (ap, char *))) g_ptr_array_add (args, arg); va_end (ap); @@ -74,7 +101,7 @@ setup_config (GError **error, const char *config_file, const char *config_dir, . g_ptr_array_free (args, TRUE); - config = nm_config_setup (cli, &local_error); + config = nm_config_setup (cli, (char **) atomic_section_prefixes, &local_error); if (error) { g_assert (!config); g_assert (local_error); @@ -97,7 +124,7 @@ test_config_simple (void) gs_unref_object NMDevice *dev51 = nm_test_device_new ("00:00:00:00:00:51"); gs_unref_object NMDevice *dev52 = nm_test_device_new ("00:00:00:00:00:52"); - config = setup_config (NULL, SRCDIR "/NetworkManager.conf", "/no/such/dir", NULL); + config = setup_config (NULL, SRCDIR "/NetworkManager.conf", "", NULL, "/no/such/dir", "", NULL); g_assert_cmpstr (nm_config_data_get_config_main_file (nm_config_get_data_orig (config)), ==, SRCDIR "/NetworkManager.conf"); g_assert_cmpstr (nm_config_get_dhcp_client (config), ==, "dhclient"); @@ -176,7 +203,7 @@ test_config_non_existent (void) { GError *error = NULL; - setup_config (&error, SRCDIR "/no-such-file", "/no/such/dir", NULL); + setup_config (&error, SRCDIR "/no-such-file", "", NULL, "/no/such/dir", "", NULL); g_assert_error (error, G_KEY_FILE_ERROR, G_KEY_FILE_ERROR_NOT_FOUND); g_clear_error (&error); } @@ -186,7 +213,7 @@ test_config_parse_error (void) { GError *error = NULL; - setup_config (&error, SRCDIR "/bad.conf", "/no/such/dir", NULL); + setup_config (&error, SRCDIR "/bad.conf", "", NULL, "/no/such/dir", "", NULL); g_assert_error (error, G_KEY_FILE_ERROR, G_KEY_FILE_ERROR_PARSE); g_clear_error (&error); } @@ -197,7 +224,7 @@ test_config_override (void) NMConfig *config; const char **plugins; - config = setup_config (NULL, SRCDIR "/NetworkManager.conf", "/no/such/dir", + config = setup_config (NULL, SRCDIR "/NetworkManager.conf", "", NULL, "/no/such/dir", "", "--plugins", "alpha,beta,gamma,delta", "--connectivity-interval", "12", NULL); @@ -235,7 +262,7 @@ test_config_no_auto_default (void) g_assert_cmpint (nwrote, ==, 18); close (fd); - config = setup_config (NULL, SRCDIR "/NetworkManager.conf", "/no/such/dir", + config = setup_config (NULL, SRCDIR "/NetworkManager.conf", "", NULL, "/no/such/dir", "", "--no-auto-default", state_file, NULL); @@ -257,7 +284,7 @@ test_config_no_auto_default (void) g_object_unref (config); - config = setup_config (NULL, SRCDIR "/NetworkManager.conf", "/no/such/dir", + config = setup_config (NULL, SRCDIR "/NetworkManager.conf", "", NULL, "/no/such/dir", "", "--no-auto-default", state_file, NULL); @@ -285,7 +312,7 @@ test_config_confdir (void) char *value; GSList *specs; - config = setup_config (NULL, SRCDIR "/NetworkManager.conf", SRCDIR "/conf.d", NULL); + config = setup_config (NULL, SRCDIR "/NetworkManager.conf", "", NULL, SRCDIR "/conf.d", "", NULL); g_assert_cmpstr (nm_config_data_get_config_main_file (nm_config_get_data_orig (config)), ==, SRCDIR "/NetworkManager.conf"); g_assert_cmpstr (nm_config_get_dhcp_client (config), ==, "dhcpcd"); @@ -391,11 +418,336 @@ test_config_confdir_parse_error (void) GError *error = NULL; /* Using SRCDIR as the conf dir will pick up bad.conf */ - setup_config (&error, SRCDIR "/NetworkManager.conf", SRCDIR, NULL); + setup_config (&error, SRCDIR "/NetworkManager.conf", "", NULL, SRCDIR, "", NULL); g_assert_error (error, G_KEY_FILE_ERROR, G_KEY_FILE_ERROR_PARSE); g_clear_error (&error); } +/*****************************************************************************/ + +typedef void (*TestSetValuesUserSetFcn) (NMConfig *config, gboolean is_user, GKeyFile *keyfile_user, NMConfigChangeFlags *out_expected_changes); +typedef void (*TestSetValuesCheckStateFcn) (NMConfig *config, NMConfigData *config_data, gboolean is_change_event, NMConfigChangeFlags changes, NMConfigData *old_data); + +typedef struct { + NMConfigChangeFlags changes; + TestSetValuesCheckStateFcn check_state_fcn; +} TestSetValuesConfigChangedData; + +static void +_set_values_config_changed_cb (NMConfig *config, + NMConfigData *config_data, + NMConfigChangeFlags changes, + NMConfigData *old_data, + TestSetValuesConfigChangedData *config_changed_data) +{ + g_assert (changes != NM_CONFIG_CHANGE_NONE); + g_assert (config_changed_data); + g_assert (config_changed_data->changes == NM_CONFIG_CHANGE_NONE); + + if (changes == NM_CONFIG_CHANGE_SIGHUP) + return; + changes &= ~NM_CONFIG_CHANGE_SIGHUP; + + config_changed_data->changes = changes; + + if (config_changed_data->check_state_fcn) + config_changed_data->check_state_fcn (config, config_data, TRUE, changes, old_data); +} + +static void +_set_values_user (NMConfig *config, + const char *CONFIG_USER, + TestSetValuesUserSetFcn set_fcn, + TestSetValuesCheckStateFcn check_state_fcn) +{ + GKeyFile *keyfile_user; + gboolean success; + gs_free_error GError *error = NULL; + TestSetValuesConfigChangedData config_changed_data = { + .changes = NM_CONFIG_CHANGE_NONE, + .check_state_fcn = check_state_fcn, + }; + NMConfigChangeFlags expected_changes = NM_CONFIG_CHANGE_NONE; + gs_unref_object NMConfigData *config_data_before = NULL; + + keyfile_user = nm_config_create_keyfile (); + + success = g_key_file_load_from_file (keyfile_user, CONFIG_USER, G_KEY_FILE_NONE, &error); + nmtst_assert_success (success, error); + + if (set_fcn) + set_fcn (config, TRUE, keyfile_user, &expected_changes); + + success = g_key_file_save_to_file (keyfile_user, CONFIG_USER, &error); + nmtst_assert_success (success, error); + + g_signal_connect (G_OBJECT (config), + NM_CONFIG_SIGNAL_CONFIG_CHANGED, + G_CALLBACK (_set_values_config_changed_cb), + &config_changed_data); + + config_data_before = g_object_ref (nm_config_get_data (config)); + + if (expected_changes != NM_CONFIG_CHANGE_NONE) + g_test_expect_message ("NetworkManager", G_LOG_LEVEL_MESSAGE, "*config: update *"); + else + g_test_expect_message ("NetworkManager", G_LOG_LEVEL_MESSAGE, "*config: signal SIGHUP (no changes from disk)*"); + + nm_config_reload (config, SIGHUP); + + g_test_assert_expected_messages (); + + g_assert (expected_changes == config_changed_data.changes); + + if (check_state_fcn) + check_state_fcn (config, nm_config_get_data (config), FALSE, NM_CONFIG_CHANGE_NONE, config_data_before); + + g_signal_handlers_disconnect_by_func (config, _set_values_config_changed_cb, &config_changed_data); + + g_key_file_unref (keyfile_user); +} + +static void +_set_values_intern (NMConfig *config, + TestSetValuesUserSetFcn set_fcn, + TestSetValuesCheckStateFcn check_state_fcn) +{ + GKeyFile *keyfile_intern; + TestSetValuesConfigChangedData config_changed_data = { + .changes = NM_CONFIG_CHANGE_NONE, + .check_state_fcn = check_state_fcn, + }; + NMConfigChangeFlags expected_changes = NM_CONFIG_CHANGE_NONE; + gs_unref_object NMConfigData *config_data_before = NULL; + + config_data_before = g_object_ref (nm_config_get_data (config)); + + keyfile_intern = nm_config_data_clone_keyfile_intern (config_data_before); + + if (set_fcn) + set_fcn (config, FALSE, keyfile_intern, &expected_changes); + + g_signal_connect (G_OBJECT (config), + NM_CONFIG_SIGNAL_CONFIG_CHANGED, + G_CALLBACK (_set_values_config_changed_cb), + &config_changed_data); + + if (expected_changes != NM_CONFIG_CHANGE_NONE) + g_test_expect_message ("NetworkManager", G_LOG_LEVEL_MESSAGE, "*config: update *"); + + nm_config_set_values (config, keyfile_intern, TRUE, FALSE); + + g_test_assert_expected_messages (); + + g_assert (expected_changes == config_changed_data.changes); + + if (check_state_fcn) + check_state_fcn (config, nm_config_get_data (config), FALSE, NM_CONFIG_CHANGE_NONE, config_data_before); + + g_signal_handlers_disconnect_by_func (config, _set_values_config_changed_cb, &config_changed_data); + + g_key_file_unref (keyfile_intern); +} + +static void +_set_values_user_intern_section_set (NMConfig *config, gboolean set_user, GKeyFile *keyfile, NMConfigChangeFlags *out_expected_changes) +{ + g_key_file_set_string (keyfile, NM_CONFIG_KEYFILE_GROUPPREFIX_INTERN"section1", "key", "this-should-be-ignored"); +} + +static void +_set_values_user_intern_section_check (NMConfig *config, NMConfigData *config_data, gboolean is_change_event, NMConfigChangeFlags changes, NMConfigData *old_data) +{ + g_assert (changes == NM_CONFIG_CHANGE_NONE); + g_assert (!nm_config_data_has_group (config_data, NM_CONFIG_KEYFILE_GROUPPREFIX_INTERN"section1")); +} + +static void +_set_values_user_initial_values_set (NMConfig *config, gboolean set_user, GKeyFile *keyfile, NMConfigChangeFlags *out_expected_changes) +{ + g_key_file_remove_group (keyfile, NM_CONFIG_KEYFILE_GROUPPREFIX_INTERN"section1", NULL); + g_key_file_set_string (keyfile, "section1", "key1", "value1"); + *out_expected_changes = NM_CONFIG_CHANGE_VALUES | NM_CONFIG_CHANGE_VALUES_USER; +} + +static void +_set_values_user_initial_values_check (NMConfig *config, NMConfigData *config_data, gboolean is_change_event, NMConfigChangeFlags changes, NMConfigData *old_data) +{ + if (is_change_event) + g_assert (changes == (NM_CONFIG_CHANGE_VALUES | NM_CONFIG_CHANGE_VALUES_USER)); + assert_config_value (config_data, "section1", "key1", "value1"); +} + +static void +_set_values_intern_internal_set (NMConfig *config, gboolean set_user, GKeyFile *keyfile, NMConfigChangeFlags *out_expected_changes) +{ + g_key_file_set_string (keyfile, NM_CONFIG_KEYFILE_GROUPPREFIX_INTERN"section1", "key", "internal-section"); + *out_expected_changes = NM_CONFIG_CHANGE_VALUES | NM_CONFIG_CHANGE_VALUES_INTERN; +} + +static void +_set_values_intern_internal_check (NMConfig *config, NMConfigData *config_data, gboolean is_change_event, NMConfigChangeFlags changes, NMConfigData *old_data) +{ + if (is_change_event) + g_assert (changes == (NM_CONFIG_CHANGE_VALUES | NM_CONFIG_CHANGE_VALUES_INTERN)); + assert_config_value (config_data, NM_CONFIG_KEYFILE_GROUPPREFIX_INTERN"section1", "key", "internal-section"); +} + +static void +_set_values_user_atomic_section_1_set (NMConfig *config, gboolean set_user, GKeyFile *keyfile, NMConfigChangeFlags *out_expected_changes) +{ + g_key_file_set_string (keyfile, "atomic-prefix-1.section-a", "key1", "user-value1"); + g_key_file_set_string (keyfile, "atomic-prefix-1.section-a", "key2", "user-value2"); + g_key_file_set_string (keyfile, "atomic-prefix-1.section-b", "key1", "user-value1"); + g_key_file_set_string (keyfile, "non-atomic-prefix-1.section-a", "nap1-key1", "user-value1"); + g_key_file_set_string (keyfile, "non-atomic-prefix-1.section-a", "nap1-key2", "user-value2"); + *out_expected_changes = NM_CONFIG_CHANGE_VALUES | NM_CONFIG_CHANGE_VALUES_USER; +} + +static void +_set_values_user_atomic_section_1_check (NMConfig *config, NMConfigData *config_data, gboolean is_change_event, NMConfigChangeFlags changes, NMConfigData *old_data) +{ + if (is_change_event) + g_assert (changes == (NM_CONFIG_CHANGE_VALUES | NM_CONFIG_CHANGE_VALUES_USER)); + assert_config_value (config_data, "atomic-prefix-1.section-a", "key1", "user-value1"); + assert_config_value (config_data, "atomic-prefix-1.section-a", "key2", "user-value2"); + assert_config_value (config_data, "atomic-prefix-1.section-b", "key1", "user-value1"); + assert_config_value (config_data, "non-atomic-prefix-1.section-a", "nap1-key1", "user-value1"); + assert_config_value (config_data, "non-atomic-prefix-1.section-a", "nap1-key2", "user-value2"); +} + +static void +_set_values_intern_atomic_section_1_set (NMConfig *config, gboolean set_user, GKeyFile *keyfile, NMConfigChangeFlags *out_expected_changes) +{ + g_key_file_set_string (keyfile, "atomic-prefix-1.section-a", "key1", "intern-value1"); + g_key_file_set_string (keyfile, "atomic-prefix-1.section-a", "key3", "intern-value3"); + g_key_file_set_string (keyfile, "non-atomic-prefix-1.section-a", "nap1-key1", "intern-value1"); + g_key_file_set_string (keyfile, "non-atomic-prefix-1.section-a", "nap1-key3", "intern-value3"); + *out_expected_changes = NM_CONFIG_CHANGE_VALUES | NM_CONFIG_CHANGE_VALUES_INTERN; +} + +static void +_set_values_intern_atomic_section_1_check (NMConfig *config, NMConfigData *config_data, gboolean is_change_event, NMConfigChangeFlags changes, NMConfigData *old_data) +{ + if (is_change_event) + g_assert (changes == (NM_CONFIG_CHANGE_VALUES | NM_CONFIG_CHANGE_VALUES_INTERN)); + assert_config_value (config_data, "atomic-prefix-1.section-a", "key1", "intern-value1"); + assert_config_value (config_data, "atomic-prefix-1.section-a", "key2", NULL); + assert_config_value (config_data, "atomic-prefix-1.section-a", "key3", "intern-value3"); + assert_config_value (config_data, "atomic-prefix-1.section-b", "key1", "user-value1"); + assert_config_value (config_data, "non-atomic-prefix-1.section-a", "nap1-key1", "intern-value1"); + assert_config_value (config_data, "non-atomic-prefix-1.section-a", "nap1-key2", "user-value2"); + assert_config_value (config_data, "non-atomic-prefix-1.section-a", "nap1-key3", "intern-value3"); + g_assert ( nm_config_data_is_intern_atomic_group (config_data, "atomic-prefix-1.section-a")); + g_assert (!nm_config_data_is_intern_atomic_group (config_data, "atomic-prefix-1.section-b")); + g_assert (!nm_config_data_is_intern_atomic_group (config_data, "non-atomic-prefix-1.section-a")); +} + +static void +_set_values_user_atomic_section_2_set (NMConfig *config, gboolean set_user, GKeyFile *keyfile, NMConfigChangeFlags *out_expected_changes) +{ + g_key_file_set_string (keyfile, "atomic-prefix-1.section-a", "key1", "user-value1-x"); + g_key_file_set_string (keyfile, "atomic-prefix-1.section-a", "key2", "user-value2"); + g_key_file_set_string (keyfile, "non-atomic-prefix-1.section-a", "nap1-key1", "user-value1-x"); + g_key_file_set_string (keyfile, "non-atomic-prefix-1.section-a", "nap1-key2", "user-value2-x"); + *out_expected_changes = NM_CONFIG_CHANGE_VALUES | NM_CONFIG_CHANGE_VALUES_USER | NM_CONFIG_CHANGE_VALUES_INTERN; +} + +static void +_set_values_user_atomic_section_2_check (NMConfig *config, NMConfigData *config_data, gboolean is_change_event, NMConfigChangeFlags changes, NMConfigData *old_data) +{ + if (is_change_event) + g_assert (changes == (NM_CONFIG_CHANGE_VALUES | NM_CONFIG_CHANGE_VALUES_USER | NM_CONFIG_CHANGE_VALUES_INTERN)); + assert_config_value (config_data, "atomic-prefix-1.section-a", "key1", "user-value1-x"); + assert_config_value (config_data, "atomic-prefix-1.section-a", "key2", "user-value2"); + assert_config_value (config_data, "non-atomic-prefix-1.section-a", "nap1-key1", "user-value1-x"); + assert_config_value (config_data, "non-atomic-prefix-1.section-a", "nap1-key2", "user-value2-x"); + assert_config_value (config_data, "non-atomic-prefix-1.section-a", "nap1-key3", "intern-value3"); + g_assert (!nm_config_data_is_intern_atomic_group (config_data, "atomic-prefix-1.section-a")); + g_assert (!nm_config_data_is_intern_atomic_group (config_data, "atomic-prefix-1.section-b")); + g_assert (!nm_config_data_is_intern_atomic_group (config_data, "non-atomic-prefix-1.section-a")); +} + +static void +_set_values_intern_atomic_section_2_set (NMConfig *config, gboolean set_user, GKeyFile *keyfile, NMConfigChangeFlags *out_expected_changes) +{ + /* let's hide an atomic section and one key. */ + g_key_file_set_string (keyfile, "atomic-prefix-1.section-a", NM_CONFIG_KEYFILE_KEY_ATOMIC_SECTION_WAS, "any-value"); + g_key_file_set_string (keyfile, "non-atomic-prefix-1.section-a", NM_CONFIG_KEYFILE_KEYPREFIX_WAS"nap1-key1", "any-value"); + g_key_file_set_string (keyfile, "non-atomic-prefix-1.section-a", "nap1-key3", "intern-value3"); + g_key_file_set_string (keyfile, NM_CONFIG_KEYFILE_GROUPPREFIX_INTERN"with-whitespace", "key1", " b c\\, d "); + g_key_file_set_value (keyfile, NM_CONFIG_KEYFILE_GROUPPREFIX_INTERN"with-whitespace", "key2", " b c\\, d "); + *out_expected_changes = NM_CONFIG_CHANGE_VALUES | NM_CONFIG_CHANGE_VALUES_INTERN; +} + +static void +_set_values_intern_atomic_section_2_check (NMConfig *config, NMConfigData *config_data, gboolean is_change_event, NMConfigChangeFlags changes, NMConfigData *old_data) +{ + if (is_change_event) + g_assert (changes == (NM_CONFIG_CHANGE_VALUES | NM_CONFIG_CHANGE_VALUES_INTERN)); + g_assert (!nm_config_data_has_group (config_data, "atomic-prefix-1.section-a")); + assert_config_value (config_data, "atomic-prefix-1.section-b", "key1", "user-value1"); + assert_config_value (config_data, "non-atomic-prefix-1.section-a", "nap1-key1", NULL); + assert_config_value (config_data, "non-atomic-prefix-1.section-a", "nap1-key2", "user-value2-x"); + assert_config_value (config_data, "non-atomic-prefix-1.section-a", "nap1-key3", "intern-value3"); + g_assert (!nm_config_data_is_intern_atomic_group (config_data, "atomic-prefix-1.section-a")); + g_assert (!nm_config_data_is_intern_atomic_group (config_data, "atomic-prefix-1.section-b")); + g_assert (!nm_config_data_is_intern_atomic_group (config_data, "non-atomic-prefix-1.section-a")); + assert_config_value (config_data, NM_CONFIG_KEYFILE_GROUPPREFIX_INTERN"with-whitespace", "key1", " b c\\, d "); + assert_config_value (config_data, NM_CONFIG_KEYFILE_GROUPPREFIX_INTERN"with-whitespace", "key2", " b c\\, d "); +} + +static void +test_config_set_values (void) +{ + gs_unref_object NMConfig *config = NULL; + const char *CONFIG_USER = SRCDIR"/test-set-values-user.conf"; + const char *CONFIG_INTERN = SRCDIR"/test-set-values-intern.conf"; + const char *atomic_section_prefixes[] = { + "atomic-prefix-1.", + "atomic-prefix-2.", + NULL, + }; + + g_assert (g_file_set_contents (CONFIG_USER, "", 0, NULL)); + g_assert (g_file_set_contents (CONFIG_INTERN, "", 0, NULL)); + + config = setup_config (NULL, CONFIG_USER, CONFIG_INTERN, atomic_section_prefixes, "", "", NULL); + + _set_values_user (config, CONFIG_USER, + _set_values_user_intern_section_set, + _set_values_user_intern_section_check); + + _set_values_user (config, CONFIG_USER, + _set_values_user_initial_values_set, + _set_values_user_initial_values_check); + + _set_values_intern (config, + _set_values_intern_internal_set, + _set_values_intern_internal_check); + + _set_values_user (config, CONFIG_USER, + _set_values_user_atomic_section_1_set, + _set_values_user_atomic_section_1_check); + + _set_values_intern (config, + _set_values_intern_atomic_section_1_set, + _set_values_intern_atomic_section_1_check); + + _set_values_user (config, CONFIG_USER, + _set_values_user_atomic_section_2_set, + _set_values_user_atomic_section_2_check); + + _set_values_intern (config, + _set_values_intern_atomic_section_2_set, + _set_values_intern_atomic_section_2_check); + + g_assert (remove (CONFIG_USER) == 0); + g_assert (remove (CONFIG_INTERN) == 0); +} + +/*****************************************************************************/ + NMTST_DEFINE (); int @@ -419,6 +771,8 @@ main (int argc, char **argv) g_test_add_func ("/config/confdir", test_config_confdir); g_test_add_func ("/config/confdir-parse-error", test_config_confdir_parse_error); + g_test_add_func ("/config/set-values", test_config_set_values); + /* This one has to come last, because it leaves its values in * nm-config.c's global variables, and there's no way to reset * those to NULL. |