core: move handling of hostname from plugins to core
authorBeniamino Galvani <bgalvani@redhat.com>
Mon, 23 Mar 2015 08:15:47 +0000 (09:15 +0100)
committerBeniamino Galvani <bgalvani@redhat.com>
Fri, 12 Jun 2015 13:59:39 +0000 (15:59 +0200)
How to write and read the machine hostname is something that has been
handled until now by plugins; this is questionable since the method
using for storing the hostname should depend only on the distro used
and not on which plugins are enabled.

This commit moves all hostname-related functions from plugins to the
core and allows to specify the method used to load and store the
hostname at build time with the

  --with-hostname-persist=default|suse|gentoo

configure option.

'default' method stores the hostname to /etc/hostname and monitors it
to detect runtime changes.

When the selected method is 'suse', the hostname gets read from and
written to /etc/HOSTNAME; the file /etc/sysconfig/network/dhcp is also
read to detect if the hostname is dynamic and thus invalid. Both files
are monitored for changes.

'gentoo' method relies on /etc/conf.d/hostname for storing the
hostname.

configure.ac
src/settings/nm-settings.c

index 442f25a..7b1f9eb 100644 (file)
@@ -119,6 +119,13 @@ if test -z "$config_plugins_default" -o "$config_plugins_default" = no; then
     test "$enable_config_plugin_ibft" = "yes" && config_plugins_default="$config_plugins_default,ibft"
     config_plugins_default="${config_plugins_default#,}"
 fi
+
+test "$enable_ifcfg_rh" = "yes"           && distro_plugins="$distro_plugins,ifcfg-rh"
+test "$enable_ifcfg_suse" = "yes"         && distro_plugins="$distro_plugins,ifcfg-suse"
+test "$enable_ifupdown" = "yes"           && distro_plugins="$distro_plugins,ifupdown"
+test "$enable_ifnet" = "yes"              && distro_plugins="$distro_plugins,ifnet"
+distro_plugins="${distro_plugins#,}"
+
 AC_DEFINE_UNQUOTED(CONFIG_PLUGINS_DEFAULT, "$config_plugins_default", [Default configuration option for main.plugins setting])
 
 if test "$enable_ifcfg_rh" = "yes"; then
@@ -347,6 +354,24 @@ fi
 PKG_CHECK_MODULES(SYSTEMD_200, [systemd >= 200], [have_systemd_200=yes],[have_systemd_200=no])
 AM_CONDITIONAL(HAVE_SYSTEMD_200, test "${have_systemd_200}" = "yes")
 
+# Hostname persist mode
+AC_ARG_WITH(hostname-persist, AS_HELP_STRING([--with-hostname-persist=default|suse|gentoo],
+       [Hostname persist method]))
+
+AS_IF([test "$with_hostname_persist" = "suse"], hostname_persist=suse)
+AS_IF([test "$with_hostname_persist" = "gentoo"], hostname_persist=gentoo)
+AS_IF([test "$with_hostname_persist" = "default"], hostname_persist=default)
+# if the method was not explicitly set, try to guess it from the enabled plugins
+AS_IF([test -z "$hostname_persist" -a "$distro_plugins" = "ifcfg-suse"], hostname_persist=suse)
+AS_IF([test -z "$hostname_persist" -a "$distro_plugins" = "ifnet"], hostname_persist=gentoo)
+AS_IF([test -z "$hostname_persist"], hostname_persist=default)
+
+if test "$hostname_persist" = suse; then
+       AC_DEFINE(HOSTNAME_PERSIST_SUSE, 1, [Enable SuSE hostname persist method])
+elif test "$hostname_persist" = gentoo; then
+       AC_DEFINE(HOSTNAME_PERSIST_GENTOO, 1, [Enable Gentoo hostname persist method])
+fi
+
 # Session tracking support
 AC_ARG_WITH(systemd-logind, AS_HELP_STRING([--with-systemd-logind=yes|no],
        [Support systemd session tracking]))
@@ -1063,6 +1088,7 @@ else
 fi
 echo "  polkit agent: ${enable_polkit_agent}"
 echo "  selinux: $have_selinux"
+echo "  hostname persist: ${hostname_persist}"
 echo
 
 echo "Features:"
