/* SPDX-License-Identifier: GPL-2.0-or-later */
/*
 * Copyright (C) 2006 - 2010 Red Hat, Inc.
 * Copyright (C) 2007 - 2008 Novell, Inc.
 */

#include "src/core/nm-default-daemon.h"

#include "nm-supplicant-manager.h"

#include "libnm-core-intern/nm-core-internal.h"
#include "nm-dbus-manager.h"
#include "libnm-glib-aux/nm-dbus-aux.h"
#include "libnm-glib-aux/nm-ref-string.h"
#include "nm-supplicant-interface.h"
#include "nm-supplicant-types.h"
#include "libnm-platform/nm-platform.h"

/*****************************************************************************/

#define CREATE_IFACE_TRY_COUNT_MAX 7u

struct _NMSupplMgrCreateIfaceHandle {
    NMSupplicantManager                 *self;
    CList                                create_iface_lst;
    GCancellable                        *cancellable;
    NMSupplicantManagerCreateInterfaceCb callback;
    gpointer                             callback_user_data;
    NMShutdownWaitObjHandle             *shutdown_handle;
    NMRefString                         *name_owner;
    GError                              *fail_on_idle_error;
    NMSupplicantDriver                   driver;
    int                                  ifindex;
    guint                                fail_on_idle_id;
    guint                                create_iface_try_count : 5;
};

enum {
    AVAILABLE_CHANGED,
    LAST_SIGNAL,
};

static guint signals[LAST_SIGNAL] = {0};

typedef struct {
    GDBusConnection *dbus_connection;

    NMRefString *name_owner;

    GCancellable *get_name_owner_cancellable;
    GCancellable *get_capabilities_cancellable;
    GCancellable *poke_name_owner_cancellable;

    GHashTable *supp_ifaces;
    CList       supp_lst_head;

    CList create_iface_lst_head;

    NMSupplCapMask capabilities;

    guint name_owner_changed_id;
    guint interface_removed_id;
    guint poke_name_owner_timeout_id;
    guint available_reset_id;

    /* see nm_supplicant_manager_get_available(). */
    NMTernary available : 2;

} NMSupplicantManagerPrivate;

struct _NMSupplicantManager {
    GObject                    parent;
    NMSupplicantManagerPrivate _priv;
};

struct _NMSupplicantManagerClass {
    GObjectClass parent;
};

G_DEFINE_TYPE(NMSupplicantManager, nm_supplicant_manager, G_TYPE_OBJECT)

#define NM_SUPPLICANT_MANAGER_GET_PRIVATE(self) \
    _NM_GET_PRIVATE(self, NMSupplicantManager, NM_IS_SUPPLICANT_MANAGER)

NM_DEFINE_SINGLETON_GETTER(NMSupplicantManager,
                           nm_supplicant_manager_get,
                           NM_TYPE_SUPPLICANT_MANAGER);

/*****************************************************************************/

#define _NMLOG_DOMAIN      LOGD_SUPPLICANT
#define _NMLOG(level, ...) __NMLOG_DEFAULT(level, _NMLOG_DOMAIN, "supplicant", __VA_ARGS__)

/*****************************************************************************/

NM_CACHED_QUARK_FCN("nm-supplicant-error-quark", nm_supplicant_error_quark);

/*****************************************************************************/

static void     _create_iface_proceed_all(NMSupplicantManager *self, GError *error);
static void     _supp_iface_add(NMSupplicantManager   *self,
                                NMRefString           *iface_path,
                                NMSupplicantInterface *supp_iface);
static void     _supp_iface_remove_one(NMSupplicantManager   *self,
                                       NMSupplicantInterface *supp_iface,
                                       gboolean               force_remove_from_supplicant,
                                       const char            *reason);
static void     _create_iface_dbus_call_get_interface(NMSupplicantManager         *self,
                                                      NMSupplMgrCreateIfaceHandle *handle,
                                                      const char                  *ifname);
static void     _create_iface_dbus_call_create_interface(NMSupplicantManager         *self,
                                                         NMSupplMgrCreateIfaceHandle *handle,
                                                         const char                  *ifname);
static gboolean _create_iface_fail_on_idle_cb(gpointer user_data);

static gboolean _available_reset_cb(gpointer user_data);

/*****************************************************************************/

NM_UTILS_LOOKUP_STR_DEFINE(nm_supplicant_driver_to_string,
                           NMSupplicantDriver,
                           NM_UTILS_LOOKUP_DEFAULT_WARN(NULL),
                           NM_UTILS_LOOKUP_ITEM(NM_SUPPLICANT_DRIVER_UNKNOWN, "???"),
                           NM_UTILS_LOOKUP_ITEM(NM_SUPPLICANT_DRIVER_WIRELESS,
                                                NM_WPAS_DEFAULT_WIFI_DRIVER),
                           NM_UTILS_LOOKUP_ITEM(NM_SUPPLICANT_DRIVER_WIRED, "wired"),
                           NM_UTILS_LOOKUP_ITEM(NM_SUPPLICANT_DRIVER_MACSEC, "macsec_linux"), );

/*****************************************************************************/

NMTernary
nm_supplicant_manager_is_available(NMSupplicantManager *self)
{
    g_return_val_if_fail(NM_IS_SUPPLICANT_MANAGER(self), NM_TERNARY_FALSE);

    return NM_SUPPLICANT_MANAGER_GET_PRIVATE(self)->available;
}

NMRefString *
nm_supplicant_manager_get_dbus_name_owner(NMSupplicantManager *self)
{
    g_return_val_if_fail(NM_IS_SUPPLICANT_MANAGER(self), NULL);

    return NM_SUPPLICANT_MANAGER_GET_PRIVATE(self)->name_owner;
}

GDBusConnection *
nm_supplicant_manager_get_dbus_connection(NMSupplicantManager *self)
{
    g_return_val_if_fail(NM_IS_SUPPLICANT_MANAGER(self), NULL);

    return NM_SUPPLICANT_MANAGER_GET_PRIVATE(self)->dbus_connection;
}

NMSupplCapMask
nm_supplicant_manager_get_global_capabilities(NMSupplicantManager *self)
{
    g_return_val_if_fail(NM_IS_SUPPLICANT_MANAGER(self), NM_SUPPL_CAP_MASK_NONE);

    return NM_SUPPLICANT_MANAGER_GET_PRIVATE(self)->capabilities;
}

/*****************************************************************************/

static void
_caps_set(NMSupplicantManagerPrivate *priv, NMSupplCapType type, NMTernary value)
{
    priv->capabilities = NM_SUPPL_CAP_MASK_SET(priv->capabilities, type, value);
}

/*****************************************************************************/

