From ee3ece9dac985034c5c1f81a6769b40fd7856579 Mon Sep 17 00:00:00 2001 From: Dan Williams Date: Sat, 22 May 2010 15:55:30 +0000 Subject: core: add networking enable/disable knob distinct from sleep/wake (rh #589108) (bgo #346615) Since forever we've used sleep/wake as the way to implement Networking Enabled. When the state file was introduced to make the networking and wifi states persistent, we ran into a bug where a failed suspend (like if the machine ran out of power while suspended) would result in networking being disabled on reboot since suspend/resume used the same knob as enable/disable. This patch adds a distinct call for enable/disable networking which changes the state file, while sleep/wake no longer change the state file. --- diff --git a/introspection/nm-manager-client.xml b/introspection/nm-manager-client.xml index 9a0d4da..7a9e311 100644 --- a/introspection/nm-manager-client.xml +++ b/introspection/nm-manager-client.xml @@ -39,6 +39,12 @@ object. dbus-glib generates the same bound function names for D-Bus the methods + + + + + + diff --git a/introspection/nm-manager.xml b/introspection/nm-manager.xml index 3adbd9c..46db5af 100644 --- a/introspection/nm-manager.xml +++ b/introspection/nm-manager.xml @@ -87,7 +87,10 @@ - Control the NetworkManager daemon's sleep state. When asleep, all interfaces that it manages are deactivated. When awake, devices are available to be activated. + Control the NetworkManager daemon's sleep state. When asleep, all + interfaces that it manages are deactivated. When awake, devices are + available to be activated. This command should not be called directly + by users or clients; it is intended for system suspend/resume tracking. @@ -96,6 +99,23 @@ + + + + Control whether overall networking is enabled or disabled. When + disabled, all interfaces that NM manages are deactivated. When enabled, + all managed interfaces are re-enabled and available to be activated. + This command should be used by clients that provide to users the ability + to enable/disable all networking. + + + + If FALSE, indicates that all networking should be disabled. If TRUE, + indicates that NetworkManager should begin managing network devices. + + + + @@ -118,6 +138,13 @@ + + + Indicates if overall networking is currently enabled or not. See the + Enable() method. + + + Indicates if wireless is currently enabled or not. diff --git a/libnm-glib/libnm-glib.ver b/libnm-glib/libnm-glib.ver index 47312d0..6d48fb3 100644 --- a/libnm-glib/libnm-glib.ver +++ b/libnm-glib/libnm-glib.ver @@ -36,6 +36,8 @@ global: nm_client_get_manager_running; nm_client_get_state; nm_client_get_type; + nm_client_networking_get_enabled; + nm_client_networking_set_enabled; nm_client_new; nm_client_sleep; nm_client_wireless_get_enabled; diff --git a/libnm-glib/nm-client.c b/libnm-glib/nm-client.c index 05fa4db..dd10e24 100644 --- a/libnm-glib/nm-client.c +++ b/libnm-glib/nm-client.c @@ -18,7 +18,7 @@ * Boston, MA 02110-1301 USA. * * Copyright (C) 2007 - 2008 Novell, Inc. - * Copyright (C) 2007 - 2008 Red Hat, Inc. + * Copyright (C) 2007 - 2010 Red Hat, Inc. */ #include @@ -58,6 +58,7 @@ typedef struct { GPtrArray *devices; GPtrArray *active_connections; + gboolean networking_enabled; gboolean wireless_enabled; gboolean wireless_hw_enabled; @@ -69,6 +70,7 @@ enum { PROP_0, PROP_STATE, PROP_MANAGER_RUNNING, + PROP_NETWORKING_ENABLED, PROP_WIRELESS_ENABLED, PROP_WIRELESS_HARDWARE_ENABLED, PROP_WWAN_ENABLED, @@ -250,6 +252,7 @@ register_for_property_changed (NMClient *client) NMClientPrivate *priv = NM_CLIENT_GET_PRIVATE (client); const NMPropertiesChangedInfo property_changed_info[] = { { NM_CLIENT_STATE, _nm_object_demarshal_generic, &priv->state }, + { NM_CLIENT_NETWORKING_ENABLED, _nm_object_demarshal_generic, &priv->networking_enabled }, { NM_CLIENT_WIRELESS_ENABLED, _nm_object_demarshal_generic, &priv->wireless_enabled }, { NM_CLIENT_WIRELESS_HARDWARE_ENABLED, _nm_object_demarshal_generic, &priv->wireless_hw_enabled }, { NM_CLIENT_WWAN_ENABLED, _nm_object_demarshal_generic, &priv->wwan_enabled }, @@ -427,6 +430,9 @@ get_property (GObject *object, case PROP_MANAGER_RUNNING: g_value_set_boolean (value, priv->manager_running); break; + case PROP_NETWORKING_ENABLED: + g_value_set_boolean (value, priv->networking_enabled); + break; case PROP_WIRELESS_ENABLED: g_value_set_boolean (value, priv->wireless_enabled); break; @@ -490,6 +496,19 @@ nm_client_class_init (NMClientClass *client_class) G_PARAM_READABLE)); /** + * NMClient::networking-enabled: + * + * Whether networking is enabled. + **/ + g_object_class_install_property + (object_class, PROP_NETWORKING_ENABLED, + g_param_spec_boolean (NM_CLIENT_NETWORKING_ENABLED, + "NetworkingEnabled", + "Is networking enabled", + TRUE, + G_PARAM_READABLE)); + + /** * NMClient::wireless-enabled: * * Whether wireless is enabled. @@ -1042,27 +1061,41 @@ nm_client_get_state (NMClient *client) } /** - * nm_client_sleep: + * nm_client_networking_set_enabled: * @client: a #NMClient - * @sleep: %TRUE to put the daemon to sleep + * @enabled: %TRUE to set networking enabled, %FALSE to set networking disabled * - * Enables or disables networking. When the daemon is put to sleep, it'll deactivate and disable - * all the active devices. + * Enables or disables networking. When networking is disabled, all controlled + * interfaces are disconnected and deactivated. When networking is enabled, + * all controlled interfaces are available for activation. **/ void -nm_client_sleep (NMClient *client, gboolean sleep) +nm_client_networking_set_enabled (NMClient *client, gboolean enable) { GError *err = NULL; g_return_if_fail (NM_IS_CLIENT (client)); - if (!org_freedesktop_NetworkManager_sleep (NM_CLIENT_GET_PRIVATE (client)->client_proxy, sleep, &err)) { - g_warning ("Error in sleep: %s", err->message); + if (!org_freedesktop_NetworkManager_enable (NM_CLIENT_GET_PRIVATE (client)->client_proxy, enable, &err)) { + g_warning ("Error enabling/disabling networking: %s", err->message); g_error_free (err); } } /** + * nm_client_sleep: + * @client: a #NMClient + * @sleep: %TRUE to put the daemon to sleep + * + * Deprecated; use nm_client_networking_set_enabled() instead. + **/ +void +nm_client_sleep (NMClient *client, gboolean sleep) +{ + nm_client_networking_set_enabled (client, !sleep); +} + +/** * nm_client_get_manager_running: * @client: a #NMClient * diff --git a/libnm-glib/nm-client.h b/libnm-glib/nm-client.h index 6aafc08..6b912e0 100644 --- a/libnm-glib/nm-client.h +++ b/libnm-glib/nm-client.h @@ -18,7 +18,7 @@ * Boston, MA 02110-1301 USA. * * Copyright (C) 2007 - 2008 Novell, Inc. - * Copyright (C) 2007 - 2008 Red Hat, Inc. + * Copyright (C) 2007 - 2010 Red Hat, Inc. */ #ifndef NM_CLIENT_H @@ -43,6 +43,7 @@ G_BEGIN_DECLS #define NM_CLIENT_STATE "state" #define NM_CLIENT_MANAGER_RUNNING "manager-running" +#define NM_CLIENT_NETWORKING_ENABLED "networking-enabled" #define NM_CLIENT_WIRELESS_ENABLED "wireless-enabled" #define NM_CLIENT_WIRELESS_HARDWARE_ENABLED "wireless-hardware-enabled" #define NM_CLIENT_WWAN_ENABLED "wwan-enabled" @@ -88,6 +89,9 @@ void nm_client_activate_connection (NMClient *client, void nm_client_deactivate_connection (NMClient *client, NMActiveConnection *active); +gboolean nm_client_networking_get_enabled (NMClient *client); +void nm_client_networking_set_enabled (NMClient *client, gboolean enabled); + gboolean nm_client_wireless_get_enabled (NMClient *client); void nm_client_wireless_set_enabled (NMClient *client, gboolean enabled); gboolean nm_client_wireless_hardware_get_enabled (NMClient *client); diff --git a/src/nm-manager.c b/src/nm-manager.c index f158e50..9655567 100644 --- a/src/nm-manager.c +++ b/src/nm-manager.c @@ -58,13 +58,6 @@ #define NM_AUTOIP_DBUS_SERVICE "org.freedesktop.nm_avahi_autoipd" #define NM_AUTOIP_DBUS_IFACE "org.freedesktop.nm_avahi_autoipd" -#define NM_MANAGER_STATE "state" -#define NM_MANAGER_WIRELESS_ENABLED "wireless-enabled" -#define NM_MANAGER_WIRELESS_HARDWARE_ENABLED "wireless-hardware-enabled" -#define NM_MANAGER_WWAN_ENABLED "wwan-enabled" -#define NM_MANAGER_WWAN_HARDWARE_ENABLED "wwan-hardware-enabled" -#define NM_MANAGER_ACTIVE_CONNECTIONS "active-connections" - static gboolean impl_manager_get_devices (NMManager *manager, GPtrArray **devices, GError **err); static void impl_manager_activate_connection (NMManager *manager, const char *service_name, @@ -79,6 +72,8 @@ static gboolean impl_manager_deactivate_connection (NMManager *manager, static gboolean impl_manager_sleep (NMManager *manager, gboolean sleep, GError **err); +static gboolean impl_manager_enable (NMManager *manager, gboolean enable, GError **err); + static gboolean impl_manager_set_logging (NMManager *manager, const char *level, const char *domains, @@ -190,6 +185,7 @@ typedef struct { RadioState radio_states[RFKILL_TYPE_MAX]; gboolean sleeping; + gboolean net_enabled; NMVPNManager *vpn_manager; guint vpn_manager_id; @@ -228,6 +224,7 @@ static guint signals[LAST_SIGNAL] = { 0 }; enum { PROP_0, PROP_STATE, + PROP_NETWORKING_ENABLED, PROP_WIRELESS_ENABLED, PROP_WIRELESS_HARDWARE_ENABLED, PROP_WWAN_ENABLED, @@ -251,6 +248,7 @@ typedef enum NM_MANAGER_ERROR_PERMISSION_DENIED, NM_MANAGER_ERROR_CONNECTION_NOT_ACTIVE, NM_MANAGER_ERROR_ALREADY_ASLEEP_OR_AWAKE, + NM_MANAGER_ERROR_ALREADY_ENABLED_OR_DISABLED, } NMManagerError; #define NM_MANAGER_ERROR (nm_manager_error_quark ()) @@ -293,6 +291,8 @@ nm_manager_error_get_type (void) ENUM_ENTRY (NM_MANAGER_ERROR_CONNECTION_NOT_ACTIVE, "ConnectionNotActive"), /* The manager is already in the requested sleep state */ ENUM_ENTRY (NM_MANAGER_ERROR_ALREADY_ASLEEP_OR_AWAKE, "AlreadyAsleepOrAwake"), + /* The manager is already in the requested enabled/disabled state */ + ENUM_ENTRY (NM_MANAGER_ERROR_ALREADY_ENABLED_OR_DISABLED, "AlreadyEnabledOrDisabled"), { 0, 0, 0 }, }; etype = g_enum_register_static ("NMManagerError", values); @@ -300,6 +300,16 @@ nm_manager_error_get_type (void) return etype; } +static gboolean +manager_sleeping (NMManager *self) +{ + NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE (self); + + if (priv->sleeping || !priv->net_enabled) + return TRUE; + return FALSE; +} + static void vpn_manager_connection_deactivated_cb (NMVPNManager *manager, NMVPNConnection *vpn, @@ -372,9 +382,9 @@ nm_manager_update_state (NMManager *manager) priv = NM_MANAGER_GET_PRIVATE (manager); - if (priv->sleeping) { + if (manager_sleeping (manager)) new_state = NM_STATE_ASLEEP; - } else { + else { GSList *iter; for (iter = priv->devices; iter; iter = iter->next) { @@ -1239,7 +1249,7 @@ manager_set_radio_enabled (NMManager *manager, } /* Don't touch devices if asleep/networking disabled */ - if (priv->sleeping) + if (manager_sleeping (manager)) return; /* enable/disable wireless devices as required */ @@ -1559,7 +1569,7 @@ add_device (NMManager *self, NMDevice *device) /* Start the device if it's supposed to be managed */ unmanaged_specs = nm_sysconfig_settings_get_unmanaged_specs (priv->sys_settings); - if ( !priv->sleeping + if ( !manager_sleeping (self) && !nm_device_interface_spec_match_list (NM_DEVICE_INTERFACE (device), unmanaged_specs)) { nm_device_set_managed (device, TRUE, @@ -1810,7 +1820,6 @@ udev_device_added_cb (NMUdevManager *udev_mgr, gpointer user_data) { NMManager *self = NM_MANAGER (user_data); - NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE (self); GObject *device; guint32 ifindex; @@ -1818,7 +1827,7 @@ udev_device_added_cb (NMUdevManager *udev_mgr, if (find_device_by_ifindex (self, ifindex)) return; - device = creator_fn (udev_mgr, udev_device, priv->sleeping); + device = creator_fn (udev_mgr, udev_device, manager_sleeping (self)); if (device) add_device (self, NM_DEVICE (device)); } @@ -2646,54 +2655,25 @@ impl_manager_deactivate_connection (NMManager *manager, error); } -static gboolean -impl_manager_sleep (NMManager *self, gboolean sleep, GError **error) +static void +do_sleep_wake (NMManager *self) { - NMManagerPrivate *priv; + NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE (self); + const GSList *unmanaged_specs; GSList *iter; - g_return_val_if_fail (NM_IS_MANAGER (self), FALSE); - - priv = NM_MANAGER_GET_PRIVATE (self); - - if (priv->sleeping == sleep) { - g_set_error (error, - NM_MANAGER_ERROR, NM_MANAGER_ERROR_ALREADY_ASLEEP_OR_AWAKE, - "Already %s", sleep ? "asleep" : "awake"); - return FALSE; - } - - priv->sleeping = sleep; - - /* Update "NetworkingEnabled" key in state file */ - if (priv->state_file) { - GError *err = NULL; - gboolean networking_enabled = !sleep; - - if (!write_value_to_state_file (priv->state_file, - "main", "NetworkingEnabled", - G_TYPE_BOOLEAN, (gpointer) &networking_enabled, - &err)) { - nm_log_warn (LOGD_SUSPEND, "writing to state file %s failed: (%d) %s.", - priv->state_file, - err ? err->code : -1, - (err && err->message) ? err->message : "unknown"); - } - - } - - if (sleep) { - nm_log_info (LOGD_SUSPEND, "sleeping..."); + if (manager_sleeping (self)) { + nm_log_info (LOGD_SUSPEND, "sleeping or disabling..."); /* Just deactivate and down all devices from the device list, - * we'll remove them in 'wake' for speed's sake. + * to keep things fast the device list will get resynced when + * the manager wakes up. */ for (iter = priv->devices; iter; iter = iter->next) nm_device_set_managed (NM_DEVICE (iter->data), FALSE, NM_DEVICE_STATE_REASON_SLEEPING); - } else { - const GSList *unmanaged_specs; - nm_log_info (LOGD_SUSPEND, "waking up..."); + } else { + nm_log_info (LOGD_SUSPEND, "waking up and re-enabling..."); unmanaged_specs = nm_sysconfig_settings_get_unmanaged_specs (priv->sys_settings); @@ -2738,11 +2718,82 @@ impl_manager_sleep (NMManager *self, gboolean sleep, GError **error) } nm_manager_update_state (self); +} + +static gboolean +impl_manager_sleep (NMManager *self, gboolean sleep, GError **error) +{ + NMManagerPrivate *priv; + + g_return_val_if_fail (NM_IS_MANAGER (self), FALSE); + + priv = NM_MANAGER_GET_PRIVATE (self); + + if (priv->sleeping == sleep) { + g_set_error (error, + NM_MANAGER_ERROR, NM_MANAGER_ERROR_ALREADY_ASLEEP_OR_AWAKE, + "Already %s", sleep ? "asleep" : "awake"); + return FALSE; + } + + nm_log_info (LOGD_SUSPEND, "%s requested (sleeping: %s enabled: %s)", + sleep ? "sleep" : "wake", + priv->sleeping ? "yes" : "no", + priv->net_enabled ? "yes" : "no"); + + priv->sleeping = sleep; + + do_sleep_wake (self); g_object_notify (G_OBJECT (self), NM_MANAGER_SLEEPING); return TRUE; } +static gboolean +impl_manager_enable (NMManager *self, gboolean enable, GError **error) +{ + NMManagerPrivate *priv; + + g_return_val_if_fail (NM_IS_MANAGER (self), FALSE); + + priv = NM_MANAGER_GET_PRIVATE (self); + + if (priv->net_enabled == enable) { + g_set_error (error, + NM_MANAGER_ERROR, NM_MANAGER_ERROR_ALREADY_ENABLED_OR_DISABLED, + "Already %s", enable ? "enabled" : "disabled"); + return FALSE; + } + + /* Update "NetworkingEnabled" key in state file */ + if (priv->state_file) { + GError *err = NULL; + + if (!write_value_to_state_file (priv->state_file, + "main", "NetworkingEnabled", + G_TYPE_BOOLEAN, (gpointer) &enable, + &err)) { + /* Not a hard error */ + nm_log_warn (LOGD_SUSPEND, "writing to state file %s failed: (%d) %s.", + priv->state_file, + err ? err->code : -1, + (err && err->message) ? err->message : "unknown"); + } + } + + nm_log_info (LOGD_SUSPEND, "%s requested (sleeping: %s enabled: %s)", + enable ? "enable" : "disable", + priv->sleeping ? "yes" : "no", + priv->net_enabled ? "yes" : "no"); + + priv->net_enabled = enable; + + do_sleep_wake (self); + + g_object_notify (G_OBJECT (self), NM_MANAGER_NETWORKING_ENABLED); + return TRUE; +} + /* Legacy 0.6 compatibility interface */ static gboolean @@ -2910,9 +2961,9 @@ nm_manager_start (NMManager *self) manager_set_radio_enabled (self, rstate, rstate->enabled && enabled); } - /* Log overall networking status - asleep/running */ + /* Log overall networking status - enabled/disabled */ nm_log_info (LOGD_CORE, "Networking is %s by state file", - priv->sleeping ? "disabled" : "enabled"); + priv->net_enabled ? "enabled" : "disabled"); system_unmanaged_devices_changed_cb (priv->sys_settings, NULL, self); system_hostname_changed_cb (priv->sys_settings, NULL, self); @@ -2964,7 +3015,7 @@ nm_manager_get (const char *config_file, priv->state_file = g_strdup (state_file); - priv->sleeping = !initial_net_enabled; + priv->net_enabled = initial_net_enabled; priv->radio_states[RFKILL_TYPE_WLAN].enabled = initial_wifi_enabled; priv->radio_states[RFKILL_TYPE_WWAN].enabled = initial_wwan_enabled; @@ -3088,6 +3139,10 @@ set_property (GObject *object, guint prop_id, NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE (self); switch (prop_id) { + case PROP_NETWORKING_ENABLED: + /* Construct only for now */ + priv->net_enabled = g_value_get_boolean (value); + break; case PROP_WIRELESS_ENABLED: manager_set_radio_enabled (NM_MANAGER (object), &priv->radio_states[RFKILL_TYPE_WLAN], @@ -3116,6 +3171,9 @@ get_property (GObject *object, guint prop_id, nm_manager_update_state (self); g_value_set_uint (value, priv->state); break; + case PROP_NETWORKING_ENABLED: + g_value_set_boolean (value, priv->net_enabled); + break; case PROP_WIRELESS_ENABLED: g_value_set_boolean (value, priv->radio_states[RFKILL_TYPE_WLAN].enabled); break; @@ -3256,6 +3314,14 @@ nm_manager_class_init (NMManagerClass *manager_class) G_PARAM_READABLE)); g_object_class_install_property + (object_class, PROP_NETWORKING_ENABLED, + g_param_spec_boolean (NM_MANAGER_NETWORKING_ENABLED, + "NetworkingEnabled", + "Is networking enabled", + TRUE, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY)); + + g_object_class_install_property (object_class, PROP_WIRELESS_ENABLED, g_param_spec_boolean (NM_MANAGER_WIRELESS_ENABLED, "WirelessEnabled", @@ -3304,6 +3370,7 @@ nm_manager_class_init (NMManagerClass *manager_class) NULL, G_PARAM_READABLE | NM_PROPERTY_PARAM_NO_EXPORT)); + /* Sleeping is not exported over D-Bus */ g_object_class_install_property (object_class, PROP_SLEEPING, g_param_spec_boolean (NM_MANAGER_SLEEPING, diff --git a/src/nm-manager.h b/src/nm-manager.h index 1090409..0f4d72f 100644 --- a/src/nm-manager.h +++ b/src/nm-manager.h @@ -16,7 +16,7 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. * * Copyright (C) 2007 - 2008 Novell, Inc. - * Copyright (C) 2007 - 2008 Red Hat, Inc. + * Copyright (C) 2007 - 2010 Red Hat, Inc. */ #ifndef NM_MANAGER_H @@ -35,6 +35,14 @@ #define NM_IS_MANAGER_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((obj), NM_TYPE_MANAGER)) #define NM_MANAGER_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), NM_TYPE_MANAGER, NMManagerClass)) +#define NM_MANAGER_STATE "state" +#define NM_MANAGER_NETWORKING_ENABLED "networking-enabled" +#define NM_MANAGER_WIRELESS_ENABLED "wireless-enabled" +#define NM_MANAGER_WIRELESS_HARDWARE_ENABLED "wireless-hardware-enabled" +#define NM_MANAGER_WWAN_ENABLED "wwan-enabled" +#define NM_MANAGER_WWAN_HARDWARE_ENABLED "wwan-hardware-enabled" +#define NM_MANAGER_ACTIVE_CONNECTIONS "active-connections" + /* Not exported */ #define NM_MANAGER_HOSTNAME "hostname" #define NM_MANAGER_SLEEPING "sleeping" diff --git a/src/nm-policy.c b/src/nm-policy.c index a7bd96f..455545b 100644 --- a/src/nm-policy.c +++ b/src/nm-policy.c @@ -718,13 +718,14 @@ hostname_changed (NMManager *manager, GParamSpec *pspec, gpointer user_data) static void sleeping_changed (NMManager *manager, GParamSpec *pspec, gpointer user_data) { - gboolean sleeping = FALSE; + gboolean sleeping = FALSE, enabled = FALSE; GSList *connections, *iter; g_object_get (G_OBJECT (manager), NM_MANAGER_SLEEPING, &sleeping, NULL); + g_object_get (G_OBJECT (manager), NM_MANAGER_NETWORKING_ENABLED, &enabled, NULL); /* Clear the invalid flag on all connections so they'll get retried on wakeup */ - if (sleeping) { + if (sleeping || !enabled) { connections = nm_manager_get_connections (manager, NM_CONNECTION_SCOPE_SYSTEM); connections = g_slist_concat (connections, nm_manager_get_connections (manager, NM_CONNECTION_SCOPE_USER)); for (iter = connections; iter; iter = g_slist_next (iter)) @@ -1055,6 +1056,10 @@ nm_policy_new (NMManager *manager, NMVPNManager *vpn_manager) G_CALLBACK (sleeping_changed), policy); policy->signal_ids = g_slist_append (policy->signal_ids, (gpointer) id); + id = g_signal_connect (manager, "notify::" NM_MANAGER_NETWORKING_ENABLED, + G_CALLBACK (sleeping_changed), policy); + policy->signal_ids = g_slist_append (policy->signal_ids, (gpointer) id); + id = g_signal_connect (manager, "device-added", G_CALLBACK (device_added), policy); policy->signal_ids = g_slist_append (policy->signal_ids, (gpointer) id); -- cgit v0.8.3-6-g21f6