index a1e1d9b..e921755 100644 (file)
 #include <dbus/dbus.h>
 #include <dbus/dbus-glib-lowlevel.h>
 
+#if HAVE_SELINUX
+#include <selinux/selinux.h>
+#endif
+
 #include "gsystem-local-alloc.h"
 #include <nm-dbus-interface.h>
 #include <nm-connection.h>
@@ -94,6 +98,20 @@ EXPORT(nm_settings_connection_replace_settings)
 EXPORT(nm_settings_connection_replace_and_commit)
 /* END LINKER CRACKROCK */
 
+#define HOSTNAME_FILE_DEFAULT   "/etc/hostname"
+#define HOSTNAME_FILE_SUSE      "/etc/HOSTNAME"
+#define HOSTNAME_FILE_GENTOO    "/etc/conf.d/hostname"
+#define IFCFG_DIR               SYSCONFDIR "/sysconfig/network"
+#define CONF_DHCP               IFCFG_DIR "/dhcp"
+
+#if defined(HOSTNAME_PERSIST_SUSE)
+#define HOSTNAME_FILE           HOSTNAME_FILE_SUSE
+#elif defined(HOSTNAME_PERSIST_GENTOO)
+#define HOSTNAME_FILE           HOSTNAME_FILE_GENTOO
+#else
+#define HOSTNAME_FILE           HOSTNAME_FILE_DEFAULT
+#endif
+
 static void claim_connection (NMSettings *self,
                               NMSettingsConnection *connection);
 
@@ -152,6 +170,15 @@ typedef struct {
        GSList *get_connections_cache;
 
        gboolean startup_complete;
+
+       struct {
+               char *value;
+               char *file;
+               GFileMonitor *monitor;
+               GFileMonitor *dhcp_monitor;
+               guint monitor_id;
+               guint dhcp_monitor_id;
+       } hostname;
 } NMSettingsPrivate;
 
 #define NM_SETTINGS_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), NM_TYPE_SETTINGS, NMSettingsPrivate))
@@ -497,7 +524,7 @@ get_plugin (NMSettings *self, guint32 capability)
 
        g_return_val_if_fail (self != NULL, NULL);
 
-       /* Do any of the plugins support setting the hostname? */
+       /* Do any of the plugins support the given capability? */
        for (iter = priv->plugins; iter; iter = iter->next) {
                NMSystemConfigInterfaceCapabilities caps = NM_SYSTEM_CONFIG_INTERFACE_CAP_NONE;
 
@@ -509,30 +536,88 @@ get_plugin (NMSettings *self, guint32 capability)
        return NULL;
 }
 