static void
_dbus_call_remove_interface(GDBusConnection *dbus_connection,
                            const char      *name_owner,
                            const char      *iface_path)
{
    nm_assert(G_IS_DBUS_CONNECTION(dbus_connection));
    nm_assert(name_owner);
    nm_assert(iface_path);

    g_dbus_connection_call(dbus_connection,
                           name_owner,
                           NM_WPAS_DBUS_PATH,
                           NM_WPAS_DBUS_INTERFACE,
                           "RemoveInterface",
                           g_variant_new("(o)", iface_path),
                           G_VARIANT_TYPE("()"),
                           G_DBUS_CALL_FLAGS_NO_AUTO_START,
                           10000,
                           NULL,
                           NULL,
                           NULL);
}

void
_nm_supplicant_manager_dbus_call_remove_interface(NMSupplicantManager *self,
                                                  const char          *name_owner,
                                                  const char          *iface_path)
{
    _dbus_call_remove_interface(NM_SUPPLICANT_MANAGER_GET_PRIVATE(self)->dbus_connection,
                                name_owner,
                                iface_path);
}

/*****************************************************************************/

static void
on_supplicant_wfd_ies_set(GObject *source_object, GAsyncResult *result, gpointer user_data)
{
    gs_unref_variant GVariant *res   = NULL;
    gs_free_error GError      *error = NULL;

    res = g_dbus_connection_call_finish(G_DBUS_CONNECTION(source_object), result, &error);
    if (!res)
        _LOGD("failed to set WFD IEs on wpa_supplicant: %s", error->message);
}

/**
 * nm_supplicant_manager_set_wfd_ies:
 * @self: the #NMSupplicantManager
 * @wfd_ies: a #GBytes with the WFD IEs or %NULL
 *
 * This function sets the global WFD IEs on wpa_supplicant. Note that
 * it would make more sense if this was per-device, but wpa_supplicant
 * simply does not work that way.
 * */
void
nm_supplicant_manager_set_wfd_ies(NMSupplicantManager *self, GBytes *wfd_ies)
{
    NMSupplicantManagerPrivate *priv;
    GVariantBuilder             params;

    g_return_if_fail(NM_IS_SUPPLICANT_MANAGER(self));

    priv = NM_SUPPLICANT_MANAGER_GET_PRIVATE(self);

    if (!priv->name_owner)
        return;

    _LOGD("setting WFD IEs for P2P operation on %s", priv->name_owner->str);

    g_variant_builder_init(&params, G_VARIANT_TYPE("(ssv)"));

    g_variant_builder_add(&params, "s", NM_WPAS_DBUS_INTERFACE);
    g_variant_builder_add(&params, "s", "WFDIEs");
    g_variant_builder_add_value(&params, g_variant_new_variant(nm_g_bytes_to_variant_ay(wfd_ies)));

    g_dbus_connection_call(priv->dbus_connection,
                           priv->name_owner->str,
                           NM_WPAS_DBUS_PATH,
                           DBUS_INTERFACE_PROPERTIES,
                           "Set",
                           g_variant_builder_end(&params),
                           G_VARIANT_TYPE("()"),
                           G_DBUS_CALL_FLAGS_NO_AUTO_START,
                           3000,
                           NULL,
                           on_supplicant_wfd_ies_set,
                           NULL);
}

/*****************************************************************************/

static gboolean
_poke_name_owner_timeout_cb(gpointer user_data)
{
    NMSupplicantManager        *self              = user_data;
    NMSupplicantManagerPrivate *priv              = NM_SUPPLICANT_MANAGER_GET_PRIVATE(self);
    gs_free_error GError       *error             = NULL;
    gboolean                    available_changed = FALSE;

    nm_assert(!priv->name_owner);

    priv->poke_name_owner_timeout_id = 0;
    nm_clear_g_cancellable(&priv->poke_name_owner_cancellable);

    _LOGT("poke service \"%s\" failed for good with timeout%s",
          NM_WPAS_DBUS_SERVICE,
          (priv->available == NM_TERNARY_DEFAULT) ? " (set as not available)" : "");

    if (priv->available == NM_TERNARY_DEFAULT) {
        /* the available flag usually only changes together with the name-owner.
         * However, if we tries to poke the service but failed to start it (with
         * timeout), was also set it as (hard) not available. */
        priv->available = NM_TERNARY_FALSE;
        nm_clear_g_source(&priv->available_reset_id);
        priv->available_reset_id = g_timeout_add_seconds(60, _available_reset_cb, self);
        available_changed        = TRUE;
    }

    nm_utils_error_set(&error,
                       NM_UTILS_ERROR_UNKNOWN,
                       "Failed to D-Bus activate wpa_supplicant service");

    _create_iface_proceed_all(self, error);

    if (available_changed) {
        /* We delay the emitting of the notification after aborting all
         * create-iface handles. */
        g_signal_emit(self, signals[AVAILABLE_CHANGED], 0);
    }

    return G_SOURCE_REMOVE;
}

static void
_poke_name_owner_cb(GObject *source, GAsyncResult *result, gpointer user_data)
{
    gs_unref_variant GVariant *res   = NULL;
    gs_free_error GError      *error = NULL;

    res = g_dbus_connection_call_finish(G_DBUS_CONNECTION(source), result, &error);
    if (nm_utils_error_is_cancelled(error))
        return;

    if (!res)
        _LOGT("poke service \"%s\" failed: %s", NM_WPAS_DBUS_SERVICE, error->message);
    else
        _LOGT("poke service \"%s\" succeeded", NM_WPAS_DBUS_SERVICE);

    /* in both cases, we react the same: we wait for the name owner to appear
     * or hit the timeout. */
}

static void
_poke_name_owner(NMSupplicantManager *self)
{
    NMSupplicantManagerPrivate *priv = NM_SUPPLICANT_MANAGER_GET_PRIVATE(self);

    if (priv->poke_name_owner_cancellable)
        return;

    _LOGT("poke service \"%s\"...", NM_WPAS_DBUS_SERVICE);

    priv->poke_name_owner_cancellable = g_cancellable_new();
    priv->poke_name_owner_timeout_id  = g_timeout_add(3000, _poke_name_owner_timeout_cb, self);
    nm_dbus_connection_call_start_service_by_name(priv->dbus_connection,
                                                  NM_WPAS_DBUS_SERVICE,
                                                  5000,
                                                  priv->poke_name_owner_cancellable,
                                                  _poke_name_owner_cb,
                                                  self);
}

/*****************************************************************************/

static void
_create_iface_complete(NMSupplMgrCreateIfaceHandle *handle,
                       NMSupplicantInterface       *supp_iface,
                       GError                      *error)
{
    nm_assert(!supp_iface || NM_IS_SUPPLICANT_INTERFACE(supp_iface));
    nm_assert((!!supp_iface) != (!!error));

    c_list_unlink(&handle->create_iface_lst);

    nm_clear_g_source(&handle->fail_on_idle_id);

    if (handle->callback) {
        NMSupplicantManagerCreateInterfaceCb callback;

        nm_assert(NM_IS_SUPPLICANT_MANAGER(handle->self));

        callback         = handle->callback;
        handle->callback = NULL;
        callback(handle->self, handle, supp_iface, error, handle->callback_user_data);
    }

    g_clear_error(&handle->fail_on_idle_error);

    g_clear_object(&handle->self);

    if (handle->shutdown_handle) {
        /* we have a pending CreateInterface request. We keep the handle
         * instance alive. This is to remove the device again, once the
         * request completes. */
        return;
    }

    nm_clear_g_cancellable(&handle->cancellable);
    nm_ref_string_unref(handle->name_owner);

    nm_g_slice_free(handle);
}

