/* SPDX-License-Identifier: GPL-2.0-or-later */
/*
* Copyright (C) 2011 Red Hat, Inc.
* Copyright (C) 2013 Thomas Bechtold <thomasbechtold@jpberlin.de>
*/
#include "src/core/nm-default-daemon.h"
#include "nm-config.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"
#include "nm-keyfile-utils.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;
NMConfigConfigureAndQuitType 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
* /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;
char *log_level;
char *log_domains;
NMConfigConfigureAndQuitType 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;
/* the hash table of device states. It is only loaded from disk
* once and kept immutable afterwards.
*
* We also read all state file at once. We don't want to support
* that they are changed outside of NM (at least not while NM is running).
* Hence, we read them once, that's it. */
GHashTable *device_states;
char **warnings;
} 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);
/*****************************************************************************/
int
nm_config_parse_boolean(const char *str, int default_value)
{
return _nm_utils_ascii_str_to_bool(str, default_value);
}
int
nm_config_keyfile_get_boolean(const GKeyFile *keyfile,
const char * section,
const char * key,
int 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);
}
gint64
nm_config_keyfile_get_int64(const GKeyFile *keyfile,
const char * section,
const char * key,
guint base,
gint64 min,
gint64 max,
gint64 fallback)
{
gint64 v;
int errsv;
char * str;
g_return_val_if_fail(keyfile, fallback);
g_return_val_if_fail(section, fallback);
g_return_val_if_fail(key, fallback);
str = g_key_file_get_value((GKeyFile *) keyfile, section, key, NULL);
v = _nm_utils_ascii_str_to_int64(str, base, min, max, fallback);
if (str) {
errsv = errno;
g_free(str);
errno = errsv;
}
return v;
}
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);
}
/*****************************************************************************/
const char *const *
nm_config_get_warnings(NMConfig *config)
{
return (const char *const *) NM_CONFIG_GET_PRIVATE(config)->warnings;
}
void
nm_config_clear_warnings(NMConfig *config)
{
nm_clear_pointer(&NM_CONFIG_GET_PRIVATE(config)->warnings, g_strfreev);
}
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;
}
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;
}
NMConfigConfigureAndQuitType
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;
}
const char *
nm_config_get_no_auto_default_file(NMConfig *config)
{
return NM_CONFIG_GET_PRIVATE(config)->no_auto_default_file;
}
/*****************************************************************************/
static char **
no_auto_default_from_file(const char *no_auto_default_file)
{
gs_free char *data = NULL;
const char ** list = NULL;
gsize i;
if (no_auto_default_file && g_file_get_contents(no_auto_default_file, &data, NULL, NULL))
list = nm_utils_strsplit_set(data, "\n");
if (list) {
for (i = 0; list[i]; i++)
list[i] = nm_utils_str_utf8safe_unescape_cp(list[i], NM_UTILS_STR_UTF8_SAFE_FLAG_NONE);
}
/* The returned buffer here is not at all compact. That means, it has additional
* memory allocations and is larger than needed. That means, you should not keep
* this result around, only process it further and free it. */
return (char **) list;
}
static gboolean
no_auto_default_to_file(const char * no_auto_default_file,
const char *const *no_auto_default,
GError ** error)
{
nm_auto_free_gstring GString *data = NULL;
gsize i;
data = g_string_new("");
for (i = 0; no_auto_default && no_auto_default[i]; i++) {
gs_free char *s_to_free = NULL;
const char * s = no_auto_default[i];
s = nm_utils_str_utf8safe_escape(s,
NM_UTILS_STR_UTF8_SAFE_FLAG_ESCAPE_CTRL
| NM_UTILS_STR_UTF8_SAFE_FLAG_ESCAPE_NON_ASCII,
&s_to_free);
g_string_append(data, s);
g_string_append_c(data, '\n');
}
return g_file_set_contents(no_auto_default_file, data->str, data->len, error);
}
gboolean
nm_config_get_no_auto_default_for_device(NMConfig *self, NMDevice *device)
{
NMConfigPrivate *priv;
g_return_val_if_fail(NM_IS_CONFIG(self), FALSE);
priv = NM_CONFIG_GET_PRIVATE(self);
if (priv->configure_and_quit == NM_CONFIG_CONFIGURE_AND_QUIT_INITRD)
return TRUE;
return nm_config_data_get_no_auto_default_for_device(priv->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;
gs_free char * spec_to_free = NULL;
const char * ifname;
const char * hw_address;
const char * spec;
const char *const * no_auto_default_current;
gs_free const char **no_auto_default_new = NULL;
gboolean is_fake;
gsize len;
gssize idx;
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_full(device, TRUE, &is_fake);
if (!hw_address) {
/* No MAC address, not even a fake one. We don't do anything for this device. */
return;
}
if (is_fake) {
/* A fake MAC address, no point in storing it to the file.
* Also, nm_match_spec_device() would ignore fake MAC addresses.
*
* Instead, try the interface-name... */
ifname = nm_device_get_ip_iface(device);
if (!nm_utils_ifname_valid_kernel(ifname, NULL))
return;
spec_to_free = g_strdup_printf(NM_MATCH_SPEC_INTERFACE_NAME_TAG "=%s", ifname);
spec = spec_to_free;
} else
spec = hw_address;
no_auto_default_current = nm_config_data_get_no_auto_default(priv->config_data);
len = NM_PTRARRAY_LEN(no_auto_default_current);
idx = nm_utils_ptrarray_find_binary_search((gconstpointer *) no_auto_default_current,
len,
spec,
nm_strcmp_with_data,
NULL,
NULL,
NULL);
if (idx >= 0) {
/* @spec 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;
}
idx = ~idx;
no_auto_default_new = g_new(const char *, len + 2);
if (idx > 0)
memcpy(no_auto_default_new, no_auto_default_current, sizeof(const char *) * idx);
no_auto_default_new[idx] = spec;
if (idx < len)
memcpy(&no_auto_default_new[idx + 1],
&no_auto_default_current[idx],
sizeof(const char *) * (len - idx));
no_auto_default_new[len + 1] = NULL;
if (!no_auto_default_to_file(priv->no_auto_default_file, no_auto_default_new, &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, 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)
{
nm_clear_g_free(&cli->config_main_file);
nm_clear_g_free(&cli->config_dir);
nm_clear_g_free(&cli->system_config_dir);
nm_clear_g_free(&cli->no_auto_default_file);
nm_clear_g_free(&cli->intern_config_file);
nm_clear_g_free(&cli->state_file);
nm_clear_g_free(&cli->plugins);
cli->configure_and_quit = NM_CONFIG_CONFIGURE_AND_QUIT_DISABLED;
cli->is_debug = FALSE;
nm_clear_g_free(&cli->connectivity_uri);
nm_clear_g_free(&cli->connectivity_response);
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);
}
static NMConfigConfigureAndQuitType
string_to_configure_and_quit(const char *value, GError **error)
{
NMConfigConfigureAndQuitType ret;
if (value == NULL)
return NM_CONFIG_CONFIGURE_AND_QUIT_DISABLED;
if (nm_streq(value, "initrd"))
return NM_CONFIG_CONFIGURE_AND_QUIT_INITRD;
ret = nm_config_parse_boolean(value, NM_CONFIG_CONFIGURE_AND_QUIT_INVALID);
if (ret == NM_CONFIG_CONFIGURE_AND_QUIT_INVALID)
g_set_error(error, 1, 0, N_("'%s' is not valid"), value);
return ret;
}
static gboolean
parse_configure_and_quit(const char *option_name,
const char *value,
gpointer user_data,
GError ** error)
{
NMConfigCmdLineOptions *cli = user_data;
if (value == NULL)
cli->configure_and_quit = NM_CONFIG_CONFIGURE_AND_QUIT_ENABLED;
else
cli->configure_and_quit = string_to_configure_and_quit(value, error);
if (cli->configure_and_quit == NM_CONFIG_CONFIGURE_AND_QUIT_INVALID) {
g_prefix_error(error, N_("Bad '%s' option: "), option_name);
return FALSE;
}
return TRUE;
}
void
nm_config_cmd_line_options_add_to_entries(NMConfigCmdLineOptions *cli, GOptionContext *opt_ctx)
{
GOptionGroup *group;
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,
G_OPTION_FLAG_OPTIONAL_ARG,
G_OPTION_ARG_CALLBACK,
parse_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_return_if_fail(opt_ctx);
g_return_if_fail(cli);
group = g_option_group_new("nm",
N_("NetworkManager options"),
N_("Show NetworkManager options"),
cli,
NULL);
g_option_group_add_entries(group, config_options);
g_option_context_add_group(opt_ctx, group);
}
/*****************************************************************************/
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;
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 ?: "");
}
/* 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 = *pa;
const char *b = *pb;
gboolean a_is_connection, b_is_connection;
gboolean a_is_device, b_is_device;
a_is_connection = NM_STR_HAS_PREFIX(a, NM_CONFIG_KEYFILE_GROUPPREFIX_CONNECTION);
b_is_connection = NM_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 its 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 = NM_STR_HAS_PREFIX(a, NM_CONFIG_KEYFILE_GROUPPREFIX_DEVICE);
b_is_device = NM_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 its 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) (nm_streq(group, "" group_v "") && nm_streq(key, "" key_v ""))
return _IS(NM_CONFIG_KEYFILE_GROUP_MAIN, NM_CONFIG_KEYFILE_KEY_MAIN_NO_AUTO_DEFAULT)
|| _IS(NM_CONFIG_KEYFILE_GROUP_MAIN, NM_CONFIG_KEYFILE_KEY_MAIN_IGNORE_CARRIER)
|| _IS(NM_CONFIG_KEYFILE_GROUP_MAIN, NM_CONFIG_KEYFILE_KEY_MAIN_ASSUME_IPV6LL_ONLY)
|| _IS(NM_CONFIG_KEYFILE_GROUP_KEYFILE, NM_CONFIG_KEYFILE_KEY_KEYFILE_UNMANAGED_DEVICES)
|| (NM_STR_HAS_PREFIX(group, NM_CONFIG_KEYFILE_GROUPPREFIX_CONNECTION)
&& nm_streq(key, NM_CONFIG_KEYFILE_KEY_MATCH_DEVICE))
|| (NM_STR_HAS_PREFIX(group, NM_CONFIG_KEYFILE_GROUPPREFIX_DEVICE)
&& nm_streq(key, NM_CONFIG_KEYFILE_KEY_MATCH_DEVICE));
}
static gboolean
_setting_is_string_list(const char *group, const char *key)
{
return _IS(NM_CONFIG_KEYFILE_GROUP_MAIN, NM_CONFIG_KEYFILE_KEY_MAIN_PLUGINS)
|| _IS(NM_CONFIG_KEYFILE_GROUP_MAIN, NM_CONFIG_KEYFILE_KEY_MAIN_DEBUG)
|| _IS(NM_CONFIG_KEYFILE_GROUP_LOGGING, NM_CONFIG_KEYFILE_KEY_LOGGING_DOMAINS)
|| NM_STR_HAS_PREFIX(group, NM_CONFIG_KEYFILE_GROUPPREFIX_TEST_APPEND_STRINGLIST);
#undef _IS
}
typedef struct {
char * group;
const char *const *keys;
bool is_prefix : 1;
bool is_connection : 1;
} ConfigGroup;
/* The following comment is used by check-config-options.sh, don't remove it. */
/* START OPTION LIST */
static const ConfigGroup config_groups[] = {
{
.group = NM_CONFIG_KEYFILE_GROUP_MAIN,
.keys = NM_MAKE_STRV(NM_CONFIG_KEYFILE_KEY_MAIN_ASSUME_IPV6LL_ONLY,
NM_CONFIG_KEYFILE_KEY_MAIN_AUTH_POLKIT,
NM_CONFIG_KEYFILE_KEY_MAIN_AUTOCONNECT_RETRIES_DEFAULT,
NM_CONFIG_KEYFILE_KEY_MAIN_CONFIGURE_AND_QUIT,
NM_CONFIG_KEYFILE_KEY_MAIN_DEBUG,
NM_CONFIG_KEYFILE_KEY_MAIN_DHCP,
NM_CONFIG_KEYFILE_KEY_MAIN_DNS,
NM_CONFIG_KEYFILE_KEY_MAIN_HOSTNAME_MODE,
NM_CONFIG_KEYFILE_KEY_MAIN_IGNORE_CARRIER,
NM_CONFIG_KEYFILE_KEY_MAIN_MONITOR_CONNECTION_FILES,
NM_CONFIG_KEYFILE_KEY_MAIN_NO_AUTO_DEFAULT,
NM_CONFIG_KEYFILE_KEY_MAIN_PLUGINS,
NM_CONFIG_KEYFILE_KEY_MAIN_RC_MANAGER,
NM_CONFIG_KEYFILE_KEY_MAIN_SLAVES_ORDER,
NM_CONFIG_KEYFILE_KEY_MAIN_SYSTEMD_RESOLVED, ),
},
{
.group = NM_CONFIG_KEYFILE_GROUP_LOGGING,
.keys = NM_MAKE_STRV(NM_CONFIG_KEYFILE_KEY_LOGGING_AUDIT,
NM_CONFIG_KEYFILE_KEY_LOGGING_BACKEND,
NM_CONFIG_KEYFILE_KEY_LOGGING_DOMAINS,
NM_CONFIG_KEYFILE_KEY_LOGGING_LEVEL, ),
},
{
.group = NM_CONFIG_KEYFILE_GROUP_CONNECTIVITY,
.keys = NM_MAKE_STRV(NM_CONFIG_KEYFILE_KEY_CONNECTIVITY_ENABLED,
NM_CONFIG_KEYFILE_KEY_CONNECTIVITY_INTERVAL,
NM_CONFIG_KEYFILE_KEY_CONNECTIVITY_RESPONSE,
NM_CONFIG_KEYFILE_KEY_CONNECTIVITY_URI, ),
},
{
.group = NM_CONFIG_KEYFILE_GROUP_KEYFILE,
.keys = NM_MAKE_STRV(NM_CONFIG_KEYFILE_KEY_KEYFILE_HOSTNAME,
NM_CONFIG_KEYFILE_KEY_KEYFILE_PATH,
NM_CONFIG_KEYFILE_KEY_KEYFILE_UNMANAGED_DEVICES, ),
},
{
.group = NM_CONFIG_KEYFILE_GROUP_IFUPDOWN,
.keys = NM_MAKE_STRV(NM_CONFIG_KEYFILE_KEY_IFUPDOWN_MANAGED, ),
},
{
.group = NM_CONFIG_KEYFILE_GROUPPREFIX_DEVICE,
.is_prefix = TRUE,
.keys = NM_MAKE_STRV(NM_CONFIG_KEYFILE_KEY_DEVICE_CARRIER_WAIT_TIMEOUT,
NM_CONFIG_KEYFILE_KEY_DEVICE_IGNORE_CARRIER,
NM_CONFIG_KEYFILE_KEY_DEVICE_MANAGED,
NM_CONFIG_KEYFILE_KEY_DEVICE_SRIOV_NUM_VFS,
NM_CONFIG_KEYFILE_KEY_DEVICE_WIFI_BACKEND,
NM_CONFIG_KEYFILE_KEY_DEVICE_WIFI_SCAN_RAND_MAC_ADDRESS,
NM_CONFIG_KEYFILE_KEY_DEVICE_WIFI_SCAN_GENERATE_MAC_ADDRESS_MASK,
NM_CONFIG_KEYFILE_KEY_DEVICE_WIFI_IWD_AUTOCONNECT,
NM_CONFIG_KEYFILE_KEY_MATCH_DEVICE,
NM_CONFIG_KEYFILE_KEY_STOP_MATCH, ),
},
{
.group = NM_CONFIG_KEYFILE_GROUP_GLOBAL_DNS,
.keys = NM_MAKE_STRV(NM_CONFIG_KEYFILE_KEY_GLOBAL_DNS_OPTIONS,
NM_CONFIG_KEYFILE_KEY_GLOBAL_DNS_SEARCHES, ),
},
{
.group = NM_CONFIG_KEYFILE_GROUPPREFIX_GLOBAL_DNS_DOMAIN,
.is_prefix = TRUE,
.keys = NM_MAKE_STRV(NM_CONFIG_KEYFILE_KEY_GLOBAL_DNS_DOMAIN_SERVERS,
NM_CONFIG_KEYFILE_KEY_GLOBAL_DNS_DOMAIN_OPTIONS, ),
},
{
.group = NM_CONFIG_KEYFILE_GROUPPREFIX_CONNECTION,
.is_prefix = TRUE,
.is_connection = TRUE,
.keys =
NM_MAKE_STRV(NM_CONFIG_KEYFILE_KEY_MATCH_DEVICE, NM_CONFIG_KEYFILE_KEY_STOP_MATCH, ),
},
{} /* sentinel */
};
/* The following comment is used by check-config-options.sh, don't remove it. */
/* END OPTION LIST */
static gboolean
check_config_key(const char *group, const char *key)
{
const ConfigGroup *g;
const char *const *k;
const char ** ptr;
#if NM_MORE_ASSERTS > 10
{
static gboolean checked = FALSE;
const char ** ptr1, **ptr2;
/* check for duplicate elements in the static list */
if (!checked) {
for (ptr1 = __start_connection_defaults; ptr1 < __stop_connection_defaults; ptr1++) {
for (ptr2 = ptr1 + 1; ptr2 < __stop_connection_defaults; ptr2++)
nm_assert(!nm_streq(*ptr1, *ptr2));
}
checked = TRUE;
}
}
#endif
for (g = config_groups; g->group; g++) {
if ((!g->is_prefix && nm_streq(group, g->group))
|| (g->is_prefix && g_str_has_prefix(group, g->group)))
break;
}
if (!g->group)
return FALSE;
for (k = g->keys; *k; k++) {
if (nm_streq(key, *k))
return TRUE;
}
if (g->is_connection) {
for (ptr = __start_connection_defaults; ptr < __stop_connection_defaults; ptr++) {
if (nm_streq(key, *ptr))
return TRUE;
}
return FALSE;
}
return FALSE;
}
static gboolean
read_config(GKeyFile * keyfile,
gboolean is_base_config,
const char *dirname,
const char *path,
GPtrArray * warnings,
GError ** error)
{
nm_auto_unref_keyfile GKeyFile *kf = NULL;
gs_strfreev char ** groups = NULL;
gs_free char * path_free = NULL;
gsize ngroups;
gsize nkeys;
int g;
int k;
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);
return FALSE;
}
if (ignore_config_snippet(kf, is_base_config))
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];
gs_strfreev char **keys = NULL;
if (NM_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++) {
gs_free char *new_value = NULL;
const char * key;
char last_char;
gsize key_len;
key = keys[k];
nm_assert(key && *key);
if (NM_STR_HAS_PREFIX_WITH_MORE(key, NM_CONFIG_KEYFILE_KEYPREFIX_WAS)
|| NM_STR_HAS_PREFIX_WITH_MORE(key, NM_CONFIG_KEYFILE_KEYPREFIX_SET)) {
/* these keys are protected. We ignore them if the user sets them. */
continue;
}
if (nm_streq(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;
gboolean old_val_was_set = FALSE;
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) {
gs_free_error GError *old_error = NULL;
old_val =
g_key_file_get_string_list(keyfile, group, base_key, NULL, &old_error);
new_val = g_key_file_get_string_list(kf, group, key, NULL, NULL);
if (nm_streq(group, NM_CONFIG_KEYFILE_GROUP_MAIN)
&& nm_streq(base_key, "plugins")) {
old_val_was_set = !nm_keyfile_error_is_not_found(old_error);
if (!old_val && !old_val_was_set) {
/* we must fill the unspecified value with the compile-time default. */
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);
old_val_was_set = TRUE;
}
}
} 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);
nm_auto_free_slist GSList *old_specs = nm_match_spec_split(old_sval);
nm_auto_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 omitting 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 {
nm_auto_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 && !old_val_was_set)
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);
if (!check_config_key(group, key)) {
g_ptr_array_add(warnings,
g_strdup_printf("unknown key '%s' in section [%s] of file '%s'",
key,
group,
path));
}
}
}
return TRUE;
}
static gboolean
read_base_config(GKeyFile * keyfile,
const char *cli_config_main_file,
char ** out_config_main_file,
GPtrArray * warnings,
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, warnings, 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, warnings, &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", 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, warnings, &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", 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", 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 (NM_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,
GPtrArray * warnings,
GError ** error)
{
nm_auto_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 = "";
nm_assert(config_dir);
nm_assert(system_config_dir);
nm_assert(!out_config_main_file || !*out_config_main_file);
nm_assert(!out_config_description || !*out_config_description);
nm_assert(!error || !*error);
nm_assert(warnings);
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, warnings, 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, warnings, 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,
warnings,
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], warnings, error))
return NULL;
}
/* Merge settings from command line. They overwrite everything read from
* config files. */
if (cli) {
if (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);
}
switch (cli->configure_and_quit) {
case NM_CONFIG_CONFIGURE_AND_QUIT_INVALID:
g_assert_not_reached();
break;
case NM_CONFIG_CONFIGURE_AND_QUIT_DISABLED:
/* do nothing */
break;
case NM_CONFIG_CONFIGURE_AND_QUIT_ENABLED:
g_key_file_set_boolean(keyfile,
NM_CONFIG_KEYFILE_GROUP_MAIN,
"configure-and-quit",
TRUE);
break;
case NM_CONFIG_CONFIGURE_AND_QUIT_INITRD:
g_key_file_set_string(keyfile,
NM_CONFIG_KEYFILE_GROUP_MAIN,
"configure-and-quit",
"initrd");
break;
}
if (cli->connectivity_uri && cli->connectivity_uri[0])
g_key_file_set_string(keyfile,
NM_CONFIG_KEYFILE_GROUP_CONNECTIVITY,
"uri",
cli->connectivity_uri);
if (cli->connectivity_interval >= 0)
g_key_file_set_integer(keyfile,
NM_CONFIG_KEYFILE_GROUP_CONNECTIVITY,
"interval",
cli->connectivity_interval);
if (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 (nm_streq(group, NM_CONFIG_KEYFILE_GROUP_CONFIG))
continue;
keys = g_key_file_get_keys(keyfile, group, NULL, NULL);
if (!keys)
continue;
is_intern = NM_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 (!nm_streq0(conf_section_was, conf_section_is)) {
/* 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 (nm_streq(key, NM_CONFIG_KEYFILE_KEY_ATOMIC_SECTION_WAS))
continue;
g_key_file_set_value(keyfile_intern, group, key, value_set);
} else if (NM_STR_HAS_PREFIX_WITH_MORE(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 (!nm_streq0(value_conf, value_was)) {
/* 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 (NM_STR_HAS_PREFIX_WITH_MORE(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 (!nm_streq0(value_conf, value_was)) {
/* 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 (NM_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;
const char *g_b = *b;
gboolean a_is, b_is;
a_is = NM_STR_HAS_PREFIX(g_a, NM_CONFIG_KEYFILE_GROUPPREFIX_INTERN);
b_is = NM_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 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 = NM_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 || is_atomic)
g_key_file_set_value(keyfile, group, key, value_set);
else {
gs_free char *value_was = NULL;
if (NM_STR_HAS_PREFIX_WITH_MORE(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 (NM_STR_HAS_PREFIX_WITH_MORE(key, NM_CONFIG_KEYFILE_KEYPREFIX_WAS)) {
const char *key_base = &key[NM_STRLEN(NM_CONFIG_KEYFILE_KEYPREFIX_WAS)];
if (NM_STR_HAS_PREFIX_WITH_MORE(key_base, NM_CONFIG_KEYFILE_KEYPREFIX_SET)
|| NM_STR_HAS_PREFIX_WITH_MORE(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 (nm_streq0(value_set, value_was)) {
/* 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 (NM_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,
NM_CONFIG_KEYFILE_KEY_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,
NM_CONFIG_KEYFILE_KEY_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,
NM_CONFIG_KEYFILE_KEY_GLOBAL_DNS_DOMAIN_SERVERS,
nm_global_dns_domain_get_servers(domain),
-1);
nm_config_keyfile_set_string_list(keyfile,
group_name,
NM_CONFIG_KEYFILE_KEY_GLOBAL_DNS_DOMAIN_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;
}
/*****************************************************************************/
void
nm_config_set_connectivity_check_enabled(NMConfig *self, gboolean enabled)
{
NMConfigPrivate *priv;
GKeyFile * keyfile;
g_return_if_fail(NM_IS_CONFIG(self));
priv = NM_CONFIG_GET_PRIVATE(self);
g_return_if_fail(priv->config_data);
keyfile = nm_config_data_clone_keyfile_intern(priv->config_data);
/* Remove existing groups */
g_key_file_remove_group(keyfile, NM_CONFIG_KEYFILE_GROUP_CONNECTIVITY, NULL);
g_key_file_set_value(keyfile,
NM_CONFIG_KEYFILE_GROUP_CONNECTIVITY,
"enabled",
enabled ? "true" : "false");
nm_config_set_values(self, keyfile, TRUE, FALSE);
g_key_file_unref(keyfile);
}
/**
* 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;
int 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;
if (priv->configure_and_quit != NM_CONFIG_CONFIGURE_AND_QUIT_DISABLED)
return;
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 potentially 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"
#define DEVICE_RUN_STATE_KEYFILE_KEY_DEVICE_NM_OWNED "nm-owned"
#define DEVICE_RUN_STATE_KEYFILE_KEY_DEVICE_ROUTE_METRIC_DEFAULT_ASPIRED \
"route-metric-default-aspired"
#define DEVICE_RUN_STATE_KEYFILE_KEY_DEVICE_ROUTE_METRIC_DEFAULT_EFFECTIVE \
"route-metric-default-effective"
#define DEVICE_RUN_STATE_KEYFILE_KEY_DEVICE_ROOT_PATH "root-path"
#define DEVICE_RUN_STATE_KEYFILE_KEY_DEVICE_NEXT_SERVER "next-server"
static NM_UTILS_LOOKUP_STR_DEFINE(
_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;
NMTernary nm_owned;
char * p;
guint32 route_metric_default_effective;
guint32 route_metric_default_aspired;
nm_assert(kf);
nm_assert(ifindex > 0);
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;
}
nm_owned = nm_config_keyfile_get_boolean(kf,
DEVICE_RUN_STATE_KEYFILE_GROUP_DEVICE,
DEVICE_RUN_STATE_KEYFILE_KEY_DEVICE_NM_OWNED,
NM_TERNARY_DEFAULT);
/* metric zero is not a valid metric. While zero valid for IPv4, for IPv6 it is an alias
* for 1024. Since we handle here IPv4 and IPv6 the same, we cannot allow zero. */
route_metric_default_effective = nm_config_keyfile_get_int64(
kf,
DEVICE_RUN_STATE_KEYFILE_GROUP_DEVICE,
DEVICE_RUN_STATE_KEYFILE_KEY_DEVICE_ROUTE_METRIC_DEFAULT_EFFECTIVE,
10,
1,
G_MAXUINT32,
0);
if (route_metric_default_effective) {
route_metric_default_aspired = nm_config_keyfile_get_int64(
kf,
DEVICE_RUN_STATE_KEYFILE_GROUP_DEVICE,
DEVICE_RUN_STATE_KEYFILE_KEY_DEVICE_ROUTE_METRIC_DEFAULT_EFFECTIVE,
10,
1,
route_metric_default_effective,
route_metric_default_effective);
} else
route_metric_default_aspired = 0;
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;
device_state->nm_owned = nm_owned;
device_state->route_metric_default_aspired = route_metric_default_aspired;
device_state->route_metric_default_effective = route_metric_default_effective;
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;
}
#define DEVICE_STATE_FILENAME_LEN_MAX 60
/**
* 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 "/") + DEVICE_STATE_FILENAME_LEN_MAX + 1];
nm_auto_unref_keyfile GKeyFile *kf = NULL;
const char * nm_owned_str;
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))
return NULL;
device_state = _config_device_state_data_new(ifindex, kf);
nm_owned_str = device_state->nm_owned == NM_TERNARY_TRUE
? ", nm-owned=1"
: (device_state->nm_owned == NM_TERNARY_FALSE ? ", nm-owned=0" : "");
_LOGT("device-state: %s #%d (%s); managed=%s%s%s%s%s%s%s%s, "
"route-metric-default=%" G_GUINT32_FORMAT "-%" G_GUINT32_FORMAT "",
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,
"",
""),
nm_owned_str,
device_state->route_metric_default_aspired,
device_state->route_metric_default_effective);
return device_state;
}
static int
_device_state_parse_filename(const char *filename)
{
if (!filename || !filename[0])
return 0;
if (!NM_STRCHAR_ALL(filename, ch, g_ascii_isdigit(ch)))
return 0;
return _nm_utils_ascii_str_to_int64(filename, 10, 1, G_MAXINT, 0);
}
GHashTable *
nm_config_device_state_load_all(void)
{
GHashTable *states;
GDir * dir;
const char *fn;
int ifindex;
states = g_hash_table_new_full(nm_direct_hash, NULL, NULL, g_free);
dir = g_dir_open(NM_CONFIG_DEVICE_STATE_DIR, 0, NULL);
if (!dir)
return states;
while ((fn = g_dir_read_name(dir))) {
NMConfigDeviceStateData *state;
ifindex = _device_state_parse_filename(fn);
if (ifindex <= 0)
continue;
state = nm_config_device_state_load(ifindex);
if (!state)
continue;
if (!g_hash_table_insert(states, GINT_TO_POINTER(ifindex), state))
nm_assert_not_reached();
}
g_dir_close(dir);
return states;
}
gboolean
nm_config_device_state_write(int ifindex,
NMConfigDeviceStateManagedType managed,
const char * perm_hw_addr_fake,
const char * connection_uuid,
NMTernary nm_owned,
guint32 route_metric_default_aspired,
guint32 route_metric_default_effective,
const char * next_server,
const char * root_path)
{
char path[NM_STRLEN(NM_CONFIG_DEVICE_STATE_DIR "/") + DEVICE_STATE_FILENAME_LEN_MAX + 1];
GError *local = NULL;
nm_auto_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 (nm_owned != NM_TERNARY_DEFAULT) {
g_key_file_set_boolean(kf,
DEVICE_RUN_STATE_KEYFILE_GROUP_DEVICE,
DEVICE_RUN_STATE_KEYFILE_KEY_DEVICE_NM_OWNED,
nm_owned);
}
if (route_metric_default_effective != 0) {
g_key_file_set_int64(kf,
DEVICE_RUN_STATE_KEYFILE_GROUP_DEVICE,
DEVICE_RUN_STATE_KEYFILE_KEY_DEVICE_ROUTE_METRIC_DEFAULT_EFFECTIVE,
route_metric_default_effective);
if (route_metric_default_aspired != route_metric_default_effective) {
g_key_file_set_int64(kf,
DEVICE_RUN_STATE_KEYFILE_GROUP_DEVICE,
DEVICE_RUN_STATE_KEYFILE_KEY_DEVICE_ROUTE_METRIC_DEFAULT_ASPIRED,
route_metric_default_aspired);
}
}
if (next_server) {
g_key_file_set_string(kf,
DEVICE_RUN_STATE_KEYFILE_GROUP_DEVICE,
DEVICE_RUN_STATE_KEYFILE_KEY_DEVICE_NEXT_SERVER,
next_server);
}
if (root_path) {
g_key_file_set_string(kf,
DEVICE_RUN_STATE_KEYFILE_GROUP_DEVICE,
DEVICE_RUN_STATE_KEYFILE_KEY_DEVICE_ROOT_PATH,
root_path);
}
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, "
"route-metric-default=%" G_GUINT32_FORMAT "-%" G_GUINT32_FORMAT "%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, "", ""),
route_metric_default_aspired,
route_metric_default_effective,
NM_PRINT_FMT_QUOTED(next_server, ", next-server=", next_server, "", ""),
NM_PRINT_FMT_QUOTED(root_path, ", root-path=", root_path, "", ""));
return TRUE;
}
void
nm_config_device_state_prune_stale(GHashTable *preserve_ifindexes, NMPlatform *preserve_in_platform)
{
GDir * dir;
const char *fn;
char buf[NM_STRLEN(NM_CONFIG_DEVICE_STATE_DIR "/") + DEVICE_STATE_FILENAME_LEN_MAX + 1] =
NM_CONFIG_DEVICE_STATE_DIR "/";
char *buf_p = &buf[NM_STRLEN(NM_CONFIG_DEVICE_STATE_DIR "/")];
dir = g_dir_open(NM_CONFIG_DEVICE_STATE_DIR, 0, NULL);
if (!dir)
return;
while ((fn = g_dir_read_name(dir))) {
int ifindex;
gsize fn_len;
ifindex = _device_state_parse_filename(fn);
if (ifindex <= 0)
continue;
if (preserve_ifindexes
&& g_hash_table_contains(preserve_ifindexes, GINT_TO_POINTER(ifindex)))
continue;
if (preserve_in_platform && nm_platform_link_get(preserve_in_platform, ifindex))
continue;
fn_len = strlen(fn);
nm_assert(fn_len > 0);
nm_assert(&buf_p[fn_len] < &buf[G_N_ELEMENTS(buf)]);
memcpy(buf_p, fn, fn_len + 1u);
nm_assert(({
char bb[30];
nm_streq0(nm_sprintf_buf(bb, "%d", ifindex), buf_p);
}));
_LOGT("device-state: prune #%d (%s)", ifindex, buf);
(void) unlink(buf);
}
g_dir_close(dir);
}
/*****************************************************************************/
static GHashTable *
_device_state_get_all(NMConfig *self)
{
NMConfigPrivate *priv = NM_CONFIG_GET_PRIVATE(self);
if (G_UNLIKELY(!priv->device_states))
priv->device_states = nm_config_device_state_load_all();
return priv->device_states;
}
/**
* nm_config_device_state_get_all:
* @self: the #NMConfig
*
* This function exists to give convenient access to all
* device states. Do not ever try to modify the returned
* hash, it's supposed to be immutable.
*
* Returns: the internal #GHashTable object with all device states.
*/
const GHashTable *
nm_config_device_state_get_all(NMConfig *self)
{
g_return_val_if_fail(NM_IS_CONFIG(self), NULL);
return _device_state_get_all(self);
}
const NMConfigDeviceStateData *
nm_config_device_state_get(NMConfig *self, int ifindex)
{
g_return_val_if_fail(NM_IS_CONFIG(self), NULL);
g_return_val_if_fail(ifindex > 0, NULL);
return g_hash_table_lookup(_device_state_get_all(self), GINT_TO_POINTER(ifindex));
}
/*****************************************************************************/
void
nm_config_reload(NMConfig *self, NMConfigChangeFlags reload_flags, gboolean emit_warnings)
{
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;
gs_unref_ptrarray GPtrArray *warnings = NULL;
guint i;
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;
}
warnings = g_ptr_array_new_with_free_func(g_free);
/* 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,
warnings,
&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);
if (emit_warnings) {
nm_config_data_get_warnings(priv->config_data_orig, warnings);
for (i = 0; i < warnings->len; i++)
_LOGW("%s", (const char *) warnings->pdata[i]);
}
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("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: ", " ", priv->no_auto_default_file, NULL);
priv->config_data = new_data;
} else if (had_new_data)
_LOGI("signal: %s (no changes from disk)",
nm_config_change_flags_to_string(changes, NULL, 0));
else
_LOGI("signal: %s", nm_config_change_flags_to_string(changes, NULL, 0));
g_signal_emit(self, signals[SIGNAL_CONFIG_CHANGED], 0, 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 (" NM_HASH_OBFUSCATE_PTR_FMT ")",
"NMConfig",
NM_HASH_OBFUSCATE_PTR(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);
nm_auto_unref_keyfile GKeyFile *keyfile = NULL;
nm_auto_unref_keyfile GKeyFile *keyfile_intern = NULL;
gs_free char * config_main_file = NULL;
gs_free char * config_description = NULL;
gs_strfreev char ** no_auto_default = NULL;
gs_unref_ptrarray GPtrArray *warnings = NULL;
gs_free char * configure_and_quit = 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);
warnings = g_ptr_array_new_with_free_func(g_free);
keyfile = read_entire_config(&priv->cli,
priv->config_dir,
priv->system_config_dir,
&config_main_file,
&config_description,
warnings,
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->log_level = nm_strstrip(g_key_file_get_string(keyfile,
NM_CONFIG_KEYFILE_GROUP_LOGGING,
NM_CONFIG_KEYFILE_KEY_LOGGING_LEVEL,
NULL));
priv->log_domains = nm_strstrip(g_key_file_get_string(keyfile,
NM_CONFIG_KEYFILE_GROUP_LOGGING,
NM_CONFIG_KEYFILE_KEY_LOGGING_DOMAINS,
NULL));
configure_and_quit =
nm_strstrip(g_key_file_get_string(keyfile,
NM_CONFIG_KEYFILE_GROUP_MAIN,
NM_CONFIG_KEYFILE_KEY_MAIN_CONFIGURE_AND_QUIT,
NULL));
priv->configure_and_quit = string_to_configure_and_quit(configure_and_quit, error);
if (priv->configure_and_quit == NM_CONFIG_CONFIGURE_AND_QUIT_INVALID)
return 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
&& priv->configure_and_quit == NM_CONFIG_CONFIGURE_AND_QUIT_DISABLED) {
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);
nm_config_data_get_warnings(priv->config_data_orig, warnings);
priv->config_data = g_object_ref(priv->config_data_orig);
if (warnings->len > 0) {
g_ptr_array_add(warnings, NULL);
priv->warnings = (char **) g_ptr_array_free(g_steal_pointer(&warnings), FALSE);
}
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(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);
g_strfreev(priv->warnings);
_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;
}