mirror of
https://gitlab.freedesktop.org/NetworkManager/NetworkManager.git
synced 2025-12-24 19:30:07 +01:00
For manged=unknown, we don't write the value to the device state keyfile. The results in an empty file, or at least, a keyfile that doesn't have device.managed set. On read, we must treat a missing device.managed flag as unknown, and not as unmanaged. Otherwise, on restart a device becomes marked as explicitly unmanaged. This was broken by commit142ebb1"core: only persist explicit managed state in device's state file", where we started conditionally to no longer write the managed state. Reported-by: Michael Biebl <mbiebl@debian.org> Fixes:142ebb1037
2450 lines
82 KiB
C
2450 lines
82 KiB
C
/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
|
|
/* NetworkManager -- Network link manager
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation; either version 2 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License along
|
|
* with this program; if not, write to the Free Software Foundation, Inc.,
|
|
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
|
*
|
|
* Copyright (C) 2011 Red Hat, Inc.
|
|
* Copyright (C) 2013 Thomas Bechtold <thomasbechtold@jpberlin.de>
|
|
*/
|
|
|
|
#include "nm-default.h"
|
|
|
|
#include "nm-config.h"
|
|
|
|
#include <string.h>
|
|
#include <stdio.h>
|
|
|
|
#include "nm-utils.h"
|
|
#include "devices/nm-device.h"
|
|
#include "NetworkManagerUtils.h"
|
|
#include "nm-core-internal.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 RUN_CONFIG_DIR NMRUNDIR "/conf.d"
|
|
#define DEFAULT_NO_AUTO_DEFAULT_FILE NMSTATEDIR "/no-auto-default.state"
|
|
#define DEFAULT_INTERN_CONFIG_FILE NMSTATEDIR "/NetworkManager-intern.conf"
|
|
#define DEFAULT_STATE_FILE NMSTATEDIR "/NetworkManager.state"
|
|
|
|
/*****************************************************************************/
|
|
|
|
struct NMConfigCmdLineOptions {
|
|
char *config_main_file;
|
|
char *intern_config_file;
|
|
char *config_dir;
|
|
char *system_config_dir;
|
|
char *state_file;
|
|
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
|
|
* set or not via GOptionEntry
|
|
*/
|
|
int connectivity_interval;
|
|
char *connectivity_response;
|
|
|
|
/* @first_start is not provided by command line. It is a convenient hack
|
|
* to pass in an argument to NMConfig. This makes NMConfigCmdLineOptions a
|
|
* misnomer.
|
|
*
|
|
* It is true, if NM is started the first time -- contrary to a restart
|
|
* during the same boot up. That is determined by the content of the
|
|
* /var/run/NetworManager state directory. */
|
|
bool first_start;
|
|
};
|
|
|
|
typedef struct {
|
|
NMConfigState p;
|
|
} State;
|
|
|
|
/*****************************************************************************/
|
|
|
|
NM_GOBJECT_PROPERTIES_DEFINE_BASE (
|
|
PROP_CMD_LINE_OPTIONS,
|
|
PROP_ATOMIC_SECTION_PREFIXES,
|
|
);
|
|
|
|
enum {
|
|
SIGNAL_CONFIG_CHANGED,
|
|
LAST_SIGNAL,
|
|
};
|
|
|
|
static guint signals[LAST_SIGNAL] = { 0 };
|
|
|
|
typedef struct {
|
|
NMConfigCmdLineOptions cli;
|
|
|
|
NMConfigData *config_data;
|
|
NMConfigData *config_data_orig;
|
|
|
|
char *config_dir;
|
|
char *system_config_dir;
|
|
char *no_auto_default_file;
|
|
char *intern_config_file;
|
|
|
|
gboolean monitor_connection_files;
|
|
|
|
char *log_level;
|
|
char *log_domains;
|
|
|
|
gboolean configure_and_quit;
|
|
|
|
char **atomic_section_prefixes;
|
|
|
|
/* The state. This is actually a mutable data member and it makes sense:
|
|
* The regular config is immutable (NMConfigData) and can old be swapped
|
|
* as a whole (via nm_config_set_values() or during reload). Thus, it can
|
|
* be changed, but it is still immutable and is swapped atomically as a
|
|
* whole. Also, we emit a config-changed signal on that occasion.
|
|
*
|
|
* For state, there are no events. You can query it and set it.
|
|
* It only gets read *once* at startup, and later is cached and only
|
|
* written out to disk. Hence, no need for the immutable dance here
|
|
* because the state changes only on explicit actions from the daemon
|
|
* itself. */
|
|
State *state;
|
|
} NMConfigPrivate;
|
|
|
|
struct _NMConfig {
|
|
GObject parent;
|
|
NMConfigPrivate _priv;
|
|
};
|
|
|
|
struct _NMConfigClass {
|
|
GObjectClass parent;
|
|
};
|
|
|
|
static void nm_config_initable_iface_init (GInitableIface *iface);
|
|
|
|
G_DEFINE_TYPE_WITH_CODE (NMConfig, nm_config, G_TYPE_OBJECT,
|
|
G_IMPLEMENT_INTERFACE (G_TYPE_INITABLE, nm_config_initable_iface_init);
|
|
)
|
|
|
|
#define NM_CONFIG_GET_PRIVATE(self) _NM_GET_PRIVATE (self, NMConfig, NM_IS_CONFIG)
|
|
|
|
/*****************************************************************************/
|
|
|
|
#define _NMLOG_DOMAIN LOGD_CORE
|
|
#define _NMLOG(level, ...) __NMLOG_DEFAULT (level, _NMLOG_DOMAIN, "config", __VA_ARGS__)
|
|
|
|
/*****************************************************************************/
|
|
|
|
static void _set_config_data (NMConfig *self, NMConfigData *new_data, NMConfigChangeFlags reload_flags);
|
|
|
|
/*****************************************************************************/
|
|
|
|
#define _HAS_PREFIX(str, prefix) \
|
|
({ \
|
|
const char *_str = (str); \
|
|
g_str_has_prefix ( _str, ""prefix"") && _str[NM_STRLEN(prefix)] != '\0'; \
|
|
})
|
|
|
|
/*****************************************************************************/
|
|
|
|
gint
|
|
nm_config_parse_boolean (const char *str,
|
|
gint default_value)
|
|
{
|
|
return _nm_utils_ascii_str_to_bool (str, default_value);
|
|
}
|
|
|
|
gint
|
|
nm_config_keyfile_get_boolean (const GKeyFile *keyfile,
|
|
const char *section,
|
|
const char *key,
|
|
gint default_value)
|
|
{
|
|
gs_free char *str = NULL;
|
|
|
|
g_return_val_if_fail (keyfile != NULL, default_value);
|
|
g_return_val_if_fail (section != NULL, default_value);
|
|
g_return_val_if_fail (key != NULL, default_value);
|
|
|
|
str = g_key_file_get_value ((GKeyFile *) keyfile, section, key, NULL);
|
|
return nm_config_parse_boolean (str, default_value);
|
|
}
|
|
|
|
char *
|
|
nm_config_keyfile_get_value (const GKeyFile *keyfile,
|
|
const char *section,
|
|
const char *key,
|
|
NMConfigGetValueFlags flags)
|
|
{
|
|
char *value;
|
|
|
|
if (NM_FLAGS_HAS (flags, NM_CONFIG_GET_VALUE_RAW))
|
|
value = g_key_file_get_value ((GKeyFile *) keyfile, section, key, NULL);
|
|
else
|
|
value = g_key_file_get_string ((GKeyFile *) keyfile, section, key, NULL);
|
|
|
|
if (!value)
|
|
return NULL;
|
|
|
|
if (NM_FLAGS_HAS (flags, NM_CONFIG_GET_VALUE_STRIP))
|
|
g_strstrip (value);
|
|
|
|
if ( NM_FLAGS_HAS (flags, NM_CONFIG_GET_VALUE_NO_EMPTY)
|
|
&& !*value) {
|
|
g_free (value);
|
|
return NULL;
|
|
}
|
|
|
|
return value;
|
|
}
|
|
|
|
void
|
|
nm_config_keyfile_set_string_list (GKeyFile *keyfile,
|
|
const char *group,
|
|
const char *key,
|
|
const char *const* strv,
|
|
gssize len)
|
|
{
|
|
gsize l;
|
|
char *new_value;
|
|
|
|
if (len < 0)
|
|
len = strv ? g_strv_length ((char **) strv) : 0;
|
|
|
|
g_key_file_set_string_list (keyfile, group, key, strv, len);
|
|
|
|
/* g_key_file_set_string_list() appends a trailing separator to the value.
|
|
* We don't like that, get rid of it. */
|
|
|
|
new_value = g_key_file_get_value (keyfile, group, key, NULL);
|
|
if (!new_value)
|
|
return;
|
|
|
|
l = strlen (new_value);
|
|
if (l > 0 && new_value[l - 1] == NM_CONFIG_KEYFILE_LIST_SEPARATOR) {
|
|
/* Maybe we should check that value doesn't end with "\\,", i.e.
|
|
* with an escaped separator. But the way g_key_file_set_string_list()
|
|
* is implemented (currently), it always adds a trailing separator. */
|
|
new_value[l - 1] = '\0';
|
|
g_key_file_set_value (keyfile, group, key, new_value);
|
|
}
|
|
g_free (new_value);
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
|
|
NMConfigData *
|
|
nm_config_get_data (NMConfig *config)
|
|
{
|
|
g_return_val_if_fail (config != NULL, NULL);
|
|
|
|
return NM_CONFIG_GET_PRIVATE (config)->config_data;
|
|
}
|
|
|
|
/* The NMConfigData instance is reloadable and will be swapped on reload.
|
|
* nm_config_get_data_orig() returns the original configuration, when the NMConfig
|
|
* instance was created. */
|
|
NMConfigData *
|
|
nm_config_get_data_orig (NMConfig *config)
|
|
{
|
|
g_return_val_if_fail (config != NULL, NULL);
|
|
|
|
return NM_CONFIG_GET_PRIVATE (config)->config_data_orig;
|
|
}
|
|
|
|
gboolean
|
|
nm_config_get_monitor_connection_files (NMConfig *config)
|
|
{
|
|
g_return_val_if_fail (config != NULL, FALSE);
|
|
|
|
return NM_CONFIG_GET_PRIVATE (config)->monitor_connection_files;
|
|
}
|
|
|
|
const char *
|
|
nm_config_get_log_level (NMConfig *config)
|
|
{
|
|
g_return_val_if_fail (config != NULL, NULL);
|
|
|
|
return NM_CONFIG_GET_PRIVATE (config)->log_level;
|
|
}
|
|
|
|
const char *
|
|
nm_config_get_log_domains (NMConfig *config)
|
|
{
|
|
g_return_val_if_fail (config != NULL, NULL);
|
|
|
|
return NM_CONFIG_GET_PRIVATE (config)->log_domains;
|
|
}
|
|
|
|
gboolean
|
|
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;
|
|
}
|
|
|
|
gboolean
|
|
nm_config_get_first_start (NMConfig *config)
|
|
{
|
|
return NM_CONFIG_GET_PRIVATE (config)->cli.first_start;
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
|
|
static char **
|
|
no_auto_default_from_file (const char *no_auto_default_file)
|
|
{
|
|
GPtrArray *no_auto_default_new;
|
|
char **list;
|
|
guint i;
|
|
char *data;
|
|
|
|
no_auto_default_new = g_ptr_array_new ();
|
|
|
|
if ( no_auto_default_file
|
|
&& g_file_get_contents (no_auto_default_file, &data, NULL, NULL)) {
|
|
list = g_strsplit (data, "\n", -1);
|
|
for (i = 0; list[i]; i++) {
|
|
if ( *list[i]
|
|
&& nm_utils_hwaddr_valid (list[i], -1)
|
|
&& nm_utils_strv_find_first (list, i, list[i]) < 0)
|
|
g_ptr_array_add (no_auto_default_new, list[i]);
|
|
else
|
|
g_free (list[i]);
|
|
}
|
|
g_free (list);
|
|
g_free (data);
|
|
}
|
|
|
|
g_ptr_array_add (no_auto_default_new, NULL);
|
|
return (char **) g_ptr_array_free (no_auto_default_new, FALSE);
|
|
}
|
|
|
|
static gboolean
|
|
no_auto_default_to_file (const char *no_auto_default_file, const char *const*no_auto_default, GError **error)
|
|
{
|
|
GString *data;
|
|
gboolean success;
|
|
guint i;
|
|
|
|
data = g_string_new ("");
|
|
for (i = 0; no_auto_default && no_auto_default[i]; i++) {
|
|
g_string_append (data, no_auto_default[i]);
|
|
g_string_append_c (data, '\n');
|
|
}
|
|
success = g_file_set_contents (no_auto_default_file, data->str, data->len, error);
|
|
g_string_free (data, TRUE);
|
|
return success;
|
|
}
|
|
|
|
gboolean
|
|
nm_config_get_no_auto_default_for_device (NMConfig *self, NMDevice *device)
|
|
{
|
|
g_return_val_if_fail (NM_IS_CONFIG (self), FALSE);
|
|
|
|
return nm_config_data_get_no_auto_default_for_device (NM_CONFIG_GET_PRIVATE (self)->config_data, device);
|
|
}
|
|
|
|
void
|
|
nm_config_set_no_auto_default_for_device (NMConfig *self, NMDevice *device)
|
|
{
|
|
NMConfigPrivate *priv;
|
|
GError *error = NULL;
|
|
NMConfigData *new_data = NULL;
|
|
const char *hw_address;
|
|
const char *const*no_auto_default_current;
|
|
GPtrArray *no_auto_default_new = NULL;
|
|
guint i;
|
|
|
|
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_permanent_hw_address (device);
|
|
if (!hw_address)
|
|
return;
|
|
|
|
no_auto_default_current = nm_config_data_get_no_auto_default (priv->config_data);
|
|
|
|
if (nm_utils_strv_find_first ((char **) no_auto_default_current, -1, hw_address) >= 0) {
|
|
/* @hw_address is already blocked. We don't have to update our in-memory representation.
|
|
* Maybe we should write to no_auto_default_file anew, but let's save that too. */
|
|
return;
|
|
}
|
|
|
|
no_auto_default_new = g_ptr_array_new ();
|
|
for (i = 0; no_auto_default_current && no_auto_default_current[i]; i++)
|
|
g_ptr_array_add (no_auto_default_new, (char *) no_auto_default_current[i]);
|
|
g_ptr_array_add (no_auto_default_new, (char *) hw_address);
|
|
g_ptr_array_add (no_auto_default_new, NULL);
|
|
|
|
if (!no_auto_default_to_file (priv->no_auto_default_file, (const char *const*) no_auto_default_new->pdata, &error)) {
|
|
_LOGW ("Could not update no-auto-default.state file: %s",
|
|
error->message);
|
|
g_error_free (error);
|
|
}
|
|
|
|
new_data = nm_config_data_new_update_no_auto_default (priv->config_data, (const char *const*) no_auto_default_new->pdata);
|
|
|
|
/* unref no_auto_default_set here. Note that _set_config_data() probably invalidates the content of the array. */
|
|
g_ptr_array_unref (no_auto_default_new);
|
|
|
|
_set_config_data (self, new_data, NM_CONFIG_CHANGE_CAUSE_NO_AUTO_DEFAULT);
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
|
|
static void
|
|
_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->state_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;
|
|
cli->first_start = FALSE;
|
|
}
|
|
|
|
static void
|
|
_nm_config_cmd_line_options_copy (const NMConfigCmdLineOptions *cli, NMConfigCmdLineOptions *dst)
|
|
{
|
|
g_return_if_fail (cli);
|
|
g_return_if_fail (dst);
|
|
g_return_if_fail (cli != dst);
|
|
|
|
_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->state_file = g_strdup (cli->state_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;
|
|
dst->first_start = cli->first_start;
|
|
}
|
|
|
|
NMConfigCmdLineOptions *
|
|
nm_config_cmd_line_options_new (gboolean first_start)
|
|
{
|
|
NMConfigCmdLineOptions *cli = g_new0 (NMConfigCmdLineOptions, 1);
|
|
|
|
_nm_config_cmd_line_options_clear (cli);
|
|
|
|
cli->first_start = first_start;
|
|
|
|
return cli;
|
|
}
|
|
|
|
void
|
|
nm_config_cmd_line_options_free (NMConfigCmdLineOptions *cli)
|
|
{
|
|
g_return_if_fail (cli);
|
|
|
|
_nm_config_cmd_line_options_clear (cli);
|
|
g_free (cli);
|
|
}
|
|
|
|
void
|
|
nm_config_cmd_line_options_add_to_entries (NMConfigCmdLineOptions *cli,
|
|
GOptionContext *opt_ctx)
|
|
{
|
|
g_return_if_fail (opt_ctx);
|
|
g_return_if_fail (cli);
|
|
|
|
{
|
|
GOptionEntry config_options[] = {
|
|
{ "config", 0, 0, G_OPTION_ARG_FILENAME, &cli->config_main_file, N_("Config file location"), DEFAULT_CONFIG_MAIN_FILE },
|
|
{ "config-dir", 0, 0, G_OPTION_ARG_FILENAME, &cli->config_dir, N_("Config directory location"), DEFAULT_CONFIG_DIR },
|
|
{ "system-config-dir", 0, 0, G_OPTION_ARG_FILENAME, &cli->system_config_dir, N_("System config directory location"), DEFAULT_SYSTEM_CONFIG_DIR },
|
|
{ "intern-config", 0, 0, G_OPTION_ARG_FILENAME, &cli->intern_config_file, N_("Internal config file location"), DEFAULT_INTERN_CONFIG_FILE },
|
|
{ "state-file", 0, 0, G_OPTION_ARG_FILENAME, &cli->state_file, N_("State file location"), DEFAULT_STATE_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"), DEFAULT_NO_AUTO_DEFAULT_FILE },
|
|
{ "plugins", 0, 0, G_OPTION_ARG_STRING, &cli->plugins, N_("List of plugins separated by ','"), NM_CONFIG_DEFAULT_MAIN_PLUGINS },
|
|
{ "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" },
|
|
{ "connectivity-interval", 0, G_OPTION_FLAG_HIDDEN, G_OPTION_ARG_INT, &cli->connectivity_interval, N_("The interval between connectivity checks (in seconds)"), G_STRINGIFY (NM_CONFIG_DEFAULT_CONNECTIVITY_INTERVAL) },
|
|
{ "connectivity-response", 0, G_OPTION_FLAG_HIDDEN, G_OPTION_ARG_STRING, &cli->connectivity_response, N_("The expected start of the response"), NM_CONFIG_DEFAULT_CONNECTIVITY_RESPONSE },
|
|
{ 0 },
|
|
};
|
|
|
|
g_option_context_add_main_entries (opt_ctx, config_options, NULL);
|
|
}
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
|
|
GKeyFile *
|
|
nm_config_create_keyfile ()
|
|
{
|
|
GKeyFile *keyfile;
|
|
|
|
keyfile = g_key_file_new ();
|
|
g_key_file_set_list_separator (keyfile, NM_CONFIG_KEYFILE_LIST_SEPARATOR);
|
|
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_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)
|
|
{
|
|
const char *a, *b;
|
|
gboolean a_is_connection, b_is_connection;
|
|
gboolean a_is_device, b_is_device;
|
|
|
|
a = *pa;
|
|
b = *pb;
|
|
|
|
a_is_connection = g_str_has_prefix (a, NM_CONFIG_KEYFILE_GROUPPREFIX_CONNECTION);
|
|
b_is_connection = g_str_has_prefix (b, NM_CONFIG_KEYFILE_GROUPPREFIX_CONNECTION);
|
|
|
|
if (a_is_connection != b_is_connection) {
|
|
/* one is a [connection*] entry, the other not. We sort [connection*] entries
|
|
* after. */
|
|
if (a_is_connection)
|
|
return 1;
|
|
return -1;
|
|
}
|
|
if (a_is_connection) {
|
|
/* both are [connection.\+] entries. Reverse their order.
|
|
* One of the sections might be literally [connection]. That section
|
|
* is special and it's order will be fixed later. It doesn't actually
|
|
* matter here how it compares with [connection.\+] sections. */
|
|
return pa > pb ? -1 : 1;
|
|
}
|
|
|
|
a_is_device = g_str_has_prefix (a, NM_CONFIG_KEYFILE_GROUPPREFIX_DEVICE);
|
|
b_is_device = g_str_has_prefix (b, NM_CONFIG_KEYFILE_GROUPPREFIX_DEVICE);
|
|
|
|
if (a_is_device != b_is_device) {
|
|
/* one is a [device*] entry, the other not. We sort [device*] entries
|
|
* after. */
|
|
if (a_is_device)
|
|
return 1;
|
|
return -1;
|
|
}
|
|
if (a_is_device) {
|
|
/* both are [device.\+] entries. Reverse their order.
|
|
* One of the sections might be literally [device]. That section
|
|
* is special and it's order will be fixed later. It doesn't actually
|
|
* matter here how it compares with [device.\+] sections. */
|
|
return pa > pb ? -1 : 1;
|
|
}
|
|
|
|
/* don't reorder the rest. */
|
|
return 0;
|
|
}
|
|
|
|
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)
|
|
{
|
|
#define _IS(group_v, key_v) (strcmp (group, (""group_v)) == 0 && strcmp (key, (""key_v)) == 0)
|
|
return _IS (NM_CONFIG_KEYFILE_GROUP_MAIN, "no-auto-default")
|
|
|| _IS (NM_CONFIG_KEYFILE_GROUP_MAIN, "ignore-carrier")
|
|
|| _IS (NM_CONFIG_KEYFILE_GROUP_MAIN, "assume-ipv6ll-only")
|
|
|| _IS (NM_CONFIG_KEYFILE_GROUP_KEYFILE, "unmanaged-devices")
|
|
|| (g_str_has_prefix (group, NM_CONFIG_KEYFILE_GROUPPREFIX_CONNECTION) && !strcmp (key, "match-device"))
|
|
|| (g_str_has_prefix (group, NM_CONFIG_KEYFILE_GROUPPREFIX_DEVICE ) && !strcmp (key, "match-device"));
|
|
}
|
|
|
|
static gboolean
|
|
_setting_is_string_list (const char *group, const char *key)
|
|
{
|
|
return _IS (NM_CONFIG_KEYFILE_GROUP_MAIN, "plugins")
|
|
|| _IS (NM_CONFIG_KEYFILE_GROUP_MAIN, NM_CONFIG_KEYFILE_KEY_MAIN_DEBUG)
|
|
|| _IS (NM_CONFIG_KEYFILE_GROUP_LOGGING, "domains")
|
|
|| g_str_has_prefix (group, NM_CONFIG_KEYFILE_GROUPPREFIX_TEST_APPEND_STRINGLIST);
|
|
#undef _IS
|
|
}
|
|
|
|
static gboolean
|
|
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;
|
|
}
|
|
|
|
_LOGD ("Reading config file '%s'", path);
|
|
|
|
kf = nm_config_create_keyfile ();
|
|
if (!g_key_file_load_from_file (kf, path, G_KEY_FILE_NONE, error)) {
|
|
g_prefix_error (error, "%s: ", path);
|
|
g_key_file_free (kf);
|
|
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 it into the global configuration, and it doesn't make sense to preserve the
|
|
* group beyond this point. */
|
|
g_key_file_remove_group (kf, NM_CONFIG_KEYFILE_GROUP_CONFIG, NULL);
|
|
|
|
/* Override the current settings with the new ones */
|
|
groups = g_key_file_get_groups (kf, &ngroups);
|
|
if (!groups)
|
|
ngroups = 0;
|
|
|
|
/* Within one file we reverse the order of the '[connection.\+] sections.
|
|
* Here we merge the current file (@kf) into @keyfile. As we merge multiple
|
|
* files, earlier sections (with lower priority) will be added first.
|
|
* But within one file, we want a top-to-bottom order. This means we
|
|
* must reverse the order within each file.
|
|
* 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(). */
|
|
_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;
|
|
for (k = 0; keys[k]; k++) {
|
|
const char *key;
|
|
char *new_value;
|
|
char last_char;
|
|
gsize key_len;
|
|
|
|
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
|
|
&& (last_char == '+' || last_char == '-')) {
|
|
gs_free char *base_key = g_strndup (key, key_len - 1);
|
|
gboolean is_string_list;
|
|
|
|
is_string_list = _setting_is_string_list (group, base_key);
|
|
|
|
if ( is_string_list
|
|
|| _setting_is_device_spec (group, base_key)) {
|
|
gs_unref_ptrarray GPtrArray *new = g_ptr_array_new_with_free_func (g_free);
|
|
char **iter_val;
|
|
gs_strfreev char **old_val = NULL;
|
|
gs_free char **new_val = NULL;
|
|
|
|
if (is_string_list) {
|
|
old_val = g_key_file_get_string_list (keyfile, group, base_key, NULL, NULL);
|
|
new_val = g_key_file_get_string_list (kf, group, key, NULL, NULL);
|
|
if (!old_val && !g_key_file_has_key (keyfile, group, base_key, NULL)) {
|
|
/* we must fill the unspecified value with the compile-time default. */
|
|
if (nm_streq (group, NM_CONFIG_KEYFILE_GROUP_MAIN) && nm_streq (base_key, "plugins")) {
|
|
g_key_file_set_value (keyfile, group, base_key, NM_CONFIG_DEFAULT_MAIN_PLUGINS);
|
|
old_val = g_key_file_get_string_list (keyfile, group, base_key, NULL, NULL);
|
|
}
|
|
}
|
|
} else {
|
|
gs_free char *old_sval = nm_config_keyfile_get_value (keyfile, group, base_key, NM_CONFIG_GET_VALUE_TYPE_SPEC);
|
|
gs_free char *new_sval = nm_config_keyfile_get_value (kf, group, key, NM_CONFIG_GET_VALUE_TYPE_SPEC);
|
|
gs_free_slist GSList *old_specs = nm_match_spec_split (old_sval);
|
|
gs_free_slist GSList *new_specs = nm_match_spec_split (new_sval);
|
|
|
|
/* the key is a device spec. This is a special kind of string-list, that
|
|
* we must split differently. */
|
|
old_val = _nm_utils_slist_to_strv (old_specs, FALSE);
|
|
new_val = _nm_utils_slist_to_strv (new_specs, FALSE);
|
|
}
|
|
|
|
/* merge the string lists, by omiting duplicates. */
|
|
|
|
for (iter_val = old_val; iter_val && *iter_val; iter_val++) {
|
|
if ( last_char != '-'
|
|
|| nm_utils_strv_find_first (new_val, -1, *iter_val) < 0)
|
|
g_ptr_array_add (new, g_strdup (*iter_val));
|
|
}
|
|
for (iter_val = new_val; iter_val && *iter_val; iter_val++) {
|
|
/* don't add duplicates. That means an "option=a,b"; "option+=a,c" results in "option=a,b,c" */
|
|
if ( last_char == '+'
|
|
&& nm_utils_strv_find_first (old_val, -1, *iter_val) < 0)
|
|
g_ptr_array_add (new, *iter_val);
|
|
else
|
|
g_free (*iter_val);
|
|
}
|
|
|
|
if (new->len > 0) {
|
|
if (is_string_list)
|
|
nm_config_keyfile_set_string_list (keyfile, group, base_key, (const char *const*) new->pdata, new->len);
|
|
else {
|
|
gs_free_slist GSList *specs = NULL;
|
|
gs_free char *specs_joined = NULL;
|
|
|
|
g_ptr_array_add (new, NULL);
|
|
specs = _nm_utils_strv_to_slist ((char **) new->pdata, FALSE);
|
|
|
|
specs_joined = nm_match_spec_join (specs);
|
|
|
|
g_key_file_set_value (keyfile, group, base_key, specs_joined);
|
|
}
|
|
} else {
|
|
if (is_string_list)
|
|
g_key_file_remove_key (keyfile, group, base_key, NULL);
|
|
else
|
|
g_key_file_set_value (keyfile, group, base_key, "");
|
|
}
|
|
} else {
|
|
/* For any other settings we don't support extending the option with +/-.
|
|
* Just drop the key. */
|
|
}
|
|
continue;
|
|
}
|
|
|
|
new_value = g_key_file_get_value (kf, group, key, NULL);
|
|
g_key_file_set_value (keyfile, group, key, new_value);
|
|
g_free (new_value);
|
|
}
|
|
g_strfreev (keys);
|
|
}
|
|
g_strfreev (groups);
|
|
g_key_file_free (kf);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static gboolean
|
|
read_base_config (GKeyFile *keyfile,
|
|
const char *cli_config_main_file,
|
|
char **out_config_main_file,
|
|
GError **error)
|
|
{
|
|
GError *my_error = NULL;
|
|
|
|
g_return_val_if_fail (keyfile, FALSE);
|
|
g_return_val_if_fail (out_config_main_file && !*out_config_main_file, FALSE);
|
|
g_return_val_if_fail (!error || !*error, FALSE);
|
|
|
|
/* 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, TRUE, NULL, cli_config_main_file, error)) {
|
|
*out_config_main_file = g_strdup (cli_config_main_file);
|
|
return TRUE;
|
|
} else
|
|
return FALSE;
|
|
}
|
|
|
|
/* Even though we prefer NetworkManager.conf, we need to check the
|
|
* old nm-system-settings.conf first to preserve compat with older
|
|
* setups. In package managed systems dropping a NetworkManager.conf
|
|
* onto the system would make NM use it instead of nm-system-settings.conf,
|
|
* changing behavior during an upgrade. We don't want that.
|
|
*/
|
|
|
|
/* Try deprecated nm-system-settings.conf first */
|
|
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;
|
|
}
|
|
|
|
if (!g_error_matches (my_error, G_KEY_FILE_ERROR, G_KEY_FILE_ERROR_NOT_FOUND)) {
|
|
_LOGW ("Old default config file invalid: %s\n",
|
|
my_error->message);
|
|
}
|
|
g_clear_error (&my_error);
|
|
|
|
/* Try the standard config file location next */
|
|
if (read_config (keyfile, TRUE, NULL, DEFAULT_CONFIG_MAIN_FILE, &my_error)) {
|
|
*out_config_main_file = g_strdup (DEFAULT_CONFIG_MAIN_FILE);
|
|
return TRUE;
|
|
}
|
|
|
|
if (!g_error_matches (my_error, G_KEY_FILE_ERROR, G_KEY_FILE_ERROR_NOT_FOUND)) {
|
|
_LOGW ("Default config file invalid: %s\n",
|
|
my_error->message);
|
|
g_propagate_error (error, my_error);
|
|
return FALSE;
|
|
}
|
|
g_clear_error (&my_error);
|
|
|
|
/* If for some reason no config file exists, use the default
|
|
* config file path.
|
|
*/
|
|
*out_config_main_file = g_strdup (DEFAULT_CONFIG_MAIN_FILE);
|
|
_LOGI ("No config file found or given; using %s\n",
|
|
DEFAULT_CONFIG_MAIN_FILE);
|
|
return TRUE;
|
|
}
|
|
|
|
static GPtrArray *
|
|
_get_config_dir_files (const char *config_dir)
|
|
{
|
|
GFile *dir;
|
|
GFileEnumerator *direnum;
|
|
GFileInfo *info;
|
|
GPtrArray *confs;
|
|
const char *name;
|
|
|
|
g_return_val_if_fail (config_dir, NULL);
|
|
|
|
confs = g_ptr_array_new_with_free_func (g_free);
|
|
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) {
|
|
while ((info = g_file_enumerator_next_file (direnum, NULL, NULL))) {
|
|
name = g_file_info_get_name (info);
|
|
if (g_str_has_suffix (name, ".conf"))
|
|
g_ptr_array_add (confs, g_strdup (name));
|
|
g_object_unref (info);
|
|
}
|
|
g_object_unref (direnum);
|
|
}
|
|
g_object_unref (dir);
|
|
|
|
g_ptr_array_sort (confs, nm_strcmp_p);
|
|
return confs;
|
|
}
|
|
|
|
static void
|
|
_confs_to_description (GString *str, const GPtrArray *confs, const char *name)
|
|
{
|
|
guint i;
|
|
|
|
if (!confs->len)
|
|
return;
|
|
|
|
for (i = 0; i < confs->len; i++) {
|
|
if (i == 0)
|
|
g_string_append_printf (str, " (%s: ", name);
|
|
else
|
|
g_string_append (str, ", ");
|
|
g_string_append (str, confs->pdata[i]);
|
|
}
|
|
g_string_append (str, ")");
|
|
}
|
|
|
|
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)
|
|
{
|
|
gs_unref_keyfile GKeyFile *keyfile = NULL;
|
|
gs_unref_ptrarray GPtrArray *system_confs = NULL;
|
|
gs_unref_ptrarray GPtrArray *confs = NULL;
|
|
gs_unref_ptrarray GPtrArray *run_confs = NULL;
|
|
guint i;
|
|
gs_free char *o_config_main_file = NULL;
|
|
const char *run_config_dir = "";
|
|
|
|
g_return_val_if_fail (config_dir, 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);
|
|
|
|
if ( (""RUN_CONFIG_DIR)[0] == '/'
|
|
&& !nm_streq (RUN_CONFIG_DIR, system_config_dir)
|
|
&& !nm_streq (RUN_CONFIG_DIR, config_dir))
|
|
run_config_dir = RUN_CONFIG_DIR;
|
|
|
|
/* create a default configuration file. */
|
|
keyfile = nm_config_create_keyfile ();
|
|
|
|
system_confs = _get_config_dir_files (system_config_dir);
|
|
confs = _get_config_dir_files (config_dir);
|
|
run_confs = _get_config_dir_files (run_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 or run_config_dir, skip it. */
|
|
if (nm_utils_strv_find_first ((char **) confs->pdata, confs->len, filename) >= 0 ||
|
|
nm_utils_strv_find_first ((char **) run_confs->pdata, run_confs->len, filename) >= 0) {
|
|
g_ptr_array_remove_index (system_confs, i);
|
|
continue;
|
|
}
|
|
|
|
if (!read_config (keyfile, FALSE, system_config_dir, filename, error))
|
|
return NULL;
|
|
i++;
|
|
}
|
|
|
|
for (i = 0; i < run_confs->len; ) {
|
|
const char *filename = run_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 (run_confs, i);
|
|
continue;
|
|
}
|
|
|
|
if (!read_config (keyfile, FALSE, run_config_dir, filename, error))
|
|
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))
|
|
return NULL;
|
|
|
|
g_assert (o_config_main_file);
|
|
|
|
for (i = 0; i < confs->len; i++) {
|
|
if (!read_config (keyfile, FALSE, config_dir, confs->pdata[i], error))
|
|
return NULL;
|
|
}
|
|
|
|
/* Merge settings from command line. They overwrite everything read from
|
|
* config files. */
|
|
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);
|
|
}
|
|
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)
|
|
g_key_file_set_integer (keyfile, NM_CONFIG_KEYFILE_GROUP_CONNECTIVITY, "interval", cli->connectivity_interval);
|
|
if (cli && cli->connectivity_response && cli->connectivity_response[0])
|
|
g_key_file_set_string (keyfile, NM_CONFIG_KEYFILE_GROUP_CONNECTIVITY, "response", cli->connectivity_response);
|
|
|
|
if (out_config_description) {
|
|
GString *str;
|
|
|
|
str = g_string_new (o_config_main_file);
|
|
_confs_to_description (str, system_confs, "lib");
|
|
_confs_to_description (str, run_confs, "run");
|
|
_confs_to_description (str, confs, "etc");
|
|
*out_config_description = g_string_free (str, FALSE);
|
|
}
|
|
NM_SET_OUT (out_config_main_file, g_steal_pointer (&o_config_main_file));
|
|
return g_steal_pointer (&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, '+');
|
|
/* fall through */
|
|
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[NM_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[NM_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][NM_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;
|
|
|
|
_LOGD ("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[NM_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);
|
|
|
|
_LOGD ("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_match_spec (const GKeyFile *keyfile, const char *group, const char *key, gboolean *out_has_key)
|
|
{
|
|
gs_free char *value = NULL;
|
|
|
|
/* nm_match_spec_split() already supports full escaping and is basically
|
|
* a modified version of g_key_file_parse_value_as_string(). So we first read
|
|
* the raw value (g_key_file_get_value()), and do the parsing ourselves. */
|
|
value = g_key_file_get_value ((GKeyFile *) keyfile, group, key, NULL);
|
|
if (out_has_key)
|
|
*out_has_key = !!value;
|
|
return nm_match_spec_split (value);
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
|
|
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);
|
|
|
|
_LOGD ("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)) {
|
|
_LOGW ("error saving internal configuration \"%s\": %s", priv->intern_config_file, local->message);
|
|
g_clear_error (&local);
|
|
}
|
|
} else
|
|
_LOGD ("don't persist internal configuration (no file set, use --intern-config?)");
|
|
}
|
|
if (new_data)
|
|
_set_config_data (self, new_data, NM_CONFIG_CHANGE_CAUSE_SET_VALUES);
|
|
|
|
g_key_file_unref (keyfile_new);
|
|
}
|
|
|
|
/******************************************************************************
|
|
* State
|
|
******************************************************************************/
|
|
|
|
static const char *
|
|
state_get_filename (const NMConfigCmdLineOptions *cli)
|
|
{
|
|
/* For an empty filename, we assume the user wants to disable
|
|
* state. NMConfig will not try to read it nor write it out. */
|
|
if (!cli->state_file)
|
|
return DEFAULT_STATE_FILE;
|
|
return cli->state_file[0] ? cli->state_file : NULL;
|
|
}
|
|
|
|
static State *
|
|
state_new (void)
|
|
{
|
|
State *state;
|
|
|
|
state = g_slice_new0 (State);
|
|
state->p.net_enabled = TRUE;
|
|
state->p.wifi_enabled = TRUE;
|
|
state->p.wwan_enabled = TRUE;
|
|
|
|
return state;
|
|
}
|
|
|
|
static void
|
|
state_free (State *state)
|
|
{
|
|
if (!state)
|
|
return;
|
|
g_slice_free (State, state);
|
|
}
|
|
|
|
static State *
|
|
state_new_from_file (const char *filename)
|
|
{
|
|
GKeyFile *keyfile;
|
|
gs_free_error GError *error = NULL;
|
|
State *state;
|
|
|
|
state = state_new ();
|
|
|
|
if (!filename)
|
|
return state;
|
|
|
|
keyfile = g_key_file_new ();
|
|
g_key_file_set_list_separator (keyfile, ',');
|
|
if (!g_key_file_load_from_file (keyfile, filename, G_KEY_FILE_NONE, &error)) {
|
|
if (g_error_matches (error, G_FILE_ERROR, G_FILE_ERROR_NOENT))
|
|
_LOGD ("state: missing state file \"%s\": %s", filename, error->message);
|
|
else
|
|
_LOGW ("state: error reading state file \"%s\": %s", filename, error->message);
|
|
goto out;
|
|
}
|
|
|
|
_LOGD ("state: successfully read state file \"%s\"", filename);
|
|
|
|
state->p.net_enabled = nm_config_keyfile_get_boolean (keyfile, "main", "NetworkingEnabled", state->p.net_enabled);
|
|
state->p.wifi_enabled = nm_config_keyfile_get_boolean (keyfile, "main", "WirelessEnabled", state->p.wifi_enabled);
|
|
state->p.wwan_enabled = nm_config_keyfile_get_boolean (keyfile, "main", "WWANEnabled", state->p.wwan_enabled);
|
|
|
|
out:
|
|
g_key_file_unref (keyfile);
|
|
return state;
|
|
}
|
|
|
|
const NMConfigState *
|
|
nm_config_state_get (NMConfig *self)
|
|
{
|
|
NMConfigPrivate *priv;
|
|
|
|
g_return_val_if_fail (NM_IS_CONFIG (self), NULL);
|
|
|
|
priv = NM_CONFIG_GET_PRIVATE (self);
|
|
|
|
if (G_UNLIKELY (!priv->state)) {
|
|
/* read the state from file lazy on first access. The reason is that
|
|
* we want to log a failure to read the file via nm-logging.
|
|
*
|
|
* So we cannot read the state during construction of NMConfig,
|
|
* because at that time nm-logging is not yet configured.
|
|
*/
|
|
priv->state = state_new_from_file (state_get_filename (&priv->cli));
|
|
}
|
|
|
|
return &priv->state->p;
|
|
}
|
|
|
|
static void
|
|
state_write (NMConfig *self)
|
|
{
|
|
NMConfigPrivate *priv = NM_CONFIG_GET_PRIVATE (self);
|
|
const char *filename;
|
|
GString *str;
|
|
GError *error = NULL;
|
|
|
|
filename = state_get_filename (&priv->cli);
|
|
|
|
if (!filename) {
|
|
priv->state->p.dirty = FALSE;
|
|
return;
|
|
}
|
|
|
|
str = g_string_sized_new (256);
|
|
|
|
/* Let's construct the keyfile data by hand. */
|
|
|
|
g_string_append (str, "[main]\n");
|
|
g_string_append_printf (str, "NetworkingEnabled=%s\n", priv->state->p.net_enabled ? "true" : "false");
|
|
g_string_append_printf (str, "WirelessEnabled=%s\n", priv->state->p.wifi_enabled ? "true" : "false");
|
|
g_string_append_printf (str, "WWANEnabled=%s\n", priv->state->p.wwan_enabled ? "true" : "false");
|
|
|
|
if (!g_file_set_contents (filename,
|
|
str->str, str->len,
|
|
&error)) {
|
|
_LOGD ("state: error writing state file \"%s\": %s", filename, error->message);
|
|
g_clear_error (&error);
|
|
/* we leave the state dirty. That potentally means, that we try to
|
|
* write the file over and over again, although it isn't possible. */
|
|
priv->state->p.dirty = TRUE;
|
|
} else
|
|
priv->state->p.dirty = FALSE;
|
|
|
|
_LOGT ("state: success writing state file \"%s\"", filename);
|
|
|
|
g_string_free (str, TRUE);
|
|
}
|
|
|
|
void
|
|
_nm_config_state_set (NMConfig *self,
|
|
gboolean allow_persist,
|
|
gboolean force_persist,
|
|
...)
|
|
{
|
|
NMConfigPrivate *priv;
|
|
va_list ap;
|
|
NMConfigRunStatePropertyType property_type;
|
|
|
|
g_return_if_fail (NM_IS_CONFIG (self));
|
|
|
|
priv = NM_CONFIG_GET_PRIVATE (self);
|
|
|
|
va_start (ap, force_persist);
|
|
|
|
/* We expect that the NMConfigRunStatePropertyType is an integer type <= sizeof (int).
|
|
* Smaller would be fine, since the variadic arguments get promoted to int.
|
|
* Larger would be a problem, also, because we want that "0" is a valid sentinel. */
|
|
G_STATIC_ASSERT_EXPR (sizeof (NMConfigRunStatePropertyType) <= sizeof (int));
|
|
|
|
while ((property_type = va_arg (ap, int)) != NM_CONFIG_STATE_PROPERTY_NONE) {
|
|
bool *p_bool, v_bool;
|
|
|
|
switch (property_type) {
|
|
case NM_CONFIG_STATE_PROPERTY_NETWORKING_ENABLED:
|
|
p_bool = &priv->state->p.net_enabled;
|
|
break;
|
|
case NM_CONFIG_STATE_PROPERTY_WIFI_ENABLED:
|
|
p_bool = &priv->state->p.wifi_enabled;
|
|
break;
|
|
case NM_CONFIG_STATE_PROPERTY_WWAN_ENABLED:
|
|
p_bool = &priv->state->p.wwan_enabled;
|
|
break;
|
|
default:
|
|
va_end (ap);
|
|
g_return_if_reached ();
|
|
}
|
|
|
|
v_bool = va_arg (ap, gboolean);
|
|
if (*p_bool == v_bool)
|
|
continue;
|
|
*p_bool = v_bool;
|
|
priv->state->p.dirty = TRUE;
|
|
}
|
|
|
|
va_end (ap);
|
|
|
|
if ( allow_persist
|
|
&& (force_persist || priv->state->p.dirty))
|
|
state_write (self);
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
|
|
#define DEVICE_RUN_STATE_KEYFILE_GROUP_DEVICE "device"
|
|
#define DEVICE_RUN_STATE_KEYFILE_KEY_DEVICE_MANAGED "managed"
|
|
#define DEVICE_RUN_STATE_KEYFILE_KEY_DEVICE_PERM_HW_ADDR_FAKE "perm-hw-addr-fake"
|
|
#define DEVICE_RUN_STATE_KEYFILE_KEY_DEVICE_CONNECTION_UUID "connection-uuid"
|
|
|
|
NM_UTILS_LOOKUP_STR_DEFINE_STATIC (_device_state_managed_type_to_str, NMConfigDeviceStateManagedType,
|
|
NM_UTILS_LOOKUP_DEFAULT_NM_ASSERT ("unknown"),
|
|
NM_UTILS_LOOKUP_STR_ITEM (NM_CONFIG_DEVICE_STATE_MANAGED_TYPE_UNKNOWN, "unknown"),
|
|
NM_UTILS_LOOKUP_STR_ITEM (NM_CONFIG_DEVICE_STATE_MANAGED_TYPE_UNMANAGED, "unmanaged"),
|
|
NM_UTILS_LOOKUP_STR_ITEM (NM_CONFIG_DEVICE_STATE_MANAGED_TYPE_MANAGED, "managed"),
|
|
);
|
|
|
|
static NMConfigDeviceStateData *
|
|
_config_device_state_data_new (int ifindex, GKeyFile *kf)
|
|
{
|
|
NMConfigDeviceStateData *device_state;
|
|
NMConfigDeviceStateManagedType managed_type = NM_CONFIG_DEVICE_STATE_MANAGED_TYPE_UNKNOWN;
|
|
gs_free char *connection_uuid = NULL;
|
|
gs_free char *perm_hw_addr_fake = NULL;
|
|
gsize connection_uuid_len;
|
|
gsize perm_hw_addr_fake_len;
|
|
char *p;
|
|
|
|
nm_assert (ifindex > 0);
|
|
|
|
if (kf) {
|
|
switch (nm_config_keyfile_get_boolean (kf,
|
|
DEVICE_RUN_STATE_KEYFILE_GROUP_DEVICE,
|
|
DEVICE_RUN_STATE_KEYFILE_KEY_DEVICE_MANAGED,
|
|
-1)) {
|
|
case TRUE:
|
|
managed_type = NM_CONFIG_DEVICE_STATE_MANAGED_TYPE_MANAGED;
|
|
connection_uuid = nm_config_keyfile_get_value (kf,
|
|
DEVICE_RUN_STATE_KEYFILE_GROUP_DEVICE,
|
|
DEVICE_RUN_STATE_KEYFILE_KEY_DEVICE_CONNECTION_UUID,
|
|
NM_CONFIG_GET_VALUE_STRIP | NM_CONFIG_GET_VALUE_NO_EMPTY);
|
|
break;
|
|
case FALSE:
|
|
managed_type = NM_CONFIG_DEVICE_STATE_MANAGED_TYPE_UNMANAGED;
|
|
break;
|
|
case -1:
|
|
/* missing property in keyfile. */
|
|
break;
|
|
}
|
|
|
|
perm_hw_addr_fake = nm_config_keyfile_get_value (kf,
|
|
DEVICE_RUN_STATE_KEYFILE_GROUP_DEVICE,
|
|
DEVICE_RUN_STATE_KEYFILE_KEY_DEVICE_PERM_HW_ADDR_FAKE,
|
|
NM_CONFIG_GET_VALUE_STRIP | NM_CONFIG_GET_VALUE_NO_EMPTY);
|
|
if (perm_hw_addr_fake) {
|
|
char *normalized;
|
|
|
|
normalized = nm_utils_hwaddr_canonical (perm_hw_addr_fake, -1);
|
|
g_free (perm_hw_addr_fake);
|
|
perm_hw_addr_fake = normalized;
|
|
}
|
|
}
|
|
|
|
connection_uuid_len = connection_uuid ? strlen (connection_uuid) + 1 : 0;
|
|
perm_hw_addr_fake_len = perm_hw_addr_fake ? strlen (perm_hw_addr_fake) + 1 : 0;
|
|
|
|
device_state = g_malloc (sizeof (NMConfigDeviceStateData) +
|
|
connection_uuid_len +
|
|
perm_hw_addr_fake_len);
|
|
|
|
device_state->ifindex = ifindex;
|
|
device_state->managed = managed_type;
|
|
device_state->connection_uuid = NULL;
|
|
device_state->perm_hw_addr_fake = NULL;
|
|
|
|
p = (char *) (&device_state[1]);
|
|
if (connection_uuid) {
|
|
memcpy (p, connection_uuid, connection_uuid_len);
|
|
device_state->connection_uuid = p;
|
|
p += connection_uuid_len;
|
|
}
|
|
if (perm_hw_addr_fake) {
|
|
memcpy (p, perm_hw_addr_fake, perm_hw_addr_fake_len);
|
|
device_state->perm_hw_addr_fake = p;
|
|
p += perm_hw_addr_fake_len;
|
|
}
|
|
|
|
return device_state;
|
|
}
|
|
|
|
/**
|
|
* nm_config_device_state_load:
|
|
* @ifindex: the ifindex for which the state is to load
|
|
*
|
|
* Returns: (transfer full): a run state object.
|
|
* Must be freed with g_free().
|
|
*/
|
|
NMConfigDeviceStateData *
|
|
nm_config_device_state_load (int ifindex)
|
|
{
|
|
NMConfigDeviceStateData *device_state;
|
|
char path[NM_STRLEN (NM_CONFIG_DEVICE_STATE_DIR) + 60];
|
|
gs_unref_keyfile GKeyFile *kf = NULL;
|
|
|
|
g_return_val_if_fail (ifindex > 0, NULL);
|
|
|
|
nm_sprintf_buf (path, "%s/%d", NM_CONFIG_DEVICE_STATE_DIR, ifindex);
|
|
|
|
kf = nm_config_create_keyfile ();
|
|
if (!g_key_file_load_from_file (kf, path, G_KEY_FILE_NONE, NULL))
|
|
g_clear_pointer (&kf, g_key_file_unref);
|
|
|
|
device_state = _config_device_state_data_new (ifindex, kf);
|
|
|
|
_LOGT ("device-state: %s #%d (%s); managed=%s%s%s%s%s%s%s",
|
|
kf ? "read" : "miss",
|
|
ifindex, path,
|
|
_device_state_managed_type_to_str (device_state->managed),
|
|
NM_PRINT_FMT_QUOTED (device_state->connection_uuid, ", connection-uuid=", device_state->connection_uuid, "", ""),
|
|
NM_PRINT_FMT_QUOTED (device_state->perm_hw_addr_fake, ", perm-hw-addr-fake=", device_state->perm_hw_addr_fake, "", ""));
|
|
|
|
return device_state;
|
|
}
|
|
|
|
gboolean
|
|
nm_config_device_state_write (int ifindex,
|
|
NMConfigDeviceStateManagedType managed,
|
|
const char *perm_hw_addr_fake,
|
|
const char *connection_uuid)
|
|
{
|
|
char path[NM_STRLEN (NM_CONFIG_DEVICE_STATE_DIR) + 60];
|
|
GError *local = NULL;
|
|
gs_unref_keyfile GKeyFile *kf = NULL;
|
|
|
|
g_return_val_if_fail (ifindex > 0, FALSE);
|
|
g_return_val_if_fail (!connection_uuid || *connection_uuid, FALSE);
|
|
g_return_val_if_fail (managed == NM_CONFIG_DEVICE_STATE_MANAGED_TYPE_MANAGED || !connection_uuid, FALSE);
|
|
|
|
nm_assert (!perm_hw_addr_fake || nm_utils_hwaddr_valid (perm_hw_addr_fake, -1));
|
|
|
|
nm_sprintf_buf (path, "%s/%d", NM_CONFIG_DEVICE_STATE_DIR, ifindex);
|
|
|
|
kf = nm_config_create_keyfile ();
|
|
if (NM_IN_SET (managed,
|
|
NM_CONFIG_DEVICE_STATE_MANAGED_TYPE_MANAGED,
|
|
NM_CONFIG_DEVICE_STATE_MANAGED_TYPE_UNMANAGED)) {
|
|
g_key_file_set_boolean (kf,
|
|
DEVICE_RUN_STATE_KEYFILE_GROUP_DEVICE,
|
|
DEVICE_RUN_STATE_KEYFILE_KEY_DEVICE_MANAGED,
|
|
managed == NM_CONFIG_DEVICE_STATE_MANAGED_TYPE_MANAGED);
|
|
}
|
|
if (perm_hw_addr_fake) {
|
|
g_key_file_set_string (kf,
|
|
DEVICE_RUN_STATE_KEYFILE_GROUP_DEVICE,
|
|
DEVICE_RUN_STATE_KEYFILE_KEY_DEVICE_PERM_HW_ADDR_FAKE,
|
|
perm_hw_addr_fake);
|
|
}
|
|
if (connection_uuid) {
|
|
g_key_file_set_string (kf,
|
|
DEVICE_RUN_STATE_KEYFILE_GROUP_DEVICE,
|
|
DEVICE_RUN_STATE_KEYFILE_KEY_DEVICE_CONNECTION_UUID,
|
|
connection_uuid);
|
|
}
|
|
|
|
if (!g_key_file_save_to_file (kf, path, &local)) {
|
|
_LOGW ("device-state: write #%d (%s) failed: %s", ifindex, path, local->message);
|
|
g_error_free (local);
|
|
return FALSE;
|
|
}
|
|
_LOGT ("device-state: write #%d (%s); managed=%s%s%s%s%s%s%s",
|
|
ifindex, path,
|
|
_device_state_managed_type_to_str (managed),
|
|
NM_PRINT_FMT_QUOTED (connection_uuid, ", connection-uuid=", connection_uuid, "", ""),
|
|
NM_PRINT_FMT_QUOTED (perm_hw_addr_fake, ", perm-hw-addr-fake=", perm_hw_addr_fake, "", ""));
|
|
return TRUE;
|
|
}
|
|
|
|
void
|
|
nm_config_device_state_prune_unseen (GHashTable *seen_ifindexes)
|
|
{
|
|
GDir *dir;
|
|
const char *fn;
|
|
int ifindex;
|
|
gsize fn_len;
|
|
gsize i;
|
|
char buf[NM_STRLEN (NM_CONFIG_DEVICE_STATE_DIR"/") + 30 + 3] = NM_CONFIG_DEVICE_STATE_DIR"/";
|
|
char *buf_p = &buf[NM_STRLEN (NM_CONFIG_DEVICE_STATE_DIR"/")];
|
|
|
|
g_return_if_fail (seen_ifindexes);
|
|
|
|
dir = g_dir_open (NM_CONFIG_DEVICE_STATE_DIR, 0, NULL);
|
|
if (!dir)
|
|
return;
|
|
|
|
while ((fn = g_dir_read_name (dir))) {
|
|
fn_len = strlen (fn);
|
|
|
|
/* skip over file names that are not plain integers. */
|
|
for (i = 0; i < fn_len; i++) {
|
|
if (!g_ascii_isdigit (fn[i]))
|
|
break;
|
|
}
|
|
if (fn_len == 0 || i != fn_len)
|
|
continue;
|
|
|
|
ifindex = _nm_utils_ascii_str_to_int64 (fn, 10, 1, G_MAXINT, 0);
|
|
if (!ifindex)
|
|
continue;
|
|
|
|
if (g_hash_table_contains (seen_ifindexes, GINT_TO_POINTER (ifindex)))
|
|
continue;
|
|
|
|
memcpy (buf_p, fn, fn_len + 1);
|
|
_LOGT ("device-state: prune #%d (%s)", ifindex, buf);
|
|
(void) unlink (buf);
|
|
}
|
|
|
|
g_dir_close (dir);
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
|
|
void
|
|
nm_config_reload (NMConfig *self, NMConfigChangeFlags reload_flags)
|
|
{
|
|
NMConfigPrivate *priv;
|
|
GError *error = NULL;
|
|
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));
|
|
g_return_if_fail ( reload_flags
|
|
&& !NM_FLAGS_ANY (reload_flags, ~NM_CONFIG_CHANGE_CAUSES)
|
|
&& !NM_FLAGS_ANY (reload_flags, NM_CONFIG_CHANGE_CAUSE_NO_AUTO_DEFAULT
|
|
| NM_CONFIG_CHANGE_CAUSE_SET_VALUES));
|
|
|
|
priv = NM_CONFIG_GET_PRIVATE (self);
|
|
|
|
if (!NM_FLAGS_ANY (reload_flags, NM_CONFIG_CHANGE_CAUSE_SIGHUP | NM_CONFIG_CHANGE_CAUSE_CONF)) {
|
|
/* unless SIGHUP is specified, we don't reload the configuration from disc. */
|
|
_set_config_data (self, NULL, reload_flags);
|
|
return;
|
|
}
|
|
|
|
/* pass on the original command line options. This means, that
|
|
* options specified at command line cannot ever be reloaded from
|
|
* file. That seems desirable.
|
|
*/
|
|
keyfile = read_entire_config (&priv->cli,
|
|
priv->config_dir,
|
|
priv->system_config_dir,
|
|
&config_main_file,
|
|
&config_description,
|
|
&error);
|
|
if (!keyfile) {
|
|
_LOGE ("Failed to reload the configuration: %s", error->message);
|
|
g_clear_error (&error);
|
|
_set_config_data (self, NULL, reload_flags);
|
|
return;
|
|
}
|
|
|
|
no_auto_default = no_auto_default_from_file (priv->no_auto_default_file);
|
|
|
|
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, reload_flags);
|
|
}
|
|
|
|
NM_UTILS_FLAGS2STR_DEFINE (nm_config_change_flags_to_string, NMConfigChangeFlags,
|
|
|
|
NM_UTILS_FLAGS2STR (NM_CONFIG_CHANGE_CAUSE_CONF, "CONF"),
|
|
NM_UTILS_FLAGS2STR (NM_CONFIG_CHANGE_CAUSE_DNS_RC, "DNS_RC"),
|
|
NM_UTILS_FLAGS2STR (NM_CONFIG_CHANGE_CAUSE_DNS_FULL, "DNS_FULL"),
|
|
NM_UTILS_FLAGS2STR (NM_CONFIG_CHANGE_CAUSE_SIGHUP, "SIGHUP"),
|
|
NM_UTILS_FLAGS2STR (NM_CONFIG_CHANGE_CAUSE_SIGUSR1, "SIGUSR1"),
|
|
NM_UTILS_FLAGS2STR (NM_CONFIG_CHANGE_CAUSE_SIGUSR2, "SIGUSR2"),
|
|
NM_UTILS_FLAGS2STR (NM_CONFIG_CHANGE_CAUSE_NO_AUTO_DEFAULT, "NO_AUTO_DEFAULT"),
|
|
NM_UTILS_FLAGS2STR (NM_CONFIG_CHANGE_CAUSE_SET_VALUES, "SET_VALUES"),
|
|
|
|
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, NMConfigChangeFlags reload_flags)
|
|
{
|
|
NMConfigPrivate *priv = NM_CONFIG_GET_PRIVATE (self);
|
|
NMConfigData *old_data = priv->config_data;
|
|
NMConfigChangeFlags changes, changes_diff;
|
|
gboolean had_new_data = !!new_data;
|
|
|
|
nm_assert (reload_flags);
|
|
nm_assert (!NM_FLAGS_ANY (reload_flags, ~NM_CONFIG_CHANGE_CAUSES));
|
|
nm_assert ( NM_IN_SET (reload_flags, NM_CONFIG_CHANGE_CAUSE_NO_AUTO_DEFAULT, NM_CONFIG_CHANGE_CAUSE_SET_VALUES)
|
|
|| !NM_FLAGS_ANY (reload_flags, NM_CONFIG_CHANGE_CAUSE_NO_AUTO_DEFAULT | NM_CONFIG_CHANGE_CAUSE_SET_VALUES));
|
|
|
|
changes = reload_flags;
|
|
|
|
if (new_data) {
|
|
changes_diff = nm_config_data_diff (old_data, new_data);
|
|
if (changes_diff == NM_CONFIG_CHANGE_NONE)
|
|
g_clear_object (&new_data);
|
|
else
|
|
changes |= changes_diff;
|
|
}
|
|
|
|
if ( NM_IN_SET (reload_flags,
|
|
NM_CONFIG_CHANGE_CAUSE_NO_AUTO_DEFAULT,
|
|
NM_CONFIG_CHANGE_CAUSE_SET_VALUES,
|
|
NM_CONFIG_CHANGE_CAUSE_CONF)
|
|
&& !new_data) {
|
|
/* no relevant changes that should be propagated. Return silently. */
|
|
return;
|
|
}
|
|
|
|
if (new_data) {
|
|
_LOGI ("config: signal %s (%s)",
|
|
nm_config_change_flags_to_string (changes, NULL, 0),
|
|
nm_config_data_get_config_description (new_data));
|
|
nm_config_data_log (new_data, "CONFIG: ", " ", NULL);
|
|
priv->config_data = new_data;
|
|
} else if (had_new_data)
|
|
_LOGI ("config: signal %s (no changes from disk)", nm_config_change_flags_to_string (changes, NULL, 0));
|
|
else
|
|
_LOGI ("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);
|
|
if (new_data)
|
|
g_object_unref (old_data);
|
|
}
|
|
|
|
NM_DEFINE_SINGLETON_REGISTER (NMConfig);
|
|
|
|
NMConfig *
|
|
nm_config_get (void)
|
|
{
|
|
g_assert (singleton_instance);
|
|
return singleton_instance;
|
|
}
|
|
|
|
NMConfig *
|
|
nm_config_setup (const NMConfigCmdLineOptions *cli, char **atomic_section_prefixes, GError **error)
|
|
{
|
|
g_assert (!singleton_instance);
|
|
|
|
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;
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
|
|
static void
|
|
set_property (GObject *object, guint prop_id,
|
|
const GValue *value, GParamSpec *pspec)
|
|
{
|
|
NMConfig *self = NM_CONFIG (object);
|
|
NMConfigPrivate *priv = NM_CONFIG_GET_PRIVATE (self);
|
|
NMConfigCmdLineOptions *cli;
|
|
char **strv;
|
|
|
|
switch (prop_id) {
|
|
case PROP_CMD_LINE_OPTIONS:
|
|
/* construct-only */
|
|
cli = g_value_get_pointer (value);
|
|
if (!cli)
|
|
_nm_config_cmd_line_options_clear (&priv->cli);
|
|
else
|
|
_nm_config_cmd_line_options_copy (cli, &priv->cli);
|
|
break;
|
|
case PROP_ATOMIC_SECTION_PREFIXES:
|
|
/* construct-only */
|
|
strv = g_value_get_boxed (value);
|
|
if (strv && !strv[0])
|
|
strv = NULL;
|
|
priv->atomic_section_prefixes = g_strdupv (strv);
|
|
break;
|
|
default:
|
|
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
|
|
break;
|
|
}
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
|
|
static gboolean
|
|
init_sync (GInitable *initable, GCancellable *cancellable, GError **error)
|
|
{
|
|
NMConfig *self = NM_CONFIG (initable);
|
|
NMConfigPrivate *priv = NM_CONFIG_GET_PRIVATE (self);
|
|
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;
|
|
const char *s;
|
|
|
|
if (priv->config_dir) {
|
|
/* Object is already initialized. */
|
|
if (priv->config_data)
|
|
return TRUE;
|
|
g_set_error (error, G_KEY_FILE_ERROR, G_KEY_FILE_ERROR_NOT_FOUND, "unspecified error");
|
|
g_return_val_if_reached (FALSE);
|
|
}
|
|
|
|
s = priv->cli.config_dir ?: ""DEFAULT_CONFIG_DIR;
|
|
priv->config_dir = g_strdup (s[0] == '/' ? s : "");
|
|
|
|
s = priv->cli.system_config_dir ?: ""DEFAULT_SYSTEM_CONFIG_DIR;
|
|
if ( s[0] != '/'
|
|
|| nm_streq (s, priv->config_dir))
|
|
s = "";
|
|
priv->system_config_dir = g_strdup (s);
|
|
|
|
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);
|
|
if (!keyfile)
|
|
return FALSE;
|
|
|
|
/* Initialize read only private members */
|
|
|
|
if (priv->cli.no_auto_default_file)
|
|
priv->no_auto_default_file = g_strdup (priv->cli.no_auto_default_file);
|
|
else
|
|
priv->no_auto_default_file = g_strdup (DEFAULT_NO_AUTO_DEFAULT_FILE);
|
|
|
|
priv->monitor_connection_files = nm_config_keyfile_get_boolean (keyfile, NM_CONFIG_KEYFILE_GROUP_MAIN, "monitor-connection-files", FALSE);
|
|
|
|
priv->log_level = nm_strstrip (g_key_file_get_string (keyfile, NM_CONFIG_KEYFILE_GROUP_LOGGING, "level", NULL));
|
|
priv->log_domains = nm_strstrip (g_key_file_get_string (keyfile, NM_CONFIG_KEYFILE_GROUP_LOGGING, "domains", NULL));
|
|
|
|
priv->configure_and_quit = nm_config_keyfile_get_boolean (keyfile, NM_CONFIG_KEYFILE_GROUP_MAIN, "configure-and-quit", FALSE);
|
|
|
|
no_auto_default = no_auto_default_from_file (priv->no_auto_default_file);
|
|
|
|
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;
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
|
|
static void
|
|
nm_config_init (NMConfig *config)
|
|
{
|
|
}
|
|
|
|
NMConfig *
|
|
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));
|
|
}
|
|
|
|
static void
|
|
finalize (GObject *gobject)
|
|
{
|
|
NMConfigPrivate *priv = NM_CONFIG_GET_PRIVATE ((NMConfig *) gobject);
|
|
|
|
state_free (priv->state);
|
|
|
|
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_free (priv->log_level);
|
|
g_free (priv->log_domains);
|
|
g_strfreev (priv->atomic_section_prefixes);
|
|
|
|
_nm_config_cmd_line_options_clear (&priv->cli);
|
|
|
|
g_clear_object (&priv->config_data);
|
|
g_clear_object (&priv->config_data_orig);
|
|
|
|
G_OBJECT_CLASS (nm_config_parent_class)->finalize (gobject);
|
|
}
|
|
|
|
static void
|
|
nm_config_class_init (NMConfigClass *config_class)
|
|
{
|
|
GObjectClass *object_class = G_OBJECT_CLASS (config_class);
|
|
|
|
object_class->finalize = finalize;
|
|
object_class->set_property = set_property;
|
|
|
|
obj_properties[PROP_CMD_LINE_OPTIONS] =
|
|
g_param_spec_pointer (NM_CONFIG_CMD_LINE_OPTIONS, "", "",
|
|
G_PARAM_WRITABLE |
|
|
G_PARAM_CONSTRUCT_ONLY |
|
|
G_PARAM_STATIC_STRINGS);
|
|
|
|
obj_properties[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);
|
|
|
|
g_object_class_install_properties (object_class, _PROPERTY_ENUMS_LAST, obj_properties);
|
|
|
|
signals[SIGNAL_CONFIG_CHANGED] =
|
|
g_signal_new (NM_CONFIG_SIGNAL_CONFIG_CHANGED,
|
|
G_OBJECT_CLASS_TYPE (object_class),
|
|
G_SIGNAL_RUN_FIRST,
|
|
0,
|
|
NULL, NULL, NULL,
|
|
G_TYPE_NONE,
|
|
3,
|
|
NM_TYPE_CONFIG_DATA,
|
|
/* Use plain guint type for changes argument. This avoids
|
|
* glib/ffi bug https://bugzilla.redhat.com/show_bug.cgi?id=1260577 */
|
|
/* NM_TYPE_CONFIG_CHANGE_FLAGS, */
|
|
G_TYPE_UINT,
|
|
NM_TYPE_CONFIG_DATA);
|
|
|
|
G_STATIC_ASSERT_EXPR (sizeof (guint) == sizeof (NMConfigChangeFlags));
|
|
G_STATIC_ASSERT_EXPR (((gint64) ((NMConfigChangeFlags) -1)) > ((gint64) 0));
|
|
}
|
|
|
|
static void
|
|
nm_config_initable_iface_init (GInitableIface *iface)
|
|
{
|
|
iface->init = init_sync;
|
|
}
|