static void
_create_iface_add(NMSupplicantManager         *self,
                  NMSupplMgrCreateIfaceHandle *handle,
                  const char                  *iface_path_str,
                  gboolean                     created_by_us)
{
    NMSupplicantManagerPrivate            *priv       = NM_SUPPLICANT_MANAGER_GET_PRIVATE(self);
    nm_auto_ref_string NMRefString        *iface_path = NULL;
    gs_unref_object NMSupplicantInterface *supp_iface = NULL;

    iface_path = nm_ref_string_new(iface_path_str);

    supp_iface = g_hash_table_lookup(priv->supp_ifaces, iface_path);
    if (supp_iface) {
        /* Now this is odd... Reuse the same interface. */
        g_object_ref(supp_iface);
        _LOGT("create-iface[" NM_HASH_OBFUSCATE_PTR_FMT
              "]: interface %s on %s created (already existing)",
              NM_HASH_OBFUSCATE_PTR(handle),
              iface_path_str,
              priv->name_owner->str);
        _create_iface_complete(handle, supp_iface, NULL);
        return;
    }

    _LOGT("create-iface[" NM_HASH_OBFUSCATE_PTR_FMT "]: interface %s on %s created%s",
          NM_HASH_OBFUSCATE_PTR(handle),
          iface_path_str,
          priv->name_owner->str,
          created_by_us ? " (created by us)" : "");

    supp_iface = nm_supplicant_interface_new(self, iface_path, handle->ifindex, handle->driver);

    _supp_iface_add(self, iface_path, supp_iface);

    _create_iface_complete(handle, supp_iface, NULL);
}

static void
_create_iface_dbus_call_get_interface_cb(GObject *source, GAsyncResult *result, gpointer user_data)
{
    GDBusConnection             *dbus_connection = G_DBUS_CONNECTION(source);
    NMSupplMgrCreateIfaceHandle *handle;
    NMSupplicantManager         *self;
    NMSupplicantManagerPrivate  *priv;
    gs_unref_variant GVariant   *res   = NULL;
    gs_free_error GError        *error = NULL;
    const char                  *iface_path_str;

    res = g_dbus_connection_call_finish(dbus_connection, result, &error);

    if (nm_utils_error_is_cancelled(error))
        return;

    handle = user_data;
    nm_assert(handle->callback);

    self = handle->self;
    priv = NM_SUPPLICANT_MANAGER_GET_PRIVATE(self);

    nm_assert(handle->name_owner == priv->name_owner);

    if (!res) {
        char ifname[NM_IFNAMSIZ];

        if (handle->create_iface_try_count < CREATE_IFACE_TRY_COUNT_MAX
            && nm_dbus_error_is(error, NM_WPAS_ERROR_UNKNOWN_IFACE)
            && nm_platform_if_indextoname(NM_PLATFORM_GET, handle->ifindex, ifname)) {
            /* Before, supplicant told us the interface existed. Was there a race?
             * Try again. */
            _LOGT("create-iface[" NM_HASH_OBFUSCATE_PTR_FMT
                  "]: D-Bus call failed to get interface. Try to create it again (ifname \"%s\")",
                  NM_HASH_OBFUSCATE_PTR(handle),
                  ifname);
            _create_iface_dbus_call_create_interface(self, handle, ifname);
            return;
        }

        g_clear_object(&handle->cancellable);
        _LOGT("create-iface[" NM_HASH_OBFUSCATE_PTR_FMT "]: D-Bus call to get interface failed: %s",
              NM_HASH_OBFUSCATE_PTR(handle),
              error->message);
        _create_iface_complete(handle, NULL, error);
        return;
    }

    g_clear_object(&handle->cancellable);

    g_variant_get(res, "(&o)", &iface_path_str);

    _create_iface_add(self, handle, iface_path_str, FALSE);
}

static void
_create_iface_dbus_call_create_interface_cb(GObject      *source,
                                            GAsyncResult *result,
                                            gpointer      user_data)
{
    GDBusConnection             *dbus_connection = G_DBUS_CONNECTION(source);
    NMSupplMgrCreateIfaceHandle *handle          = user_data;
    NMSupplicantManager         *self;
    NMSupplicantManagerPrivate  *priv;
    gs_unref_variant GVariant   *res   = NULL;
    gs_free_error GError        *error = NULL;
    const char                  *iface_path_str;
    char                         ifname[NM_IFNAMSIZ];

    res = g_dbus_connection_call_finish(dbus_connection, result, &error);

    nm_shutdown_wait_obj_unregister(g_steal_pointer(&handle->shutdown_handle));

    if (!res) {
        if (handle->callback && ({
                nm_assert(handle->self);
                TRUE;
            })
            && nm_dbus_error_is(error, NM_WPAS_ERROR_EXISTS_ERROR)
            && nm_platform_if_indextoname(NM_PLATFORM_GET, handle->ifindex, ifname)) {
            self = handle->self;
            _LOGT("create-iface[" NM_HASH_OBFUSCATE_PTR_FMT
                  "]: D-Bus call failed to create interface. Try to get existing interface (ifname "
                  "\"%s\")",
                  NM_HASH_OBFUSCATE_PTR(handle),
                  ifname);
            _create_iface_dbus_call_get_interface(self, handle, ifname);
            return;
        }
        g_clear_object(&handle->cancellable);
        _LOGT("create-iface[" NM_HASH_OBFUSCATE_PTR_FMT "]: D-Bus call failed: %s",
              NM_HASH_OBFUSCATE_PTR(handle),
              error->message);
        _create_iface_complete(handle, NULL, error);
        return;
    }

    g_clear_object(&handle->cancellable);

    self = handle->self;
    priv = self ? NM_SUPPLICANT_MANAGER_GET_PRIVATE(self) : NULL;

    g_variant_get(res, "(&o)", &iface_path_str);

    if (!handle->callback || priv->name_owner != handle->name_owner) {
        if (!handle->callback) {
            _LOGT("create-iface[" NM_HASH_OBFUSCATE_PTR_FMT
                  "]: request already cancelled but still remove interface %s in %s",
                  NM_HASH_OBFUSCATE_PTR(handle),
                  iface_path_str,
                  handle->name_owner->str);
            nm_utils_error_set(&error, NM_UTILS_ERROR_UNKNOWN, "Request already cancelled");
        } else {
            _LOGT("create-iface[" NM_HASH_OBFUSCATE_PTR_FMT
                  "]: name owner changed, still remove interface %s in %s",
                  NM_HASH_OBFUSCATE_PTR(handle),
                  iface_path_str,
                  handle->name_owner->str);
            nm_utils_error_set(&error,
                               NM_UTILS_ERROR_UNKNOWN,
                               "The name owner changed since creating the interface");
        }
        _dbus_call_remove_interface(dbus_connection, handle->name_owner->str, iface_path_str);
        _create_iface_complete(handle, NULL, error);
        return;
    }

    _create_iface_add(self, handle, iface_path_str, TRUE);
}

