summary refs log tree commit diff
path: root/src/devices/wwan/nm-modem-manager.c
diff options
context:
space:
mode:
authorMichael Biebl <biebl@debian.org>2017-11-07 00:14:39 +0100
committerMichael Biebl <biebl@debian.org>2017-11-07 00:14:39 +0100
commit90e8691111889a7b5f3c812f5a41f15a8a058913 (patch)
treef101a879eca27c34a9bfa5f3da52266b22539a36 /src/devices/wwan/nm-modem-manager.c
parentbdb6eeb0670658255c2a4c3c501c0a27fa8cfe55 (diff)
New upstream version 1.9.90 upstream/1.9.90
Diffstat (limited to 'src/devices/wwan/nm-modem-manager.c')
-rw-r--r--src/devices/wwan/nm-modem-manager.c813
1 files changed, 555 insertions, 258 deletions
diff --git a/src/devices/wwan/nm-modem-manager.c b/src/devices/wwan/nm-modem-manager.c
index b1f6d92e..59cd2bca 100644
--- a/src/devices/wwan/nm-modem-manager.c
+++ b/src/devices/wwan/nm-modem-manager.c
@@ -45,6 +45,10 @@
 
 /*****************************************************************************/
 
+NM_GOBJECT_PROPERTIES_DEFINE (NMModemManager,
+	PROP_NAME_OWNER,
+);
+
 enum {
 	MODEM_ADDED,
 	LAST_SIGNAL,
@@ -54,14 +58,38 @@ static guint signals[LAST_SIGNAL] = { 0 };
 
 typedef struct {
 	GDBusConnection *dbus_connection;
-	MMManager *modem_manager;
-	guint mm_launch_id;
-	gulong mm_name_owner_changed_id;
-	gulong mm_object_added_id;
-	gulong mm_object_removed_id;
+
+	/* used during g_bus_get() and later during mm_manager_new(). */
+	GCancellable *main_cancellable;
+
+	struct {
+		MMManager *manager;
+		GCancellable *poke_cancellable;
+		gulong handle_name_owner_changed_id;
+		gulong handle_object_added_id;
+		gulong handle_object_removed_id;
+		guint relaunch_id;
+
+		/* this only has one use: that the <info> logging line about
+		 * ModemManager available distinguishes between first-time
+		 * and later name-owner-changed. */
+		enum {
+			LOG_AVAILABLE_NOT_INITIALIZED = 0,
+			LOG_AVAILABLE_YES,
+			LOG_AVAILABLE_NO,
+		} log_available:3;
+
+		GDBusProxy *proxy;
+		GCancellable *proxy_cancellable;
+		guint proxy_ref_count;
+		char *proxy_name_owner;
+	} modm;
 
 #if WITH_OFONO
-	GDBusProxy *ofono_proxy;
+	struct {
+		GDBusProxy *proxy;
+		GCancellable *cancellable;
+	} ofono;
 #endif
 
 	GHashTable *modems;
@@ -82,19 +110,35 @@ G_DEFINE_TYPE (NMModemManager, nm_modem_manager, G_TYPE_OBJECT)
 
 /*****************************************************************************/
 
+#define _NMLOG_DOMAIN      LOGD_MB
+#define _NMLOG(level, ...) __NMLOG_DEFAULT (level, _NMLOG_DOMAIN, "modem-manager", __VA_ARGS__)
+
+/*****************************************************************************/
+
+NM_DEFINE_SINGLETON_GETTER (NMModemManager, nm_modem_manager_get, NM_TYPE_MODEM_MANAGER);
+
+/*****************************************************************************/
+
+static void modm_schedule_manager_relaunch (NMModemManager *self,
+                                            guint n_seconds);
+static void modm_ensure_manager (NMModemManager *self);
+
+/*****************************************************************************/
+
 static void
 handle_new_modem (NMModemManager *self, NMModem *modem)
 {
+	NMModemManagerPrivate *priv = NM_MODEM_MANAGER_GET_PRIVATE (self);
 	const char *path;
 
 	path = nm_modem_get_path (modem);
-	if (g_hash_table_lookup (self->_priv.modems, path)) {
+	if (g_hash_table_lookup (priv->modems, path)) {
 		g_warn_if_reached ();
 		return;
 	}
 
 	/* Track the new modem */
-	g_hash_table_insert (self->_priv.modems, g_strdup (path), modem);
+	g_hash_table_insert (priv->modems, g_strdup (path), modem);
 	g_signal_emit (self, signals[MODEM_ADDED], 0, modem);
 }
 
@@ -105,22 +149,27 @@ remove_one_modem (gpointer key, gpointer value, gpointer user_data)
 	return TRUE;
 }
 
+/*****************************************************************************/
+
 static void
-clear_modem_manager (NMModemManager *self)
+modm_clear_manager (NMModemManager *self)
 {
-	if (!self->_priv.modem_manager)
+	NMModemManagerPrivate *priv = NM_MODEM_MANAGER_GET_PRIVATE (self);
+
+	if (!priv->modm.manager)
 		return;
-	nm_clear_g_signal_handler (self->_priv.modem_manager, &self->_priv.mm_name_owner_changed_id);
-	nm_clear_g_signal_handler (self->_priv.modem_manager, &self->_priv.mm_object_added_id);
-	nm_clear_g_signal_handler (self->_priv.modem_manager, &self->_priv.mm_object_removed_id);
-	g_clear_object (&self->_priv.modem_manager);
+	nm_clear_g_signal_handler (priv->modm.manager, &priv->modm.handle_name_owner_changed_id);
+	nm_clear_g_signal_handler (priv->modm.manager, &priv->modm.handle_object_added_id);
+	nm_clear_g_signal_handler (priv->modm.manager, &priv->modm.handle_object_removed_id);
+	g_clear_object (&priv->modm.manager);
 }
 
 static void
-modem_object_added (MMManager *modem_manager,
-                    MMObject  *modem_object,
-                    NMModemManager *self)
+modm_handle_object_added (MMManager *modem_manager,
+                          MMObject  *modem_object,
+                          NMModemManager *self)
 {
+	NMModemManagerPrivate *priv = NM_MODEM_MANAGER_GET_PRIVATE (self);
 	const gchar *path;
 	MMModem *modem_iface;
 	NMModem *modem;
@@ -128,21 +177,21 @@ modem_object_added (MMManager *modem_manager,
 
 	/* Ensure we don't have the same modem already */
 	path = mm_object_get_path (modem_object);
-	if (g_hash_table_lookup (self->_priv.modems, path)) {
-		nm_log_warn (LOGD_MB, "modem with path %s already exists, ignoring", path);
+	if (g_hash_table_lookup (priv->modems, path)) {
+		_LOGW ("modem with path %s already exists, ignoring", path);
 		return;
 	}
 
 	/* Ensure we have the 'Modem' interface at least */
 	modem_iface = mm_object_peek_modem (modem_object);
 	if (!modem_iface) {
-		nm_log_warn (LOGD_MB, "modem with path %s doesn't have the Modem interface, ignoring", path);
+		_LOGW ("modem with path %s doesn't have the Modem interface, ignoring", path);
 		return;
 	}
 
 	/* Ensure we have a primary port reported */
 	if (!mm_modem_get_primary_port (modem_iface)) {
-		nm_log_warn (LOGD_MB, "modem with path %s has unknown primary port, ignoring", path);
+		_LOGW ("modem with path %s has unknown primary port, ignoring", path);
 		return;
 	}
 
@@ -150,65 +199,68 @@ modem_object_added (MMManager *modem_manager,
 	modem = nm_modem_broadband_new (G_OBJECT (modem_object), &error);
 	if (modem)
 		handle_new_modem (self, modem);
-	else {
-		nm_log_warn (LOGD_MB, "failed to create modem: %s",
-		             error->message);
-	}
+	else
+		_LOGW ("failed to create modem: %s", error->message);
 	g_clear_error (&error);
 }
 
 static void
-modem_object_removed (MMManager *manager,
-                      MMObject  *modem_object,
-                      NMModemManager *self)
+modm_handle_object_removed (MMManager *manager,
+                            MMObject  *modem_object,
+                            NMModemManager *self)
 {
+	NMModemManagerPrivate *priv = NM_MODEM_MANAGER_GET_PRIVATE (self);
 	NMModem *modem;
 	const gchar *path;
 
 	path = mm_object_get_path (modem_object);
-	modem = (NMModem *) g_hash_table_lookup (self->_priv.modems, path);
+	modem = (NMModem *) g_hash_table_lookup (priv->modems, path);
 	if (!modem)
 		return;
 
 	nm_modem_emit_removed (modem);
-	g_hash_table_remove (self->_priv.modems, path);
+	g_hash_table_remove (priv->modems, path);
 }
 
 static void
-modem_manager_available (NMModemManager *self)
+modm_manager_available (NMModemManager *self)
 {
+	NMModemManagerPrivate *priv = NM_MODEM_MANAGER_GET_PRIVATE (self);
 	GList *modems, *l;
 
-	nm_log_info (LOGD_MB, "ModemManager available in the bus");
+	if (priv->modm.log_available != LOG_AVAILABLE_YES) {
+		_LOGI ("ModemManager %savailable", priv->modm.log_available ? "now " : "");
+		priv->modm.log_available = LOG_AVAILABLE_YES;
+	}
 
 	/* Update initial modems list */
-	modems = g_dbus_object_manager_get_objects (G_DBUS_OBJECT_MANAGER (self->_priv.modem_manager));
+	modems = g_dbus_object_manager_get_objects (G_DBUS_OBJECT_MANAGER (priv->modm.manager));
 	for (l = modems; l; l = g_list_next (l))
-		modem_object_added (self->_priv.modem_manager, MM_OBJECT (l->data), self);
+		modm_handle_object_added (priv->modm.manager, MM_OBJECT (l->data), self);
 	g_list_free_full (modems, (GDestroyNotify) g_object_unref);
 }
 
-static void schedule_modem_manager_relaunch (NMModemManager *self,
-                                             guint n_seconds);
-static void ensure_modem_manager (NMModemManager *self);
-
 static void
-modem_manager_name_owner_changed (MMManager *modem_manager,
-                                  GParamSpec *pspec,
-                                  NMModemManager *self)
+modm_handle_name_owner_changed (MMManager *modem_manager,
+                                GParamSpec *pspec,
+                                NMModemManager *self)
 {
+	NMModemManagerPrivate *priv = NM_MODEM_MANAGER_GET_PRIVATE (self);
 	gchar *name_owner;
 
 	/* Quit poking, if any */
-	nm_clear_g_source (&self->_priv.mm_launch_id);
+	nm_clear_g_source (&priv->modm.relaunch_id);
 
 	name_owner = g_dbus_object_manager_client_get_name_owner (G_DBUS_OBJECT_MANAGER_CLIENT (modem_manager));
 	if (!name_owner) {
-		nm_log_info (LOGD_MB, "ModemManager disappeared from bus");
+		if (priv->modm.log_available != LOG_AVAILABLE_NO) {
+			_LOGI ("ModemManager %savailable", priv->modm.log_available ? "no longer " : "not ");
+			priv->modm.log_available = LOG_AVAILABLE_NO;
+		}
 
 		/* If not managed by systemd, schedule relaunch */
 		if (!sd_booted ())
-			schedule_modem_manager_relaunch (self, 0);
+			modm_schedule_manager_relaunch (self, 0);
 
 		return;
 	}
@@ -220,18 +272,322 @@ modem_manager_name_owner_changed (MMManager *modem_manager,
 	 * nor 'object-removed' if it was created while there was no ModemManager in
 	 * the bus. This hack avoids this issue until we get a GIO with the fix
 	 * included... */
-	clear_modem_manager (self);
-	ensure_modem_manager (self);
+	modm_clear_manager (self);
+	modm_ensure_manager (self);
 
 	/* Whenever GDBusObjectManagerClient is fixed, we can just do the following:
-	 * modem_manager_available (self);
+	 * modm_manager_available (self);
 	 */
 }
 
+static void
+modm_manager_poke_cb (GObject *connection,
+                      GAsyncResult *res,
+                      gpointer user_data)
+{
+	NMModemManager *self;
+	NMModemManagerPrivate *priv;
+	gs_free_error GError *error = NULL;
+	gs_unref_variant GVariant *result = NULL;
+
+	result = g_dbus_connection_call_finish (G_DBUS_CONNECTION (connection), res, &error);
+
+	if (   !result
+	    && g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
+		return;
+
+	self = user_data;
+	priv = NM_MODEM_MANAGER_GET_PRIVATE (self);
+
+	g_clear_object (&priv->modm.poke_cancellable);
+
+	if (error) {
+		_LOGW ("error poking ModemManager: %s", error->message);
+
+		/* Don't reschedule poke is MM service doesn't exist. */
+		if (   !g_error_matches (error, G_DBUS_ERROR, G_DBUS_ERROR_SERVICE_UNKNOWN)
+			&& !g_error_matches (error, G_DBUS_ERROR, G_DBUS_ERROR_SPAWN_SERVICE_NOT_FOUND)) {
+
+			/* Setup timeout to relaunch */
+			modm_schedule_manager_relaunch (self, MODEM_POKE_INTERVAL);
+		}
+	}
+}
+
+static void
+modm_manager_poke (NMModemManager *self)
+{
+	NMModemManagerPrivate *priv = NM_MODEM_MANAGER_GET_PRIVATE (self);
+
+	nm_clear_g_cancellable (&priv->modm.poke_cancellable);
+	priv->modm.poke_cancellable = g_cancellable_new ();
+
+	/* If there is no current owner right away, ensure we poke to get one */
+	g_dbus_connection_call (priv->dbus_connection,
+	                        NM_MODEM_MANAGER_MM_DBUS_SERVICE,
+	                        NM_MODEM_MANAGER_MM_DBUS_PATH,
+	                        DBUS_INTERFACE_PEER,
+	                        "Ping",
+	                        NULL,
+	                        NULL,
+	                        G_DBUS_CALL_FLAGS_NONE,
+	                        -1,
+	                        priv->modm.poke_cancellable,
+	                        modm_manager_poke_cb,
+	                        self);
+}
+
+static void
+modm_manager_check_name_owner (NMModemManager *self)
+{
+	NMModemManagerPrivate *priv = NM_MODEM_MANAGER_GET_PRIVATE (self);
+	gs_free gchar *name_owner = NULL;
+
+	name_owner = g_dbus_object_manager_client_get_name_owner (G_DBUS_OBJECT_MANAGER_CLIENT (priv->modm.manager));
+	if (name_owner) {
+		modm_manager_available (self);
+		return;
+	}
+
+	/* If the lifecycle is not managed by systemd, poke */
+	if (!sd_booted ())
+		modm_manager_poke (self);
+}
+
+static void
+modm_manager_new_cb (GObject *source,
+                     GAsyncResult *res,
+                     gpointer user_data)
+{
+	NMModemManager *self;
+	NMModemManagerPrivate *priv;
+	gs_free_error GError *error = NULL;
+	MMManager *modem_manager;
+
+	modem_manager = mm_manager_new_finish (res, &error);
+	if (   !modem_manager
+	    && g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
+		return;
+
+	self = user_data;
+	priv = NM_MODEM_MANAGER_GET_PRIVATE (self);
+
+	nm_assert (!priv->modm.manager);
+
+	g_clear_object (&priv->main_cancellable);
+
+	if (!modem_manager) {
+		/* We're not really supposed to get any error here. If we do get one,
+		 * though, just re-schedule the MMManager creation after some time.
+		 * During this period, name-owner changes won't be followed. */
+		_LOGW ("error creating ModemManager client: %s", error->message);
+		/* Setup timeout to relaunch */
+		modm_schedule_manager_relaunch (self, MODEM_POKE_INTERVAL);
+		return;
+	}
+
+	priv->modm.manager = modem_manager;
+
+	/* Setup signals in the GDBusObjectManagerClient */
+	priv->modm.handle_name_owner_changed_id =
+	    g_signal_connect (priv->modm.manager,
+	                      "notify::name-owner",
+	                      G_CALLBACK (modm_handle_name_owner_changed),
+	                      self);
+	priv->modm.handle_object_added_id =
+	    g_signal_connect (priv->modm.manager,
+	                      "object-added",
+	                      G_CALLBACK (modm_handle_object_added),
+	                      self);
+	priv->modm.handle_object_removed_id =
+	    g_signal_connect (priv->modm.manager,
+	                      "object-removed",
+	                      G_CALLBACK (modm_handle_object_removed),
+	                      self);
+
+	modm_manager_check_name_owner (self);
+}
+
+static void
+modm_ensure_manager (NMModemManager *self)
+{
+	NMModemManagerPrivate *priv = NM_MODEM_MANAGER_GET_PRIVATE (self);
+
+	g_assert (priv->dbus_connection);
+
+	/* Create the GDBusObjectManagerClient. We do not request to autostart, as
+	 * we don't really want the MMManager creation to fail. We can always poke
+	 * later on if we want to request the autostart */
+	if (!priv->modm.manager) {
+		if (!priv->main_cancellable)
+			priv->main_cancellable = g_cancellable_new ();
+		mm_manager_new (priv->dbus_connection,
+		                G_DBUS_OBJECT_MANAGER_CLIENT_FLAGS_DO_NOT_AUTO_START,
+		                priv->main_cancellable,
+		                modm_manager_new_cb,
+		                self);
+		return;
+	}
+
+	/* If already available, recheck name owner! */
+	modm_manager_check_name_owner (self);
+}
+
+static gboolean
+modm_schedule_manager_relaunch_cb (NMModemManager *self)
+{
+	NMModemManagerPrivate *priv = NM_MODEM_MANAGER_GET_PRIVATE (self);
+
+	priv->modm.relaunch_id = 0;
+	modm_ensure_manager (self);
+	return G_SOURCE_REMOVE;
+}
+
+static void
+modm_schedule_manager_relaunch (NMModemManager *self,
+                                guint n_seconds)
+{
+	NMModemManagerPrivate *priv = NM_MODEM_MANAGER_GET_PRIVATE (self);
+
+	/* No need to pass an extra reference to self; timeout/idle will be
+	 * cancelled if the object gets disposed. */
+	if (n_seconds)
+		priv->modm.relaunch_id = g_timeout_add_seconds (n_seconds, (GSourceFunc)modm_schedule_manager_relaunch_cb, self);
+	else
+		priv->modm.relaunch_id = g_idle_add ((GSourceFunc)modm_schedule_manager_relaunch_cb, self);
+}
+
+/*****************************************************************************/
+
+static void
+modm_proxy_name_owner_reset (NMModemManager *self)
+{
+	NMModemManagerPrivate *priv = NM_MODEM_MANAGER_GET_PRIVATE (self);
+	char *name = NULL;
+
+	if (priv->modm.proxy)
+		name = g_dbus_proxy_get_name_owner (priv->modm.proxy);
+
+	if (nm_streq0 (priv->modm.proxy_name_owner, name)) {
+		g_free (name);
+		return;
+	}
+	g_free (priv->modm.proxy_name_owner);
+	priv->modm.proxy_name_owner = name;
+
+	_notify (self, PROP_NAME_OWNER);
+}
+
+static void
+modm_proxy_name_owner_changed_cb (GObject    *object,
+                                  GParamSpec *pspec,
+                                  gpointer    user_data)
+{
+	modm_proxy_name_owner_reset (user_data);
+}
+
+static void
+modm_proxy_new_cb (GObject *source_object,
+                   GAsyncResult *result,
+                   gpointer user_data)
+{
+	NMModemManager *self;
+	NMModemManagerPrivate *priv;
+	GDBusProxy *proxy;
+	gs_free_error GError *error = NULL;
+
+	proxy = g_dbus_proxy_new_for_bus_finish (result, &error);
+	if (   !proxy
+	    && g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
+		return;
+
+	self = user_data;
+	priv = NM_MODEM_MANAGER_GET_PRIVATE (self);
+
+	g_clear_object (&priv->modm.proxy_cancellable);
+
+	if (!proxy) {
+		_LOGW ("could not obtain D-Bus proxy for ModemManager: %s", error->message);
+		return;
+	}
+
+	priv->modm.proxy = proxy;
+	g_signal_connect (priv->modm.proxy, "notify::g-name-owner",
+	                  G_CALLBACK (modm_proxy_name_owner_changed_cb), self);
+
+	modm_proxy_name_owner_reset (self);
+}
+
+void
+nm_modem_manager_name_owner_ref (NMModemManager *self)
+{
+	NMModemManagerPrivate *priv;
+
+	g_return_if_fail (NM_IS_MODEM_MANAGER (self));
+
+	priv = NM_MODEM_MANAGER_GET_PRIVATE (self);
+
+	if (priv->modm.proxy_ref_count++ > 0) {
+		/* only try once to create the proxy. If proxy creation
+		 * for the first "ref" failed, it's unclear what to do.
+		 * The proxy is hosed. */
+		return;
+	}
+
+	nm_assert (!priv->modm.proxy && !priv->modm.proxy_cancellable);
+
+	priv->modm.proxy_cancellable = g_cancellable_new ();
+
+	g_dbus_proxy_new_for_bus (G_BUS_TYPE_SYSTEM,
+	                            G_DBUS_PROXY_FLAGS_DO_NOT_LOAD_PROPERTIES
+	                          | G_DBUS_PROXY_FLAGS_DO_NOT_CONNECT_SIGNALS
+	                          | G_DBUS_PROXY_FLAGS_DO_NOT_AUTO_START,
+	                          NULL,
+	                          NM_MODEM_MANAGER_MM_DBUS_SERVICE,
+	                          NM_MODEM_MANAGER_MM_DBUS_PATH,
+	                          NM_MODEM_MANAGER_MM_DBUS_INTERFACE,
+	                          priv->modm.proxy_cancellable,
+	                          modm_proxy_new_cb,
+	                          self);
+}
+
+void
+nm_modem_manager_name_owner_unref (NMModemManager *self)
+{
+	NMModemManagerPrivate *priv;
+
+	g_return_if_fail (NM_IS_MODEM_MANAGER (self));
+
+	priv = NM_MODEM_MANAGER_GET_PRIVATE (self);
+
+	g_return_if_fail (priv->modm.proxy_ref_count > 0);
+
+	if (--priv->modm.proxy_ref_count > 0)
+		return;
+
+	nm_clear_g_cancellable (&priv->modm.proxy_cancellable);
+	g_clear_object (&priv->modm.proxy);
+
+	modm_proxy_name_owner_reset (self);
+}
+
+const char *
+nm_modem_manager_name_owner_get (NMModemManager *self)
+{
+	g_return_val_if_fail (NM_IS_MODEM_MANAGER (self), NULL);
+	nm_assert (NM_MODEM_MANAGER_GET_PRIVATE (self)->modm.proxy_ref_count > 0);
+
+	return NM_MODEM_MANAGER_GET_PRIVATE (self)->modm.proxy_name_owner;
+}
+
+/*****************************************************************************/
+
 #if WITH_OFONO
+
 static void
 ofono_create_modem (NMModemManager *self, const char *path)
 {
+	NMModemManagerPrivate *priv = NM_MODEM_MANAGER_GET_PRIVATE (self);
 	NMModem *modem = NULL;
 
 	/* Ensure duplicate modems aren't created.  Because we're not using the
@@ -239,12 +595,12 @@ ofono_create_modem (NMModemManager *self, const char *path)
 	 * receive ModemAdded signals before GetModems() returns, so some of the
 	 * modems returned from GetModems() may already have been created.
 	 */
-	if (!g_hash_table_lookup (self->_priv.modems, path)) {
+	if (!g_hash_table_lookup (priv->modems, path)) {
 		modem = nm_modem_ofono_new (path);
 		if (modem)
 			handle_new_modem (self, modem);
 		else
-			nm_log_warn (LOGD_MB, "Failed to create oFono modem for %s", path);
+			_LOGW ("Failed to create oFono modem for %s", path);
 	}
 }
 
@@ -256,80 +612,96 @@ ofono_signal_cb (GDBusProxy *proxy,
                  gpointer user_data)
 {
 	NMModemManager *self = NM_MODEM_MANAGER (user_data);
+	NMModemManagerPrivate *priv = NM_MODEM_MANAGER_GET_PRIVATE (self);
 	gchar *object_path;
 	NMModem *modem;
 
 	if (g_strcmp0 (signal_name, "ModemAdded") == 0) {
 		g_variant_get (parameters, "(oa{sv})", &object_path, NULL);
-		nm_log_info (LOGD_MB, "oFono modem appeared: %s", object_path);
+		_LOGI ("oFono modem appeared: %s", object_path);
 
 		ofono_create_modem (NM_MODEM_MANAGER (user_data), object_path);
 		g_free (object_path);
 	} else if (g_strcmp0 (signal_name, "ModemRemoved") == 0) {
 		g_variant_get (parameters, "(o)", &object_path);
-		nm_log_info (LOGD_MB, "oFono modem removed: %s", object_path);
+		_LOGI ("oFono modem removed: %s", object_path);
 
-		modem = (NMModem *) g_hash_table_lookup (self->_priv.modems, object_path);
+		modem = (NMModem *) g_hash_table_lookup (priv->modems, object_path);
 		if (modem) {
 			nm_modem_emit_removed (modem);
-			g_hash_table_remove (self->_priv.modems, object_path);
+			g_hash_table_remove (priv->modems, object_path);
 		} else {
-			nm_log_warn (LOGD_MB, "could not remove modem %s, not found in table",
-			             object_path);
+			_LOGW ("could not remove modem %s, not found in table",
+			       object_path);
 		}
 		g_free (object_path);
 	}
 }
 
 static void
-ofono_enumerate_devices_done (GDBusProxy *proxy, GAsyncResult *res, gpointer user_data)
+ofono_enumerate_devices_done (GObject *proxy,
+                              GAsyncResult *res,
+                              gpointer user_data)
 {
-	NMModemManager *manager = NM_MODEM_MANAGER (user_data);
+	NMModemManager *self;
+	NMModemManagerPrivate *priv;
 	gs_free_error GError *error = NULL;
 	GVariant *results;
 	GVariantIter *iter;
 	const char *path;
 
-	results = g_dbus_proxy_call_finish (proxy, res, &error);
-	if (results) {
-		g_variant_get (results, "(a(oa{sv}))", &iter);
-		while (g_variant_iter_loop (iter, "(&oa{sv})", &path, NULL))
-			ofono_create_modem (manager, path);
-		g_variant_iter_free (iter);
-		g_variant_unref (results);
-	}
+	results = g_dbus_proxy_call_finish (G_DBUS_PROXY (proxy), res, &error);
+	if (   !results
+	    && g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
+		return;
 
-	if (error) {
-		nm_log_warn (LOGD_MB, "failed to enumerate oFono devices: %s",
-		             error->message);
+	self = NM_MODEM_MANAGER (user_data);
+	priv = NM_MODEM_MANAGER_GET_PRIVATE (self);
+
+	g_clear_object (&priv->ofono.cancellable);
+
+	if (!results) {
+		_LOGW ("failed to enumerate oFono devices: %s",
+		       error->message);
+		return;
 	}
+
+	g_variant_get (results, "(a(oa{sv}))", &iter);
+	while (g_variant_iter_loop (iter, "(&oa{sv})", &path, NULL))
+		ofono_create_modem (self, path);
+	g_variant_iter_free (iter);
+	g_variant_unref (results);
 }
 
 static void
-ofono_check_name_owner (NMModemManager *self)
+ofono_check_name_owner (NMModemManager *self, gboolean first_invocation)
 {
+	NMModemManagerPrivate *priv = NM_MODEM_MANAGER_GET_PRIVATE (self);
 	gs_free char *name_owner = NULL;
 
-	name_owner = g_dbus_proxy_get_name_owner (G_DBUS_PROXY (self->_priv.ofono_proxy));
+	name_owner = g_dbus_proxy_get_name_owner (G_DBUS_PROXY (priv->ofono.proxy));
 	if (name_owner) {
-		nm_log_info (LOGD_MB, "oFono is now available");
+		_LOGI ("oFono is %savailable", first_invocation ? "" : "now ");
+
+		nm_clear_g_cancellable (&priv->ofono.cancellable);
+		priv->ofono.cancellable = g_cancellable_new ();
 
-		g_dbus_proxy_call (self->_priv.ofono_proxy,
+		g_dbus_proxy_call (priv->ofono.proxy,
 		                   "GetModems",
 		                   NULL,
 		                   G_DBUS_CALL_FLAGS_NONE,
 		                   -1,
-		                   NULL,
-		                   (GAsyncReadyCallback) ofono_enumerate_devices_done,
-		                   g_object_ref (self));
+		                   priv->ofono.cancellable,
+		                   ofono_enumerate_devices_done,
+		                   self);
 	} else {
 		GHashTableIter iter;
 		NMModem *modem;
 
-		nm_log_info (LOGD_MB, "oFono disappeared from bus");
+		_LOGI ("oFono is %savailable", first_invocation ? "not " : "no longer ");
 
 		/* Remove any oFono modems that might be left around */
-		g_hash_table_iter_init (&iter, self->_priv.modems);
+		g_hash_table_iter_init (&iter, priv->modems);
 		while (g_hash_table_iter_next (&iter, NULL, (gpointer) &modem)) {
 			if (NM_IS_MODEM_OFONO (modem)) {
 				nm_modem_emit_removed (modem);
@@ -344,219 +716,121 @@ ofono_name_owner_changed (GDBusProxy *ofono_proxy,
                           GParamSpec *pspec,
                           NMModemManager *self)
 {
-	ofono_check_name_owner (self);
+	ofono_check_name_owner (self, FALSE);
 }
 
 static void
-ofono_proxy_new_cb (GObject *source_object, GAsyncResult *res, gpointer user_data)
+ofono_proxy_new_cb (GObject *source_object,
+                    GAsyncResult *res,
+                    gpointer user_data)
 {
-	gs_unref_object NMModemManager *self = NM_MODEM_MANAGER (user_data);
+	NMModemManager *self;
+	NMModemManagerPrivate *priv;
 	gs_free_error GError *error = NULL;
+	GDBusProxy *proxy;
 
-	self->_priv.ofono_proxy = g_dbus_proxy_new_finish (res, &error);
-	if (error) {
-		nm_log_warn (LOGD_MB, "error getting oFono bus proxy: %s", error->message);
+	proxy = g_dbus_proxy_new_finish (res, &error);
+	if (   !proxy
+	    && g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
+		return;
+
+	self = NM_MODEM_MANAGER (user_data);
+	priv = NM_MODEM_MANAGER_GET_PRIVATE (self);
+
+	g_clear_object (&priv->ofono.cancellable);
+
+	if (!proxy) {
+		_LOGW ("error getting oFono bus proxy: %s", error->message);
 		return;
 	}
 
-	g_signal_connect (self->_priv.ofono_proxy,
+	priv->ofono.proxy = proxy;
+
+	g_signal_connect (priv->ofono.proxy,
 	                  "notify::g-name-owner",
 	                  G_CALLBACK (ofono_name_owner_changed),
 	                  self);
 
-	g_signal_connect (self->_priv.ofono_proxy,
+	g_signal_connect (priv->ofono.proxy,
 	                  "g-signal",
 	                  G_CALLBACK (ofono_signal_cb),
 	                  self);
 
-	ofono_check_name_owner (self);
+	ofono_check_name_owner (self, TRUE);
 }
 
 static void
-ensure_ofono_client (NMModemManager *self)
+ofono_init_proxy (NMModemManager *self)
 {
-	g_assert (self->_priv.dbus_connection);
-	g_dbus_proxy_new (self->_priv.dbus_connection,
+	NMModemManagerPrivate *priv = NM_MODEM_MANAGER_GET_PRIVATE (self);
+
+	nm_assert (priv->dbus_connection);
+	nm_assert (!priv->ofono.cancellable);
+
+	priv->ofono.cancellable = g_cancellable_new ();
+
+	g_dbus_proxy_new (priv->dbus_connection,
 	                  G_DBUS_PROXY_FLAGS_DO_NOT_AUTO_START,
 	                  NULL,
 	                  OFONO_DBUS_SERVICE,
 	                  OFONO_DBUS_PATH,
 	                  OFONO_DBUS_INTERFACE,
-	                  NULL,
-	                  (GAsyncReadyCallback) ofono_proxy_new_cb,
-	                  g_object_ref (self));
+	                  priv->ofono.cancellable,
+	                  ofono_proxy_new_cb,
+	                  self);
 }
 #endif
 
-static void
-modem_manager_poke_cb (GDBusConnection *connection,
-                       GAsyncResult *res,
-                       NMModemManager *self)
-{
-	GError *error = NULL;
-	GVariant *result;
-
-	result = g_dbus_connection_call_finish (connection, res, &error);
-	if (error) {
-		nm_log_warn (LOGD_MB, "error poking ModemManager: %s",
-					error ? error->message : "");
-
-		/* Don't reschedule poke is MM service doesn't exist. */
-		if (!g_error_matches (error, G_DBUS_ERROR, G_DBUS_ERROR_SERVICE_UNKNOWN)
-			&& !g_error_matches (error, G_DBUS_ERROR, G_DBUS_ERROR_SPAWN_SERVICE_NOT_FOUND)) {
-
-			/* Setup timeout to relaunch */
-			schedule_modem_manager_relaunch (self, MODEM_POKE_INTERVAL);
-		}
-
-		g_error_free (error);
-	} else
-		g_variant_unref (result);
-
-	/* Balance refcount */
-	g_object_unref (self);
-}
-
-static void
-modem_manager_poke (NMModemManager *self)
-{
-	/* If there is no current owner right away, ensure we poke to get one */
-	g_dbus_connection_call (self->_priv.dbus_connection,
-	                        "org.freedesktop.ModemManager1",
-	                        "/org/freedesktop/ModemManager1",
-	                        DBUS_INTERFACE_PEER,
-	                        "Ping",
-	                        NULL, /* inputs */
-	                        NULL, /* outputs */
-	                        G_DBUS_CALL_FLAGS_NONE,
-	                        -1,
-	                        NULL, /* cancellable */
-	                        (GAsyncReadyCallback)modem_manager_poke_cb, /* callback */
-	                        g_object_ref (self)); /* user_data */
-}
+/*****************************************************************************/
 
 static void
-modem_manager_check_name_owner (NMModemManager *self)
+bus_get_ready (GObject *source,
+               GAsyncResult *res,
+               gpointer user_data)
 {
-	gs_free gchar *name_owner = NULL;
+	NMModemManager *self;
+	NMModemManagerPrivate *priv;
+	gs_free_error GError *error = NULL;
+	GDBusConnection *connection;
 
-	name_owner = g_dbus_object_manager_client_get_name_owner (G_DBUS_OBJECT_MANAGER_CLIENT (self->_priv.modem_manager));
-	if (name_owner) {
-		/* Available! */
-		modem_manager_available (self);
+	connection = g_bus_get_finish (res, &error);
+	if (   !connection
+	    && g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
 		return;
-	}
-
-	/* If the lifecycle is not managed by systemd, poke */
-	if (!sd_booted ())
-		modem_manager_poke (self);
-}
-
-static void
-manager_new_ready (GObject *source,
-                   GAsyncResult *res,
-                   NMModemManager *self)
-{
-	/* Note we always get an extra reference to self here */
 
-	GError *error = NULL;
+	self = NM_MODEM_MANAGER (user_data);
+	priv = NM_MODEM_MANAGER_GET_PRIVATE (self);
 
-	g_return_if_fail (!self->_priv.modem_manager);
-
-	self->_priv.modem_manager = mm_manager_new_finish (res, &error);
-	if (!self->_priv.modem_manager) {
-		/* We're not really supposed to get any error here. If we do get one,
-		 * though, just re-schedule the MMManager creation after some time.
-		 * During this period, name-owner changes won't be followed. */
-		nm_log_warn (LOGD_MB, "error creating ModemManager client: %s", error->message);
-		g_error_free (error);
-		/* Setup timeout to relaunch */
-		schedule_modem_manager_relaunch (self, MODEM_POKE_INTERVAL);
-	} else {
-		/* Setup signals in the GDBusObjectManagerClient */
-		self->_priv.mm_name_owner_changed_id =
-		    g_signal_connect (self->_priv.modem_manager,
-		                      "notify::name-owner",
-		                      G_CALLBACK (modem_manager_name_owner_changed),
-		                      self);
-		self->_priv.mm_object_added_id =
-		    g_signal_connect (self->_priv.modem_manager,
-		                      "object-added",
-		                      G_CALLBACK (modem_object_added),
-		                      self);
-		self->_priv.mm_object_removed_id =
-		    g_signal_connect (self->_priv.modem_manager,
-		                      "object-removed",
-		                      G_CALLBACK (modem_object_removed),
-		                      self);
-
-		modem_manager_check_name_owner (self);
-	}
-
-	/* Balance refcount */
-	g_object_unref (self);
-}
-
-static void
-ensure_modem_manager (NMModemManager *self)
-{
-	g_assert (self->_priv.dbus_connection);
-
-	/* Create the GDBusObjectManagerClient. We do not request to autostart, as
-	 * we don't really want the MMManager creation to fail. We can always poke
-	 * later on if we want to request the autostart */
-	if (!self->_priv.modem_manager) {
-		mm_manager_new (self->_priv.dbus_connection,
-		                G_DBUS_OBJECT_MANAGER_CLIENT_FLAGS_DO_NOT_AUTO_START,
-		                NULL,
-		                (GAsyncReadyCallback)manager_new_ready,
-		                g_object_ref (self));
+	if (!connection) {
+		_LOGW ("error getting bus connection: %s", error->message);
 		return;
 	}
 
-	/* If already available, recheck name owner! */
-	modem_manager_check_name_owner (self);
-}
+	priv->dbus_connection = connection;
 
-static gboolean
-mm_launch_cb (NMModemManager *self)
-{
-	self->_priv.mm_launch_id = 0;
-	ensure_modem_manager (self);
-	return G_SOURCE_REMOVE;
+	modm_ensure_manager (self);
+#if WITH_OFONO
+	ofono_init_proxy (self);
+#endif
 }
 
-static void
-schedule_modem_manager_relaunch (NMModemManager *self,
-                                 guint n_seconds)
-{
-	/* No need to pass an extra reference to self; timeout/idle will be
-	 * cancelled if the object gets disposed. */
-	if (n_seconds)
-		self->_priv.mm_launch_id = g_timeout_add_seconds (n_seconds, (GSourceFunc)mm_launch_cb, self);
-	else
-		self->_priv.mm_launch_id = g_idle_add ((GSourceFunc)mm_launch_cb, self);
-}
+/*****************************************************************************/
 
 static void
-bus_get_ready (GObject *source,
-               GAsyncResult *res,
-               gpointer user_data)
+get_property (GObject *object, guint prop_id,
+              GValue *value, GParamSpec *pspec)
 {
-	gs_unref_object NMModemManager *self = NM_MODEM_MANAGER (user_data);
-	gs_free_error GError *error = NULL;
-
-	self->_priv.dbus_connection = g_bus_get_finish (res, &error);
-	if (!self->_priv.dbus_connection) {
-		nm_log_warn (LOGD_MB, "error getting bus connection: %s", error->message);
-		return;
+	NMModemManager *self = NM_MODEM_MANAGER (object);
+	NMModemManagerPrivate *priv = NM_MODEM_MANAGER_GET_PRIVATE (self);
+
+	switch (prop_id) {
+	case PROP_NAME_OWNER:
+		g_value_set_string (value, priv->modm.proxy_name_owner);
+		break;
+	default:
+		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+		break;
 	}
-
-	/* Got the bus, ensure clients */
-	ensure_modem_manager (self);
-#if WITH_OFONO
-	ensure_ofono_client (self);
-#endif
 }
 
 /*****************************************************************************/
@@ -564,36 +838,50 @@ bus_get_ready (GObject *source,
 static void
 nm_modem_manager_init (NMModemManager *self)
 {
-	self->_priv.modems = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_object_unref);
+	NMModemManagerPrivate *priv = NM_MODEM_MANAGER_GET_PRIVATE (self);
+
+	priv->modems = g_hash_table_new_full (nm_str_hash, g_str_equal, g_free, g_object_unref);
+
+	priv->main_cancellable = g_cancellable_new ();
 
 	g_bus_get (G_BUS_TYPE_SYSTEM,
-	           NULL,
-	           (GAsyncReadyCallback)bus_get_ready,
-	           g_object_ref (self));
+	           priv->main_cancellable,
+	           bus_get_ready,
+	           self);
 }
 
 static void
 dispose (GObject *object)
 {
 	NMModemManager *self = NM_MODEM_MANAGER (object);
+	NMModemManagerPrivate *priv = NM_MODEM_MANAGER_GET_PRIVATE (self);
 
-	nm_clear_g_source (&self->_priv.mm_launch_id);
+	nm_clear_g_cancellable (&priv->main_cancellable);
+	nm_clear_g_cancellable (&priv->modm.poke_cancellable);
 
-	clear_modem_manager (self);
+	nm_clear_g_source (&priv->modm.relaunch_id);
+
+	nm_clear_g_cancellable (&priv->modm.proxy_cancellable);
+	g_clear_object (&priv->modm.proxy);
+	nm_clear_g_free (&priv->modm.proxy_name_owner);
+
+	modm_clear_manager (self);
 
 #if WITH_OFONO
-	if (self->_priv.ofono_proxy) {
-		g_signal_handlers_disconnect_by_func (self->_priv.ofono_proxy, ofono_name_owner_changed, self);
-		g_signal_handlers_disconnect_by_func (self->_priv.ofono_proxy, ofono_signal_cb, self);
-		g_clear_object (&self->_priv.ofono_proxy);
+	if (priv->ofono.proxy) {
+		g_signal_handlers_disconnect_by_func (priv->ofono.proxy, ofono_name_owner_changed, self);
+		g_signal_handlers_disconnect_by_func (priv->ofono.proxy, ofono_signal_cb, self);
+		g_clear_object (&priv->ofono.proxy);
 	}
+	nm_clear_g_cancellable (&priv->ofono.cancellable);
 #endif
 
-	g_clear_object (&self->_priv.dbus_connection);
+	g_clear_object (&priv->dbus_connection);
 
-	if (self->_priv.modems) {
-		g_hash_table_foreach_remove (self->_priv.modems, remove_one_modem, object);
-		g_hash_table_destroy (self->_priv.modems);
+	if (priv->modems) {
+		g_hash_table_foreach_remove (priv->modems, remove_one_modem, object);
+		g_hash_table_destroy (priv->modems);
+		priv->modems = NULL;
 	}
 
 	G_OBJECT_CLASS (nm_modem_manager_parent_class)->dispose (object);
@@ -605,6 +893,15 @@ nm_modem_manager_class_init (NMModemManagerClass *klass)
 	GObjectClass *object_class = G_OBJECT_CLASS (klass);
 
 	object_class->dispose = dispose;
+	object_class->get_property = get_property;
+
+	obj_properties[PROP_NAME_OWNER] =
+	     g_param_spec_string (NM_MODEM_MANAGER_NAME_OWNER, "", "",
+	                          NULL,
+	                          G_PARAM_READABLE
+	                          | G_PARAM_STATIC_STRINGS);
+
+	g_object_class_install_properties (object_class, _PROPERTY_ENUMS_LAST, obj_properties);
 
 	signals[MODEM_ADDED] =
 	    g_signal_new (NM_MODEM_MANAGER_MODEM_ADDED,