+#if defined(HOSTNAME_PERSIST_GENTOO)
+static gchar *
+read_hostname_gentoo (const char *path)
+{
+       gchar *contents = NULL, *result = NULL, *tmp;
+       gchar **all_lines = NULL;
+       guint line_num, i;
+
+       if (!g_file_get_contents (path, &contents, NULL, NULL))
+               return NULL;
+       all_lines = g_strsplit (contents, "\n", 0);
+       line_num = g_strv_length (all_lines);
+       for (i = 0; i < line_num; i++) {
+               g_strstrip (all_lines[i]);
+               if (all_lines[i][0] == '#' || all_lines[i][0] == '\0')
+                       continue;
+               if (g_str_has_prefix (all_lines[i], "hostname=")) {
+                       tmp = &all_lines[i][STRLEN ("hostname=")];
+                       result = g_shell_unquote (tmp, NULL);
+                       break;
+               }
+       }
+       g_strfreev (all_lines);
+       g_free (contents);
+       return result;
+}
+#endif
+
+#if defined(HOSTNAME_PERSIST_SUSE)
+static gboolean
+hostname_is_dynamic (void)
+{
+       GIOChannel *channel;
+       char *str = NULL;
+       gboolean dynamic = FALSE;
+
+       channel = g_io_channel_new_file (CONF_DHCP, "r", NULL);
+       if (!channel)
+               return dynamic;
+
+       while (g_io_channel_read_line (channel, &str, NULL, NULL, NULL) != G_IO_STATUS_EOF) {
+               if (str) {
+                       g_strstrip (str);
+                       if (g_str_has_prefix (str, "DHCLIENT_SET_HOSTNAME="))
+                               dynamic = strcmp (&str[STRLEN ("DHCLIENT_SET_HOSTNAME=")], "\"yes\"") == 0;
+                       g_free (str);
+               }
+       }
+
+       g_io_channel_shutdown (channel, FALSE, NULL);
+       g_io_channel_unref (channel);
+
+       return dynamic;
+}
+#endif
+
 /* Returns an allocated string which the caller owns and must eventually free */
 char *
 nm_settings_get_hostname (NMSettings *self)
 {
        NMSettingsPrivate *priv = NM_SETTINGS_GET_PRIVATE (self);
-       GSList *iter;
        char *hostname = NULL;
 
-       /* Hostname returned is the hostname returned from the first plugin
-        * that provides one.
-        */
-       for (iter = priv->plugins; iter; iter = iter->next) {
-               NMSystemConfigInterfaceCapabilities caps = NM_SYSTEM_CONFIG_INTERFACE_CAP_NONE;
+#if defined(HOSTNAME_PERSIST_GENTOO)
+       hostname = read_hostname_gentoo (priv->hostname.file);
+#else
 
-               g_object_get (G_OBJECT (iter->data), NM_SYSTEM_CONFIG_INTERFACE_CAPABILITIES, &caps, NULL);
-               if (caps & NM_SYSTEM_CONFIG_INTERFACE_CAP_MODIFY_HOSTNAME) {
-                       g_object_get (G_OBJECT (iter->data), NM_SYSTEM_CONFIG_INTERFACE_HOSTNAME, &hostname, NULL);
-                       if (hostname && strlen (hostname))
-                               return hostname;
-                       g_free (hostname);
-               }
+#if defined(HOSTNAME_PERSIST_SUSE)
+       if (priv->hostname.dhcp_monitor_id && hostname_is_dynamic ())
+               return NULL;
+#endif
+       if (g_file_get_contents (priv->hostname.file, &hostname, NULL, NULL))
+               g_strchomp (hostname);
+
+#endif /* HOSTNAME_PERSIST_GENTOO */
+
+       if (hostname && !hostname[0]) {
+               g_free (hostname);
+               hostname = NULL;
        }
 
-       return NULL;
+       return hostname;
 }
 
 static gboolean
@@ -595,14 +680,6 @@ unrecognized_specs_changed (NMSystemConfigInterface *config,
                      nm_system_config_interface_get_unrecognized_specs);
 }
 
