core: fix not waiting until mode change is complete

In some properties such as Discoverable it is desirable to wait the mode
change to complete so that reading it after changing will never return
something different.
diff --git a/src/adapter.c b/src/adapter.c
index d1fae55..2a6ac18 100644
--- a/src/adapter.c
+++ b/src/adapter.c
@@ -105,6 +105,7 @@
 	uint8_t mode;			/* off, connectable, discoverable,
 					 * limited */
 	uint8_t global_mode;		/* last valid global mode */
+	struct session_req *pending_mode;
 	int state;			/* standard inq, periodic inq, name
 					 * resloving */
 	GSList *found_devices;
@@ -412,10 +413,47 @@
 		adapter->pending_cod = adapter->wanted_cod;
 }
 
-static int set_mode(struct btd_adapter *adapter, uint8_t new_mode)
+static struct session_req *session_ref(struct session_req *req)
+{
+	req->refcount++;
+
+	DBG("%p: ref=%d", req, req->refcount);
+
+	return req;
+}
+
+static struct session_req *create_session(struct btd_adapter *adapter,
+					DBusConnection *conn, DBusMessage *msg,
+					uint8_t mode, GDBusWatchFunction cb)
+{
+	const char *sender = dbus_message_get_sender(msg);
+	struct session_req *req;
+
+	req = g_new0(struct session_req, 1);
+	req->adapter = adapter;
+	req->conn = dbus_connection_ref(conn);
+	req->msg = dbus_message_ref(msg);
+	req->mode = mode;
+
+	if (cb == NULL)
+		return session_ref(req);
+
+	req->owner = g_strdup(sender);
+	req->id = g_dbus_add_disconnect_watch(conn, sender, cb, req, NULL);
+
+	info("%s session %p with %s activated",
+		req->mode ? "Mode" : "Discovery", req, sender);
+
+	return session_ref(req);
+}
+
+static int set_mode(struct btd_adapter *adapter, uint8_t new_mode,
+			DBusMessage *msg)
 {
 	int err;
-	const char *modestr;
+
+	if (adapter->pending_mode != NULL)
+		return -EALREADY;
 
 	if (!adapter->up && new_mode != MODE_OFF) {
 		err = adapter_ops->set_powered(adapter->dev_id, TRUE);
@@ -456,11 +494,18 @@
 	}
 
 done:
-	modestr = mode2str(new_mode);
+	DBG("%s", mode2str(new_mode));
 
-	write_device_mode(&adapter->bdaddr, modestr);
-
-	adapter->mode = new_mode;
+	if (msg != NULL)
+		/* Wait for mode change to reply */
+		adapter->pending_mode = create_session(adapter, connection,
+							msg, new_mode, NULL);
+	else {
+		/* Nothing to reply just write the new mode */
+		const char *modestr = mode2str(new_mode);
+		adapter->mode = new_mode;
+		write_device_mode(&adapter->bdaddr, modestr);
+	}
 
 	return 0;
 }
@@ -477,7 +522,7 @@
 	if (mode == adapter->mode)
 		return dbus_message_new_method_return(msg);
 
-	err = set_mode(adapter, mode);
+	err = set_mode(adapter, mode, NULL);
 	if (err < 0)
 		return failed_strerror(msg, -err);
 
@@ -501,11 +546,11 @@
 	if (mode == adapter->mode)
 		return dbus_message_new_method_return(msg);
 
-	err = set_mode(adapter, mode);
+	err = set_mode(adapter, mode, msg);
 	if (err < 0)
 		return failed_strerror(msg, -err);
 
-	return dbus_message_new_method_return(msg);
+	return 0;
 }
 
 static DBusMessage *set_pairable(DBusConnection *conn, DBusMessage *msg,
@@ -528,7 +573,7 @@
 				adapter->discov_timeout <= 60) ?
 					MODE_LIMITED : MODE_DISCOVERABLE;
 
-	err = set_mode(adapter, mode);
+	err = set_mode(adapter, mode, NULL);
 	if (err < 0 && msg)
 		return failed_strerror(msg, -err);
 