static void
_create_iface_dbus_call_get_interface(NMSupplicantManager         *self,
                                      NMSupplMgrCreateIfaceHandle *handle,
                                      const char                  *ifname)
{
    NMSupplicantManagerPrivate *priv = NM_SUPPLICANT_MANAGER_GET_PRIVATE(self);

    nm_assert(handle->cancellable);
    nm_assert(!handle->shutdown_handle);

    g_dbus_connection_call(priv->dbus_connection,
                           priv->name_owner->str,
                           NM_WPAS_DBUS_PATH,
                           NM_WPAS_DBUS_INTERFACE,
                           "GetInterface",
                           g_variant_new("(s)", ifname),
                           G_VARIANT_TYPE("(o)"),
                           G_DBUS_CALL_FLAGS_NONE,
                           5000,
                           handle->cancellable,
                           _create_iface_dbus_call_get_interface_cb,
                           handle);
}

static void
_create_iface_dbus_call_create_interface(NMSupplicantManager         *self,
                                         NMSupplMgrCreateIfaceHandle *handle,
                                         const char                  *ifname)
{
    NMSupplicantManagerPrivate *priv = NM_SUPPLICANT_MANAGER_GET_PRIVATE(self);
    GVariantBuilder             builder;

    nm_assert(priv->name_owner == handle->name_owner);
    nm_assert(handle->cancellable);
    nm_assert(!handle->shutdown_handle);
    nm_assert(handle->create_iface_try_count <= CREATE_IFACE_TRY_COUNT_MAX);

    g_variant_builder_init(&builder, G_VARIANT_TYPE_VARDICT);
    g_variant_builder_add(&builder,
                          "{sv}",
                          "Driver",
                          g_variant_new_string(nm_supplicant_driver_to_string(handle->driver)));
    g_variant_builder_add(&builder, "{sv}", "Ifname", g_variant_new_string(ifname));

    handle->shutdown_handle = nm_shutdown_wait_obj_register_cancellable_full(
        handle->cancellable,
        g_strdup_printf("wpas-create-" NM_HASH_OBFUSCATE_PTR_FMT, NM_HASH_OBFUSCATE_PTR(handle)),
        TRUE);
    handle->create_iface_try_count++;
    g_dbus_connection_call(priv->dbus_connection,
                           handle->name_owner->str,
                           NM_WPAS_DBUS_PATH,
                           NM_WPAS_DBUS_INTERFACE,
                           "CreateInterface",
                           g_variant_new("(a{sv})", &builder),
                           G_VARIANT_TYPE("(o)"),
                           G_DBUS_CALL_FLAGS_NONE,
                           5000,
                           handle->cancellable,
                           _create_iface_dbus_call_create_interface_cb,
                           handle);
}

static void
_create_iface_dbus_start(NMSupplicantManager *self, NMSupplMgrCreateIfaceHandle *handle)
{
    NMSupplicantManagerPrivate *priv = NM_SUPPLICANT_MANAGER_GET_PRIVATE(self);
    char                        ifname[NM_IFNAMSIZ];

    nm_assert(priv->name_owner);
    nm_assert(!handle->cancellable);

    if (!nm_platform_if_indextoname(NM_PLATFORM_GET, handle->ifindex, ifname)) {
        nm_utils_error_set(&handle->fail_on_idle_error,
                           NM_UTILS_ERROR_UNKNOWN,
                           "Cannot find interface %d",
                           handle->ifindex);
        _LOGT("create-iface[" NM_HASH_OBFUSCATE_PTR_FMT
              "]: creating interface fails to find interface name for ifindex %d",
              NM_HASH_OBFUSCATE_PTR(handle),
              handle->ifindex);
        handle->fail_on_idle_id = g_idle_add(_create_iface_fail_on_idle_cb, handle);
        return;
    }

    /* Our handle keeps @self alive. That means, when NetworkManager shall shut
     * down, it's the responsibility of the callers to cancel the handles,
     * to initiate coordinated shutdown.
     *
     * However, we now issue a CreateInterface call. Even if the handle gets cancelled
     * (because of shutdown, or because the caller is no longer interested in the
     * result), we don't want to cancel this request. Instead, we want to get
     * the interface path and remove it right away.
     *
     * That means, the D-Bus call cannot be cancelled (because we always care about
     * the result). Only the @handle can be cancelled, but parts of the handle will
     * stick around to complete the task.
     *
     * See also handle->shutdown_handle.
     */
    handle->name_owner  = nm_ref_string_ref(priv->name_owner);
    handle->cancellable = g_cancellable_new();
    _LOGT("create-iface[" NM_HASH_OBFUSCATE_PTR_FMT "]: creating interface (ifname \"%s\")...",
          NM_HASH_OBFUSCATE_PTR(handle),
          ifname);
    _create_iface_dbus_call_create_interface(self, handle, ifname);
}

static gboolean
_create_iface_fail_on_idle_cb(gpointer user_data)
{
    NMSupplMgrCreateIfaceHandle *handle = user_data;

    handle->fail_on_idle_id = 0;

    _LOGT("create-iface[" NM_HASH_OBFUSCATE_PTR_FMT "]: fail with internal error: %s",
          NM_HASH_OBFUSCATE_PTR(handle),
          handle->fail_on_idle_error->message);

    _create_iface_complete(handle, NULL, handle->fail_on_idle_error);
    return G_SOURCE_REMOVE;
}

