| /* |
| * |
| * BlueZ - Bluetooth protocol stack for Linux |
| * |
| * Copyright (C) 2004-2007 Marcel Holtmann <marcel@holtmann.org> |
| * |
| * |
| * This program is free software; you can redistribute it and/or modify |
| * it under the terms of the GNU General Public License as published by |
| * the Free Software Foundation; either version 2 of the License, or |
| * (at your option) any later version. |
| * |
| * This program is distributed in the hope that it will be useful, |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| * GNU General Public License for more details. |
| * |
| * You should have received a copy of the GNU General Public License |
| * along with this program; if not, write to the Free Software |
| * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA |
| * |
| */ |
| |
| #ifdef HAVE_CONFIG_H |
| #include <config.h> |
| #endif |
| |
| #include <errno.h> |
| #include <stdio.h> |
| #include <stdint.h> |
| #include <stdlib.h> |
| #include <string.h> |
| #include <termios.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 "storage.h" |
| |
| #define SERIAL_PORT_INTERFACE "org.bluez.serial.Port" |
| |
| struct rfcomm_node { |
| int16_t id; /* RFCOMM device id */ |
| bdaddr_t src; /* Source (local) address */ |
| bdaddr_t dst; /* Destination address */ |
| char *svcname; /* RFCOMM service name */ |
| char *device; /* 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 */ |
| }; |
| |
| static GSList *connected_nodes = NULL; |
| static GSList *bound_nodes = NULL; |
| |
| static struct rfcomm_node *find_node_by_name(GSList *nodes, const char *dev) |
| { |
| GSList *l; |
| |
| for (l = nodes; l != NULL; l = l->next) { |
| struct rfcomm_node *node = l->data; |
| if (!strcmp(node->device, dev)) |
| return node; |
| } |
| |
| return NULL; |
| } |
| |
| static DBusHandlerResult port_get_address(DBusConnection *conn, |
| DBusMessage *msg, void *data) |
| { |
| struct rfcomm_node *node = data; |
| DBusMessage *reply; |
| char bda[18]; |
| const char *pbda = bda; |
| |
| reply = dbus_message_new_method_return(msg); |
| if (!reply) |
| return DBUS_HANDLER_RESULT_NEED_MEMORY; |
| |
| ba2str(&node->dst, bda); |
| dbus_message_append_args(reply, |
| DBUS_TYPE_STRING, &pbda, |
| DBUS_TYPE_INVALID); |
| return send_message_and_unref(conn, reply); |
| |
| } |
| |
| static DBusHandlerResult port_get_device(DBusConnection *conn, |
| DBusMessage *msg, void *data) |
| { |
| struct rfcomm_node *node = data; |
| DBusMessage *reply; |
| |
| reply = dbus_message_new_method_return(msg); |
| if (!reply) |
| return DBUS_HANDLER_RESULT_NEED_MEMORY; |
| |
| dbus_message_append_args(reply, |
| DBUS_TYPE_STRING, &node->device, |
| DBUS_TYPE_INVALID); |
| return send_message_and_unref(conn, reply); |
| |
| } |
| |
| static DBusHandlerResult port_get_adapter(DBusConnection *conn, |
| DBusMessage *msg, void *data) |
| { |
| struct rfcomm_node *node = data; |
| DBusMessage *reply; |
| char addr[18]; |
| const char *paddr = addr; |
| |
| ba2str(&node->src, addr); |
| |
| reply = dbus_message_new_method_return(msg); |
| if (!reply) |
| return DBUS_HANDLER_RESULT_NEED_MEMORY; |
| |
| dbus_message_append_args(reply, |
| DBUS_TYPE_STRING, &paddr, |
| DBUS_TYPE_INVALID); |
| |
| return send_message_and_unref(conn, reply); |
| } |
| |
| |
| static DBusHandlerResult port_get_name(DBusConnection *conn, |
| DBusMessage *msg, void *data) |
| { |
| struct rfcomm_node *node = data; |
| DBusMessage *reply; |
| const char *pname; |
| char *name = NULL; |
| |
| reply = dbus_message_new_method_return(msg); |
| if (!reply) |
| return DBUS_HANDLER_RESULT_NEED_MEMORY; |
| |
| read_device_name(&node->src, &node->dst, &name); |
| |
| pname = (name ? name : ""); |
| dbus_message_append_args(reply, |
| DBUS_TYPE_STRING, &pname, |
| DBUS_TYPE_INVALID); |
| |
| if (name) |
| g_free(name); |
| |
| return send_message_and_unref(conn, reply); |
| } |
| |
| static DBusHandlerResult port_get_service_name(DBusConnection *conn, |
| DBusMessage *msg, void *data) |
| { |
| struct rfcomm_node *node = data; |
| DBusMessage *reply; |
| |
| reply = dbus_message_new_method_return(msg); |
| if (!reply) |
| return DBUS_HANDLER_RESULT_NEED_MEMORY; |
| |
| dbus_message_append_args(reply, |
| DBUS_TYPE_STRING, &node->svcname, |
| DBUS_TYPE_INVALID); |
| return send_message_and_unref(conn, reply); |
| } |
| |
| static DBusHandlerResult port_get_info(DBusConnection *conn, |
| DBusMessage *msg, void *data) |
| { |
| struct rfcomm_node *node = data; |
| DBusMessage *reply; |
| DBusMessageIter iter, dict; |
| char bda[18]; |
| const char *pbda = bda; |
| |
| reply = dbus_message_new_method_return(msg); |
| if (!reply) |
| return DBUS_HANDLER_RESULT_NEED_MEMORY; |
| |
| dbus_message_iter_init_append(reply, &iter); |
| |
| dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY, |
| DBUS_DICT_ENTRY_BEGIN_CHAR_AS_STRING |
| DBUS_TYPE_STRING_AS_STRING DBUS_TYPE_VARIANT_AS_STRING |
| DBUS_DICT_ENTRY_END_CHAR_AS_STRING, &dict); |
| |
| dbus_message_iter_append_dict_entry(&dict, "device", |
| DBUS_TYPE_STRING, &node->device); |
| |
| ba2str(&node->dst, bda); |
| dbus_message_iter_append_dict_entry(&dict, "address", |
| DBUS_TYPE_STRING, &pbda); |
| |
| dbus_message_iter_close_container(&iter, &dict); |
| |
| return send_message_and_unref(conn, reply); |
| } |
| |
| static DBusMethodVTable port_methods[] = { |
| { "GetAddress", port_get_address, "", "s" }, |
| { "GetDevice", port_get_device, "", "s" }, |
| { "GetAdapter", port_get_adapter, "", "s" }, |
| { "GetName", port_get_name, "", "s" }, |
| { "GetServiceName", port_get_service_name, "", "s" }, |
| { "GetInfo", port_get_info, "", "a{sv}" }, |
| { NULL, NULL, NULL, NULL }, |
| }; |
| |
| static DBusSignalVTable port_signals[] = { |
| { NULL, NULL } |
| }; |
| |
| static void rfcomm_node_free(struct rfcomm_node *node) |
| { |
| if (node->device) |
| g_free(node->device); |
| if (node->conn) |
| dbus_connection_unref(node->conn); |
| if (node->owner) |
| g_free(node->owner); |
| if (node->svcname) |
| g_free(node->svcname); |
| if (node->io) { |
| g_source_remove(node->io_id); |
| g_io_channel_close(node->io); |
| g_io_channel_unref(node->io); |
| } |
| rfcomm_release(node->id); |
| g_free(node); |
| } |
| |
| static void connection_owner_exited(const char *name, struct rfcomm_node *node) |
| { |
| debug("Connect requestor %s exited. Releasing %s node", |
| name, node->device); |
| |
| dbus_connection_emit_signal(node->conn, SERIAL_MANAGER_PATH, |
| SERIAL_MANAGER_INTERFACE, "ServiceDisconnected" , |
| DBUS_TYPE_STRING, &node->device, |
| 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->device); |
| |
| 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->device, |
| DBUS_TYPE_INVALID); |
| |
| connected_nodes = g_slist_remove(connected_nodes, node); |
| rfcomm_node_free(node); |
| |
| return FALSE; |
| } |
| |
| static void port_handler_unregister(DBusConnection *conn, void *data) |
| { |
| struct rfcomm_node *node = data; |
| |
| debug("Unregistered serial port: %s", node->device); |
| |
| bound_nodes = g_slist_remove(bound_nodes, node); |
| rfcomm_node_free(node); |
| } |
| |
| int port_add_listener(DBusConnection *conn, int16_t id, bdaddr_t *dst, |
| int fd, const char *dev, const char *owner) |
| { |
| struct rfcomm_node *node; |
| |
| node = g_new0(struct rfcomm_node, 1); |
| bacpy(&node->dst, dst); |
| node->id = id; |
| node->device = g_strdup(dev); |
| node->conn = dbus_connection_ref(conn); |
| 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); |
| |
| /* Service connection listener */ |
| return name_listener_add(conn, owner, |
| (name_cb_t) connection_owner_exited, node); |
| } |
| |
| int port_remove_listener(const char *owner, const char *dev) |
| { |
| struct rfcomm_node *node; |
| |
| node = find_node_by_name(connected_nodes, dev); |
| if (!node) |
| return -ENOENT; |
| if (strcmp(node->owner, owner) != 0) |
| return -EPERM; |
| |
| name_listener_remove(node->conn, owner, |
| (name_cb_t) connection_owner_exited, node); |
| |
| connected_nodes = g_slist_remove(connected_nodes, node); |
| rfcomm_node_free(node); |
| |
| return 0; |
| } |
| |
| int port_register(DBusConnection *conn, int16_t id, bdaddr_t *src, |
| bdaddr_t *dst, const char *dev, char *ppath, const char *svc) |
| { |
| char path[MAX_PATH_LENGTH]; |
| struct rfcomm_node *node; |
| |
| node = g_new0(struct rfcomm_node, 1); |
| bacpy(&node->dst, dst); |
| bacpy(&node->src, src); |
| node->id = id; |
| node->device = g_strdup(dev); |
| node->conn = dbus_connection_ref(conn); |
| node->svcname = g_strdup(svc?:"Bluetooth RFCOMM port"); |
| |
| snprintf(path, MAX_PATH_LENGTH, "%s/rfcomm%hd", SERIAL_MANAGER_PATH, id); |
| |
| if (!dbus_connection_create_object_path(conn, path, node, |
| port_handler_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", dev, path); |
| |
| if (ppath) |
| strcpy(ppath, path); |
| |
| bound_nodes = g_slist_append(bound_nodes, node); |
| |
| return 0; |
| } |
| |
| int port_unregister(const char *path) |
| { |
| struct rfcomm_node *node; |
| char dev[16]; |
| int16_t id; |
| |
| if (sscanf(path, SERIAL_MANAGER_PATH"/rfcomm%hd", &id) != 1) |
| return -ENOENT; |
| |
| snprintf(dev, sizeof(dev), "/dev/rfcomm%hd", id); |
| node = find_node_by_name(bound_nodes, dev); |
| if (!node) |
| return -ENOENT; |
| |
| dbus_connection_destroy_object_path(node->conn, path); |
| |
| return 0; |
| } |