@@ -608,6 +653,13 @@
 {
 	struct btd_adapter *adapter = req->adapter;
 
+	/* Ignore set_mode session */
+	if (req->owner == NULL)
+		return;
+
+	DBG("%s session %p with %s deactivated",
+		req->mode ? "Mode" : "Discovery", req, req->owner);
+
 	if (req->mode) {
 		uint8_t mode;
 
@@ -621,7 +673,7 @@
 
 		DBG("Switching to '%s' mode", mode2str(mode));
 
-		set_mode(adapter, mode);
+		set_mode(adapter, mode, NULL);
 	} else {
 		adapter->disc_sessions = g_slist_remove(adapter->disc_sessions,
 							req);
@@ -649,9 +701,6 @@
 
 static void session_free(struct session_req *req)
 {
-	DBG("%s session %p with %s deactivated",
-		req->mode ? "Mode" : "Discovery", req, req->owner);
-
 	if (req->id)
 		g_dbus_remove_watch(req->conn, req->id);
 
@@ -674,15 +723,6 @@
 	session_free(req);
 }
 
-static struct session_req *session_ref(struct session_req *req)
-{
-	req->refcount++;
-
-	DBG("%p: ref=%d", req, req->refcount);
-
-	return req;
-}
-
 static void session_unref(struct session_req *req)
 {
 	req->refcount--;
@@ -695,30 +735,6 @@
 	session_free(req);
 }
 
-static struct session_req *create_session(struct btd_adapter *adapter,
-					DBusConnection *conn, DBusMessage *msg,
-					uint8_t mode, GDBusWatchFunction cb)
-{
-	struct session_req *req;
-	const char *sender = dbus_message_get_sender(msg);
-
-	req = g_new0(struct session_req, 1);
-	req->adapter = adapter;
-	req->conn = dbus_connection_ref(conn);
-	req->msg = dbus_message_ref(msg);
-	req->owner = g_strdup(dbus_message_get_sender(msg));
-	req->mode = mode;
-
-	if (cb)
-		req->id = g_dbus_add_disconnect_watch(conn, sender, cb, req,
-							NULL);
-
-	info("%s session %p with %s activated",
-		req->mode ? "Mode" : "Discovery", req, sender);
-
-	return session_ref(req);
-}
-
 static void confirm_mode_cb(struct agent *agent, DBusError *derr, void *data)
 {
 	struct session_req *req = data;
@@ -733,7 +749,7 @@
 		return;
 	}
 
-	err = set_mode(req->adapter, req->mode);
+	err = set_mode(req->adapter, req->mode, NULL);
 	if (err < 0)
 		reply = failed_strerror(req->msg, -err);
 	else
@@ -2850,6 +2866,45 @@
 	adapter->oor_devices = g_slist_copy(adapter->found_devices);
 }
 
+static void set_mode_complete(struct btd_adapter *adapter)
+{
+	struct session_req *pending;
+	const char *modestr;
+	int err;
+
+	if (adapter->pending_mode == NULL)
+		return;
+
+	pending = adapter->pending_mode;
+	adapter->pending_mode = NULL;
+
+	err = (pending->mode != adapter->mode) ? -EINVAL : 0;
+
+	if (pending->msg != NULL) {
+		DBusMessage *msg = pending->msg;
+		DBusMessage *reply;
+
+		if (err < 0)
+			reply = failed_strerror(msg, -err);
+		else
+			reply = g_dbus_create_reply(msg, DBUS_TYPE_INVALID);
+
+		g_dbus_send_message(connection, reply);
+	}
+
+	modestr = mode2str(adapter->mode);
+
+	DBG("%s", modestr);
+
+	/* Only store if the mode matches the pending */
+	if (err == 0)
+		write_device_mode(&adapter->bdaddr, modestr);
+	else
+		error("unable to set mode: %s", mode2str(pending->mode));
+
+	session_unref(pending);
+}
+
 void adapter_mode_changed(struct btd_adapter *adapter, uint8_t scan_mode)
 {
 	const gchar *path = adapter_get_path(adapter);
@@ -2909,6 +2964,8 @@
 				DBUS_TYPE_BOOLEAN, &discoverable);
 
 	adapter->scan_mode = scan_mode;
+
+	set_mode_complete(adapter);
 }
 
 struct agent *adapter_get_agent(struct btd_adapter *adapter)