NMSupplMgrCreateIfaceHandle *
nm_supplicant_manager_create_interface(NMSupplicantManager                 *self,
                                       int                                  ifindex,
                                       NMSupplicantDriver                   driver,
                                       NMSupplicantManagerCreateInterfaceCb callback,
                                       gpointer                             user_data)
{
    NMSupplicantManagerPrivate  *priv;
    NMSupplMgrCreateIfaceHandle *handle;

    g_return_val_if_fail(NM_IS_SUPPLICANT_MANAGER(self), NULL);
    g_return_val_if_fail(ifindex > 0, NULL);
    g_return_val_if_fail(callback, NULL);
    nm_assert(nm_supplicant_driver_to_string(driver));

    priv = NM_SUPPLICANT_MANAGER_GET_PRIVATE(self);

    handle  = g_slice_new(NMSupplMgrCreateIfaceHandle);
    *handle = (NMSupplMgrCreateIfaceHandle) {
        .self               = g_object_ref(self),
        .callback           = callback,
        .callback_user_data = user_data,
        .driver             = driver,
        .ifindex            = ifindex,
    };
    c_list_link_tail(&priv->create_iface_lst_head, &handle->create_iface_lst);

    if (!priv->dbus_connection) {
        _LOGT("create-iface[" NM_HASH_OBFUSCATE_PTR_FMT
              "]: new request interface %d (driver %s). Fail because no D-Bus connection to talk "
              "to wpa_supplicant...",
              NM_HASH_OBFUSCATE_PTR(handle),
              ifindex,
              nm_supplicant_driver_to_string(driver));
        nm_utils_error_set(&handle->fail_on_idle_error,
                           NM_UTILS_ERROR_UNKNOWN,
                           "No D-Bus connection to talk to wpa_supplicant");
        handle->fail_on_idle_id = g_idle_add(_create_iface_fail_on_idle_cb, handle);
        return handle;
    }

    if (!priv->name_owner) {
        _LOGT(
            "create-iface[" NM_HASH_OBFUSCATE_PTR_FMT "]: new request interface %d (driver %s). %s",
            NM_HASH_OBFUSCATE_PTR(handle),
            ifindex,
            nm_supplicant_driver_to_string(driver),
            priv->poke_name_owner_cancellable ? "Waiting for supplicant..." : "Poke supplicant...");
        _poke_name_owner(self);
        return handle;
    }

    if (priv->get_capabilities_cancellable) {
        _LOGT("create-iface[" NM_HASH_OBFUSCATE_PTR_FMT
              "]: new request interface %d (driver %s). Waiting to fetch capabilities for %s...",
              NM_HASH_OBFUSCATE_PTR(handle),
              ifindex,
              nm_supplicant_driver_to_string(driver),
              priv->name_owner->str);
        return handle;
    }

    _LOGT("create-iface[" NM_HASH_OBFUSCATE_PTR_FMT
          "]: new request interface %d (driver %s). create interface on %s...",
          NM_HASH_OBFUSCATE_PTR(handle),
          ifindex,
          nm_supplicant_driver_to_string(driver),
          priv->name_owner->str);

    _create_iface_dbus_start(self, handle);
    return handle;
}

static void
_create_iface_proceed_all(NMSupplicantManager *self, GError *error)
{
    NMSupplicantManagerPrivate  *priv = NM_SUPPLICANT_MANAGER_GET_PRIVATE(self);
    NMSupplMgrCreateIfaceHandle *handle;

    nm_assert(error || priv->name_owner);
    nm_assert(error || !priv->get_capabilities_cancellable);

    if (c_list_is_empty(&priv->create_iface_lst_head))
        return;

    if (error) {
        CList alt_list;

        /* we move the handles we want to proceed to a alternative list.
         * That is, because we invoke callbacks to the caller, who might
         * create another request right away. We don't want to proceed
         * that one. */
        c_list_init(&alt_list);
        c_list_splice(&alt_list, &priv->create_iface_lst_head);

        while ((handle =
                    c_list_last_entry(&alt_list, NMSupplMgrCreateIfaceHandle, create_iface_lst))) {
            /* We don't need to keep @self alive. Every handle holds a reference already. */
            _LOGT("create-iface[" NM_HASH_OBFUSCATE_PTR_FMT "]: create interface failed: %s",
                  NM_HASH_OBFUSCATE_PTR(handle),
                  error->message);
            _create_iface_complete(handle, NULL, error);
        }
        return;
    }

    /* start all the handles. This does not invoke callbacks, so the list of handles
     * cannot be modified while we iterate it. */
    c_list_for_each_entry (handle, &priv->create_iface_lst_head, create_iface_lst) {
        _LOGT("create-iface[" NM_HASH_OBFUSCATE_PTR_FMT "]: create interface on %s...",
              NM_HASH_OBFUSCATE_PTR(handle),
              priv->name_owner->str);
        _create_iface_dbus_start(self, handle);
    }
}

void
nm_supplicant_manager_create_interface_cancel(NMSupplMgrCreateIfaceHandle *handle)
{
    gs_free_error GError *error = NULL;

    if (!handle)
        return;

    g_return_if_fail(NM_IS_SUPPLICANT_MANAGER(handle->self));
    g_return_if_fail(handle->callback);
    nm_assert(!c_list_is_empty(&handle->create_iface_lst));

    nm_utils_error_set_cancelled(&error, FALSE, NULL);
    _create_iface_complete(handle, NULL, error);
}

NMSupplicantInterface *
nm_supplicant_manager_create_interface_from_path(NMSupplicantManager *self, const char *object_path)
{
    NMSupplicantManagerPrivate     *priv;
    NMSupplicantInterface          *supp_iface;
    nm_auto_ref_string NMRefString *iface_path = NULL;

    g_return_val_if_fail(NM_IS_SUPPLICANT_MANAGER(self), NULL);
    g_return_val_if_fail(object_path, NULL);

    priv = NM_SUPPLICANT_MANAGER_GET_PRIVATE(self);

    iface_path = nm_ref_string_new(object_path);

    supp_iface = g_hash_table_lookup(priv->supp_ifaces, iface_path);

    if (supp_iface)
        return g_object_ref(supp_iface);

    supp_iface = nm_supplicant_interface_new(self, iface_path, 0, NM_SUPPLICANT_DRIVER_UNKNOWN);

    _supp_iface_add(self, iface_path, supp_iface);

    return supp_iface;
}

/*****************************************************************************/

static void
_dbus_interface_removed_cb(GDBusConnection *connection,
                           const char      *sender_name,
                           const char      *object_path,
                           const char      *signal_interface_name,
                           const char      *signal_name,
                           GVariant        *parameters,
                           gpointer         user_data)
{
    NMSupplicantManager            *self = user_data;
    NMSupplicantManagerPrivate     *priv = NM_SUPPLICANT_MANAGER_GET_PRIVATE(self);
    NMSupplicantInterface          *supp_iface;
    const char                     *iface_path_str;
    nm_auto_ref_string NMRefString *iface_path = NULL;

    nm_assert(nm_streq(sender_name, priv->name_owner->str));

    if (!g_variant_is_of_type(parameters, G_VARIANT_TYPE("(o)")))
        return;

    g_variant_get(parameters, "(&o)", &iface_path_str);

    iface_path = nm_ref_string_new(iface_path_str);

    supp_iface = g_hash_table_lookup(priv->supp_ifaces, iface_path);
    if (!supp_iface)
        return;

    _supp_iface_remove_one(self, supp_iface, FALSE, "InterfaceRemoved signal from wpa_supplicant");
}

/*****************************************************************************/

