summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--serial/manager.c493
-rw-r--r--serial/manager.h6
-rw-r--r--serial/port.c258
-rw-r--r--serial/port.h11
4 files changed, 535 insertions, 233 deletions
diff --git a/serial/manager.c b/serial/manager.c
index b5ced583..c33f51cb 100644
--- a/serial/manager.c
+++ b/serial/manager.c
@@ -48,26 +48,11 @@
#include "logging.h"
#include "error.h"
+#include "port.h"
#include "manager.h"
-#define SERIAL_MANAGER_PATH "/org/bluez/serial"
-#define SERIAL_MANAGER_INTERFACE "org.bluez.serial.Manager"
-
#define BASE_UUID "00000000-0000-1000-8000-00805F9B34FB"
-/* Waiting for udev to create the device node */
-#define MAX_OPEN_TRIES 5
-#define OPEN_WAIT 300 /* ms */
-
-struct rfcomm_node {
- int16_t id; /* RFCOMM device id */
- char *name; /* RFCOMM device name */
- DBusConnection *conn; /* for name listener handling */
- char *owner; /* Bus name */
- GIOChannel *io; /* Connected node IO Channel */
- guint io_id; /* IO Channel ID */
-};
-
struct pending_connect {
DBusConnection *conn;
DBusMessage *msg;
@@ -101,7 +86,6 @@ static struct {
};
static DBusConnection *connection = NULL;
-static GSList *connected_nodes = NULL;
static GSList *pending_connects = NULL;
static int rfcomm_ctl = -1;
@@ -120,32 +104,10 @@ static void pending_connect_free(struct pending_connect *pc)
g_free(pc);
}
-static void rfcomm_node_free(struct rfcomm_node *node)
-{
- if (node->name)
- g_free(node->name);
- if (node->conn)
- dbus_connection_unref(node->conn);
- if (node->owner)
- g_free(node->owner);
- if (node->io) {
- g_source_remove(node->io_id);
- g_io_channel_unref(node->io);
- }
- g_free(node);
-}
-
-static struct rfcomm_node *find_node_by_name(const char *name)
+static void pending_connect_remove(struct pending_connect *pc)
{
- GSList *l;
-
- for (l = connected_nodes; l != NULL; l = l->next) {
- struct rfcomm_node *node = l->data;
- if (!strcmp(node->name, name))
- return node;
- }
-
- return NULL;
+ pending_connects = g_slist_remove(pending_connects, pc);
+ pending_connect_free(pc);
}
static struct pending_connect *find_pending_connect_by_pattern(const char *bda,
@@ -176,7 +138,7 @@ static uint16_t str2class(const char *pattern)
return 0;
}
-static int rfcomm_release(int16_t id)
+int rfcomm_release(int16_t id)
{
struct rfcomm_dev_req req;
@@ -202,102 +164,59 @@ static int rfcomm_release(int16_t id)
return 0;
}
-static void connect_service_exited(const char *name, struct rfcomm_node *node)
-{
- debug("Connect requestor %s exited. Releasing %s node",
- name, node->name);
-
- rfcomm_release(node->id);
-
- dbus_connection_emit_signal(node->conn, SERIAL_MANAGER_PATH,
- SERIAL_MANAGER_INTERFACE, "ServiceDisconnected" ,
- DBUS_TYPE_STRING, &node->name,
- DBUS_TYPE_INVALID);
-
- connected_nodes = g_slist_remove(connected_nodes, node);
- rfcomm_node_free(node);
-}
-
-static gboolean rfcomm_disconnect_cb(GIOChannel *io,
- GIOCondition cond, struct rfcomm_node *node)
-{
- debug("RFCOMM node %s was disconnected", node->name);
-
- if (cond & (G_IO_ERR | G_IO_HUP))
- g_io_channel_close(io);
-
- name_listener_remove(node->conn, node->owner,
- (name_cb_t) connect_service_exited, node);
-
- dbus_connection_emit_signal(node->conn, SERIAL_MANAGER_PATH,
- SERIAL_MANAGER_INTERFACE, "ServiceDisconnected" ,
- DBUS_TYPE_STRING, &node->name,
- DBUS_TYPE_INVALID);
-
- connected_nodes = g_slist_remove(connected_nodes, node);
- rfcomm_node_free(node);
-
- return FALSE;
-}
-
-static int add_rfcomm_node(GIOChannel *io, int id, const char *name,
- DBusConnection *conn, const char *owner)
+static int rfcomm_bind(bdaddr_t *src, bdaddr_t *dst, uint8_t ch)
{
- struct rfcomm_node *node;
-
- node = g_new0(struct rfcomm_node, 1);
- node->id = id;
- node->name = g_strdup(name);
- node->conn = dbus_connection_ref(conn);
- node->owner = g_strdup(owner);
- node->io = io;
+ struct rfcomm_dev_req req;
+ int id;
- node->io_id = g_io_add_watch(io, G_IO_ERR | G_IO_NVAL | G_IO_HUP,
- (GIOFunc) rfcomm_disconnect_cb, node);
+ memset(&req, 0, sizeof(req));
+ req.dev_id = -1;
+ req.flags = 0;
+ bacpy(&req.src, src);
+ bacpy(&req.dst, dst);
+ req.channel = ch;
- connected_nodes = g_slist_append(connected_nodes, node);
+ id = ioctl(rfcomm_ctl, RFCOMMCREATEDEV, &req);
+ if (id < 0) {
+ int err = errno;
+ error("RFCOMMCREATEDEV failed: %s (%d)", strerror(err), err);
+ return -err;
+ }
- return name_listener_add(node->conn, owner,
- (name_cb_t) connect_service_exited, node);
+ return id;
}
-static gboolean rfcomm_connect_cb_continue(struct pending_connect *pc)
+static void open_notify(int fd, int err, void *data)
{
- const char *owner = dbus_message_get_sender(pc->msg);
+ char port_name[16];
+ char path[MAX_PATH_LENGTH];
+ const char *pname = port_name;
+ const char *ppath = path;
+ const char *owner;
DBusMessage *reply;
- char node_name[16];
- const char *pname = node_name;
- int fd;
+ struct pending_connect *pc = data;
+
+ if (err) {
+ /* Max tries exceeded */
+ err_connection_failed(pc->conn, pc->msg, strerror(err));
+ return;
+ }
if (pc->canceled) {
rfcomm_release(pc->id);
err_connection_canceled(pc->conn, pc->msg);
- goto fail;
+ return;
}
/* Check if the caller is still present */
+ owner = dbus_message_get_sender(pc->msg);
if (!dbus_bus_name_has_owner(pc->conn, owner, NULL)) {
error("Connect requestor %s exited", owner);
rfcomm_release(pc->id);
- goto fail;
- }
-
- snprintf(node_name, sizeof(node_name), "/dev/rfcomm%d", pc->id);
- fd = open(node_name, O_RDONLY | O_NOCTTY);
- if (fd < 0) {
- int err = errno;
- error("Could not open %s: %s (%d)",
- node_name, strerror(err), err);
- if (++pc->ntries >= MAX_OPEN_TRIES) {
- rfcomm_release(pc->id);
- err_connection_failed(pc->conn, pc->msg, strerror(err));
- goto fail;
- }
- return TRUE;
+ return;
}
- add_rfcomm_node(g_io_channel_unix_new(fd), pc->id,
- node_name, pc->conn, owner);
+ snprintf(port_name, sizeof(port_name), "/dev/rfcomm%d", pc->id);
/* Reply to the requestor */
reply = dbus_message_new_method_return(pc->msg);
@@ -307,24 +226,23 @@ static gboolean rfcomm_connect_cb_continue(struct pending_connect *pc)
send_message_and_unref(pc->conn, reply);
/* Send the D-Bus signal */
+ port_register(pc->conn, pc->id, fd, pname, owner, path);
+ dbus_connection_emit_signal(pc->conn, SERIAL_MANAGER_PATH,
+ SERIAL_MANAGER_INTERFACE, "PortCreated" ,
+ DBUS_TYPE_STRING, &ppath,
+ DBUS_TYPE_INVALID);
+
dbus_connection_emit_signal(pc->conn, SERIAL_MANAGER_PATH,
SERIAL_MANAGER_INTERFACE, "ServiceConnected" ,
DBUS_TYPE_STRING, &pname,
- DBUS_TYPE_INVALID);
-
-fail:
- pending_connects = g_slist_remove(pending_connects, pc);
- pending_connect_free(pc);
+ DBUS_TYPE_INVALID);
- return FALSE;
}
static gboolean rfcomm_connect_cb(GIOChannel *chan,
GIOCondition cond, struct pending_connect *pc)
{
- DBusMessage *reply;
- char node_name[16];
- const char *pname = node_name;
+ char port_name[16];
struct rfcomm_dev_req req;
int sk, err, fd, close_chan = 1;
@@ -384,37 +302,20 @@ static gboolean rfcomm_connect_cb(GIOChannel *chan,
goto fail;
}
-
- snprintf(node_name, sizeof(node_name), "/dev/rfcomm%d", pc->id);
-
- fd = open(node_name, O_RDONLY | O_NOCTTY);
+ snprintf(port_name, sizeof(port_name), "/dev/rfcomm%d", pc->id);
+ /* Addressing connect port */
+ fd = port_open(port_name, open_notify, pc,
+ (udata_free_t) pending_connect_remove);
if (fd < 0) {
- g_timeout_add(OPEN_WAIT,
- (GSourceFunc) rfcomm_connect_cb_continue, pc);
g_io_channel_close(chan);
+ /* Open in progress: Wait the callback */
return FALSE;
}
- add_rfcomm_node(g_io_channel_unix_new(fd), pc->id, node_name,
- pc->conn, dbus_message_get_sender(pc->msg));
-
- /* Reply to the requestor */
- reply = dbus_message_new_method_return(pc->msg);
- dbus_message_append_args(reply,
- DBUS_TYPE_STRING, &pname,
- DBUS_TYPE_INVALID);
- send_message_and_unref(pc->conn, reply);
-
- /* Send the D-Bus signal */
- dbus_connection_emit_signal(pc->conn, SERIAL_MANAGER_PATH,
- SERIAL_MANAGER_INTERFACE, "ServiceConnected" ,
- DBUS_TYPE_STRING, &pname,
- DBUS_TYPE_INVALID);
-
+ open_notify(fd, 0, pc);
fail:
pending_connects = g_slist_remove(pending_connects, pc);
pending_connect_free(pc);
-
if (close_chan)
g_io_channel_close(chan);
@@ -537,16 +438,48 @@ static void record_reply(DBusPendingCall *call, void *data)
goto fail;
}
- pc->channel = ch;
- err = rfcomm_connect(pc);
- if (err < 0) {
- error("RFCOMM connection failed");
- err_connection_failed(pc->conn, pc->msg, strerror(-err));
- goto fail;
+ if (dbus_message_has_member(pc->msg, "CreatePort")) {
+ char path[MAX_PATH_LENGTH];
+ char port_name[16];
+ const char *ppath = path;
+ DBusMessage *reply;
+ bdaddr_t dst;
+
+ str2ba(pc->bda, &dst);
+ err = rfcomm_bind(&pc->src, &dst, ch);
+ if (err < 0) {
+ err_failed(pc->conn, pc->msg, strerror(-err));
+ goto fail;
+ }
+
+ snprintf(port_name, sizeof(port_name), "/dev/rfcomm%d", err);
+ port_register(pc->conn, err, -1, port_name, NULL, path);
+
+ reply = dbus_message_new_method_return(pc->msg);
+ dbus_message_append_args(reply,
+ DBUS_TYPE_STRING, &ppath,
+ DBUS_TYPE_INVALID);
+ send_message_and_unref(pc->conn, reply);
+
+ dbus_connection_emit_signal(pc->conn, SERIAL_MANAGER_PATH,
+ SERIAL_MANAGER_INTERFACE, "PortCreated" ,
+ DBUS_TYPE_STRING, &ppath,
+ DBUS_TYPE_INVALID);
+ } else {
+ /* ConnectService */
+ pc->channel = ch;
+ err = rfcomm_connect(pc);
+ if (err < 0) {
+ error("RFCOMM connection failed");
+ err_connection_failed(pc->conn, pc->msg, strerror(-err));
+ goto fail;
+ }
+
+ /* Wait the connect callback */
+ dbus_message_unref(reply);
+ return;
}
- dbus_message_unref(reply);
- return;
fail:
dbus_message_unref(reply);
pending_connects = g_slist_remove(pending_connects, pc);
@@ -663,10 +596,153 @@ static int get_handles(struct pending_connect *pc, const char *uuid,
return 0;
}
+static int pattern2uuid128(const char *pattern, char *uuid, size_t size)
+{
+ uint16_t cls;
+
+ /* Friendly name */
+ cls = str2class(pattern);
+ if (cls) {
+ uuid_t uuid16, uuid128;
+
+ sdp_uuid16_create(&uuid16, cls);
+ sdp_uuid16_to_uuid128(&uuid128, &uuid16);
+ sdp_uuid2strn(&uuid128, uuid, size);
+ return 0;
+ }
+
+ /* UUID 128*/
+ if ((strlen(pattern) == 36) &&
+ (strncasecmp(BASE_UUID, pattern, 3) == 0) &&
+ (strncasecmp(BASE_UUID + 8, pattern + 8, 28) == 0)) {
+
+ strncpy(uuid, pattern, size);
+ return 0;
+ }
+
+ return -EINVAL;
+}
+
+static int pattern2long(const char *pattern, long *pval)
+{
+ char *endptr;
+ long val;
+
+ errno = 0;
+ val = strtol(pattern, &endptr, 0);
+ if ((errno == ERANGE && (val == LONG_MAX || val == LONG_MIN)) ||
+ (errno != 0 && val == 0) || (pattern == endptr)) {
+ return -EINVAL;
+ }
+
+ *pval = val;
+
+ return 0;
+}
+
static DBusHandlerResult create_port(DBusConnection *conn,
DBusMessage *msg, void *data)
{
- return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
+ struct pending_connect *pending, *pc;
+ DBusMessage *reply;
+ DBusError derr;
+ bdaddr_t src, dst;
+ char path[MAX_PATH_LENGTH];
+ const char *bda, *pattern, *ppath = path;
+ long val;
+ int dev_id, err;
+ char port_name[16];
+ char uuid[37];
+
+ dbus_error_init(&derr);
+ if (!dbus_message_get_args(msg, &derr,
+ DBUS_TYPE_STRING, &bda,
+ DBUS_TYPE_STRING, &pattern,
+ DBUS_TYPE_INVALID)) {
+ err_invalid_args(conn, msg, derr.message);
+ dbus_error_free(&derr);
+ return DBUS_HANDLER_RESULT_HANDLED;
+ }
+
+ pending = find_pending_connect_by_pattern(bda, pattern);
+ if (pending)
+ return err_connection_in_progress(conn, msg);
+
+ dev_id = hci_get_route(NULL);
+ if ((dev_id < 0) || (hci_devba(dev_id, &src) < 0))
+ return err_failed(conn, msg, "Adapter not available");
+
+ pc = g_new0(struct pending_connect, 1);
+ bacpy(&pc->src, &src);
+ pc->conn = dbus_connection_ref(conn);
+ pc->msg = dbus_message_ref(msg);
+ pc->bda = g_strdup(bda);
+ pc->pattern = g_strdup(pattern);
+ pc->adapter_path = g_malloc0(16);
+ snprintf(pc->adapter_path, 16, "/org/bluez/hci%d", dev_id);
+
+ memset(uuid, 0, sizeof(uuid));
+
+ /* Friendly name or uuid128 */
+ if (pattern2uuid128(pattern, uuid, sizeof(uuid)) == 0) {
+ if (get_handles(pc, uuid, handles_reply) < 0) {
+ pending_connect_free(pc);
+ return err_not_supported(conn, msg);
+ }
+ pending_connects = g_slist_append(pending_connects, pc);
+ return DBUS_HANDLER_RESULT_HANDLED;
+ }
+
+ /* Record handle or channel */
+ err = pattern2long(pattern, &val);
+ if (err < 0) {
+ pending_connect_free(pc);
+ return err_invalid_args(conn, msg, "invalid pattern");
+ }
+
+ /* Record handle: starts at 0x10000 */
+ if (strncasecmp("0x", pattern, 2) == 0) {
+ if (val < 0x10000) {
+ pending_connect_free(pc);
+ return err_invalid_args(conn, msg,
+ "invalid record handle");
+ }
+
+ if (get_record(pc, val, record_reply) < 0) {
+ pending_connect_free(pc);
+ return err_not_supported(conn, msg);
+ }
+ pending_connects = g_slist_append(pending_connects, pc);
+ return DBUS_HANDLER_RESULT_HANDLED;
+ }
+
+ pending_connect_free(pc);
+ /* RFCOMM Channel range: 1 - 30 */
+ if (val < 1 || val > 30)
+ return err_invalid_args(conn, msg,
+ "invalid RFCOMM channel");
+
+ str2ba(bda, &dst);
+ err = rfcomm_bind(&src, &dst, val);
+ if (err < 0)
+ return err_failed(conn, msg, strerror(-err));
+
+ snprintf(port_name, sizeof(port_name), "/dev/rfcomm%d", err);
+ port_register(conn, err, -1, port_name, NULL, path);
+
+ reply = dbus_message_new_method_return(msg);
+ if (!reply)
+ return DBUS_HANDLER_RESULT_NEED_MEMORY;
+ dbus_message_append_args(reply,
+ DBUS_TYPE_STRING, &ppath,
+ DBUS_TYPE_INVALID);
+ send_message_and_unref(conn, reply);
+
+ dbus_connection_emit_signal(conn, SERIAL_MANAGER_PATH,
+ SERIAL_MANAGER_INTERFACE, "PortCreated" ,
+ DBUS_TYPE_STRING, &ppath,
+ DBUS_TYPE_INVALID);
+ return DBUS_HANDLER_RESULT_HANDLED;
}
static DBusHandlerResult list_ports(DBusConnection *conn,
@@ -688,11 +764,9 @@ static DBusHandlerResult connect_service(DBusConnection *conn,
DBusError derr;
bdaddr_t src;
const char *bda, *pattern;
- char *endptr;
long val;
int dev_id, err;
- uint16_t cls;
- char tmp[37];
+ char uuid[37];
dbus_error_init(&derr);
if (!dbus_message_get_args(msg, &derr,
@@ -721,39 +795,11 @@ static DBusHandlerResult connect_service(DBusConnection *conn,
pc->adapter_path = g_malloc0(16);
snprintf(pc->adapter_path, 16, "/org/bluez/hci%d", dev_id);
- memset(tmp, 0, sizeof(tmp));
-
- /* Friendly name */
- cls = str2class(pattern);
- if (cls) {
- uuid_t uuid16, uuid128;
-
- sdp_uuid16_create(&uuid16, cls);
- sdp_uuid16_to_uuid128(&uuid128, &uuid16);
- sdp_uuid2strn(&uuid128, tmp, sizeof(tmp));
-
- if (get_handles(pc, tmp, handles_reply) < 0) {
- pending_connect_free(pc);
- return err_not_supported(conn, msg);
- }
- pending_connects = g_slist_append(pending_connects, pc);
- return DBUS_HANDLER_RESULT_HANDLED;
- }
+ memset(uuid, 0, sizeof(uuid));
- /* UUID 128*/
- if (strlen(pattern) == 36) {
- strcpy(tmp, pattern);
- tmp[4] = '0';
- tmp[5] = '0';
- tmp[6] = '0';
- tmp[7] = '0';
-
- if (strcasecmp(BASE_UUID, tmp) != 0) {
- pending_connect_free(pc);
- return err_invalid_args(conn, msg, "invalid UUID");
- }
-
- if (get_handles(pc, pattern, handles_reply) < 0) {
+ /* Friendly name or uuid128 */
+ if (pattern2uuid128(pattern, uuid, sizeof(uuid)) == 0) {
+ if (get_handles(pc, uuid, handles_reply) < 0) {
pending_connect_free(pc);
return err_not_supported(conn, msg);
}
@@ -761,12 +807,11 @@ static DBusHandlerResult connect_service(DBusConnection *conn,
return DBUS_HANDLER_RESULT_HANDLED;
}
- errno = 0;
- val = strtol(pattern, &endptr, 0);
- if ((errno == ERANGE && (val == LONG_MAX || val == LONG_MIN)) ||
- (errno != 0 && val == 0) || (pattern == endptr)) {
+ /* Record handle or channel */
+ err = pattern2long(pattern, &val);
+ if (err < 0) {
pending_connect_free(pc);
- return err_invalid_args(conn, msg, "Invalid pattern");
+ return err_invalid_args(conn, msg, "invalid pattern");
}
/* Record handle: starts at 0x10000 */
@@ -810,11 +855,10 @@ static DBusHandlerResult connect_service(DBusConnection *conn,
static DBusHandlerResult disconnect_service(DBusConnection *conn,
DBusMessage *msg, void *data)
{
- DBusMessage *reply;
DBusError derr;
- struct rfcomm_node *node;
- const char *name;
+ const char *name, *owner;
int err;
+ int id;
dbus_error_init(&derr);
if (!dbus_message_get_args(msg, &derr,
@@ -825,34 +869,22 @@ static DBusHandlerResult disconnect_service(DBusConnection *conn,
return DBUS_HANDLER_RESULT_HANDLED;
}
- node = find_node_by_name(name);
- if (!node)
- return err_does_not_exist(conn, msg, "Invalid node");
+ if (sscanf(name, "/dev/rfcomm%d", &id) != 1)
+ return err_invalid_args(conn, msg, "invalid RFCOMM node");
- if (strcmp(node->owner, dbus_message_get_sender(msg)) != 0)
- return err_not_authorized(conn, msg);
+ owner = port_get_owner(conn, id);
+ if (!owner)
+ return err_does_not_exist(conn, msg, "Invalid RFCOMM node");
- reply = dbus_message_new_method_return(msg);
- if (!reply)
- return DBUS_HANDLER_RESULT_NEED_MEMORY;
+ if (strcmp(owner, dbus_message_get_sender(msg)) != 0)
+ return err_not_authorized(conn, msg);
- err = rfcomm_release(node->id);
- if (err < 0) {
- dbus_message_unref(reply);
+ err = rfcomm_release(id);
+ if (err < 0)
return err_failed(conn, msg, strerror(-err));
- }
-
- dbus_connection_emit_signal(conn, SERIAL_MANAGER_PATH,
- SERIAL_MANAGER_INTERFACE, "ServiceDisconnected" ,
- DBUS_TYPE_STRING, &node->name,
- DBUS_TYPE_INVALID);
-
- name_listener_remove(node->conn, node->owner,
- (name_cb_t) connect_service_exited, node);
- connected_nodes = g_slist_remove(connected_nodes, node);
- rfcomm_node_free(node);
- return send_message_and_unref(conn, reply);
+ return send_message_and_unref(conn,
+ dbus_message_new_method_return(msg));
}
static DBusHandlerResult cancel_connect_service(DBusConnection *conn,
@@ -888,13 +920,6 @@ static DBusHandlerResult cancel_connect_service(DBusConnection *conn,
static void manager_unregister(DBusConnection *conn, void *data)
{
- if (connected_nodes) {
- g_slist_foreach(connected_nodes,
- (GFunc) rfcomm_node_free, NULL);
- g_slist_free(connected_nodes);
- connected_nodes = NULL;
- }
-
if (pending_connects) {
g_slist_foreach(pending_connects,
(GFunc) pending_connect_free, NULL);
@@ -916,6 +941,8 @@ static DBusMethodVTable manager_methods[] = {
static DBusSignalVTable manager_signals[] = {
{ "ServiceConnected", "s" },
{ "ServiceDisconnected", "s" },
+ { "PortCreated", "s" },
+ { "PortRemoved", "s" },
{ NULL, NULL }
};
diff --git a/serial/manager.h b/serial/manager.h
index f09aa294..4004d81e 100644
--- a/serial/manager.h
+++ b/serial/manager.h
@@ -21,5 +21,11 @@
*
*/
+#define SERIAL_MANAGER_PATH "/org/bluez/serial"
+#define SERIAL_MANAGER_INTERFACE "org.bluez.serial.Manager"
+
+#define MAX_PATH_LENGTH 32
+
int serial_init(DBusConnection *conn);
void serial_exit(void);
+int rfcomm_release(int16_t id);
diff --git a/serial/port.c b/serial/port.c
index 80e3c6df..7d3b95c8 100644
--- a/serial/port.c
+++ b/serial/port.c
@@ -25,4 +25,262 @@
#include <config.h>
#endif
+#include <errno.h>
+#include <stdio.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/ioctl.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+
+#include <glib.h>
+
+#include <bluetooth/bluetooth.h>
+#include <bluetooth/rfcomm.h>
+
+#include "dbus.h"
+#include "dbus-helper.h"
+#include "logging.h"
+
+#include "error.h"
+#include "manager.h"
#include "port.h"
+
+#define SERIAL_PORT_INTERFACE "org.bluez.serial.Port"
+
+/* Waiting for udev to create the device node */
+#define MAX_OPEN_TRIES 5
+#define OPEN_WAIT 300 /* ms */
+
+struct rfcomm_node {
+ int16_t id; /* RFCOMM device id */
+ char *name; /* RFCOMM device name */
+ DBusConnection *conn; /* for name listener handling */
+ char *owner; /* Bus name */
+ GIOChannel *io; /* Connected node IO Channel */
+ guint io_id; /* IO Channel ID */
+};
+
+struct open_context {
+ char *dev;
+ open_notify_t notify;
+ udata_free_t ufree;
+ void *udata;
+ int ntries;
+};
+
+static GSList *connected_nodes = NULL;
+
+static DBusHandlerResult port_connect(DBusConnection *conn,
+ DBusMessage *msg, void *data)
+{
+ /* FIXME: call port_open() */
+
+ return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
+}
+
+static DBusHandlerResult port_disconnect(DBusConnection *conn,
+ DBusMessage *msg, void *data)
+{
+ return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
+}
+
+static DBusHandlerResult port_get_address(DBusConnection *conn,
+ DBusMessage *msg, void *data)
+{
+ return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
+}
+
+static DBusMethodVTable port_methods[] = {
+ { "Connect", port_connect, "", "" },
+ { "Disconnect", port_disconnect, "", "" },
+ { "GetAddress", port_get_address, "", "s" },
+ { NULL, NULL, NULL, NULL },
+};
+
+static DBusSignalVTable port_signals[] = {
+ { NULL, NULL }
+};
+
+static void rfcomm_node_free(struct rfcomm_node *node)
+{
+ if (node->name)
+ g_free(node->name);
+ if (node->conn)
+ dbus_connection_unref(node->conn);
+ if (node->owner)
+ g_free(node->owner);
+ if (node->io) {
+ g_source_remove(node->io_id);
+ g_io_channel_unref(node->io);
+ }
+ g_free(node);
+}
+
+static void connection_owner_exited(const char *name, struct rfcomm_node *node)
+{
+ char path[MAX_PATH_LENGTH];
+
+ debug("Connect requestor %s exited. Releasing %s node",
+ name, node->name);
+
+ snprintf(path, MAX_PATH_LENGTH, "%s/rfcomm%d", SERIAL_MANAGER_PATH, node->id);
+ rfcomm_release(node->id);
+ dbus_connection_emit_signal(node->conn, SERIAL_MANAGER_PATH,
+ SERIAL_MANAGER_INTERFACE, "ServiceDisconnected" ,
+ DBUS_TYPE_STRING, &node->name,
+ DBUS_TYPE_INVALID);
+
+ connected_nodes = g_slist_remove(connected_nodes, node);
+ dbus_connection_destroy_object_path(node->conn, path);
+}
+
+static gboolean rfcomm_disconnect_cb(GIOChannel *io,
+ GIOCondition cond, struct rfcomm_node *node)
+{
+ debug("RFCOMM node %s was disconnected", node->name);
+
+ if (cond & (G_IO_ERR | G_IO_HUP))
+ g_io_channel_close(io);
+
+ name_listener_remove(node->conn, node->owner,
+ (name_cb_t) connection_owner_exited, node);
+
+ dbus_connection_emit_signal(node->conn, SERIAL_MANAGER_PATH,
+ SERIAL_MANAGER_INTERFACE, "ServiceDisconnected" ,
+ DBUS_TYPE_STRING, &node->name,
+ DBUS_TYPE_INVALID);
+
+ connected_nodes = g_slist_remove(connected_nodes, node);
+ rfcomm_node_free(node);
+
+ return FALSE;
+}
+
+static void port_unregister(DBusConnection *conn, void *data)
+{
+ struct rfcomm_node *node = data;
+
+ debug("Unregistered serial port: %s", node->name);
+
+ rfcomm_node_free(node);
+}
+
+int port_register(DBusConnection *conn, int id, int fd,
+ const char *name, const char *owner, char *ppath)
+{
+ char path[MAX_PATH_LENGTH];
+ struct rfcomm_node *node;
+
+ node = g_new0(struct rfcomm_node, 1);
+ node->id = id;
+ node->name = g_strdup(name);
+ node->conn = dbus_connection_ref(conn);
+
+ snprintf(path, MAX_PATH_LENGTH, "%s/rfcomm%d", SERIAL_MANAGER_PATH, id);
+
+ if (!dbus_connection_create_object_path(conn, path, node,
+ port_unregister)) {
+ error("D-Bus failed to register %s path", path);
+ rfcomm_node_free(node);
+ return -1;
+ }
+
+ if (!dbus_connection_register_interface(conn, path,
+ SERIAL_PORT_INTERFACE,
+ port_methods,
+ port_signals, NULL)) {
+ error("D-Bus failed to register %s interface",
+ SERIAL_PORT_INTERFACE);
+ dbus_connection_destroy_object_path(conn, path);
+ return -1;
+ }
+
+ info("Registered RFCOMM:%s, path:%s owner:%s", name, path, owner);
+
+ if (ppath)
+ strcpy(ppath, path);
+
+ if (fd < 0)
+ return 0;
+
+ node->owner = g_strdup(owner);
+ node->io = g_io_channel_unix_new(fd);
+ node->io_id = g_io_add_watch(node->io, G_IO_ERR | G_IO_NVAL | G_IO_HUP,
+ (GIOFunc) rfcomm_disconnect_cb, node);
+
+ connected_nodes = g_slist_append(connected_nodes, node);
+
+ /* Serial port connection listener */
+ return name_listener_add(node->conn, owner,
+ (name_cb_t) connection_owner_exited, node);
+}
+
+const char *port_get_owner(DBusConnection *conn, int16_t id)
+{
+ struct rfcomm_node *node;
+ static char path[MAX_PATH_LENGTH];
+
+ snprintf(path, MAX_PATH_LENGTH, "%s/rfcomm%d", SERIAL_MANAGER_PATH, id);
+
+ if (!dbus_connection_get_object_user_data(conn, path, (void *) &node)
+ || !node) {
+ errno = ENOENT;
+ return NULL;
+ }
+
+ return node->owner;
+}
+
+static gboolean open_continue(struct open_context *oc)
+{
+ int fd;
+
+ fd = open(oc->dev, O_RDONLY | O_NOCTTY);
+ if (fd < 0) {
+ int err = errno;
+ error("Could not open %s: %s (%d)",
+ oc->dev, strerror(err), err);
+ if (++oc->ntries >= MAX_OPEN_TRIES) {
+ /* Reporting error */
+ oc->notify(fd, err, oc->udata);
+ return FALSE;
+ }
+ return TRUE;
+ }
+ /* Connection succeeded */
+ oc->notify(fd, 0, oc->udata);
+ return FALSE;
+}
+
+static void open_context_free(void *data)
+{
+ struct open_context *oc = data;
+
+ if (oc->ufree)
+ oc->ufree(oc->udata);
+ g_free(oc->dev);
+ g_free(oc);
+}
+
+int port_open(const char *dev, open_notify_t notify, void *udata, udata_free_t ufree)
+{
+ int fd;
+
+ fd = open(dev, O_RDONLY | O_NOCTTY);
+ if (fd < 0) {
+ struct open_context *oc;
+ oc = g_new0(struct open_context, 1);
+ oc->dev = g_strdup(dev);
+ oc->notify = notify;
+ oc->ufree = ufree;
+ oc->udata = udata;
+ g_timeout_add_full(G_PRIORITY_DEFAULT_IDLE, OPEN_WAIT,
+ (GSourceFunc) open_continue, oc, open_context_free);
+ return -EINPROGRESS;
+ }
+
+ return fd;
+}
diff --git a/serial/port.h b/serial/port.h
index e87dd676..9cab2847 100644
--- a/serial/port.h
+++ b/serial/port.h
@@ -20,3 +20,14 @@
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*
*/
+
+typedef void (*open_notify_t) (int fd, int err, void *data);
+typedef void (*udata_free_t) (void *data);
+
+int port_register(DBusConnection *conn, int id, int fd,
+ const char *name, const char *owner, char *path);
+
+const char *port_get_owner(DBusConnection *conn, int16_t id);
+
+int port_open(const char *dev, open_notify_t notify,
+ void *data, udata_free_t ufree);