merge: branch 'lr/virtual-parent-bgo762731'
authorLubomir Rintel <lkundrak@v3.sk>
Sat, 26 Mar 2016 10:31:45 +0000 (11:31 +0100)
committerLubomir Rintel <lkundrak@v3.sk>
Sat, 26 Mar 2016 10:31:45 +0000 (11:31 +0100)
https://bugzilla.gnome.org/show_bug.cgi?id=762731

src/devices/nm-device.c
src/devices/nm-device.h
src/nm-active-connection.c
src/nm-active-connection.h
src/nm-manager.c

index f18b9c1..4f92ec1 100644 (file)
@@ -7735,7 +7735,7 @@ nm_device_queue_activation (NMDevice *self, NMActRequest *req)
 
        must_queue = _carrier_wait_check_act_request_must_queue (self, req);
 
-       if (!priv->act_request && !must_queue) {
+       if (!priv->act_request && !must_queue && nm_device_is_real (self)) {
                /* Just activate immediately */
                if (!_device_activate (self, req))
                        g_assert_not_reached ();
@@ -9689,39 +9689,55 @@ nm_device_recheck_available_connections (NMDevice *self)
 }
 
 /**
- * nm_device_get_available_connections:
+ * nm_device_get_best_connection:
  * @self: the #NMDevice
  * @specific_object: a specific object path if any
+ * @error: reason why no connection was returned
  *
- * Returns a list of connections available to activate on the device, taking
- * into account any device-specific details given by @specific_object (like
- * WiFi access point path).
+ * Returns a connection that's most suitable for user-initiated activation
+ * of a device, optionally with a given specific object.
  *
- * Returns: caller-owned #GPtrArray of #NMConnections
+ * Returns: the #NMSettingsConnection or %NULL (setting an @error)
  */
-GPtrArray *
-nm_device_get_available_connections (NMDevice *self, const char *specific_object)
+NMSettingsConnection *
+nm_device_get_best_connection (NMDevice *self,
+                               const char *specific_object,
+                               GError **error)
 {
        NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
+       NMSettingsConnection *connection = NULL;
+       NMSettingsConnection *candidate;
+       guint64 best_timestamp = 0;
        GHashTableIter iter;
-       guint num_available;
-       NMConnection *connection = NULL;
-       GPtrArray *array = NULL;
 
-       num_available = g_hash_table_size (priv->available_connections);
-       if (num_available > 0) {
-               array = g_ptr_array_sized_new (num_available);
-               g_hash_table_iter_init (&iter, priv->available_connections);
-               while (g_hash_table_iter_next (&iter, (gpointer) &connection, NULL)) {
-                       /* If a specific object is given, only include connections that are
-                        * compatible with it.
-                        */
-                       if (   !specific_object /* << Optimization: we know that the connection is available without @specific_object.  */
-                           || nm_device_check_connection_available (self, connection, _NM_DEVICE_CHECK_CON_AVAILABLE_FOR_USER_REQUEST, specific_object))
-                               g_ptr_array_add (array, connection);
+       g_hash_table_iter_init (&iter, priv->available_connections);
+       while (g_hash_table_iter_next (&iter, (gpointer) &candidate, NULL)) {
+               guint64 candidate_timestamp = 0;
+
+               /* If a specific object is given, only include connections that are
+                * compatible with it.
+                */
+               if (    specific_object /* << Optimization: we know that the connection is available without @specific_object.  */
+                   && !nm_device_check_connection_available (self,
+                                                             NM_CONNECTION (candidate),
+                                                             _NM_DEVICE_CHECK_CON_AVAILABLE_FOR_USER_REQUEST,
+                                                             specific_object))
+                       continue;
+
+               nm_settings_connection_get_timestamp (candidate, &candidate_timestamp);
+               if (!connection || (candidate_timestamp > best_timestamp)) {
+                       connection = candidate;
+                       best_timestamp = candidate_timestamp;
                }
        }
-       return array;
+
+       if (!connection) {
+               g_set_error (error, NM_MANAGER_ERROR, NM_MANAGER_ERROR_UNKNOWN_CONNECTION,
+                            "The device '%s' has no connections available for activation.",
+                             nm_device_get_iface (self));
+       }
+
+       return connection;
 }
 
 static void
@@ -10384,7 +10400,8 @@ _set_state_full (NMDevice *self,
        if (state <= NM_DEVICE_STATE_UNAVAILABLE) {
                if (available_connections_del_all (self))
                        available_connections_notify (self);
-               _clear_queued_act_request (priv);
+               if (old_state > NM_DEVICE_STATE_UNAVAILABLE)
+                       _clear_queued_act_request (priv);
        }
 
        /* Update the available connections list when a device first becomes available */
index 9b9edda..bd657e6 100644 (file)
@@ -529,8 +529,9 @@ gboolean nm_device_add_pending_action    (NMDevice *device, const char *action,
 gboolean nm_device_remove_pending_action (NMDevice *device, const char *action, gboolean assert_is_pending);
 gboolean nm_device_has_pending_action    (NMDevice *device);
 
-GPtrArray *nm_device_get_available_connections (NMDevice *device,
-                                                const char *specific_object);
+NMSettingsConnection *nm_device_get_best_connection (NMDevice *device,
+                                                     const char *specific_object,
+                                                     GError **error);
 
 gboolean   nm_device_check_connection_available (NMDevice *device,
                                                  NMConnection *connection,
index f275b3e..e907613 100644 (file)
@@ -59,6 +59,8 @@ typedef struct {
        NMActiveConnection *master;
        gboolean master_ready;
 
+       NMActiveConnection *parent;
+
        gboolean assumed;
 
        NMAuthChain *chain;
@@ -98,6 +100,7 @@ enum {
 enum {
        DEVICE_CHANGED,
        DEVICE_METERED_CHANGED,
+       PARENT_ACTIVE,
        LAST_SIGNAL
 };
 static guint signals[LAST_SIGNAL] = { 0 };
@@ -678,6 +681,69 @@ nm_active_connection_get_assumed (NMActiveConnection *self)
 
 /****************************************************************/
 
+static void unwatch_parent (NMActiveConnection *self);
+
+static void
+parent_destroyed (gpointer user_data, GObject *parent)
+{
+       NMActiveConnection *self = user_data;
+
+       unwatch_parent (self);
+       g_signal_emit (self, signals[PARENT_ACTIVE], 0, NULL);
+}
+
+static void
+parent_state_cb (NMActiveConnection *parent_ac,
+                 GParamSpec *pspec,
+                 gpointer user_data)
+{
+       NMActiveConnection *self = user_data;
+       NMActiveConnectionState parent_state = nm_active_connection_get_state (parent_ac);
+
+       if (parent_state < NM_ACTIVE_CONNECTION_STATE_ACTIVATED)
+               return;
+
+       unwatch_parent (self);
+       g_signal_emit (self, signals[PARENT_ACTIVE], 0, parent_ac);
+}
+
+static void
+unwatch_parent (NMActiveConnection *self)
+{
+       NMActiveConnectionPrivate *priv = NM_ACTIVE_CONNECTION_GET_PRIVATE (self);
+
+       g_signal_handlers_disconnect_by_func (priv->parent,
+                                             (GCallback) parent_state_cb,
+                                             self);
+       g_object_weak_unref ((GObject *) priv->parent, parent_destroyed, self);
+       priv->parent = NULL;
+}
+
+/**
+ * nm_active_connection_set_parent:
+ * @self: the #NMActiveConnection
+ * @parent: The #NMActiveConnection that must be active before the manager
+ * can proceed progressing the device to disconnected state for us.
+ *
+ * Sets the parent connection of @self. A "parent-active" signal will be
+ * emitted when the parent connection becomes active.
+ */
+void
+nm_active_connection_set_parent (NMActiveConnection *self, NMActiveConnection *parent)
+{
+       NMActiveConnectionPrivate *priv = NM_ACTIVE_CONNECTION_GET_PRIVATE (self);
+
+       g_return_if_fail (priv->parent == NULL);
+       priv->parent = parent;
+       g_signal_connect (priv->parent,
+                         "notify::" NM_ACTIVE_CONNECTION_STATE,
+                         (GCallback) parent_state_cb,
+                         self);
+       g_object_weak_ref ((GObject *) priv->parent, parent_destroyed, self);
+}
+
+/****************************************************************/
+
 static void
 auth_done (NMAuthChain *chain,
            GError *error,
@@ -1028,6 +1094,10 @@ dispose (GObject *object)
                                                      self);
        }
        g_clear_object (&priv->master);
+
+       if (priv->parent)
+               unwatch_parent (self);
+
        g_clear_object (&priv->subject);
 
        G_OBJECT_CLASS (nm_active_connection_parent_class)->dispose (object);
@@ -1208,6 +1278,14 @@ nm_active_connection_class_init (NMActiveConnectionClass *ac_class)
                              NULL, NULL, NULL,
                              G_TYPE_NONE, 1, G_TYPE_UINT);
 
+       signals[PARENT_ACTIVE] =
+               g_signal_new (NM_ACTIVE_CONNECTION_PARENT_ACTIVE,
+                             G_OBJECT_CLASS_TYPE (object_class),
+                             G_SIGNAL_RUN_FIRST,
+                             G_STRUCT_OFFSET (NMActiveConnectionClass, parent_active),
+                             NULL, NULL, NULL,
+                             G_TYPE_NONE, 1, NM_TYPE_ACTIVE_CONNECTION);
+
        nm_exported_object_class_add_interface (NM_EXPORTED_OBJECT_CLASS (ac_class),
                                                NMDBUS_TYPE_ACTIVE_CONNECTION_SKELETON,
                                                NULL);
index a7b3d0c..a66f8e9 100644 (file)
@@ -58,6 +58,7 @@
 /* Internal signals*/
 #define NM_ACTIVE_CONNECTION_DEVICE_CHANGED          "device-changed"
 #define NM_ACTIVE_CONNECTION_DEVICE_METERED_CHANGED  "device-metered-changed"
+#define NM_ACTIVE_CONNECTION_PARENT_ACTIVE           "parent-active"
 
 struct _NMActiveConnection {
        NMExportedObject parent;
@@ -81,6 +82,8 @@ typedef struct {
 
        void (*device_metered_changed) (NMActiveConnection *connection,
                                        NMMetered new_value);
+
+       void (*parent_active) (NMActiveConnection *connection);
 } NMActiveConnectionClass;
 
 guint64 nm_active_connection_version_id_get (NMActiveConnection *self);
@@ -148,6 +151,9 @@ gboolean      nm_active_connection_get_master_ready (NMActiveConnection *self);
 void          nm_active_connection_set_master (NMActiveConnection *self,
                                                NMActiveConnection *master);
 
+void          nm_active_connection_set_parent (NMActiveConnection *self,
+                                               NMActiveConnection *parent);
+
 void          nm_active_connection_set_assumed (NMActiveConnection *self,
                                                 gboolean assumed);
 
index 0ccb987..0606553 100644 (file)
@@ -226,6 +226,9 @@ static void active_connection_state_changed (NMActiveConnection *active,
 static void active_connection_default_changed (NMActiveConnection *active,
                                                GParamSpec *pspec,
                                                NMManager *self);
+static void active_connection_parent_active (NMActiveConnection *active,
+                                             NMActiveConnection *parent_ac,
+                                             NMManager *self);
 
 /* Returns: whether to notify D-Bus of the removal or not */
 static gboolean
@@ -244,6 +247,7 @@ active_connection_remove (NMManager *self, NMActiveConnection *active)
                g_signal_emit (self, signals[ACTIVE_CONNECTION_REMOVED], 0, active);
                g_signal_handlers_disconnect_by_func (active, active_connection_state_changed, self);
                g_signal_handlers_disconnect_by_func (active, active_connection_default_changed, self);
+               g_signal_handlers_disconnect_by_func (active, active_connection_parent_active, self);
 
                if (   nm_active_connection_get_assumed (active)
                    && (connection = nm_active_connection_get_settings_connection (active))
@@ -2723,6 +2727,71 @@ _internal_activate_vpn (NMManager *self, NMActiveConnection *active, GError **er
        return success;
 }
 
+/* Traverse the device to disconnected state. This means that the device is ready
+ * for connection and will proceed activating if there's an activation request
+ * enqueued.
+ */
+static void
+unmanaged_to_disconnected (NMDevice *device)
+{
+       /* when creating the software device, it can happen that the device is
+        * still unmanaged by NM_UNMANAGED_PLATFORM_INIT because we didn't yet
+        * get the udev event. At this point, we can no longer delay the activation
+        * and force the device to be managed. */
+       nm_device_set_unmanaged_by_flags (device, NM_UNMANAGED_PLATFORM_INIT, FALSE, NM_DEVICE_STATE_REASON_USER_REQUESTED);
+
+       nm_device_set_unmanaged_by_flags (device, NM_UNMANAGED_USER_EXPLICIT, FALSE, NM_DEVICE_STATE_REASON_USER_REQUESTED);
+
+       g_return_if_fail (nm_device_get_managed (device, FALSE));
+
+       if (nm_device_get_state (device) == NM_DEVICE_STATE_UNMANAGED) {
+               nm_device_state_changed (device,
+                                        NM_DEVICE_STATE_UNAVAILABLE,
+                                        NM_DEVICE_STATE_REASON_USER_REQUESTED);
+       }
+
+       if (   nm_device_is_available (device, NM_DEVICE_CHECK_DEV_AVAILABLE_FOR_USER_REQUEST)
+           && (nm_device_get_state (device) == NM_DEVICE_STATE_UNAVAILABLE)) {
+               nm_device_state_changed (device,
+                                        NM_DEVICE_STATE_DISCONNECTED,
+                                        NM_DEVICE_STATE_REASON_USER_REQUESTED);
+       }
+}
+
+/* The parent connection is ready; we can proceed realizing the device and
+ * progressing the device to disconencted state.
+ */
+static void
+active_connection_parent_active (NMActiveConnection *active,
+                                 NMActiveConnection *parent_ac,
+                                 NMManager *self)
+{
+       NMDevice *device = nm_active_connection_get_device (active);
+       GError *error = NULL;
+
+       g_signal_handlers_disconnect_by_func (active,
+                                             (GCallback) active_connection_parent_active,
+                                             self);
+
+       if (parent_ac) {
+               NMSettingsConnection *connection = nm_active_connection_get_settings_connection (active);
+               NMDevice *parent = nm_active_connection_get_device (parent_ac);
+
+               if (nm_device_create_and_realize (device, (NMConnection *) connection, parent, &error)) {
+                       /* We can now proceed to disconnected state so that activation proceeds. */
+                       unmanaged_to_disconnected (device);
+               } else {
+                       nm_log_warn (LOGD_CORE, "Could not realize device '%s': %s",
+                                    nm_device_get_iface (device), error->message);
+                       nm_active_connection_set_state (active, NM_ACTIVE_CONNECTION_STATE_DEACTIVATED);
+               }
+       } else {
+               nm_log_warn (LOGD_CORE, "The parent connection device '%s' depended on disappeared.",
+                            nm_device_get_iface (device));
+               nm_active_connection_set_state (active, NM_ACTIVE_CONNECTION_STATE_DEACTIVATED);
+       }
+}
+
 static gboolean
 _internal_activate_device (NMManager *self, NMActiveConnection *active, GError **error)
 {
@@ -2782,9 +2851,35 @@ _internal_activate_device (NMManager *self, NMActiveConnection *active, GError *
                NMDevice *parent;
 
                parent = find_parent_device_for_connection (self, (NMConnection *) connection, NULL);
-               if (!nm_device_create_and_realize (device, (NMConnection *) connection, parent, error)) {
-                       g_prefix_error (error, "%s failed to create resources: ", nm_device_get_iface (device));
-                       return FALSE;
+
+               if (parent && !nm_device_is_real (parent)) {
+                       NMSettingsConnection *parent_con;
+                       NMActiveConnection *parent_ac;
+
+                       parent_con = nm_device_get_best_connection (parent, NULL, error);
+                       if (!parent_con) {
+                               g_prefix_error (error, "%s failed to create parent: ", nm_device_get_iface (device));
+                               return FALSE;
+                       }
+
+                       parent_ac = nm_manager_activate_connection (self, parent_con, NULL, parent, subject, error);
+                       if (!parent_ac) {
+                               g_prefix_error (error, "%s failed to activate parent: ", nm_device_get_iface (device));
+                               return FALSE;
+                       }
+
+                       /* We can't realize now; defer until the parent device is ready. */
+                       g_signal_connect (active,
+                                         NM_ACTIVE_CONNECTION_PARENT_ACTIVE,
+                                         (GCallback) active_connection_parent_active,
+                                         self);
+                       nm_active_connection_set_parent (active, parent_ac);
+               } else {
+                       /* We can realize now; no need to wait for a parent device. */
+                       if (!nm_device_create_and_realize (device, (NMConnection *) connection, parent, error)) {
+                               g_prefix_error (error, "%s failed to create resources: ", nm_device_get_iface (device));
+                               return FALSE;
+                       }
                }
        }
 
@@ -2846,28 +2941,9 @@ _internal_activate_device (NMManager *self, NMActiveConnection *active, GError *
        if (existing)
                nm_device_steal_connection (existing, connection);
 
-       /* when creating the software device, it can happen that the device is
-        * still unmanaged by NM_UNMANAGED_PLATFORM_INIT because we didn't yet
-        * get the udev event. At this point, we can no longer delay the activation
-        * and force the device to be managed. */
-       nm_device_set_unmanaged_by_flags (device, NM_UNMANAGED_PLATFORM_INIT, FALSE, NM_DEVICE_STATE_REASON_USER_REQUESTED);
-
-       nm_device_set_unmanaged_by_flags (device, NM_UNMANAGED_USER_EXPLICIT, FALSE, NM_DEVICE_STATE_REASON_USER_REQUESTED);
-
-       g_return_val_if_fail (nm_device_get_managed (device, FALSE), FALSE);
-
-       if (nm_device_get_state (device) == NM_DEVICE_STATE_UNMANAGED) {
-               nm_device_state_changed (device,
-                                        NM_DEVICE_STATE_UNAVAILABLE,
-                                        NM_DEVICE_STATE_REASON_USER_REQUESTED);
-       }
-
-       if (   nm_device_is_available (device, NM_DEVICE_CHECK_DEV_AVAILABLE_FOR_USER_REQUEST)
-           && (nm_device_get_state (device) == NM_DEVICE_STATE_UNAVAILABLE)) {
-               nm_device_state_changed (device,
-                                        NM_DEVICE_STATE_DISCONNECTED,
-                                        NM_DEVICE_STATE_REASON_USER_REQUESTED);
-       }
+       /* If the device is there, we can ready it for the activation. */
+       if (nm_device_is_real (device))
+               unmanaged_to_disconnected (device);
 
        /* Export the new ActiveConnection to clients and start it on the device */
        nm_exported_object_export (NM_EXPORTED_OBJECT (active));
@@ -3325,11 +3401,15 @@ impl_manager_activate_connection (NMManager *self,
         * regardless of whether that connection is autoconnect-enabled or not
         * (since this is an explicit request, not an auto-activation request).
         */
-       if (!connection_path) {
-               GPtrArray *available;
-               guint64 best_timestamp = 0;
-               guint i;
-
+       if (connection_path) {
+               connection = nm_settings_get_connection_by_path (priv->settings, connection_path);
+               if (!connection) {
+                       error = g_error_new_literal (NM_MANAGER_ERROR,
+                                                    NM_MANAGER_ERROR_UNKNOWN_CONNECTION,
+                                                    "Connection could not be found.");
+                       goto error;
+               }
+       } else {
                /* If no connection is given, find a suitable connection for the given device path */
                if (!device_path) {
                        error = g_error_new_literal (NM_MANAGER_ERROR, NM_MANAGER_ERROR_UNKNOWN_DEVICE,
@@ -3343,36 +3423,9 @@ impl_manager_activate_connection (NMManager *self,
                        goto error;
                }
 
-               available = nm_device_get_available_connections (device, specific_object_path);
-               for (i = 0; available && i < available->len; i++) {
-                       NMSettingsConnection *candidate = g_ptr_array_index (available, i);
-                       guint64 candidate_timestamp = 0;
-
-                       nm_settings_connection_get_timestamp (candidate, &candidate_timestamp);
-                       if (!connection_path || (candidate_timestamp > best_timestamp)) {
-                               connection_path = nm_connection_get_path (NM_CONNECTION (candidate));
-                               best_timestamp = candidate_timestamp;
-                       }
-               }
-
-               if (available)
-                       g_ptr_array_free (available, TRUE);
-
-               if (!connection_path) {
-                       error = g_error_new_literal (NM_MANAGER_ERROR,
-                                                    NM_MANAGER_ERROR_UNKNOWN_CONNECTION,
-                                                    "The device has no connections available.");
+               connection = nm_device_get_best_connection (device, specific_object_path, &error);
+               if (!connection)
                        goto error;
-               }
-       }
-
-       g_assert (connection_path);
-       connection = nm_settings_get_connection_by_path (priv->settings, connection_path);
-       if (!connection) {
-               error = g_error_new_literal (NM_MANAGER_ERROR,
-                                            NM_MANAGER_ERROR_UNKNOWN_CONNECTION,
-                                            "Connection could not be found.");
-               goto error;
        }
 
        subject = validate_activation_request (self,