static void
_dbus_get_capabilities_cb(GVariant *res, GError *error, gpointer user_data)
{
    NMSupplicantManager        *self;
    NMSupplicantManagerPrivate *priv;

    if (nm_utils_error_is_cancelled(error))
        return;

    self = user_data;
    priv = NM_SUPPLICANT_MANAGER_GET_PRIVATE(self);

    g_clear_object(&priv->get_capabilities_cancellable);

    /* The supplicant only advertises global capabilities if the following
     * commit has been applied:
     *
     * commit 1634ac0654eba8d458640a115efc0a6cde3bac4d
     * Author: Dan Williams <dcbw@redhat.com>
     * Date:   Sat Sep 29 19:06:30 2012 +0300
     *
     * dbus: Add global capabilities property
     */
    _caps_set(priv, NM_SUPPL_CAP_TYPE_AP, NM_TERNARY_DEFAULT);
    _caps_set(priv, NM_SUPPL_CAP_TYPE_PMF, NM_TERNARY_DEFAULT);
    _caps_set(priv, NM_SUPPL_CAP_TYPE_FILS, NM_TERNARY_DEFAULT);

    /* Support for the following is newer than the capabilities property */
    _caps_set(priv, NM_SUPPL_CAP_TYPE_P2P, NM_TERNARY_FALSE);
    _caps_set(priv, NM_SUPPL_CAP_TYPE_FT, NM_TERNARY_FALSE);
    _caps_set(priv, NM_SUPPL_CAP_TYPE_SHA384, NM_TERNARY_FALSE);
    _caps_set(priv, NM_SUPPL_CAP_TYPE_MESH, NM_TERNARY_FALSE);
    _caps_set(priv, NM_SUPPL_CAP_TYPE_FAST, NM_TERNARY_FALSE);
    _caps_set(priv, NM_SUPPL_CAP_TYPE_WFD, NM_TERNARY_FALSE);

    if (res) {
        nm_auto_free_variant_iter GVariantIter *res_iter = NULL;
        const char                             *res_key;
        GVariant                               *res_val;

        g_variant_get(res, "(a{sv})", &res_iter);
        while (g_variant_iter_loop(res_iter, "{&sv}", &res_key, &res_val)) {
            if (nm_streq(res_key, "Capabilities")) {
                if (g_variant_is_of_type(res_val, G_VARIANT_TYPE_STRING_ARRAY)) {
                    gs_free const char **array = NULL;
                    const char         **a;

                    array = g_variant_get_strv(res_val, NULL);
                    _caps_set(priv, NM_SUPPL_CAP_TYPE_AP, NM_TERNARY_FALSE);
                    _caps_set(priv, NM_SUPPL_CAP_TYPE_PMF, NM_TERNARY_FALSE);
                    _caps_set(priv, NM_SUPPL_CAP_TYPE_FILS, NM_TERNARY_FALSE);
                    _caps_set(priv, NM_SUPPL_CAP_TYPE_SUITEB192, NM_TERNARY_FALSE);
                    _caps_set(priv, NM_SUPPL_CAP_TYPE_WEP, NM_TERNARY_TRUE);
                    if (array) {
                        for (a = array; *a; a++) {
                            if (nm_streq(*a, "ap")) {
                                _caps_set(priv, NM_SUPPL_CAP_TYPE_AP, NM_TERNARY_TRUE);
                                continue;
                            }
                            if (nm_streq(*a, "pmf")) {
                                _caps_set(priv, NM_SUPPL_CAP_TYPE_PMF, NM_TERNARY_TRUE);
                                continue;
                            }
                            if (nm_streq(*a, "fils")) {
                                _caps_set(priv, NM_SUPPL_CAP_TYPE_FILS, NM_TERNARY_TRUE);
                                continue;
                            }
                            if (nm_streq(*a, "p2p")) {
                                _caps_set(priv, NM_SUPPL_CAP_TYPE_P2P, NM_TERNARY_TRUE);
                                continue;
                            }
                            if (nm_streq(*a, "ft")) {
                                _caps_set(priv, NM_SUPPL_CAP_TYPE_FT, NM_TERNARY_TRUE);
                                continue;
                            }
                            if (nm_streq(*a, "sha384")) {
                                _caps_set(priv, NM_SUPPL_CAP_TYPE_SHA384, NM_TERNARY_TRUE);
                                continue;
                            }
                            if (nm_streq(*a, "mesh")) {
                                _caps_set(priv, NM_SUPPL_CAP_TYPE_MESH, NM_TERNARY_TRUE);
                                continue;
                            }
                            if (nm_streq(*a, "suiteb192")) {
                                _caps_set(priv, NM_SUPPL_CAP_TYPE_SUITEB192, NM_TERNARY_TRUE);
                                continue;
                            }
                            if (nm_streq(*a, "wep_disabled")) {
                                _caps_set(priv, NM_SUPPL_CAP_TYPE_WEP, NM_TERNARY_FALSE);
                                continue;
                            }
                        }
                    }
                }
                continue;
            }
            if (nm_streq(res_key, "EapMethods")) {
                if (g_variant_is_of_type(res_val, G_VARIANT_TYPE_STRING_ARRAY)) {
                    gs_free const char **array = NULL;
                    const char         **a;

                    array = g_variant_get_strv(res_val, NULL);
                    if (array) {
                        for (a = array; *a; a++) {
                            if (g_ascii_strcasecmp(*a, "FAST") == 0) {
                                _caps_set(priv, NM_SUPPL_CAP_TYPE_FAST, NM_TERNARY_TRUE);
                                break;
                            }
                        }
                    }
                }
                continue;
            }
            if (nm_streq(res_key, "WFDIEs")) {
                _caps_set(priv, NM_SUPPL_CAP_TYPE_WFD, NM_TERNARY_TRUE);
                continue;
            }
        }
    }

    _LOGD("supported features:"
          " AP%c"
          " PMF%c"
          " FILS%c"
          " P2P%c"
          " FT%c"
          " SHA384%c"
          " MESH%c"
          " FAST%c"
          " WFD%c"
          " SUITEB192%c"
          " WEP%c"
          "",
          NM_SUPPL_CAP_TO_CHAR(priv->capabilities, NM_SUPPL_CAP_TYPE_AP),
          NM_SUPPL_CAP_TO_CHAR(priv->capabilities, NM_SUPPL_CAP_TYPE_PMF),
          NM_SUPPL_CAP_TO_CHAR(priv->capabilities, NM_SUPPL_CAP_TYPE_FILS),
          NM_SUPPL_CAP_TO_CHAR(priv->capabilities, NM_SUPPL_CAP_TYPE_P2P),
          NM_SUPPL_CAP_TO_CHAR(priv->capabilities, NM_SUPPL_CAP_TYPE_FT),
          NM_SUPPL_CAP_TO_CHAR(priv->capabilities, NM_SUPPL_CAP_TYPE_SHA384),
          NM_SUPPL_CAP_TO_CHAR(priv->capabilities, NM_SUPPL_CAP_TYPE_MESH),
          NM_SUPPL_CAP_TO_CHAR(priv->capabilities, NM_SUPPL_CAP_TYPE_FAST),
          NM_SUPPL_CAP_TO_CHAR(priv->capabilities, NM_SUPPL_CAP_TYPE_WFD),
          NM_SUPPL_CAP_TO_CHAR(priv->capabilities, NM_SUPPL_CAP_TYPE_SUITEB192),
          NM_SUPPL_CAP_TO_CHAR(priv->capabilities, NM_SUPPL_CAP_TYPE_WEP));

    nm_assert(g_hash_table_size(priv->supp_ifaces) == 0);
    nm_assert(c_list_is_empty(&priv->supp_lst_head));

    _create_iface_proceed_all(self, NULL);
}