-static void
-hostname_changed (NMSystemConfigInterface *config,
-                  GParamSpec *pspec,
-                  gpointer user_data)
-{
-       g_object_notify (G_OBJECT (user_data), NM_SETTINGS_HOSTNAME);
-}
-
 static void
 add_plugin (NMSettings *self, NMSystemConfigInterface *plugin)
 {
@@ -616,9 +693,6 @@ add_plugin (NMSettings *self, NMSystemConfigInterface *plugin)
        priv = NM_SETTINGS_GET_PRIVATE (self);
 
        priv->plugins = g_slist_append (priv->plugins, g_object_ref (plugin));
-
-       g_signal_connect (plugin, "notify::"NM_SYSTEM_CONFIG_INTERFACE_HOSTNAME, G_CALLBACK (hostname_changed), self);
-
        nm_system_config_interface_init (plugin, NULL);
 
        g_object_get (G_OBJECT (plugin),
@@ -1442,6 +1516,56 @@ impl_settings_reload_connections (NMSettings *self,
        dbus_g_method_return (context, TRUE);
 }
 
+static gboolean
+write_hostname (NMSettingsPrivate *priv, const char *hostname)
+{
+       char *hostname_eol;
+       gboolean ret;
+       gs_free_error GError *error = NULL;
+       const char *file = priv->hostname.file;
+#if HAVE_SELINUX
+       security_context_t se_ctx_prev = NULL, se_ctx = NULL;
+       struct stat file_stat = { .st_mode = 0 };
+       mode_t st_mode = 0;
+
+       /* Get default context for hostname file and set it for fscreate */
+       if (stat (file, &file_stat) == 0)
+               st_mode = file_stat.st_mode;
+       matchpathcon (file, st_mode, &se_ctx);
+       matchpathcon_fini ();
+       getfscreatecon (&se_ctx_prev);
+       setfscreatecon (se_ctx);
+#endif
+
+#if defined (HOSTNAME_PERSIST_GENTOO)
+       hostname_eol = g_strdup_printf ("#Generated by NetworkManager\n"
+                                       "hostname=\"%s\"\n", hostname);
+#else
+       hostname_eol = g_strdup_printf ("%s\n", hostname);
+#endif
+
+       /* FIXME: g_file_set_contents() writes first to a temporary file
+        * and renames it atomically. We should hack g_file_set_contents()
+        * to set the SELINUX labels before renaming the file. */
+       ret = g_file_set_contents (file, hostname_eol, -1, &error);
+
+#if HAVE_SELINUX
+       /* Restore previous context and cleanup */
+       setfscreatecon (se_ctx_prev);
+       freecon (se_ctx);
+       freecon (se_ctx_prev);
+#endif
+
+       g_free (hostname_eol);
+
+       if (!ret) {
+               nm_log_warn (LOGD_SETTINGS, "Could not save hostname to %s: %s", file, error->message);
+               return FALSE;
+       }
+
+       return TRUE;
+}
+
 static void
 pk_hostname_cb (NMAuthChain *chain,
                 GError *chain_error,
@@ -1452,7 +1576,6 @@ pk_hostname_cb (NMAuthChain *chain,
        NMSettingsPrivate *priv = NM_SETTINGS_GET_PRIVATE (self);
        NMAuthCallResult result;
        GError *error = NULL;
-       GSList *iter;
        const char *hostname;
 
        g_assert (context);
@@ -1472,21 +1595,12 @@ pk_hostname_cb (NMAuthChain *chain,
                                             NM_SETTINGS_ERROR_PERMISSION_DENIED,
                                             "Insufficient privileges.");
        } else {
-               /* Set the hostname in all plugins */
                hostname = nm_auth_chain_get_data (chain, "hostname");
-               for (iter = priv->plugins; iter; iter = iter->next) {
-                       NMSystemConfigInterfaceCapabilities caps = NM_SYSTEM_CONFIG_INTERFACE_CAP_NONE;
 
-                       /* error will be cleared if any plugin supports saving the hostname */
+               if (!write_hostname (priv, hostname)) {
                        error = g_error_new_literal (NM_SETTINGS_ERROR,
                                                     NM_SETTINGS_ERROR_FAILED,
                                                     "Saving the hostname failed.");
-
-                       g_object_get (G_OBJECT (iter->data), NM_SYSTEM_CONFIG_INTERFACE_CAPABILITIES, &caps, NULL);
-                       if (caps & NM_SYSTEM_CONFIG_INTERFACE_CAP_MODIFY_HOSTNAME) {
-                               g_object_set (G_OBJECT (iter->data), NM_SYSTEM_CONFIG_INTERFACE_HOSTNAME, hostname, NULL);
-                               g_clear_error (&error);
-                       }
                }
        }
 
@@ -1543,14 +1657,6 @@ impl_settings_save_hostname (NMSettings *self,
                goto done;
        }
 
-       /* Do any of the plugins support setting the hostname? */
-       if (!get_plugin (self, NM_SYSTEM_CONFIG_INTERFACE_CAP_MODIFY_HOSTNAME)) {
-               error = g_error_new_literal (NM_SETTINGS_ERROR,
-                                            NM_SETTINGS_ERROR_NOT_SUPPORTED,
-                                            "None of the registered plugins support setting the hostname.");
-               goto done;
-       }
-
        chain = nm_auth_chain_new_context (context, pk_hostname_cb, self);
        if (!chain) {
                error = g_error_new_literal (NM_SETTINGS_ERROR,
@@ -1569,6 +1675,37 @@ done:
        g_clear_error (&error);
 }
 
+static void
+hostname_maybe_changed (NMSettings *settings)
+{
+       NMSettingsPrivate *priv = NM_SETTINGS_GET_PRIVATE (settings);
+       char *new_hostname;
+
+       new_hostname = nm_settings_get_hostname (settings);
+
+       if (   (new_hostname && !priv->hostname.value)
+           || (!new_hostname && priv->hostname.value)
+           || (priv->hostname.value && new_hostname && strcmp (priv->hostname.value, new_hostname))) {
+
+               nm_log_info (LOGD_SETTINGS, "hostname changed from '%s' to '%s'",
+                            priv->hostname.value, new_hostname);
+               g_free (priv->hostname.value);
+               priv->hostname.value = new_hostname;
+               g_object_notify (G_OBJECT (settings), NM_SETTINGS_HOSTNAME);
+       } else
+               g_free (new_hostname);
+}
+
+static void
+hostname_file_changed_cb (GFileMonitor *monitor,
+                          GFile *file,
+                          GFile *other_file,
+                          GFileMonitorEvent event_type,
+                          gpointer user_data)
+{
+       hostname_maybe_changed (user_data);
+}
+
 static gboolean
 have_connection_for_device (NMSettings *self, NMDevice *device)
 {
@@ -1872,6 +2009,8 @@ nm_settings_new (GError **error)
 {
        NMSettings *self;
        NMSettingsPrivate *priv;
+       GFile *file;
+       GFileMonitor *monitor;
 
        self = g_object_new (NM_TYPE_SETTINGS, NULL);
 
@@ -1889,6 +2028,34 @@ nm_settings_new (GError **error)
        load_connections (self);
        check_startup_complete (self);
 
+       priv->hostname.file = HOSTNAME_FILE;
+       priv->hostname.value = nm_settings_get_hostname (self);
+
+       /* monitor changes to hostname file */
+       file = g_file_new_for_path (priv->hostname.file);
+       monitor = g_file_monitor_file (file, G_FILE_MONITOR_NONE, NULL, NULL);
+       g_object_unref (file);
+       if (monitor) {
+               priv->hostname.monitor_id = g_signal_connect (monitor, "changed",
+                                                             G_CALLBACK (hostname_file_changed_cb),
+                                                             self);
+               priv->hostname.monitor = monitor;
+       }
+
+#if defined (HOSTNAME_PERSIST_SUSE)
+       /* monitor changes to dhcp file to know whether the hostname is valid */
+       file = g_file_new_for_path (CONF_DHCP);
+       monitor = g_file_monitor_file (file, G_FILE_MONITOR_NONE, NULL, NULL);
+       g_object_unref (file);
+       if (monitor) {
+               priv->hostname.dhcp_monitor_id = g_signal_connect (monitor, "changed",
+                                                                  G_CALLBACK (hostname_file_changed_cb),
+                                                                  self);
+               priv->hostname.dhcp_monitor = monitor;
+       }
+#endif
+
+       hostname_maybe_changed (self);
        nm_dbus_manager_register_object (priv->dbus_mgr, NM_DBUS_PATH_SETTINGS, self);
        return self;
 }
@@ -1932,6 +2099,25 @@ dispose (GObject *object)
 
        g_object_unref (priv->agent_mgr);
 
+       if (priv->hostname.monitor) {
+               if (priv->hostname.monitor_id)
+                       g_signal_handler_disconnect (priv->hostname.monitor, priv->hostname.monitor_id);
+
+               g_file_monitor_cancel (priv->hostname.monitor);
+               g_clear_object (&priv->hostname.monitor);
+       }
+
+       if (priv->hostname.dhcp_monitor) {
+               if (priv->hostname.dhcp_monitor_id)
+                       g_signal_handler_disconnect (priv->hostname.dhcp_monitor,
+                                                    priv->hostname.dhcp_monitor_id);
+
+               g_file_monitor_cancel (priv->hostname.dhcp_monitor);
+               g_clear_object (&priv->hostname.dhcp_monitor);
+       }
+
+       g_clear_pointer (&priv->hostname.value, g_free);
+
        G_OBJECT_CLASS (nm_settings_parent_class)->dispose (object);
 }
 
@@ -2001,7 +2187,7 @@ static void
 nm_settings_class_init (NMSettingsClass *class)
 {
        GObjectClass *object_class = G_OBJECT_CLASS (class);
-       
+
        g_type_class_add_private (class, sizeof (NMSettingsPrivate));
 
        /* virtual methods */