diff --git a/cli/src/connections.c b/cli/src/connections.c index 9db9e7a30f..63518f8907 100644 --- a/cli/src/connections.c +++ b/cli/src/connections.c @@ -205,7 +205,8 @@ usage (void) #endif " down [ id | uuid | path | apath ] \n\n" " add COMMON_OPTIONS TYPE_SPECIFIC_OPTIONS IP_OPTIONS\n\n" - " delete [ id | uuid | path ] \n\n\n" + " delete [ id | uuid | path ] \n\n" + " reload\n\n\n" )); } @@ -284,6 +285,7 @@ static const char *real_con_commands[] = { "down", "add", "delete", + "reload", NULL }; @@ -3221,6 +3223,32 @@ finish: return nmc->return_value; } +static NMCResultCode +do_connection_reload (NmCli *nmc, int argc, char **argv) +{ + GError *error = NULL; + + nmc->return_value = NMC_RESULT_SUCCESS; + nmc->should_wait = FALSE; + + if (!nm_client_get_manager_running (nmc->client)) { + g_string_printf (nmc->return_text, _("Error: NetworkManager is not running.")); + nmc->return_value = NMC_RESULT_ERROR_NM_NOT_RUNNING; + return nmc->return_value; + } + + if (!nm_remote_settings_reload_connections (nmc->system_settings, &error)) { + g_string_printf (nmc->return_text, _("Error: %s."), error->message); + if (error->code == NM_REMOTE_SETTINGS_ERROR_SERVICE_UNAVAILABLE) + nmc->return_value = NMC_RESULT_ERROR_NM_NOT_RUNNING; + else + nmc->return_value = NMC_RESULT_ERROR_UNKNOWN; + g_clear_error (&error); + } + + return nmc->return_value; +} + static NMCResultCode parse_cmd (NmCli *nmc, int argc, char **argv) { @@ -3263,6 +3291,9 @@ parse_cmd (NmCli *nmc, int argc, char **argv) else if (matches(*argv, "delete") == 0) { nmc->return_value = do_connection_delete (nmc, argc-1, argv+1); } + else if (matches(*argv, "reload") == 0) { + nmc->return_value = do_connection_reload (nmc, argc-1, argv+1); + } else if (nmc_arg_is_help (*argv)) { usage (); nmc->should_wait = FALSE; diff --git a/data/server.conf.in b/data/server.conf.in index 757636389b..031fbebcdc 100644 --- a/data/server.conf.in +++ b/data/server.conf.in @@ -4,6 +4,15 @@ [main] +# Normally, NetworkManager reloads connection files on disk any time +# they are changed. Setting "monitor-connection-files=false" will +# disable this behavior, and NetworkManager will then only read +# connection files at startup, and when explicitly requested via +# D-Bus. + +#monitor-connection-files=false + + # Normally, if there is an ethernet device that is not matched by any # existing configured connection, NetworkManager will create a # "default" connection for that device, using automatic (DHCP/SLAAC) diff --git a/introspection/nm-settings-connection.xml b/introspection/nm-settings-connection.xml index b1f58798d7..093738012d 100644 --- a/introspection/nm-settings-connection.xml +++ b/introspection/nm-settings-connection.xml @@ -31,6 +31,11 @@ the connection to disk. Secrets may be part of the update request and may sent to a Secret Agent for storage, depending on the the flags associated with each secret. + + Use the 'Save' method to save these changes to disk. Note + that unsaved changes will be lost if the connection is + reloaded from disk (either automatically on file change or + due to an explicit ReloadConnections call). diff --git a/introspection/nm-settings.xml b/introspection/nm-settings.xml index 794e302b23..0043ec7564 100644 --- a/introspection/nm-settings.xml +++ b/introspection/nm-settings.xml @@ -62,6 +62,11 @@ operation does not start the network connection unless (1) device is idle and able to connect to the network described by the new connection, and (2) the connection is allowed to be started automatically. + + Use the 'Save' method on the connection to save these changes + to disk. Note that unsaved changes will be lost if the + connection is reloaded from disk (either automatically on file + change or due to an explicit ReloadConnections call). @@ -77,6 +82,23 @@ + + + Tells NetworkManager to reload all connection files from disk, + including noticing any added or deleted connection files. By + default, connections are re-read automatically any time they + change, so you only need to use this command if you have set + "monitor-connection-files=false" in NetworkManager.conf. + + + + + + Success or failure. + + + + Save the hostname to persistent configuration. diff --git a/libnm-glib/libnm-glib.ver b/libnm-glib/libnm-glib.ver index 4b5fc909ec..668fbacac0 100644 --- a/libnm-glib/libnm-glib.ver +++ b/libnm-glib/libnm-glib.ver @@ -231,6 +231,7 @@ global: nm_remote_settings_new; nm_remote_settings_new_async; nm_remote_settings_new_finish; + nm_remote_settings_reload_connections; nm_remote_settings_save_hostname; nm_secret_agent_delete_secrets; nm_secret_agent_error_get_type; diff --git a/libnm-glib/nm-remote-settings.c b/libnm-glib/nm-remote-settings.c index 9dc74ef934..589d755653 100644 --- a/libnm-glib/nm-remote-settings.c +++ b/libnm-glib/nm-remote-settings.c @@ -629,6 +629,47 @@ nm_remote_settings_add_connection_unsaved (NMRemoteSettings *settings, return TRUE; } +/** + * nm_remote_settings_reload_connections: + * @settings: the #NMRemoteSettings + * @error: return location for #GError + * + * Requests that the remote settings service reload all connection + * files from disk, adding, updating, and removing connections until + * the in-memory state matches the on-disk state. + * + * Return value: %TRUE on success, %FALSE on failure + * + * Since: 0.9.10 + **/ +gboolean +nm_remote_settings_reload_connections (NMRemoteSettings *settings, + GError **error) +{ + NMRemoteSettingsPrivate *priv; + gboolean success; + + g_return_val_if_fail (NM_IS_REMOTE_SETTINGS (settings), FALSE); + + priv = NM_REMOTE_SETTINGS_GET_PRIVATE (settings); + + _nm_remote_settings_ensure_inited (settings); + + if (!priv->service_running) { + g_set_error_literal (error, NM_REMOTE_SETTINGS_ERROR, + NM_REMOTE_SETTINGS_ERROR_SERVICE_UNAVAILABLE, + "NetworkManager is not running."); + return FALSE; + } + + if (!dbus_g_proxy_call (priv->proxy, "ReloadConnections", error, + G_TYPE_INVALID, + G_TYPE_BOOLEAN, &success, + G_TYPE_INVALID)) + return FALSE; + return success; +} + static void clear_one_hash (GHashTable *table) { diff --git a/libnm-glib/nm-remote-settings.h b/libnm-glib/nm-remote-settings.h index c8d1b3e5b4..1bef4a7edb 100644 --- a/libnm-glib/nm-remote-settings.h +++ b/libnm-glib/nm-remote-settings.h @@ -45,6 +45,8 @@ G_BEGIN_DECLS * was removed before it was completely initialized * @NM_REMOTE_SETTINGS_ERROR_CONNECTION_UNAVAILABLE: the #NMRemoteConnection object * is not visible or otherwise unreadable + * @NM_REMOTE_SETTINGS_ERROR_SERVICE_UNAVAILABLE: NetworkManager is not running. + * (Since 0.9.10) * * Describes errors that may result from operations involving a #NMRemoteSettings. * @@ -53,6 +55,7 @@ typedef enum { NM_REMOTE_SETTINGS_ERROR_UNKNOWN = 0, /*< nick=UnknownError >*/ NM_REMOTE_SETTINGS_ERROR_CONNECTION_REMOVED, /*< nick=ConnectionRemoved >*/ NM_REMOTE_SETTINGS_ERROR_CONNECTION_UNAVAILABLE, /*< nick=ConnectionUnavailable >*/ + NM_REMOTE_SETTINGS_ERROR_SERVICE_UNAVAILABLE, /*< nick=ServiceUnavailable >*/ } NMRemoteSettingsError; #define NM_REMOTE_SETTINGS_ERROR nm_remote_settings_error_quark () @@ -132,6 +135,9 @@ gboolean nm_remote_settings_add_connection_unsaved (NMRemoteSettings *settings, NMRemoteSettingsAddConnectionFunc callback, gpointer user_data); +gboolean nm_remote_settings_reload_connections (NMRemoteSettings *settings, + GError **error); + gboolean nm_remote_settings_save_hostname (NMRemoteSettings *settings, const char *hostname, NMRemoteSettingsSaveHostnameFunc callback, diff --git a/man/NetworkManager.conf.xml b/man/NetworkManager.conf.xml index 6b73c824ef..7ee5dd999d 100644 --- a/man/NetworkManager.conf.xml +++ b/man/NetworkManager.conf.xml @@ -96,6 +96,16 @@ Copyright (C) 2010 - 2013 Red Hat, Inc. connection, the error is returned to the user. See below for available plugins. + + monitor-connection-files + Whether the configured settings plugin(s) + should set up file monitors and immediately pick up changes + made to connection files while NetworkManager is running. This + is enabled by default; if this key is set to + 'false', then NetworkManager will only read + the connection files at startup, and when explicitly requested + via the ReloadConnections D-Bus call. + dhcp This key sets up what DHCP client diff --git a/man/nmcli.1.in b/man/nmcli.1.in index 77675d3c65..754f80c53c 100644 --- a/man/nmcli.1.in +++ b/man/nmcli.1.in @@ -238,7 +238,7 @@ NetworkManager connections .br Get information about \fINetworkManager\fP connections and manage them. .TP -.SS \fICOMMAND\fP := { show | up | down | add | delete } +.SS \fICOMMAND\fP := { show | up | down | add | delete | reload } .sp .RS .TP @@ -501,6 +501,13 @@ its name, UUID or D-Bus path. If is ambiguous, a keyword \fIid\fP, \fIuuid\fP or \fIpath\fP can be used. .br See \fBshow active\fP above for the description of the -specifying keywords. +.TP +.B reload +.br +Reload all connection files from disk. By default, connections are re-read +automatically any time they change, so you only need to use this command when +the auto-loading feature is disabled ("monitor-connection-files=false" +in NetworkManager.conf). .RE .TP diff --git a/src/config/nm-config.c b/src/config/nm-config.c index 5a7e2ecd17..8eb40eff2b 100644 --- a/src/config/nm-config.c +++ b/src/config/nm-config.c @@ -43,6 +43,7 @@ typedef struct { GKeyFile *keyfile; char **plugins; + gboolean monitor_connection_files; char *dhcp_client; char *dns_mode; @@ -81,6 +82,14 @@ nm_config_get_plugins (NMConfig *config) return (const char **) NM_CONFIG_GET_PRIVATE (config)->plugins; } +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_dhcp_client (NMConfig *config) { @@ -434,6 +443,7 @@ nm_config_new (GError **error) GFileInfo *info; GPtrArray *confs; const char *name; + char *value; int i; g_assert (!singleton); @@ -492,6 +502,20 @@ nm_config_new (GError **error) g_key_file_set_value (priv->keyfile, "main", "plugins", cli_plugins); priv->plugins = g_key_file_get_string_list (priv->keyfile, "main", "plugins", NULL, NULL); + value = g_key_file_get_value (priv->keyfile, "main", "monitor-connection-files", NULL); + if (value) { + if (!strcmp (value, "true") || !strcmp (value, "yes") || !strcmp (value, "on")) + priv->monitor_connection_files = TRUE; + else if (!strcmp (value, "false") || !strcmp (value, "no") || !strcmp (value, "off")) + priv->monitor_connection_files = FALSE; + else { + g_warning ("Unrecognized value for main.monitor-connection-files: %s. Assuming 'false'", value); + priv->monitor_connection_files = FALSE; + } + g_free (value); + } else + priv->monitor_connection_files = TRUE; + priv->dhcp_client = g_key_file_get_value (priv->keyfile, "main", "dhcp", NULL); priv->dns_mode = g_key_file_get_value (priv->keyfile, "main", "dns", NULL); diff --git a/src/config/nm-config.h b/src/config/nm-config.h index 82cf8c57df..6518fb33bd 100644 --- a/src/config/nm-config.h +++ b/src/config/nm-config.h @@ -50,6 +50,7 @@ NMConfig *nm_config_get (void); const char *nm_config_get_path (NMConfig *config); const char **nm_config_get_plugins (NMConfig *config); +gboolean nm_config_get_monitor_connection_files (NMConfig *config); const char *nm_config_get_dhcp_client (NMConfig *config); const char *nm_config_get_dns_mode (NMConfig *config); const char *nm_config_get_log_level (NMConfig *config); diff --git a/src/settings/nm-settings.c b/src/settings/nm-settings.c index 578ca458f8..7950a8c0ad 100644 --- a/src/settings/nm-settings.c +++ b/src/settings/nm-settings.c @@ -104,6 +104,9 @@ static void impl_settings_add_connection_unsaved (NMSettings *self, GHashTable *settings, DBusGMethodInvocation *context); +static void impl_settings_reload_connections (NMSettings *self, + DBusGMethodInvocation *context); + static void impl_settings_save_hostname (NMSettings *self, const char *hostname, DBusGMethodInvocation *context); @@ -1213,6 +1216,46 @@ impl_settings_add_connection_unsaved (NMSettings *self, impl_settings_add_connection_helper (self, settings, FALSE, context); } +static void +impl_settings_reload_connections (NMSettings *self, + DBusGMethodInvocation *context) +{ + NMSettingsPrivate *priv = NM_SETTINGS_GET_PRIVATE (self); + GSList *iter; + gulong caller_uid; + GError *error = NULL; + + if (!nm_dbus_manager_get_caller_info (priv->dbus_mgr, context, NULL, &caller_uid)) { + error = g_error_new_literal (NM_SETTINGS_ERROR, + NM_SETTINGS_ERROR_PERMISSION_DENIED, + "Unable to determine request UID."); + dbus_g_method_return_error (context, error); + g_error_free (error); + return; + } + if (caller_uid != 0) { + nm_log_warn (LOGD_SETTINGS, "ReloadConnections: permission denied to %lu", caller_uid); + error = g_error_new_literal (NM_SETTINGS_ERROR, + NM_SETTINGS_ERROR_PERMISSION_DENIED, + "Permission denied"); + dbus_g_method_return_error (context, error); + g_error_free (error); + return; + } + + if (!priv->connections_loaded) { + load_connections (self); + } else { + for (iter = priv->plugins; iter; iter = g_slist_next (iter)) { + NMSystemConfigInterface *plugin = NM_SYSTEM_CONFIG_INTERFACE (iter->data); + + nm_system_config_interface_reload_connections (plugin); + } + } + + dbus_g_method_return (context, TRUE); +} + static void pk_hostname_cb (NMAuthChain *chain, GError *chain_error, diff --git a/src/settings/nm-system-config-interface.c b/src/settings/nm-system-config-interface.c index 1a9d5d58ed..0d9426da32 100644 --- a/src/settings/nm-system-config-interface.c +++ b/src/settings/nm-system-config-interface.c @@ -137,6 +137,15 @@ nm_system_config_interface_get_connections (NMSystemConfigInterface *config) return NULL; } +void +nm_system_config_interface_reload_connections (NMSystemConfigInterface *config) +{ + g_return_if_fail (config != NULL); + + if (NM_SYSTEM_CONFIG_INTERFACE_GET_INTERFACE (config)->reload_connections) + NM_SYSTEM_CONFIG_INTERFACE_GET_INTERFACE (config)->reload_connections (config); +} + GSList * nm_system_config_interface_get_unmanaged_specs (NMSystemConfigInterface *config) { diff --git a/src/settings/nm-system-config-interface.h b/src/settings/nm-system-config-interface.h index 9648fdb5d9..cd65a6a5db 100644 --- a/src/settings/nm-system-config-interface.h +++ b/src/settings/nm-system-config-interface.h @@ -89,6 +89,11 @@ struct _NMSystemConfigInterface { */ GSList * (*get_connections) (NMSystemConfigInterface *config); + /* Requests that the plugin reload all connection files from disk, + * and emit signals reflecting new, changed, and removed connections. + */ + void (*reload_connections) (NMSystemConfigInterface *config); + /* * Return a string list of specifications of devices which NetworkManager * should not manage. Returned list will be freed by the system settings @@ -137,6 +142,8 @@ void nm_system_config_interface_init (NMSystemConfigInterface *config, GSList *nm_system_config_interface_get_connections (NMSystemConfigInterface *config); +void nm_system_config_interface_reload_connections (NMSystemConfigInterface *config); + GSList *nm_system_config_interface_get_unmanaged_specs (NMSystemConfigInterface *config); NMSettingsConnection *nm_system_config_interface_add_connection (NMSystemConfigInterface *config, diff --git a/src/settings/plugins/example/plugin.c b/src/settings/plugins/example/plugin.c index c8c1bff770..710ac752f3 100644 --- a/src/settings/plugins/example/plugin.c +++ b/src/settings/plugins/example/plugin.c @@ -421,10 +421,9 @@ conf_file_changed (GFileMonitor *monitor, } /* This function starts the inotify monitors that watch the plugin's config - * file directory for new connections and changes to existing connections. - * At this time all plugins are expected to make NM aware of changes on-the-fly - * instead of requiring a SIGHUP or SIGUSR1 or some D-Bus method to say - * "reload". + * file directory for new connections and changes to existing connections + * (if not disabled by NetworkManager.conf), and for changes to the plugin's + * non-connection config files. */ static void setup_monitoring (NMSystemConfigInterface *config) @@ -438,16 +437,18 @@ setup_monitoring (NMSystemConfigInterface *config) */ priv->connections = g_hash_table_new_full (g_str_hash, g_str_equal, NULL, g_object_unref); - /* Set up the watch for our config directory */ - file = g_file_new_for_path (EXAMPLE_DIR); - monitor = g_file_monitor_directory (file, G_FILE_MONITOR_NONE, NULL, NULL); - g_object_unref (file); - if (monitor) { - /* This registers the dir_changed() function to be called whenever - * the GFileMonitor object notices a change in the directory. - */ - priv->monitor_id = g_signal_connect (monitor, "changed", G_CALLBACK (dir_changed), config); - priv->monitor = monitor; + if (nm_config_get_monitor_connection_files (nm_config_get ())) { + /* Set up the watch for our config directory */ + file = g_file_new_for_path (EXAMPLE_DIR); + monitor = g_file_monitor_directory (file, G_FILE_MONITOR_NONE, NULL, NULL); + g_object_unref (file); + if (monitor) { + /* This registers the dir_changed() function to be called whenever + * the GFileMonitor object notices a change in the directory. + */ + priv->monitor_id = g_signal_connect (monitor, "changed", G_CALLBACK (dir_changed), config); + priv->monitor = monitor; + } } /* Set up a watch on our configuration file, basically just for watching diff --git a/src/settings/plugins/ifcfg-rh/Makefile.am b/src/settings/plugins/ifcfg-rh/Makefile.am index 829378d126..49b115c992 100644 --- a/src/settings/plugins/ifcfg-rh/Makefile.am +++ b/src/settings/plugins/ifcfg-rh/Makefile.am @@ -28,6 +28,7 @@ INCLUDES = \ -I$(top_srcdir)/src/wifi \ -I$(top_srcdir)/src/settings \ -I$(top_srcdir)/src/posix-signals \ + -I$(top_srcdir)/src/config \ -I$(top_srcdir)/include \ -I$(top_builddir)/include \ -I$(top_srcdir)/libnm-glib \ diff --git a/src/settings/plugins/ifcfg-rh/plugin.c b/src/settings/plugins/ifcfg-rh/plugin.c index fac70c4e5e..034737c6d6 100644 --- a/src/settings/plugins/ifcfg-rh/plugin.c +++ b/src/settings/plugins/ifcfg-rh/plugin.c @@ -44,10 +44,12 @@ #include "plugin.h" #include "nm-system-config-interface.h" #include "nm-settings-error.h" +#include "nm-config.h" #include "nm-ifcfg-connection.h" #include "nm-inotify-helper.h" #include "shvar.h" +#include "reader.h" #include "writer.h" #include "utils.h" @@ -64,7 +66,8 @@ static gboolean impl_ifcfgrh_get_ifcfg_details (SCPluginIfcfg *plugin, static void connection_new_or_changed (SCPluginIfcfg *plugin, const char *path, - NMIfcfgConnection *existing); + NMIfcfgConnection *existing, + char **out_old_path); static void system_config_interface_init (NMSystemConfigInterface *system_config_interface_class); @@ -109,7 +112,7 @@ connection_ifcfg_changed (NMIfcfgConnection *connection, gpointer user_data) path = nm_ifcfg_connection_get_path (connection); g_return_if_fail (path != NULL); - connection_new_or_changed (plugin, path, connection); + connection_new_or_changed (plugin, path, connection, NULL); } static NMIfcfgConnection * @@ -173,35 +176,6 @@ _internal_new_connection (SCPluginIfcfg *self, return connection; } -static void -read_connections (SCPluginIfcfg *plugin) -{ - GDir *dir; - GError *err = NULL; - - dir = g_dir_open (IFCFG_DIR, 0, &err); - if (dir) { - const char *item; - - while ((item = g_dir_read_name (dir))) { - char *full_path; - - if (utils_should_ignore_file (item, TRUE)) - continue; - - full_path = g_build_filename (IFCFG_DIR, item, NULL); - if (utils_get_ifcfg_name (full_path, TRUE)) - _internal_new_connection (plugin, full_path, NULL, NULL); - g_free (full_path); - } - - g_dir_close (dir); - } else { - PLUGIN_WARN (IFCFG_PLUGIN_NAME, "Can not read directory '%s': %s", IFCFG_DIR, err->message); - g_error_free (err); - } -} - /* Monitoring */ static void @@ -242,10 +216,26 @@ find_by_path (SCPluginIfcfg *self, const char *path) return NULL; } +static NMIfcfgConnection * +find_by_uuid_from_path (SCPluginIfcfg *self, const char *path) +{ + SCPluginIfcfgPrivate *priv = SC_PLUGIN_IFCFG_GET_PRIVATE (self); + char *uuid; + + g_return_val_if_fail (path != NULL, NULL); + + uuid = uuid_from_file (path); + if (uuid) + return g_hash_table_lookup (priv->connections, uuid); + else + return NULL; +} + static void connection_new_or_changed (SCPluginIfcfg *self, const char *path, - NMIfcfgConnection *existing) + NMIfcfgConnection *existing, + char **out_old_path) { NMIfcfgConnection *new; GError *error = NULL; @@ -255,6 +245,21 @@ connection_new_or_changed (SCPluginIfcfg *self, g_return_if_fail (self != NULL); g_return_if_fail (path != NULL); + if (out_old_path) + *out_old_path = NULL; + + if (!existing) { + /* See if it's a rename */ + existing = find_by_uuid_from_path (self, path); + if (existing) { + const char *old_path = nm_ifcfg_connection_get_path (existing); + PLUGIN_PRINT (IFCFG_PLUGIN_NAME, "renaming %s -> %s", old_path, path); + if (out_old_path) + *out_old_path = g_strdup (old_path); + nm_ifcfg_connection_set_path (existing, path); + } + } + if (!existing) { /* New connection */ new = _internal_new_connection (self, path, NULL, NULL); @@ -371,7 +376,7 @@ ifcfg_dir_changed (GFileMonitor *monitor, case G_FILE_MONITOR_EVENT_CREATED: case G_FILE_MONITOR_EVENT_CHANGES_DONE_HINT: /* Update or new */ - connection_new_or_changed (plugin, ifcfg_path, connection); + connection_new_or_changed (plugin, ifcfg_path, connection, NULL); break; default: break; @@ -399,6 +404,65 @@ setup_ifcfg_monitoring (SCPluginIfcfg *plugin) } } +static void +read_connections (SCPluginIfcfg *plugin) +{ + SCPluginIfcfgPrivate *priv = SC_PLUGIN_IFCFG_GET_PRIVATE (plugin); + GDir *dir; + GError *err = NULL; + const char *item; + GHashTable *oldconns; + GHashTableIter iter; + gpointer key, value; + NMIfcfgConnection *connection; + + dir = g_dir_open (IFCFG_DIR, 0, &err); + if (!dir) { + PLUGIN_WARN (IFCFG_PLUGIN_NAME, "Can not read directory '%s': %s", IFCFG_DIR, err->message); + g_error_free (err); + return; + } + + oldconns = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL); + g_hash_table_iter_init (&iter, priv->connections); + while (g_hash_table_iter_next (&iter, NULL, &value)) + g_hash_table_insert (oldconns, g_strdup (nm_ifcfg_connection_get_path (value)), value); + + while ((item = g_dir_read_name (dir))) { + char *full_path, *old_path; + + if (utils_should_ignore_file (item, TRUE)) + continue; + + full_path = g_build_filename (IFCFG_DIR, item, NULL); + if (!utils_get_ifcfg_name (full_path, TRUE)) + goto next; + + connection = g_hash_table_lookup (oldconns, full_path); + g_hash_table_remove (oldconns, full_path); + connection_new_or_changed (plugin, full_path, connection, &old_path); + + if (old_path) { + g_hash_table_remove (oldconns, old_path); + g_free (old_path); + } + + next: + g_free (full_path); + } + + g_dir_close (dir); + + g_hash_table_iter_init (&iter, oldconns); + while (g_hash_table_iter_next (&iter, &key, &value)) { + PLUGIN_PRINT (IFCFG_PLUGIN_NAME, "removed %s.", (char *)key); + g_hash_table_iter_remove (&iter); + remove_connection (plugin, value); + } + + g_hash_table_destroy (oldconns); +} + static GSList * get_connections (NMSystemConfigInterface *config) { @@ -409,7 +473,8 @@ get_connections (NMSystemConfigInterface *config) NMIfcfgConnection *connection; if (!priv->initialized) { - setup_ifcfg_monitoring (plugin); + if (nm_config_get_monitor_connection_files (nm_config_get ())) + setup_ifcfg_monitoring (plugin); read_connections (plugin); priv->initialized = TRUE; } @@ -423,6 +488,14 @@ get_connections (NMSystemConfigInterface *config) return list; } +static void +reload_connections (NMSystemConfigInterface *config) +{ + SCPluginIfcfg *plugin = SC_PLUGIN_IFCFG (config); + + read_connections (plugin); +} + static GSList * get_unmanaged_specs (NMSystemConfigInterface *config) { @@ -854,6 +927,7 @@ system_config_interface_init (NMSystemConfigInterface *system_config_interface_c /* interface implementation */ system_config_interface_class->get_connections = get_connections; system_config_interface_class->add_connection = add_connection; + system_config_interface_class->reload_connections = reload_connections; system_config_interface_class->get_unmanaged_specs = get_unmanaged_specs; system_config_interface_class->init = init; } diff --git a/src/settings/plugins/ifcfg-rh/reader.c b/src/settings/plugins/ifcfg-rh/reader.c index 34769f38a9..e99cbe6852 100644 --- a/src/settings/plugins/ifcfg-rh/reader.c +++ b/src/settings/plugins/ifcfg-rh/reader.c @@ -4251,6 +4251,34 @@ ensure_unmanaged (shvarFile *ifcfg, PLUGIN_WARN (IFCFG_PLUGIN_NAME, " warning: NM_CONTROLLED was false but device was not uniquely identified; device will be managed"); } +char * +uuid_from_file (const char *filename) +{ + const char *ifcfg_name = NULL; + shvarFile *ifcfg; + char *uuid; + + g_return_val_if_fail (filename != NULL, NULL); + + ifcfg_name = utils_get_ifcfg_name (filename, TRUE); + if (!ifcfg_name) + return NULL; + + ifcfg = svNewFile (filename); + if (!ifcfg) + return NULL; + + /* Try for a UUID key before falling back to hashing the file name */ + uuid = svGetValue (ifcfg, "UUID", FALSE); + if (!uuid || !strlen (uuid)) { + g_free (uuid); + uuid = nm_utils_uuid_generate_from_string (ifcfg->fileName); + } + + svCloseFile (ifcfg); + return uuid; +} + NMConnection * connection_from_file (const char *filename, const char *network_file, /* for unit tests only */ diff --git a/src/settings/plugins/ifcfg-rh/reader.h b/src/settings/plugins/ifcfg-rh/reader.h index 97c727cc79..5121bb8d5e 100644 --- a/src/settings/plugins/ifcfg-rh/reader.h +++ b/src/settings/plugins/ifcfg-rh/reader.h @@ -37,4 +37,6 @@ NMConnection *connection_from_file (const char *filename, GError **error, gboolean *ignore_error); +char *uuid_from_file (const char *filename); + #endif /* __READER_H__ */ diff --git a/src/settings/plugins/ifnet/plugin.c b/src/settings/plugins/ifnet/plugin.c index 8b056a6701..169110643a 100644 --- a/src/settings/plugins/ifnet/plugin.c +++ b/src/settings/plugins/ifnet/plugin.c @@ -66,7 +66,7 @@ typedef struct { static void system_config_interface_init (NMSystemConfigInterface *class); -static void reload_connections (gpointer config); +static void reload_connections (NMSystemConfigInterface *config); G_DEFINE_TYPE_EXTENDED (SCPluginIfnet, sc_plugin_ifnet, G_TYPE_OBJECT, 0, G_IMPLEMENT_INTERFACE (NM_TYPE_SYSTEM_CONFIG_INTERFACE, system_config_interface_init)) @@ -191,12 +191,15 @@ setup_monitors (NMIfnetConnection * connection, gpointer user_data) priv->hostname_monitor = monitor_file_changes (IFNET_SYSTEM_HOSTNAME_FILE, - update_system_hostname, user_data); - priv->net_monitor = - monitor_file_changes (CONF_NET_FILE, reload_connections, user_data); - priv->wpa_monitor = - monitor_file_changes (WPA_SUPPLICANT_CONF, reload_connections, - user_data); + update_system_hostname, user_data); + if (nm_config_get_monitor_connection_files (nm_config_get ())) { + priv->net_monitor = + monitor_file_changes (CONF_NET_FILE, (FileChangedFn) reload_connections, + user_data); + priv->wpa_monitor = + monitor_file_changes (WPA_SUPPLICANT_CONF, (FileChangedFn) reload_connections, + user_data); + } } static void @@ -220,7 +223,7 @@ cancel_monitors (NMIfnetConnection * connection, gpointer user_data) } static void -reload_connections (gpointer config) +reload_connections (NMSystemConfigInterface *config) { SCPluginIfnet *self = SC_PLUGIN_IFNET (config); SCPluginIfnetPrivate *priv = SC_PLUGIN_IFNET_GET_PRIVATE (self); @@ -460,6 +463,7 @@ system_config_interface_init (NMSystemConfigInterface *class) class->get_connections = get_connections; class->get_unmanaged_specs = get_unmanaged_specs; class->add_connection = add_connection; + class->reload_connections = reload_connections; } static void diff --git a/src/settings/plugins/keyfile/plugin.c b/src/settings/plugins/keyfile/plugin.c index 52cb36d37b..9b54fe75b1 100644 --- a/src/settings/plugins/keyfile/plugin.c +++ b/src/settings/plugins/keyfile/plugin.c @@ -88,48 +88,6 @@ _internal_new_connection (SCPluginKeyfile *self, return (NMSettingsConnection *) connection; } -static void -read_connections (NMSystemConfigInterface *config) -{ - SCPluginKeyfile *self = SC_PLUGIN_KEYFILE (config); - GDir *dir; - GError *error = NULL; - const char *item; - - dir = g_dir_open (KEYFILE_DIR, 0, &error); - if (!dir) { - PLUGIN_WARN (KEYFILE_PLUGIN_NAME, "Cannot read directory '%s': (%d) %s", - KEYFILE_DIR, - error ? error->code : -1, - error && error->message ? error->message : "(unknown)"); - g_clear_error (&error); - return; - } - - while ((item = g_dir_read_name (dir))) { - NMSettingsConnection *connection; - char *full_path; - - if (nm_keyfile_plugin_utils_should_ignore_file (item)) - continue; - - full_path = g_build_filename (KEYFILE_DIR, item, NULL); - PLUGIN_PRINT (KEYFILE_PLUGIN_NAME, "parsing %s ... ", item); - - connection = _internal_new_connection (self, full_path, NULL, &error); - if (connection) { - PLUGIN_PRINT (KEYFILE_PLUGIN_NAME, " read connection '%s'", - nm_connection_get_id (NM_CONNECTION (connection))); - } else { - PLUGIN_PRINT (KEYFILE_PLUGIN_NAME, " error: %s", - (error && error->message) ? error->message : "(unknown)"); - } - g_clear_error (&error); - g_free (full_path); - } - g_dir_close (dir); -} - /* Monitoring */ static void @@ -137,6 +95,8 @@ remove_connection (SCPluginKeyfile *self, NMKeyfileConnection *connection) { g_return_if_fail (connection != NULL); + PLUGIN_PRINT (KEYFILE_PLUGIN_NAME, "removed %s.", nm_keyfile_connection_get_path (connection)); + /* Removing from the hash table should drop the last reference */ g_object_ref (connection); g_hash_table_remove (SC_PLUGIN_KEYFILE_GET_PRIVATE (self)->connections, @@ -145,6 +105,40 @@ remove_connection (SCPluginKeyfile *self, NMKeyfileConnection *connection) g_object_unref (connection); } +static void +update_connection (SCPluginKeyfile *self, + NMKeyfileConnection *connection, + const char *name) +{ + NMKeyfileConnection *tmp; + GError *error = NULL; + + tmp = nm_keyfile_connection_new (NULL, name, &error); + if (!tmp) { + /* Error; remove the connection */ + PLUGIN_PRINT (KEYFILE_PLUGIN_NAME, " error: %s", + (error && error->message) ? error->message : "(unknown)"); + g_clear_error (&error); + remove_connection (self, connection); + return; + } + + if (!nm_connection_compare (NM_CONNECTION (connection), + NM_CONNECTION (tmp), + NM_SETTING_COMPARE_FLAG_IGNORE_AGENT_OWNED_SECRETS | + NM_SETTING_COMPARE_FLAG_IGNORE_NOT_SAVED_SECRETS)) { + PLUGIN_PRINT (KEYFILE_PLUGIN_NAME, "updating %s", name); + if (!nm_settings_connection_replace_settings (NM_SETTINGS_CONNECTION (connection), + NM_CONNECTION (tmp), + FALSE, /* don't set Unsaved */ + &error)) { + /* Shouldn't ever get here as 'new' was verified by the reader already */ + g_assert_no_error (error); + } + } + g_object_unref (tmp); +} + static NMKeyfileConnection * find_by_path (SCPluginKeyfile *self, const char *path) { @@ -162,6 +156,51 @@ find_by_path (SCPluginKeyfile *self, const char *path) return NULL; } +static void +new_connection (SCPluginKeyfile *self, + const char *name, + char **out_old_path) +{ + SCPluginKeyfilePrivate *priv = SC_PLUGIN_KEYFILE_GET_PRIVATE (self); + NMKeyfileConnection *tmp, *connection; + GError *error = NULL; + + if (out_old_path) + *out_old_path = NULL; + + tmp = nm_keyfile_connection_new (NULL, name, &error); + if (!tmp) { + PLUGIN_PRINT (KEYFILE_PLUGIN_NAME, " error in connection %s: %s", name, + (error && error->message) ? error->message : "(unknown)"); + g_clear_error (&error); + return; + } + + /* Connection renames will show as different paths but same UUID */ + connection = g_hash_table_lookup (priv->connections, nm_connection_get_uuid (NM_CONNECTION (tmp))); + if (connection) { + PLUGIN_PRINT (KEYFILE_PLUGIN_NAME, "rename %s -> %s", + nm_keyfile_connection_get_path (connection), name); + if (!nm_settings_connection_replace_settings (NM_SETTINGS_CONNECTION (connection), + NM_CONNECTION (tmp), + FALSE, /* don't set Unsaved */ + &error)) { + /* Shouldn't ever get here as 'tmp' was verified by the reader already */ + g_assert_no_error (error); + } + g_object_unref (tmp); + if (out_old_path) + *out_old_path = g_strdup (nm_keyfile_connection_get_path (connection)); + nm_keyfile_connection_set_path (connection, name); + } else { + PLUGIN_PRINT (KEYFILE_PLUGIN_NAME, "new connection %s", name); + g_hash_table_insert (priv->connections, + (gpointer) nm_connection_get_uuid (NM_CONNECTION (tmp)), + tmp); + g_signal_emit_by_name (self, NM_SYSTEM_CONFIG_INTERFACE_CONNECTION_ADDED, tmp); + } +} + static void dir_changed (GFileMonitor *monitor, GFile *file, @@ -171,10 +210,8 @@ dir_changed (GFileMonitor *monitor, { NMSystemConfigInterface *config = NM_SYSTEM_CONFIG_INTERFACE (user_data); SCPluginKeyfile *self = SC_PLUGIN_KEYFILE (config); - SCPluginKeyfilePrivate *priv = SC_PLUGIN_KEYFILE_GET_PRIVATE (self); + NMKeyfileConnection *connection; char *full_path; - NMKeyfileConnection *connection, *tmp; - GError *error = NULL; full_path = g_file_get_path (file); if (nm_keyfile_plugin_utils_should_ignore_file (full_path)) { @@ -186,67 +223,15 @@ dir_changed (GFileMonitor *monitor, switch (event_type) { case G_FILE_MONITOR_EVENT_DELETED: - if (connection) { - PLUGIN_PRINT (KEYFILE_PLUGIN_NAME, "removed %s.", full_path); + if (connection) remove_connection (SC_PLUGIN_KEYFILE (config), connection); - } break; case G_FILE_MONITOR_EVENT_CREATED: case G_FILE_MONITOR_EVENT_CHANGES_DONE_HINT: - if (connection) { - /* Update */ - tmp = nm_keyfile_connection_new (NULL, full_path, &error); - if (tmp) { - if (!nm_connection_compare (NM_CONNECTION (connection), - NM_CONNECTION (tmp), - NM_SETTING_COMPARE_FLAG_IGNORE_AGENT_OWNED_SECRETS | - NM_SETTING_COMPARE_FLAG_IGNORE_NOT_SAVED_SECRETS)) { - PLUGIN_PRINT (KEYFILE_PLUGIN_NAME, "updating %s", full_path); - if (!nm_settings_connection_replace_settings (NM_SETTINGS_CONNECTION (connection), - NM_CONNECTION (tmp), - FALSE, /* don't set Unsaved */ - &error)) { - /* Shouldn't ever get here as 'new' was verified by the reader already */ - g_assert_no_error (error); - } - } - g_object_unref (tmp); - } else { - /* Error; remove the connection */ - PLUGIN_PRINT (KEYFILE_PLUGIN_NAME, " error: %s", - (error && error->message) ? error->message : "(unknown)"); - g_clear_error (&error); - remove_connection (SC_PLUGIN_KEYFILE (config), connection); - } - } else { - /* New */ - PLUGIN_PRINT (KEYFILE_PLUGIN_NAME, "updating %s", full_path); - tmp = nm_keyfile_connection_new (NULL, full_path, &error); - if (tmp) { - /* Connection renames will show as different paths but same UUID */ - connection = g_hash_table_lookup (priv->connections, nm_connection_get_uuid (NM_CONNECTION (tmp))); - if (connection) { - if (!nm_settings_connection_replace_settings (NM_SETTINGS_CONNECTION (connection), - NM_CONNECTION (tmp), - FALSE, /* don't set Unsaved */ - &error)) { - /* Shouldn't ever get here as 'tmp' was verified by the reader already */ - g_assert_no_error (error); - } - g_object_unref (tmp); - nm_keyfile_connection_set_path (connection, full_path); - } else { - g_hash_table_insert (priv->connections, - (gpointer) nm_connection_get_uuid (NM_CONNECTION (tmp)), - tmp); - g_signal_emit_by_name (config, NM_SYSTEM_CONFIG_INTERFACE_CONNECTION_ADDED, tmp); - } - } else { - PLUGIN_PRINT (KEYFILE_PLUGIN_NAME, " error: %s", - (error && error->message) ? error->message : "(unknown)"); - g_clear_error (&error); - } - } + if (connection) + update_connection (SC_PLUGIN_KEYFILE (config), connection, full_path); + else + new_connection (SC_PLUGIN_KEYFILE (config), full_path, NULL); break; default: break; @@ -299,13 +284,15 @@ setup_monitoring (NMSystemConfigInterface *config) GFile *file; GFileMonitor *monitor; - file = g_file_new_for_path (KEYFILE_DIR); - monitor = g_file_monitor_directory (file, G_FILE_MONITOR_NONE, NULL, NULL); - g_object_unref (file); + if (nm_config_get_monitor_connection_files (nm_config_get ())) { + file = g_file_new_for_path (KEYFILE_DIR); + monitor = g_file_monitor_directory (file, G_FILE_MONITOR_NONE, NULL, NULL); + g_object_unref (file); - if (monitor) { - priv->monitor_id = g_signal_connect (monitor, "changed", G_CALLBACK (dir_changed), config); - priv->monitor = monitor; + if (monitor) { + priv->monitor_id = g_signal_connect (monitor, "changed", G_CALLBACK (dir_changed), config); + priv->monitor = monitor; + } } if (priv->conf_file) { @@ -320,6 +307,66 @@ setup_monitoring (NMSystemConfigInterface *config) } } +static void +read_connections (NMSystemConfigInterface *config) +{ + SCPluginKeyfile *self = SC_PLUGIN_KEYFILE (config); + SCPluginKeyfilePrivate *priv = SC_PLUGIN_KEYFILE_GET_PRIVATE (self); + GDir *dir; + GError *error = NULL; + const char *item; + GHashTable *oldconns; + GHashTableIter iter; + gpointer data; + + dir = g_dir_open (KEYFILE_DIR, 0, &error); + if (!dir) { + PLUGIN_WARN (KEYFILE_PLUGIN_NAME, "Cannot read directory '%s': (%d) %s", + KEYFILE_DIR, + error ? error->code : -1, + error && error->message ? error->message : "(unknown)"); + g_clear_error (&error); + return; + } + + oldconns = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL); + g_hash_table_iter_init (&iter, priv->connections); + while (g_hash_table_iter_next (&iter, NULL, &data)) + g_hash_table_insert (oldconns, g_strdup (nm_keyfile_connection_get_path (data)), data); + + while ((item = g_dir_read_name (dir))) { + NMKeyfileConnection *connection; + char *full_path, *old_path; + + if (nm_keyfile_plugin_utils_should_ignore_file (item)) + continue; + + full_path = g_build_filename (KEYFILE_DIR, item, NULL); + + connection = g_hash_table_lookup (oldconns, full_path); + if (connection) { + g_hash_table_remove (oldconns, full_path); + update_connection (self, connection, full_path); + } else { + new_connection (self, full_path, &old_path); + if (old_path) { + g_hash_table_remove (oldconns, old_path); + g_free (old_path); + } + } + + g_free (full_path); + } + g_dir_close (dir); + + g_hash_table_iter_init (&iter, oldconns); + while (g_hash_table_iter_next (&iter, NULL, &data)) { + g_hash_table_iter_remove (&iter); + remove_connection (self, data); + } + g_hash_table_destroy (oldconns); +} + /* Plugin */ static GSList * @@ -342,6 +389,12 @@ get_connections (NMSystemConfigInterface *config) return list; } +static void +reload_connections (NMSystemConfigInterface *config) +{ + read_connections (config); +} + static NMSettingsConnection * add_connection (NMSystemConfigInterface *config, NMConnection *connection, @@ -634,6 +687,7 @@ system_config_interface_init (NMSystemConfigInterface *system_config_interface_c { /* interface implementation */ system_config_interface_class->get_connections = get_connections; + system_config_interface_class->reload_connections = reload_connections; system_config_interface_class->add_connection = add_connection; system_config_interface_class->get_unmanaged_specs = get_unmanaged_specs; }