/*****************************************************************************/

void
_nm_supplicant_manager_unregister_interface(NMSupplicantManager   *self,
                                            NMSupplicantInterface *supp_iface)
{
    NMSupplicantManagerPrivate *priv = NM_SUPPLICANT_MANAGER_GET_PRIVATE(self);

    nm_assert(NM_IS_SUPPLICANT_INTERFACE(supp_iface));
    nm_assert(c_list_contains(&NM_SUPPLICANT_MANAGER_GET_PRIVATE(self)->supp_lst_head,
                              &supp_iface->supp_lst));

    c_list_unlink(&supp_iface->supp_lst);
    if (!g_hash_table_remove(priv->supp_ifaces,
                             nm_supplicant_interface_get_object_path(supp_iface)))
        nm_assert_not_reached();
}

static void
_supp_iface_add(NMSupplicantManager   *self,
                NMRefString           *iface_path,
                NMSupplicantInterface *supp_iface)
{
    NMSupplicantManagerPrivate *priv = NM_SUPPLICANT_MANAGER_GET_PRIVATE(self);

    c_list_link_tail(&priv->supp_lst_head, &supp_iface->supp_lst);
    if (!g_hash_table_insert(priv->supp_ifaces, iface_path, supp_iface))
        nm_assert_not_reached();
}

static void
_supp_iface_remove_one(NMSupplicantManager   *self,
                       NMSupplicantInterface *supp_iface,
                       gboolean               force_remove_from_supplicant,
                       const char            *reason)
{
#if NM_MORE_ASSERTS
    _nm_unused gs_unref_object NMSupplicantInterface *supp_iface_keep_alive =
        g_object_ref(supp_iface);
#endif

    nm_assert(NM_IS_SUPPLICANT_MANAGER(self));
    nm_assert(NM_IS_SUPPLICANT_INTERFACE(supp_iface));
    nm_assert(c_list_contains(&NM_SUPPLICANT_MANAGER_GET_PRIVATE(self)->supp_lst_head,
                              &supp_iface->supp_lst));

    _nm_supplicant_interface_set_state_down(supp_iface, force_remove_from_supplicant, reason);

    nm_assert(c_list_is_empty(&supp_iface->supp_lst));
}

static void
_supp_iface_remove_all(NMSupplicantManager *self,
                       gboolean             force_remove_from_supplicant,
                       const char          *reason)
{
    NMSupplicantManagerPrivate *priv = NM_SUPPLICANT_MANAGER_GET_PRIVATE(self);
    NMSupplicantInterface      *supp_iface;

    while ((supp_iface = c_list_first_entry(&priv->supp_lst_head, NMSupplicantInterface, supp_lst)))
        _supp_iface_remove_one(self, supp_iface, force_remove_from_supplicant, reason);
}

/*****************************************************************************/

static gboolean
_available_reset_cb(gpointer user_data)
{
    NMSupplicantManager        *self = user_data;
    NMSupplicantManagerPrivate *priv = NM_SUPPLICANT_MANAGER_GET_PRIVATE(self);

    priv->available_reset_id = 0;
    nm_assert(priv->available == NM_TERNARY_FALSE);
    priv->available = NM_TERNARY_DEFAULT;
    g_signal_emit(self, signals[AVAILABLE_CHANGED], 0);
    return G_SOURCE_REMOVE;
}

/*****************************************************************************/

static void
name_owner_changed(NMSupplicantManager *self, const char *name_owner, gboolean first_time)
{
    NMSupplicantManagerPrivate *priv = NM_SUPPLICANT_MANAGER_GET_PRIVATE(self);
    NMTernary                   available;
    gboolean                    available_changed = FALSE;

    nm_assert(!priv->get_name_owner_cancellable);
    nm_assert(!name_owner || name_owner[0]);
    nm_assert((first_time && !priv->name_owner)
              || (!first_time && (!!priv->name_owner) != (!!name_owner)));

    if (first_time) {
        _LOGD("wpa_supplicant name owner %s%s%s (%srunning)",
              NM_PRINT_FMT_QUOTE_STRING(name_owner),
              name_owner ? "" : "not ");
    } else {
        _LOGD("wpa_supplicant name owner \"%s\" %s (%srunning)",
              name_owner ?: priv->name_owner->str,
              name_owner ? "disappeared" : "appeared",
              name_owner ? "" : "not ");
    }

    nm_ref_string_unref(priv->name_owner);
    priv->name_owner = nm_ref_string_new(name_owner);

    nm_clear_g_dbus_connection_signal(priv->dbus_connection, &priv->interface_removed_id);

    if (name_owner) {
        if (nm_clear_g_source(&priv->poke_name_owner_timeout_id))
            _LOGT("poke service \"%s\" completed with name owner change", NM_WPAS_DBUS_SERVICE);
        nm_clear_g_cancellable(&priv->poke_name_owner_cancellable);
    }

    nm_clear_g_cancellable(&priv->get_capabilities_cancellable);

    priv->capabilities = NM_SUPPL_CAP_MASK_NONE;
    if (priv->name_owner) {
        priv->get_capabilities_cancellable = g_cancellable_new();
        nm_dbus_connection_call_get_all(priv->dbus_connection,
                                        priv->name_owner->str,
                                        NM_WPAS_DBUS_PATH,
                                        NM_WPAS_DBUS_INTERFACE,
                                        5000,
                                        priv->get_capabilities_cancellable,
                                        _dbus_get_capabilities_cb,
                                        self);
        priv->interface_removed_id = g_dbus_connection_signal_subscribe(priv->dbus_connection,
                                                                        priv->name_owner->str,
                                                                        NM_WPAS_DBUS_INTERFACE,
                                                                        "InterfaceRemoved",
                                                                        NULL,
                                                                        NULL,
                                                                        G_DBUS_SIGNAL_FLAGS_NONE,
                                                                        _dbus_interface_removed_cb,
                                                                        self,
                                                                        NULL);
    }

    /* if supplicant is running (has a name owner), we may use it.
     * If this is the first time, and supplicant is not running, we
     * may also use it (and assume that we probably could D-Bus activate
     * it).
     *
     * Otherwise, somebody else stopped supplicant. It's no longer useable to
     * us and we block auto starting it. The user has to start the service...
     *
     * Actually, below we reset the hard block after a short timeout. This
     * causes the caller to notify that supplicant may now by around and
     * retry to D-Bus activate it. */
    if (priv->name_owner)
        available = NM_TERNARY_TRUE;
    else if (first_time)
        available = NM_TERNARY_DEFAULT;
    else
        available = NM_TERNARY_FALSE;

    if (priv->available != available) {
        priv->available = available;
        _LOGD("supplicant is now %savailable",
              available == FALSE ? "not " : (available == TRUE ? "" : "maybe "));
        available_changed = TRUE;

        nm_clear_g_source(&priv->available_reset_id);
        if (available == NM_TERNARY_FALSE) {
            /* reset the availability from a hard "no" to a "maybe" in a bit. */
            priv->available_reset_id = g_timeout_add_seconds(60, _available_reset_cb, self);
        }
    }

    _supp_iface_remove_all(self, TRUE, "name-owner changed");

    if (!priv->name_owner) {
        if (priv->poke_name_owner_timeout_id) {
            /* we are still poking for the service to start. Don't cancel
             * the pending create requests just yet. */
        } else {
            gs_free_error GError *local_error = NULL;

            /* When we loose the name owner, we fail all pending creation requests. */
            nm_utils_error_set(&local_error, NM_UTILS_ERROR_UNKNOWN, "Name owner lost");
            _create_iface_proceed_all(self, local_error);
        }
    } else {
        /* We got a name-owner, but we don't do anything. Instead let
         * _dbus_get_capabilities_cb() complete and kick of the create-iface
         * handles.
         *
         * Note that before the first name-owner change, all create-iface
         * requests fail right away. So we don't have to handle them here
         * (by starting to poke the service). */
    }

    if (available_changed)
        g_signal_emit(self, signals[AVAILABLE_CHANGED], 0);
}

