wifi: avoid device double release on disable
[platform/upstream/connman.git] / plugins / wifi.c
index f5690b5..f862703 100644 (file)
@@ -93,6 +93,7 @@ struct wifi_data {
        GSList *networks;
        GSupplicantInterface *interface;
        GSupplicantState state;
+       connman_bool_t disabling;
        connman_bool_t connected;
        connman_bool_t disconnecting;
        connman_bool_t tethering;
@@ -142,9 +143,6 @@ static void wifi_newlink(unsigned flags, unsigned change, void *user_data)
 
        DBG("index %d flags %d change %d", wifi->index, flags, change);
 
-       if (!change)
-               return;
-
        if ((wifi->flags & IFF_UP) != (flags & IFF_UP)) {
                if (flags & IFF_UP)
                        DBG("interface up");
@@ -174,6 +172,7 @@ static int wifi_probe(struct connman_device *device)
        if (wifi == NULL)
                return -ENOMEM;
 
+       wifi->disabling = FALSE;
        wifi->connected = FALSE;
        wifi->disconnecting = FALSE;
        wifi->tethering = FALSE;
@@ -236,6 +235,11 @@ static void reset_autoscan(struct connman_device *device)
 
 static void stop_autoscan(struct connman_device *device)
 {
+       const struct wifi_data *wifi = connman_device_get_data(device);
+
+       if (wifi == NULL || wifi->autoscan == NULL)
+               return;
+
        reset_autoscan(device);
 
        connman_device_set_scanning(device, FALSE);
@@ -387,6 +391,8 @@ static int get_hidden_connections(int max_ssids,
                        continue;
 
                keyfile = connman_storage_load_service(services[i]);
+               if (keyfile == NULL)
+                       continue;
 
                value = g_key_file_get_boolean(keyfile,
                                        services[i], "Hidden", NULL);
@@ -425,7 +431,7 @@ static int get_hidden_connections(int max_ssids,
        /*
         * Check if there are any hidden AP that needs to be provisioned.
         */
-       entries = connman_config_get_entries();
+       entries = connman_config_get_entries("wifi");
        for (i = 0; entries && entries[i]; i++) {
                int len;
 
@@ -519,8 +525,18 @@ static void scan_callback(int result, GSupplicantInterface *interface,
                connman_device_reset_scanning(device);
 
        connman_device_set_scanning(device, FALSE);
-       start_autoscan(device);
-       connman_device_unref(device);
+
+       if (result != -ENOLINK)
+               start_autoscan(device);
+
+       /*
+        * If we are here then we were scanning; however, if we are also
+        * mid-flight disabling the interface, then wifi_disable has
+        * already unreferenced the device and we needn't do it here.
+        */
+
+       if (wifi->disabling != TRUE)
+               connman_device_unref(device);
 }
 
 static void scan_callback_hidden(int result,
@@ -528,7 +544,8 @@ static void scan_callback_hidden(int result,
 {
        struct connman_device *device = user_data;
        struct wifi_data *wifi = connman_device_get_data(device);
-       int driver_max_ssids;
+       GSupplicantScanParams *scan_params;
+       int driver_max_ssids, ret;
 
        DBG("result %d wifi %p", result, wifi);
 
@@ -537,32 +554,30 @@ static void scan_callback_hidden(int result,
 
        /*
         * Scan hidden networks so that we can autoconnect to them.
+        * We will assume 1 as a default number of ssid to scan.
         */
        driver_max_ssids = g_supplicant_interface_get_max_scan_ssids(
                                                        wifi->interface);
-       DBG("max ssids %d", driver_max_ssids);
+       if (driver_max_ssids == 0)
+               driver_max_ssids = 1;
 
-       if (driver_max_ssids > 0) {
-               GSupplicantScanParams *scan_params;
-               int ret;
+       DBG("max ssids %d", driver_max_ssids);
 
-               scan_params = g_try_malloc0(sizeof(GSupplicantScanParams));
-               if (scan_params == NULL)
-                       goto out;
+       scan_params = g_try_malloc0(sizeof(GSupplicantScanParams));
+       if (scan_params == NULL)
+               goto out;
 
-               if (get_hidden_connections(driver_max_ssids,
-                                               scan_params) > 0) {
-                       ret = g_supplicant_interface_scan(wifi->interface,
+       if (get_hidden_connections(driver_max_ssids, scan_params) > 0) {
+               ret = g_supplicant_interface_scan(wifi->interface,
                                                        scan_params,
                                                        scan_callback,
                                                        device);
-                       if (ret == 0)
-                               return;
-               }
-
-               g_supplicant_free_scan_params(scan_params);
+               if (ret == 0)
+                       return;
        }
 
+       g_supplicant_free_scan_params(scan_params);
+
 out:
        scan_callback(result, interface, user_data);
 }
@@ -718,6 +733,27 @@ static void interface_create_callback(int result,
        }
 }
 
+/*
+ * The sole function of this callback is to avoid a race between scan completion
+ * and wifi_disable that can otherwise cause a reference count underflow if the
+ * disabling state is not tracked and observed.
+ */
+static void interface_remove_callback(int result,
+                                       GSupplicantInterface *interface,
+                                                       void *user_data)
+{
+       struct wifi_data *wifi = user_data;
+
+       DBG("result %d ifname %s, wifi %p", result,
+                               g_supplicant_interface_get_ifname(interface),
+                               wifi);
+
+       if (result < 0 || wifi == NULL)
+               return;
+
+       wifi->disabling = FALSE;
+}
+
 static int wifi_enable(struct connman_device *device)
 {
        struct wifi_data *wifi = connman_device_get_data(device);
@@ -736,6 +772,8 @@ static int wifi_enable(struct connman_device *device)
        if (ret < 0)
                return ret;
 
+       wifi->disabling = FALSE;
+
        return -EINPROGRESS;
 }
 
@@ -765,10 +803,14 @@ static int wifi_disable(struct connman_device *device)
 
        remove_networks(device, wifi);
 
-       ret = g_supplicant_interface_remove(wifi->interface, NULL, NULL);
+       ret = g_supplicant_interface_remove(wifi->interface,
+                                               interface_remove_callback,
+                                                       wifi);
        if (ret < 0)
                return ret;
 
+       wifi->disabling = TRUE;
+
        return -EINPROGRESS;
 }
 
@@ -825,6 +867,8 @@ static int get_latest_connections(int max_ssids,
                        continue;
 
                keyfile = connman_storage_load_service(services[i]);
+               if (keyfile == NULL)
+                       continue;
 
                str = g_key_file_get_string(keyfile,
                                        services[i], "Favorite", NULL);
@@ -1234,6 +1278,14 @@ static void disconnect_callback(int result, GSupplicantInterface *interface,
 {
        struct wifi_data *wifi = user_data;
 
+       DBG("result %d supplicant interface %p wifi %p",
+                       result, interface, wifi);
+
+       if (result == -ECONNABORTED) {
+               DBG("wifi interface no longer available");
+               return;
+       }
+
        if (wifi->network != NULL) {
                /*
                 * if result < 0 supplican return an error because
@@ -1331,6 +1383,7 @@ static connman_bool_t is_idle(struct wifi_data *wifi)
 
        switch (wifi->state) {
        case G_SUPPLICANT_STATE_UNKNOWN:
+       case G_SUPPLICANT_STATE_DISABLED:
        case G_SUPPLICANT_STATE_DISCONNECTED:
        case G_SUPPLICANT_STATE_INACTIVE:
        case G_SUPPLICANT_STATE_SCANNING:
@@ -1360,6 +1413,7 @@ static connman_bool_t is_idle_wps(GSupplicantInterface *interface,
         * actually means that we are idling. */
        switch (wifi->state) {
        case G_SUPPLICANT_STATE_UNKNOWN:
+       case G_SUPPLICANT_STATE_DISABLED:
        case G_SUPPLICANT_STATE_DISCONNECTED:
        case G_SUPPLICANT_STATE_INACTIVE:
        case G_SUPPLICANT_STATE_SCANNING:
@@ -1530,6 +1584,7 @@ static void interface_state(GSupplicantInterface *interface)
                break;
 
        case G_SUPPLICANT_STATE_UNKNOWN:
+       case G_SUPPLICANT_STATE_DISABLED:
        case G_SUPPLICANT_STATE_ASSOCIATED:
        case G_SUPPLICANT_STATE_4WAY_HANDSHAKE:
        case G_SUPPLICANT_STATE_GROUP_HANDSHAKE: