diff options
Diffstat (limited to 'src/nm-config.c')
| -rw-r--r-- | src/nm-config.c | 1081 |
1 files changed, 931 insertions, 150 deletions
diff --git a/src/nm-config.c b/src/nm-config.c index 561d80c1..15ef886b 100644 --- a/src/nm-config.c +++ b/src/nm-config.c @@ -24,30 +24,31 @@ #include <string.h> #include <stdio.h> +#include "nm-default.h" #include "nm-config.h" -#include "nm-logging.h" #include "nm-utils.h" -#include "nm-glib-compat.h" #include "nm-device.h" #include "NetworkManagerUtils.h" -#include "gsystem-local-alloc.h" #include "nm-enum-types.h" #include "nm-core-internal.h" - -#include <gio/gio.h> -#include <glib/gi18n.h> +#include "nm-keyfile-internal.h" #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; + gboolean is_debug; char *connectivity_uri; /* We store interval as signed internally to track whether it's @@ -64,7 +65,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 +80,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,36 +114,19 @@ 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) { - gsize len; - char *s = NULL; - - if (!str) - return default_value; - - while (str[0] && g_ascii_isspace (str[0])) - str++; - - if (!str[0]) - return default_value; - - len = strlen (str); - if (g_ascii_isspace (str[len - 1])) { - s = g_strdup (str); - g_strchomp (s); - str = s; - } - - if (!g_ascii_strcasecmp (str, "true") || !g_ascii_strcasecmp (str, "yes") || !g_ascii_strcasecmp (str, "on") || !g_ascii_strcasecmp (str, "1")) - default_value = TRUE; - else if (!g_ascii_strcasecmp (str, "false") || !g_ascii_strcasecmp (str, "no") || !g_ascii_strcasecmp (str, "off") || !g_ascii_strcasecmp (str, "0")) - default_value = FALSE; - if (s) - g_free (s); - return default_value; + return nm_utils_ascii_str_to_bool (str, default_value); } gint @@ -300,6 +289,12 @@ nm_config_get_configure_and_quit (NMConfig *config) return NM_CONFIG_GET_PRIVATE (config)->configure_and_quit; } +gboolean +nm_config_get_is_debug (NMConfig *config) +{ + return NM_CONFIG_GET_PRIVATE (config)->cli.is_debug; +} + /************************************************************************/ static char ** @@ -359,7 +354,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 +365,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,9 +404,12 @@ _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; + cli->is_debug = FALSE; g_clear_pointer (&cli->connectivity_uri, g_free); g_clear_pointer (&cli->connectivity_response, g_free); cli->connectivity_interval = -1; @@ -424,10 +424,13 @@ _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->is_debug = cli->is_debug; dst->connectivity_uri = g_strdup (cli->connectivity_uri); dst->connectivity_response = g_strdup (cli->connectivity_response); dst->connectivity_interval = cli->connectivity_interval; @@ -462,9 +465,12 @@ 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 }, + { "debug", 'd', 0, G_OPTION_ARG_NONE, &cli->is_debug, N_("Don't become a daemon, and log to stderr"), NULL }, /* These three are hidden for now, and should eventually just go away. */ { "connectivity-uri", 0, G_OPTION_FLAG_HIDDEN, G_OPTION_ARG_STRING, &cli->connectivity_uri, N_("An http(s) address for checking internet connectivity"), "http://example.com" }, @@ -489,6 +495,47 @@ nm_config_create_keyfile () return keyfile; } +/* this is an external variable, to make loading testable. Other then that, + * no code is supposed to change this. */ +guint _nm_config_match_nm_version = NM_VERSION_CUR_STABLE; +char *_nm_config_match_env = NULL; + +static gboolean +ignore_config_snippet (GKeyFile *keyfile, gboolean is_base_config) +{ + GSList *specs; + gboolean as_bool; + NMMatchSpecMatchType match_type; + + if (is_base_config) + return FALSE; + + if (!g_key_file_has_key (keyfile, NM_CONFIG_KEYFILE_GROUP_CONFIG, NM_CONFIG_KEYFILE_KEY_CONFIG_ENABLE, NULL)) + return FALSE; + + /* first, let's try to parse the value as plain boolean. If that is possible, we don't treat + * the value as match-spec. */ + as_bool = nm_config_keyfile_get_boolean (keyfile, NM_CONFIG_KEYFILE_GROUP_CONFIG, NM_CONFIG_KEYFILE_KEY_CONFIG_ENABLE, -1); + if (as_bool != -1) + return !as_bool; + + if (G_UNLIKELY (!_nm_config_match_env)) { + const char *e; + + e = g_getenv ("NM_CONFIG_ENABLE_TAG"); + _nm_config_match_env = g_strdup (e ? e : ""); + } + + /* second, interpret the value as match-spec. */ + specs = nm_config_get_match_spec (keyfile, NM_CONFIG_KEYFILE_GROUP_CONFIG, NM_CONFIG_KEYFILE_KEY_CONFIG_ENABLE, NULL); + match_type = nm_match_spec_match_config (specs, + _nm_config_match_nm_version, + _nm_config_match_env); + g_slist_free_full (specs, g_free); + + return match_type != NM_MATCH_SPEC_MATCH; +} + static int _sort_groups_cmp (const char **pa, const char **pb, gpointer dummy) { @@ -528,6 +575,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 +609,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, gboolean is_base_config, 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; @@ -574,6 +639,16 @@ read_config (GKeyFile *keyfile, const char *path, GError **error) return FALSE; } + if (ignore_config_snippet (kf, is_base_config)) { + g_key_file_free (kf); + return TRUE; + } + + /* the config-group is internal to every configuration snippets. It doesn't make sense + * to merge the into the global configuration, and it doesn't make sense to preserve the + * group beyond this point. */ + g_key_file_remove_group (keyfile, NM_CONFIG_KEYFILE_GROUP_CONFIG, NULL); + /* Override the current settings with the new ones */ groups = g_key_file_get_groups (kf, &ngroups); if (!groups) @@ -587,17 +662,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 && 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 +682,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 +795,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, TRUE, NULL, cli_config_main_file, error)) { *out_config_main_file = g_strdup (cli_config_main_file); return TRUE; } else @@ -725,7 +810,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, TRUE, NULL, DEFAULT_CONFIG_MAIN_FILE_OLD, &my_error)) { *out_config_main_file = g_strdup (DEFAULT_CONFIG_MAIN_FILE_OLD); return TRUE; } @@ -738,7 +823,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, TRUE, NULL, DEFAULT_CONFIG_MAIN_FILE, &my_error)) { *out_config_main_file = g_strdup (DEFAULT_CONFIG_MAIN_FILE); return TRUE; } @@ -771,24 +856,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 +883,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, FALSE, 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 +944,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, FALSE, 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,13 +967,513 @@ 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); +} + +gboolean +nm_config_keyfile_has_global_dns_config (GKeyFile *keyfile, gboolean internal) +{ + gs_strfreev char **groups = NULL; + guint g; + const char *prefix; + + if (!keyfile) + return FALSE; + if (g_key_file_has_group (keyfile, + internal + ? NM_CONFIG_KEYFILE_GROUP_GLOBAL_DNS + : NM_CONFIG_KEYFILE_GROUP_INTERN_GLOBAL_DNS)) + return TRUE; + + groups = g_key_file_get_groups (keyfile, NULL); + if (!groups) + return FALSE; + + prefix = internal ? NM_CONFIG_KEYFILE_GROUPPREFIX_INTERN_GLOBAL_DNS_DOMAIN : NM_CONFIG_KEYFILE_GROUPPREFIX_GLOBAL_DNS_DOMAIN; + + for (g = 0; groups[g]; g++) { + if (g_str_has_prefix (groups[g], prefix)) + return TRUE; + } + return 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; + + if (!strcmp (group, NM_CONFIG_KEYFILE_GROUP_CONFIG)) + continue; + + 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: + /* + * If user configuration specifies global DNS options, the DNS + * options in internal configuration must be deleted. Otherwise a + * deletion of options from user configuration may cause the + * internal options to appear again. + */ + if (nm_config_keyfile_has_global_dns_config (keyfile_conf, FALSE)) { + if (g_key_file_remove_group (keyfile_intern, NM_CONFIG_KEYFILE_GROUP_INTERN_GLOBAL_DNS, NULL)) + needs_rewrite = TRUE; + for (g = 0; groups && groups[g]; g++) { + if ( g_str_has_prefix (groups[g], NM_CONFIG_KEYFILE_GROUPPREFIX_INTERN_GLOBAL_DNS_DOMAIN) + && groups[g][STRLEN (NM_CONFIG_KEYFILE_GROUPPREFIX_INTERN_GLOBAL_DNS_DOMAIN)]) { + g_key_file_remove_group (keyfile_intern, groups[g], NULL); + needs_rewrite = TRUE; + } + } + } + + 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) +nm_config_get_match_spec (const GKeyFile *keyfile, const char *group, const char *key, gboolean *out_has_key) { gs_free char *value = NULL; @@ -902,16 +1488,177 @@ nm_config_get_device_match_spec (const GKeyFile *keyfile, const char *group, con /************************************************************************/ +gboolean +nm_config_set_global_dns (NMConfig *self, NMGlobalDnsConfig *global_dns, GError **error) +{ + NMConfigPrivate *priv; + GKeyFile *keyfile; + char **groups; + const NMGlobalDnsConfig *old_global_dns; + guint i; + + g_return_val_if_fail (NM_IS_CONFIG (self), FALSE); + + priv = NM_CONFIG_GET_PRIVATE (self); + g_return_val_if_fail (priv->config_data, FALSE); + + old_global_dns = nm_config_data_get_global_dns_config (priv->config_data); + if (old_global_dns && !nm_global_dns_config_is_internal (old_global_dns)) { + g_set_error_literal (error, 1, 0, + "Global DNS configuration already set via configuration file"); + return FALSE; + } + + keyfile = nm_config_data_clone_keyfile_intern (priv->config_data); + + /* Remove existing groups */ + g_key_file_remove_group (keyfile, NM_CONFIG_KEYFILE_GROUP_INTERN_GLOBAL_DNS, NULL); + groups = g_key_file_get_groups (keyfile, NULL); + for (i = 0; groups[i]; i++) { + if (g_str_has_prefix (groups[i], NM_CONFIG_KEYFILE_GROUPPREFIX_INTERN_GLOBAL_DNS_DOMAIN)) + g_key_file_remove_group (keyfile, groups[i], NULL); + } + g_strfreev (groups); + + /* An empty configuration removes everything from internal configuration file */ + if (nm_global_dns_config_is_empty (global_dns)) + goto done; + + /* Set new values */ + nm_config_keyfile_set_string_list (keyfile, NM_CONFIG_KEYFILE_GROUP_INTERN_GLOBAL_DNS, + "searches", nm_global_dns_config_get_searches (global_dns), + -1); + + nm_config_keyfile_set_string_list (keyfile, NM_CONFIG_KEYFILE_GROUP_INTERN_GLOBAL_DNS, + "options", nm_global_dns_config_get_options (global_dns), + -1); + + for (i = 0; i < nm_global_dns_config_get_num_domains (global_dns); i++) { + NMGlobalDnsDomain *domain = nm_global_dns_config_get_domain (global_dns, i); + gs_free char *group_name = NULL; + + group_name = g_strdup_printf (NM_CONFIG_KEYFILE_GROUPPREFIX_INTERN_GLOBAL_DNS_DOMAIN "%s", + nm_global_dns_domain_get_name (domain)); + + nm_config_keyfile_set_string_list (keyfile, group_name, "servers", + nm_global_dns_domain_get_servers (domain), -1); + nm_config_keyfile_set_string_list (keyfile, group_name, "options", + nm_global_dns_domain_get_options (domain), -1); + } + +done: + nm_config_set_values (self, keyfile, TRUE, FALSE); + g_key_file_unref (keyfile); + + return TRUE; +} + +/** + * 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 +1675,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,58 +1685,42 @@ 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); } -static const char * -_change_flags_one_to_string (NMConfigChangeFlags flag) -{ - switch (flag) { - case NM_CONFIG_CHANGE_SIGHUP: - return "SIGHUP"; - case NM_CONFIG_CHANGE_SIGUSR1: - return "SIGUSR1"; - case NM_CONFIG_CHANGE_SIGUSR2: - return "SIGUSR2"; - case NM_CONFIG_CHANGE_CONFIG_FILES: - return "config-files"; - case NM_CONFIG_CHANGE_VALUES: - return "values"; - case NM_CONFIG_CHANGE_CONNECTIVITY: - return "connectivity"; - case NM_CONFIG_CHANGE_NO_AUTO_DEFAULT: - return "no-auto-default"; - case NM_CONFIG_CHANGE_DNS_MODE: - return "dns-mode"; - default: - g_return_val_if_reached ("unknown"); - } -} - -char * -nm_config_change_flags_to_string (NMConfigChangeFlags flags) -{ - GString *str = g_string_new (""); - NMConfigChangeFlags s = 0x01; - - while (flags) { - if (NM_FLAGS_HAS (flags, s)) { - if (str->len) - g_string_append_c (str, ','); - g_string_append (str, _change_flags_one_to_string (s)); - } - flags = flags & ~s; - s <<= 1; - } - return g_string_free (str, FALSE); -} +NM_UTILS_FLAGS2STR_DEFINE (nm_config_change_flags_to_string, NMConfigChangeFlags, + NM_UTILS_FLAGS2STR (NM_CONFIG_CHANGE_SIGHUP, "SIGHUP"), + NM_UTILS_FLAGS2STR (NM_CONFIG_CHANGE_SIGUSR1, "SIGUSR1"), + NM_UTILS_FLAGS2STR (NM_CONFIG_CHANGE_SIGUSR2, "SIGUSR2"), + NM_UTILS_FLAGS2STR (NM_CONFIG_CHANGE_CONFIG_FILES, "config-files"), + NM_UTILS_FLAGS2STR (NM_CONFIG_CHANGE_VALUES, "values"), + NM_UTILS_FLAGS2STR (NM_CONFIG_CHANGE_VALUES_USER, "values-user"), + NM_UTILS_FLAGS2STR (NM_CONFIG_CHANGE_VALUES_INTERN, "values-intern"), + NM_UTILS_FLAGS2STR (NM_CONFIG_CHANGE_CONNECTIVITY, "connectivity"), + NM_UTILS_FLAGS2STR (NM_CONFIG_CHANGE_NO_AUTO_DEFAULT, "no-auto-default"), + NM_UTILS_FLAGS2STR (NM_CONFIG_CHANGE_DNS_MODE, "dns-mode"), + NM_UTILS_FLAGS2STR (NM_CONFIG_CHANGE_RC_MANAGER, "rc-manager"), + NM_UTILS_FLAGS2STR (NM_CONFIG_CHANGE_GLOBAL_DNS_CONFIG, "global-dns-config"), +); static void _set_config_data (NMConfig *self, NMConfigData *new_data, int signal) @@ -996,7 +1728,6 @@ _set_config_data (NMConfig *self, NMConfigData *new_data, int signal) NMConfigPrivate *priv = NM_CONFIG_GET_PRIVATE (self); NMConfigData *old_data = priv->config_data; NMConfigChangeFlags changes, changes_diff; - gs_free char *log_str = NULL; gboolean had_new_data = !!new_data; switch (signal) { @@ -1027,13 +1758,13 @@ _set_config_data (NMConfig *self, NMConfigData *new_data, int signal) if (new_data) { nm_log_info (LOGD_CORE, "config: update %s (%s)", nm_config_data_get_config_description (new_data), - (log_str = nm_config_change_flags_to_string (changes))); + nm_config_change_flags_to_string (changes, NULL, 0)); nm_config_data_log (new_data, "CONFIG: ", " ", NULL); priv->config_data = new_data; } else if (had_new_data) - nm_log_info (LOGD_CORE, "config: signal %s (no changes from disk)", (log_str = nm_config_change_flags_to_string (changes))); + nm_log_info (LOGD_CORE, "config: signal %s (no changes from disk)", nm_config_change_flags_to_string (changes, NULL, 0)); else - nm_log_info (LOGD_CORE, "config: signal %s", (log_str = nm_config_change_flags_to_string (changes))); + nm_log_info (LOGD_CORE, "config: signal %s", nm_config_change_flags_to_string (changes, NULL, 0)); g_signal_emit (self, signals[SIGNAL_CONFIG_CHANGED], 0, new_data ? new_data : old_data, changes, old_data); @@ -1041,8 +1772,7 @@ _set_config_data (NMConfig *self, NMConfigData *new_data, int signal) g_object_unref (old_data); } -NM_DEFINE_SINGLETON_DESTRUCTOR (NMConfig); -NM_DEFINE_SINGLETON_WEAK_REF (NMConfig); +NM_DEFINE_SINGLETON_REGISTER (NMConfig); NMConfig * nm_config_get (void) @@ -1052,13 +1782,18 @@ 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); - if (singleton_instance) - nm_singleton_instance_weak_ref_register (); + singleton_instance = nm_config_new (cli, atomic_section_prefixes, error); + if (singleton_instance) { + nm_singleton_instance_register (); + + /* usually, you would not see this logging line because when creating the + * NMConfig instance, the logging is not yet set up to print debug message. */ + nm_log_dbg (LOGD_CORE, "setup %s singleton (%p)", "NMConfig", singleton_instance); + } return singleton_instance; } @@ -1067,10 +1802,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. */ @@ -1085,8 +1821,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); @@ -1120,23 +1874,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)); } @@ -1154,12 +1920,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); @@ -1186,6 +1955,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; @@ -1208,6 +1981,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), |