static void
name_owner_changed_cb(GDBusConnection *connection,
                      const char      *sender_name,
                      const char      *object_path,
                      const char      *interface_name,
                      const char      *signal_name,
                      GVariant        *parameters,
                      gpointer         user_data)
{
    gs_unref_object NMSupplicantManager *self = g_object_ref(user_data);
    NMSupplicantManagerPrivate          *priv = NM_SUPPLICANT_MANAGER_GET_PRIVATE(self);
    const char                          *name_owner;

    if (!g_variant_is_of_type(parameters, G_VARIANT_TYPE("(sss)")))
        return;

    if (priv->get_name_owner_cancellable)
        return;

    g_variant_get(parameters, "(&s&s&s)", NULL, NULL, &name_owner);

    name_owner = nm_str_not_empty(name_owner);

    if (nm_streq0(name_owner, nm_ref_string_get_str(priv->name_owner)))
        return;

    if (name_owner && priv->name_owner) {
        /* odd, we directly switch from one name owner to the next. Can't allow that.
         * First clear the name owner before resetting. */
        name_owner_changed(self, NULL, FALSE);
    }
    name_owner_changed(user_data, name_owner, FALSE);
}

static void
get_name_owner_cb(const char *name_owner, GError *error, gpointer user_data)
{
    NMSupplicantManager        *self = user_data;
    NMSupplicantManagerPrivate *priv = NM_SUPPLICANT_MANAGER_GET_PRIVATE(self);

    if (!name_owner && nm_utils_error_is_cancelled(error))
        return;

    self = user_data;
    priv = NM_SUPPLICANT_MANAGER_GET_PRIVATE(self);

    g_clear_object(&priv->get_name_owner_cancellable);

    name_owner_changed(self, nm_str_not_empty(name_owner), TRUE);
}

/*****************************************************************************/

static void
nm_supplicant_manager_init(NMSupplicantManager *self)
{
    NMSupplicantManagerPrivate *priv = NM_SUPPLICANT_MANAGER_GET_PRIVATE(self);

    nm_assert(priv->capabilities == NM_SUPPL_CAP_MASK_NONE);
    nm_assert(priv->available == NM_TERNARY_FALSE);

    priv->supp_ifaces = g_hash_table_new(nm_direct_hash, NULL);
    c_list_init(&priv->supp_lst_head);
    c_list_init(&priv->create_iface_lst_head);

    priv->dbus_connection = nm_g_object_ref(NM_MAIN_DBUS_CONNECTION_GET);

    if (!priv->dbus_connection) {
        _LOGI("no D-Bus connection to talk to wpa_supplicant");
        return;
    }

    priv->name_owner_changed_id =
        nm_dbus_connection_signal_subscribe_name_owner_changed(priv->dbus_connection,
                                                               NM_WPAS_DBUS_SERVICE,
                                                               name_owner_changed_cb,
                                                               self,
                                                               NULL);
    priv->get_name_owner_cancellable = g_cancellable_new();
    nm_dbus_connection_call_get_name_owner(priv->dbus_connection,
                                           NM_WPAS_DBUS_SERVICE,
                                           -1,
                                           priv->get_name_owner_cancellable,
                                           get_name_owner_cb,
                                           self);
}

static void
dispose(GObject *object)
{
    NMSupplicantManager        *self = (NMSupplicantManager *) object;
    NMSupplicantManagerPrivate *priv = NM_SUPPLICANT_MANAGER_GET_PRIVATE(self);

    _supp_iface_remove_all(self, TRUE, "NMSupplicantManager is disposing");

    nm_assert(c_list_is_empty(&priv->create_iface_lst_head));

    nm_clear_g_source(&priv->available_reset_id);

    priv->available = NM_TERNARY_FALSE;
    nm_clear_pointer(&priv->name_owner, nm_ref_string_unref);

    nm_clear_g_source(&priv->poke_name_owner_timeout_id);
    nm_clear_g_cancellable(&priv->poke_name_owner_cancellable);

    nm_clear_g_dbus_connection_signal(priv->dbus_connection, &priv->interface_removed_id);
    nm_clear_g_dbus_connection_signal(priv->dbus_connection, &priv->name_owner_changed_id);

    nm_clear_g_cancellable(&priv->get_name_owner_cancellable);
    nm_clear_g_cancellable(&priv->get_capabilities_cancellable);

    G_OBJECT_CLASS(nm_supplicant_manager_parent_class)->dispose(object);

    g_clear_object(&priv->dbus_connection);

    nm_clear_pointer(&priv->supp_ifaces, g_hash_table_destroy);
}

static void
nm_supplicant_manager_class_init(NMSupplicantManagerClass *klass)
{
    GObjectClass *object_class = G_OBJECT_CLASS(klass);

    object_class->dispose = dispose;

    signals[AVAILABLE_CHANGED] = g_signal_new(NM_SUPPLICANT_MANAGER_AVAILABLE_CHANGED,
                                              G_OBJECT_CLASS_TYPE(object_class),
                                              G_SIGNAL_RUN_LAST,
                                              0,
                                              NULL,
                                              NULL,
                                              NULL,
                                              G_TYPE_NONE,
                                              0);
}
