mirror of
https://git.kernel.org/pub/scm/bluetooth/bluez.git
synced 2024-11-26 13:44:23 +08:00
6f99dcb195
With the BtIO API the way to abort some operation is by calling g_io_channel_shutdown. This will cause the corresponding file descriptor to be closed and G_IO_NVAL to be reported to the BtIO internal watch callback. When BtIO gets G_IO_NVAL it knows not to call back to the application since the operation is considered aborted. There's unfortunately a race condition associated with this. The G_IO_NVAL will get reported only in the next main loop iteration that follows the one where the file descriptor was closed. So, if there was input or an error for the file descriptor in the same iteration where it was closed the BtIO internal watch will get called with something other than G_IO_NVAL. In other words calling g_io_channel_shutdown doesn't provide a 100% guarantee that the application callback will not get called. This patch fixes the problem by doing a secondary check for POLLNVAL by calling poll() with a zero timeout in the BtIO internal watch functions.
1275 lines
28 KiB
C
1275 lines
28 KiB
C
/*
|
|
*
|
|
* BlueZ - Bluetooth protocol stack for Linux
|
|
*
|
|
* Copyright (C) 2009 Marcel Holtmann <marcel@holtmann.org>
|
|
* Copyright (C) 2009 Nokia Corporation
|
|
*
|
|
*
|
|
* 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
|
|
*
|
|
*/
|
|
#include <stdarg.h>
|
|
#include <stdlib.h>
|
|
#include <unistd.h>
|
|
#include <errno.h>
|
|
#include <poll.h>
|
|
#include <sys/types.h>
|
|
#include <sys/socket.h>
|
|
|
|
#include <bluetooth/bluetooth.h>
|
|
#include <bluetooth/l2cap.h>
|
|
#include <bluetooth/rfcomm.h>
|
|
#include <bluetooth/sco.h>
|
|
#include <bluetooth/hci.h>
|
|
#include <bluetooth/hci_lib.h>
|
|
|
|
#include <glib.h>
|
|
|
|
#include "logging.h"
|
|
#include "btio.h"
|
|
|
|
#define ERROR_FAILED(gerr, str, err) \
|
|
g_set_error(gerr, BT_IO_ERROR, BT_IO_ERROR_FAILED, \
|
|
str ": %s (%d)", strerror(err), err)
|
|
|
|
#define DEFAULT_DEFER_TIMEOUT 30
|
|
|
|
struct set_opts {
|
|
bdaddr_t src;
|
|
bdaddr_t dst;
|
|
int defer;
|
|
int sec_level;
|
|
uint8_t channel;
|
|
uint16_t psm;
|
|
uint16_t mtu;
|
|
uint16_t imtu;
|
|
uint16_t omtu;
|
|
int master;
|
|
};
|
|
|
|
struct connect {
|
|
BtIOConnect connect;
|
|
gpointer user_data;
|
|
GDestroyNotify destroy;
|
|
};
|
|
|
|
struct accept {
|
|
BtIOConnect connect;
|
|
gpointer user_data;
|
|
GDestroyNotify destroy;
|
|
};
|
|
|
|
struct server {
|
|
BtIOConnect connect;
|
|
BtIOConfirm confirm;
|
|
gpointer user_data;
|
|
GDestroyNotify destroy;
|
|
};
|
|
|
|
static void server_remove(struct server *server)
|
|
{
|
|
if (server->destroy)
|
|
server->destroy(server->user_data);
|
|
g_free(server);
|
|
}
|
|
|
|
static void connect_remove(struct connect *conn)
|
|
{
|
|
if (conn->destroy)
|
|
conn->destroy(conn->user_data);
|
|
g_free(conn);
|
|
}
|
|
|
|
static void accept_remove(struct accept *accept)
|
|
{
|
|
if (accept->destroy)
|
|
accept->destroy(accept->user_data);
|
|
g_free(accept);
|
|
}
|
|
|
|
static gboolean check_nval(GIOChannel *io)
|
|
{
|
|
struct pollfd fds;
|
|
|
|
memset(&fds, 0, sizeof(fds));
|
|
fds.fd = g_io_channel_unix_get_fd(io);
|
|
fds.events = POLLNVAL;
|
|
|
|
if (poll(&fds, 1, 0) > 0 && (fds.revents & POLLNVAL))
|
|
return TRUE;
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
static gboolean accept_cb(GIOChannel *io, GIOCondition cond,
|
|
gpointer user_data)
|
|
{
|
|
struct accept *accept = user_data;
|
|
GError *err = NULL;
|
|
|
|
/* If the user aborted this accept attempt */
|
|
if ((cond & G_IO_NVAL) || check_nval(io))
|
|
return FALSE;
|
|
|
|
if (cond & (G_IO_HUP | G_IO_ERR))
|
|
g_set_error(&err, BT_IO_ERROR, BT_IO_ERROR_DISCONNECTED,
|
|
"HUP or ERR on socket");
|
|
|
|
accept->connect(io, err, accept->user_data);
|
|
|
|
g_clear_error(&err);
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
static gboolean connect_cb(GIOChannel *io, GIOCondition cond,
|
|
gpointer user_data)
|
|
{
|
|
struct connect *conn = user_data;
|
|
GError *gerr = NULL;
|
|
|
|
/* If the user aborted this connect attempt */
|
|
if ((cond & G_IO_NVAL) || check_nval(io))
|
|
return FALSE;
|
|
|
|
if (cond & G_IO_OUT) {
|
|
int err = 0, sock = g_io_channel_unix_get_fd(io);
|
|
socklen_t len = sizeof(err);
|
|
|
|
if (getsockopt(sock, SOL_SOCKET, SO_ERROR, &err, &len) < 0)
|
|
err = errno;
|
|
|
|
if (err)
|
|
g_set_error(&gerr, BT_IO_ERROR,
|
|
BT_IO_ERROR_CONNECT_FAILED, "%s (%d)",
|
|
strerror(err), err);
|
|
} else if (cond & (G_IO_HUP | G_IO_ERR))
|
|
g_set_error(&gerr, BT_IO_ERROR, BT_IO_ERROR_CONNECT_FAILED,
|
|
"HUP or ERR on socket");
|
|
|
|
conn->connect(io, gerr, conn->user_data);
|
|
|
|
if (gerr)
|
|
g_error_free(gerr);
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
static gboolean server_cb(GIOChannel *io, GIOCondition cond,
|
|
gpointer user_data)
|
|
{
|
|
struct server *server = user_data;
|
|
int srv_sock, cli_sock;
|
|
GIOChannel *cli_io;
|
|
|
|
/* If the user closed the server */
|
|
if ((cond & G_IO_NVAL) || check_nval(io))
|
|
return FALSE;
|
|
|
|
srv_sock = g_io_channel_unix_get_fd(io);
|
|
|
|
cli_sock = accept(srv_sock, NULL, NULL);
|
|
if (cli_sock < 0) {
|
|
error("accept: %s (%d)", strerror(errno), errno);
|
|
return TRUE;
|
|
}
|
|
|
|
cli_io = g_io_channel_unix_new(cli_sock);
|
|
|
|
g_io_channel_set_close_on_unref(cli_io, TRUE);
|
|
g_io_channel_set_flags(cli_io, G_IO_FLAG_NONBLOCK, NULL);
|
|
|
|
if (server->confirm)
|
|
server->confirm(cli_io, server->user_data);
|
|
else
|
|
server->connect(cli_io, NULL, server->user_data);
|
|
|
|
g_io_channel_unref(cli_io);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static void server_add(GIOChannel *io, BtIOConnect connect,
|
|
BtIOConfirm confirm, gpointer user_data,
|
|
GDestroyNotify destroy)
|
|
{
|
|
struct server *server;
|
|
GIOCondition cond;
|
|
|
|
server = g_new0(struct server, 1);
|
|
server->connect = connect;
|
|
server->confirm = confirm;
|
|
server->user_data = user_data;
|
|
server->destroy = destroy;
|
|
|
|
cond = G_IO_IN | G_IO_ERR | G_IO_HUP | G_IO_NVAL;
|
|
g_io_add_watch_full(io, G_PRIORITY_DEFAULT, cond, server_cb, server,
|
|
(GDestroyNotify) server_remove);
|
|
}
|
|
|
|
static void connect_add(GIOChannel *io, BtIOConnect connect,
|
|
gpointer user_data, GDestroyNotify destroy)
|
|
{
|
|
struct connect *conn;
|
|
GIOCondition cond;
|
|
|
|
conn = g_new0(struct connect, 1);
|
|
conn->connect = connect;
|
|
conn->user_data = user_data;
|
|
conn->destroy = destroy;
|
|
|
|
cond = G_IO_OUT | G_IO_ERR | G_IO_HUP | G_IO_NVAL;
|
|
g_io_add_watch_full(io, G_PRIORITY_DEFAULT, cond, connect_cb, conn,
|
|
(GDestroyNotify) connect_remove);
|
|
}
|
|
|
|
static void accept_add(GIOChannel *io, BtIOConnect connect, gpointer user_data,
|
|
GDestroyNotify destroy)
|
|
{
|
|
struct accept *accept;
|
|
GIOCondition cond;
|
|
|
|
accept = g_new0(struct accept, 1);
|
|
accept->connect = connect;
|
|
accept->user_data = user_data;
|
|
accept->destroy = destroy;
|
|
|
|
cond = G_IO_OUT | G_IO_ERR | G_IO_HUP | G_IO_NVAL;
|
|
g_io_add_watch_full(io, G_PRIORITY_DEFAULT, cond, accept_cb, accept,
|
|
(GDestroyNotify) accept_remove);
|
|
}
|
|
|
|
static int l2cap_bind(int sock, const bdaddr_t *src, uint16_t psm)
|
|
{
|
|
struct sockaddr_l2 addr;
|
|
|
|
memset(&addr, 0, sizeof(addr));
|
|
addr.l2_family = AF_BLUETOOTH;
|
|
bacpy(&addr.l2_bdaddr, src);
|
|
addr.l2_psm = htobs(psm);
|
|
|
|
return bind(sock, (struct sockaddr *) &addr, sizeof(addr));
|
|
}
|
|
|
|
static int l2cap_connect(int sock, const bdaddr_t *dst, uint16_t psm)
|
|
{
|
|
int err;
|
|
struct sockaddr_l2 addr;
|
|
|
|
memset(&addr, 0, sizeof(addr));
|
|
addr.l2_family = AF_BLUETOOTH;
|
|
bacpy(&addr.l2_bdaddr, dst);
|
|
addr.l2_psm = htobs(psm);
|
|
|
|
err = connect(sock, (struct sockaddr *) &addr, sizeof(addr));
|
|
if (err < 0 && !(errno == EAGAIN || errno == EINPROGRESS))
|
|
return err;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int l2cap_set_master(int sock, int master)
|
|
{
|
|
int flags;
|
|
socklen_t len;
|
|
|
|
len = sizeof(flags);
|
|
if (getsockopt(sock, SOL_L2CAP, L2CAP_LM, &flags, &len) < 0)
|
|
return -errno;
|
|
|
|
if (master) {
|
|
if (flags & L2CAP_LM_MASTER)
|
|
return 0;
|
|
flags |= L2CAP_LM_MASTER;
|
|
} else {
|
|
if (!(flags & L2CAP_LM_MASTER))
|
|
return 0;
|
|
flags &= ~L2CAP_LM_MASTER;
|
|
}
|
|
|
|
if (setsockopt(sock, SOL_L2CAP, L2CAP_LM, &flags, sizeof(flags)) < 0)
|
|
return -errno;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int rfcomm_set_master(int sock, int master)
|
|
{
|
|
int flags;
|
|
socklen_t len;
|
|
|
|
len = sizeof(flags);
|
|
if (getsockopt(sock, SOL_RFCOMM, RFCOMM_LM, &flags, &len) < 0)
|
|
return -errno;
|
|
|
|
if (master) {
|
|
if (flags & RFCOMM_LM_MASTER)
|
|
return 0;
|
|
flags |= RFCOMM_LM_MASTER;
|
|
} else {
|
|
if (!(flags & RFCOMM_LM_MASTER))
|
|
return 0;
|
|
flags &= ~RFCOMM_LM_MASTER;
|
|
}
|
|
|
|
if (setsockopt(sock, SOL_RFCOMM, RFCOMM_LM, &flags, sizeof(flags)) < 0)
|
|
return -errno;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int l2cap_set_lm(int sock, int level)
|
|
{
|
|
int lm_map[] = {
|
|
0,
|
|
L2CAP_LM_AUTH,
|
|
L2CAP_LM_AUTH | L2CAP_LM_ENCRYPT,
|
|
L2CAP_LM_AUTH | L2CAP_LM_ENCRYPT | L2CAP_LM_SECURE,
|
|
}, opt = lm_map[level];
|
|
|
|
if (setsockopt(sock, SOL_L2CAP, L2CAP_LM, &opt, sizeof(opt)) < 0)
|
|
return -errno;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int rfcomm_set_lm(int sock, int level)
|
|
{
|
|
int lm_map[] = {
|
|
0,
|
|
RFCOMM_LM_AUTH,
|
|
RFCOMM_LM_AUTH | RFCOMM_LM_ENCRYPT,
|
|
RFCOMM_LM_AUTH | RFCOMM_LM_ENCRYPT | RFCOMM_LM_SECURE,
|
|
}, opt = lm_map[level];
|
|
|
|
if (setsockopt(sock, SOL_RFCOMM, RFCOMM_LM, &opt, sizeof(opt)) < 0)
|
|
return -errno;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static gboolean set_sec_level(int sock, BtIOType type, int level, GError **err)
|
|
{
|
|
struct bt_security sec;
|
|
int ret;
|
|
|
|
if (level < BT_SECURITY_LOW || level > BT_SECURITY_HIGH) {
|
|
g_set_error(err, BT_IO_ERROR, BT_IO_ERROR_INVALID_ARGS,
|
|
"Valid security level range is %d-%d",
|
|
BT_SECURITY_LOW, BT_SECURITY_HIGH);
|
|
return FALSE;
|
|
}
|
|
|
|
memset(&sec, 0, sizeof(sec));
|
|
sec.level = level;
|
|
|
|
if (setsockopt(sock, SOL_BLUETOOTH, BT_SECURITY, &sec,
|
|
sizeof(sec)) == 0)
|
|
return TRUE;
|
|
|
|
if (errno != ENOPROTOOPT) {
|
|
ERROR_FAILED(err, "setsockopt(BT_SECURITY)", errno);
|
|
return FALSE;
|
|
}
|
|
|
|
if (type == BT_IO_L2CAP)
|
|
ret = l2cap_set_lm(sock, level);
|
|
else
|
|
ret = rfcomm_set_lm(sock, level);
|
|
|
|
if (ret < 0) {
|
|
ERROR_FAILED(err, "setsockopt(LM)", -ret);
|
|
return FALSE;
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static int l2cap_get_lm(int sock, int *sec_level)
|
|
{
|
|
int opt;
|
|
socklen_t len;
|
|
|
|
len = sizeof(opt);
|
|
if (getsockopt(sock, SOL_L2CAP, L2CAP_LM, &opt, &len) < 0)
|
|
return -errno;
|
|
|
|
*sec_level = 0;
|
|
|
|
if (opt & L2CAP_LM_AUTH)
|
|
*sec_level = BT_SECURITY_LOW;
|
|
if (opt & L2CAP_LM_ENCRYPT)
|
|
*sec_level = BT_SECURITY_MEDIUM;
|
|
if (opt & L2CAP_LM_SECURE)
|
|
*sec_level = BT_SECURITY_HIGH;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int rfcomm_get_lm(int sock, int *sec_level)
|
|
{
|
|
int opt;
|
|
socklen_t len;
|
|
|
|
len = sizeof(opt);
|
|
if (getsockopt(sock, SOL_RFCOMM, RFCOMM_LM, &opt, &len) < 0)
|
|
return -errno;
|
|
|
|
*sec_level = 0;
|
|
|
|
if (opt & RFCOMM_LM_AUTH)
|
|
*sec_level = BT_SECURITY_LOW;
|
|
if (opt & RFCOMM_LM_ENCRYPT)
|
|
*sec_level = BT_SECURITY_MEDIUM;
|
|
if (opt & RFCOMM_LM_SECURE)
|
|
*sec_level = BT_SECURITY_HIGH;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static gboolean get_sec_level(int sock, BtIOType type, int *level,
|
|
GError **err)
|
|
{
|
|
struct bt_security sec;
|
|
socklen_t len;
|
|
int ret;
|
|
|
|
memset(&sec, 0, sizeof(sec));
|
|
len = sizeof(sec);
|
|
if (getsockopt(sock, SOL_BLUETOOTH, BT_SECURITY, &sec, &len) == 0) {
|
|
*level = sec.level;
|
|
return TRUE;
|
|
}
|
|
|
|
if (errno != ENOPROTOOPT) {
|
|
ERROR_FAILED(err, "getsockopt(BT_SECURITY)", errno);
|
|
return FALSE;
|
|
}
|
|
|
|
if (type == BT_IO_L2CAP)
|
|
ret = l2cap_get_lm(sock, level);
|
|
else
|
|
ret = rfcomm_get_lm(sock, level);
|
|
|
|
if (ret < 0) {
|
|
ERROR_FAILED(err, "getsockopt(LM)", -ret);
|
|
return FALSE;
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static gboolean l2cap_set(int sock, int sec_level, uint16_t imtu,
|
|
uint16_t omtu, int master, GError **err)
|
|
{
|
|
if (imtu || omtu) {
|
|
struct l2cap_options l2o;
|
|
socklen_t len;
|
|
|
|
memset(&l2o, 0, sizeof(l2o));
|
|
len = sizeof(l2o);
|
|
if (getsockopt(sock, SOL_L2CAP, L2CAP_OPTIONS, &l2o,
|
|
&len) < 0) {
|
|
ERROR_FAILED(err, "getsockopt(L2CAP_OPTIONS)", errno);
|
|
return FALSE;
|
|
}
|
|
|
|
if (imtu)
|
|
l2o.imtu = imtu;
|
|
if (omtu)
|
|
l2o.omtu = omtu;
|
|
|
|
if (setsockopt(sock, SOL_L2CAP, L2CAP_OPTIONS, &l2o,
|
|
sizeof(l2o)) < 0) {
|
|
ERROR_FAILED(err, "setsockopt(L2CAP_OPTIONS)", errno);
|
|
return FALSE;
|
|
}
|
|
}
|
|
|
|
if (master >= 0 && l2cap_set_master(sock, master) < 0) {
|
|
ERROR_FAILED(err, "l2cap_set_master", errno);
|
|
return FALSE;
|
|
}
|
|
|
|
if (sec_level && !set_sec_level(sock, BT_IO_L2CAP, sec_level, err))
|
|
return FALSE;
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static int rfcomm_bind(int sock, const bdaddr_t *src, uint8_t channel)
|
|
{
|
|
struct sockaddr_rc addr;
|
|
|
|
memset(&addr, 0, sizeof(addr));
|
|
addr.rc_family = AF_BLUETOOTH;
|
|
bacpy(&addr.rc_bdaddr, src);
|
|
addr.rc_channel = channel;
|
|
|
|
return bind(sock, (struct sockaddr *) &addr, sizeof(addr));
|
|
}
|
|
|
|
static int rfcomm_connect(int sock, const bdaddr_t *dst, uint8_t channel)
|
|
{
|
|
int err;
|
|
struct sockaddr_rc addr;
|
|
|
|
memset(&addr, 0, sizeof(addr));
|
|
addr.rc_family = AF_BLUETOOTH;
|
|
bacpy(&addr.rc_bdaddr, dst);
|
|
addr.rc_channel = channel;
|
|
|
|
err = connect(sock, (struct sockaddr *) &addr, sizeof(addr));
|
|
if (err < 0 && !(errno == EAGAIN || errno == EINPROGRESS))
|
|
return err;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static gboolean rfcomm_set(int sock, int sec_level, int master, GError **err)
|
|
{
|
|
if (sec_level && !set_sec_level(sock, BT_IO_RFCOMM, sec_level, err))
|
|
return FALSE;
|
|
|
|
if (master >= 0 && rfcomm_set_master(sock, master) < 0) {
|
|
ERROR_FAILED(err, "rfcomm_set_master", errno);
|
|
return FALSE;
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static int sco_bind(int sock, const bdaddr_t *src)
|
|
{
|
|
struct sockaddr_sco addr;
|
|
|
|
memset(&addr, 0, sizeof(addr));
|
|
addr.sco_family = AF_BLUETOOTH;
|
|
bacpy(&addr.sco_bdaddr, src);
|
|
|
|
return bind(sock, (struct sockaddr *) &addr, sizeof(addr));
|
|
}
|
|
|
|
static int sco_connect(int sock, const bdaddr_t *dst)
|
|
{
|
|
struct sockaddr_sco addr;
|
|
int err;
|
|
|
|
memset(&addr, 0, sizeof(addr));
|
|
addr.sco_family = AF_BLUETOOTH;
|
|
bacpy(&addr.sco_bdaddr, dst);
|
|
|
|
err = connect(sock, (struct sockaddr *) &addr, sizeof(addr));
|
|
if (err < 0 && !(errno == EAGAIN || errno == EINPROGRESS))
|
|
return err;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static gboolean sco_set(int sock, uint16_t mtu, GError **err)
|
|
{
|
|
struct sco_options sco_opt;
|
|
socklen_t len;
|
|
|
|
if (!mtu)
|
|
return TRUE;
|
|
|
|
len = sizeof(sco_opt);
|
|
memset(&sco_opt, 0, len);
|
|
if (getsockopt(sock, SOL_SCO, SCO_OPTIONS, &sco_opt, &len) < 0) {
|
|
ERROR_FAILED(err, "getsockopt(SCO_OPTIONS)", errno);
|
|
return FALSE;
|
|
}
|
|
|
|
sco_opt.mtu = mtu;
|
|
if (setsockopt(sock, SOL_SCO, SCO_OPTIONS, &sco_opt,
|
|
sizeof(sco_opt)) < 0) {
|
|
ERROR_FAILED(err, "setsockopt(SCO_OPTIONS)", errno);
|
|
return FALSE;
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static gboolean parse_set_opts(struct set_opts *opts, GError **err,
|
|
BtIOOption opt1, va_list args)
|
|
{
|
|
BtIOOption opt = opt1;
|
|
const char *str;
|
|
|
|
memset(opts, 0, sizeof(*opts));
|
|
|
|
/* Set defaults */
|
|
opts->defer = DEFAULT_DEFER_TIMEOUT;
|
|
opts->master = -1;
|
|
|
|
while (opt != BT_IO_OPT_INVALID) {
|
|
switch (opt) {
|
|
case BT_IO_OPT_SOURCE:
|
|
str = va_arg(args, const char *);
|
|
if (strncasecmp(str, "hci", 3) == 0)
|
|
hci_devba(atoi(str + 3), &opts->src);
|
|
else
|
|
str2ba(str, &opts->src);
|
|
break;
|
|
case BT_IO_OPT_SOURCE_BDADDR:
|
|
bacpy(&opts->src, va_arg(args, const bdaddr_t *));
|
|
break;
|
|
case BT_IO_OPT_DEST:
|
|
str2ba(va_arg(args, const char *), &opts->dst);
|
|
break;
|
|
case BT_IO_OPT_DEST_BDADDR:
|
|
bacpy(&opts->dst, va_arg(args, const bdaddr_t *));
|
|
break;
|
|
case BT_IO_OPT_DEFER_TIMEOUT:
|
|
opts->defer = va_arg(args, int);
|
|
break;
|
|
case BT_IO_OPT_SEC_LEVEL:
|
|
opts->sec_level = va_arg(args, int);
|
|
break;
|
|
case BT_IO_OPT_CHANNEL:
|
|
opts->channel = va_arg(args, int);
|
|
break;
|
|
case BT_IO_OPT_PSM:
|
|
opts->psm = va_arg(args, int);
|
|
break;
|
|
case BT_IO_OPT_MTU:
|
|
opts->mtu = va_arg(args, int);
|
|
opts->imtu = opts->mtu;
|
|
opts->omtu = opts->mtu;
|
|
break;
|
|
case BT_IO_OPT_OMTU:
|
|
opts->omtu = va_arg(args, int);
|
|
if (!opts->mtu)
|
|
opts->mtu = opts->omtu;
|
|
break;
|
|
case BT_IO_OPT_IMTU:
|
|
opts->imtu = va_arg(args, int);
|
|
if (!opts->mtu)
|
|
opts->mtu = opts->imtu;
|
|
break;
|
|
case BT_IO_OPT_MASTER:
|
|
opts->master = va_arg(args, gboolean);
|
|
break;
|
|
default:
|
|
g_set_error(err, BT_IO_ERROR, BT_IO_ERROR_INVALID_ARGS,
|
|
"Unknown option %d", opt);
|
|
return FALSE;
|
|
}
|
|
|
|
opt = va_arg(args, int);
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static gboolean get_peers(int sock, struct sockaddr *src, struct sockaddr *dst,
|
|
socklen_t len, GError **err)
|
|
{
|
|
socklen_t olen;
|
|
|
|
memset(src, 0, len);
|
|
olen = len;
|
|
if (getsockname(sock, src, &olen) < 0) {
|
|
ERROR_FAILED(err, "getsockname", errno);
|
|
return FALSE;
|
|
}
|
|
|
|
memset(dst, 0, len);
|
|
olen = len;
|
|
if (getpeername(sock, dst, &olen) < 0) {
|
|
ERROR_FAILED(err, "getpeername", errno);
|
|
return FALSE;
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static int l2cap_get_info(int sock, uint16_t *handle, uint8_t *dev_class)
|
|
{
|
|
struct l2cap_conninfo info;
|
|
socklen_t len;
|
|
|
|
len = sizeof(info);
|
|
if (getsockopt(sock, SOL_L2CAP, L2CAP_CONNINFO, &info, &len) < 0)
|
|
return -errno;
|
|
|
|
if (handle)
|
|
*handle = info.hci_handle;
|
|
|
|
if (dev_class)
|
|
memcpy(dev_class, info.dev_class, 3);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static gboolean l2cap_get(int sock, GError **err, BtIOOption opt1,
|
|
va_list args)
|
|
{
|
|
BtIOOption opt = opt1;
|
|
struct sockaddr_l2 src, dst;
|
|
struct l2cap_options l2o;
|
|
int flags;
|
|
uint8_t dev_class[3];
|
|
uint16_t handle;
|
|
socklen_t len;
|
|
|
|
len = sizeof(l2o);
|
|
memset(&l2o, 0, len);
|
|
if (getsockopt(sock, SOL_L2CAP, L2CAP_OPTIONS, &l2o, &len) < 0) {
|
|
ERROR_FAILED(err, "getsockopt(L2CAP_OPTIONS)", errno);
|
|
return FALSE;
|
|
}
|
|
|
|
if (!get_peers(sock, (struct sockaddr *) &src,
|
|
(struct sockaddr *) &dst, sizeof(src), err))
|
|
return FALSE;
|
|
|
|
while (opt != BT_IO_OPT_INVALID) {
|
|
switch (opt) {
|
|
case BT_IO_OPT_SOURCE:
|
|
ba2str(&src.l2_bdaddr, va_arg(args, char *));
|
|
break;
|
|
case BT_IO_OPT_SOURCE_BDADDR:
|
|
bacpy(va_arg(args, bdaddr_t *), &src.l2_bdaddr);
|
|
break;
|
|
case BT_IO_OPT_DEST:
|
|
ba2str(&dst.l2_bdaddr, va_arg(args, char *));
|
|
break;
|
|
case BT_IO_OPT_DEST_BDADDR:
|
|
bacpy(va_arg(args, bdaddr_t *), &dst.l2_bdaddr);
|
|
break;
|
|
case BT_IO_OPT_DEFER_TIMEOUT:
|
|
len = sizeof(int);
|
|
if (getsockopt(sock, SOL_BLUETOOTH, BT_DEFER_SETUP,
|
|
va_arg(args, int *), &len) < 0) {
|
|
ERROR_FAILED(err, "getsockopt(DEFER_SETUP)",
|
|
errno);
|
|
return FALSE;
|
|
}
|
|
break;
|
|
case BT_IO_OPT_SEC_LEVEL:
|
|
if (!get_sec_level(sock, BT_IO_L2CAP,
|
|
va_arg(args, int *), err))
|
|
return FALSE;
|
|
break;
|
|
case BT_IO_OPT_PSM:
|
|
*(va_arg(args, uint16_t *)) = src.l2_psm ?
|
|
src.l2_psm : dst.l2_psm;
|
|
break;
|
|
case BT_IO_OPT_OMTU:
|
|
*(va_arg(args, uint16_t *)) = l2o.omtu;
|
|
break;
|
|
case BT_IO_OPT_IMTU:
|
|
*(va_arg(args, uint16_t *)) = l2o.imtu;
|
|
break;
|
|
case BT_IO_OPT_MASTER:
|
|
len = sizeof(flags);
|
|
if (getsockopt(sock, SOL_L2CAP, L2CAP_LM, &flags,
|
|
&len) < 0) {
|
|
ERROR_FAILED(err, "getsockopt(L2CAP_LM)",
|
|
errno);
|
|
return FALSE;
|
|
}
|
|
*(va_arg(args, gboolean *)) =
|
|
(flags & L2CAP_LM_MASTER) ? TRUE : FALSE;
|
|
break;
|
|
case BT_IO_OPT_HANDLE:
|
|
if (l2cap_get_info(sock, &handle, dev_class) < 0) {
|
|
ERROR_FAILED(err, "L2CAP_CONNINFO", errno);
|
|
return FALSE;
|
|
}
|
|
*(va_arg(args, uint16_t *)) = handle;
|
|
break;
|
|
case BT_IO_OPT_CLASS:
|
|
if (l2cap_get_info(sock, &handle, dev_class) < 0) {
|
|
ERROR_FAILED(err, "L2CAP_CONNINFO", errno);
|
|
return FALSE;
|
|
}
|
|
memcpy(va_arg(args, uint8_t *), dev_class, 3);
|
|
break;
|
|
default:
|
|
g_set_error(err, BT_IO_ERROR, BT_IO_ERROR_INVALID_ARGS,
|
|
"Unknown option %d", opt);
|
|
return FALSE;
|
|
}
|
|
|
|
opt = va_arg(args, int);
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static int rfcomm_get_info(int sock, uint16_t *handle, uint8_t *dev_class)
|
|
{
|
|
struct rfcomm_conninfo info;
|
|
socklen_t len;
|
|
|
|
len = sizeof(info);
|
|
if (getsockopt(sock, SOL_RFCOMM, RFCOMM_CONNINFO, &info, &len) < 0)
|
|
return -errno;
|
|
|
|
if (handle)
|
|
*handle = info.hci_handle;
|
|
|
|
if (dev_class)
|
|
memcpy(dev_class, info.dev_class, 3);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static gboolean rfcomm_get(int sock, GError **err, BtIOOption opt1,
|
|
va_list args)
|
|
{
|
|
BtIOOption opt = opt1;
|
|
struct sockaddr_rc src, dst;
|
|
int flags;
|
|
socklen_t len;
|
|
uint8_t dev_class[3];
|
|
uint16_t handle;
|
|
|
|
if (!get_peers(sock, (struct sockaddr *) &src,
|
|
(struct sockaddr *) &dst, sizeof(src), err))
|
|
return FALSE;
|
|
|
|
while (opt != BT_IO_OPT_INVALID) {
|
|
switch (opt) {
|
|
case BT_IO_OPT_SOURCE:
|
|
ba2str(&src.rc_bdaddr, va_arg(args, char *));
|
|
break;
|
|
case BT_IO_OPT_SOURCE_BDADDR:
|
|
bacpy(va_arg(args, bdaddr_t *), &src.rc_bdaddr);
|
|
break;
|
|
case BT_IO_OPT_DEST:
|
|
ba2str(&dst.rc_bdaddr, va_arg(args, char *));
|
|
break;
|
|
case BT_IO_OPT_DEST_BDADDR:
|
|
bacpy(va_arg(args, bdaddr_t *), &dst.rc_bdaddr);
|
|
break;
|
|
case BT_IO_OPT_DEFER_TIMEOUT:
|
|
len = sizeof(int);
|
|
if (getsockopt(sock, SOL_BLUETOOTH, BT_DEFER_SETUP,
|
|
va_arg(args, int *), &len) < 0) {
|
|
ERROR_FAILED(err, "getsockopt(DEFER_SETUP)",
|
|
errno);
|
|
return FALSE;
|
|
}
|
|
break;
|
|
case BT_IO_OPT_SEC_LEVEL:
|
|
if (!get_sec_level(sock, BT_IO_RFCOMM,
|
|
va_arg(args, int *), err))
|
|
return FALSE;
|
|
break;
|
|
case BT_IO_OPT_CHANNEL:
|
|
*(va_arg(args, uint8_t *)) = src.rc_channel ?
|
|
src.rc_channel : dst.rc_channel;
|
|
break;
|
|
case BT_IO_OPT_MASTER:
|
|
len = sizeof(flags);
|
|
if (getsockopt(sock, SOL_RFCOMM, RFCOMM_LM, &flags,
|
|
&len) < 0) {
|
|
ERROR_FAILED(err, "getsockopt(RFCOMM_LM)",
|
|
errno);
|
|
return FALSE;
|
|
}
|
|
*(va_arg(args, gboolean *)) =
|
|
(flags & RFCOMM_LM_MASTER) ? TRUE : FALSE;
|
|
break;
|
|
case BT_IO_OPT_HANDLE:
|
|
if (rfcomm_get_info(sock, &handle, dev_class) < 0) {
|
|
ERROR_FAILED(err, "RFCOMM_CONNINFO", errno);
|
|
return FALSE;
|
|
}
|
|
*(va_arg(args, uint16_t *)) = handle;
|
|
break;
|
|
case BT_IO_OPT_CLASS:
|
|
if (rfcomm_get_info(sock, &handle, dev_class) < 0) {
|
|
ERROR_FAILED(err, "RFCOMM_CONNINFO", errno);
|
|
return FALSE;
|
|
}
|
|
memcpy(va_arg(args, uint8_t *), dev_class, 3);
|
|
break;
|
|
default:
|
|
g_set_error(err, BT_IO_ERROR, BT_IO_ERROR_INVALID_ARGS,
|
|
"Unknown option %d", opt);
|
|
return FALSE;
|
|
}
|
|
|
|
opt = va_arg(args, int);
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static int sco_get_info(int sock, uint16_t *handle, uint8_t *dev_class)
|
|
{
|
|
struct sco_conninfo info;
|
|
socklen_t len;
|
|
|
|
len = sizeof(info);
|
|
if (getsockopt(sock, SOL_SCO, SCO_CONNINFO, &info, &len) < 0)
|
|
return -errno;
|
|
|
|
if (handle)
|
|
*handle = info.hci_handle;
|
|
|
|
if (dev_class)
|
|
memcpy(dev_class, info.dev_class, 3);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static gboolean sco_get(int sock, GError **err, BtIOOption opt1, va_list args)
|
|
{
|
|
BtIOOption opt = opt1;
|
|
struct sockaddr_sco src, dst;
|
|
struct sco_options sco_opt;
|
|
socklen_t len;
|
|
uint8_t dev_class[3];
|
|
uint16_t handle;
|
|
|
|
len = sizeof(sco_opt);
|
|
memset(&sco_opt, 0, len);
|
|
if (getsockopt(sock, SOL_SCO, SCO_OPTIONS, &sco_opt, &len) < 0) {
|
|
ERROR_FAILED(err, "getsockopt(SCO_OPTIONS)", errno);
|
|
return FALSE;
|
|
}
|
|
|
|
if (!get_peers(sock, (struct sockaddr *) &src,
|
|
(struct sockaddr *) &dst, sizeof(src), err))
|
|
return FALSE;
|
|
|
|
while (opt != BT_IO_OPT_INVALID) {
|
|
switch (opt) {
|
|
case BT_IO_OPT_SOURCE:
|
|
ba2str(&src.sco_bdaddr, va_arg(args, char *));
|
|
break;
|
|
case BT_IO_OPT_SOURCE_BDADDR:
|
|
bacpy(va_arg(args, bdaddr_t *), &src.sco_bdaddr);
|
|
break;
|
|
case BT_IO_OPT_DEST:
|
|
ba2str(&dst.sco_bdaddr, va_arg(args, char *));
|
|
break;
|
|
case BT_IO_OPT_DEST_BDADDR:
|
|
bacpy(va_arg(args, bdaddr_t *), &dst.sco_bdaddr);
|
|
break;
|
|
case BT_IO_OPT_MTU:
|
|
case BT_IO_OPT_IMTU:
|
|
case BT_IO_OPT_OMTU:
|
|
*(va_arg(args, uint16_t *)) = sco_opt.mtu;
|
|
break;
|
|
case BT_IO_OPT_HANDLE:
|
|
if (sco_get_info(sock, &handle, dev_class) < 0) {
|
|
ERROR_FAILED(err, "RFCOMM_CONNINFO", errno);
|
|
return FALSE;
|
|
}
|
|
*(va_arg(args, uint16_t *)) = handle;
|
|
break;
|
|
case BT_IO_OPT_CLASS:
|
|
if (sco_get_info(sock, &handle, dev_class) < 0) {
|
|
ERROR_FAILED(err, "RFCOMM_CONNINFO", errno);
|
|
return FALSE;
|
|
}
|
|
memcpy(va_arg(args, uint8_t *), dev_class, 3);
|
|
break;
|
|
default:
|
|
g_set_error(err, BT_IO_ERROR, BT_IO_ERROR_INVALID_ARGS,
|
|
"Unknown option %d", opt);
|
|
return FALSE;
|
|
}
|
|
|
|
opt = va_arg(args, int);
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static gboolean get_valist(GIOChannel *io, BtIOType type, GError **err,
|
|
BtIOOption opt1, va_list args)
|
|
{
|
|
int sock;
|
|
|
|
sock = g_io_channel_unix_get_fd(io);
|
|
|
|
switch (type) {
|
|
case BT_IO_L2RAW:
|
|
case BT_IO_L2CAP:
|
|
return l2cap_get(sock, err, opt1, args);
|
|
case BT_IO_RFCOMM:
|
|
return rfcomm_get(sock, err, opt1, args);
|
|
case BT_IO_SCO:
|
|
return sco_get(sock, err, opt1, args);
|
|
}
|
|
|
|
g_set_error(err, BT_IO_ERROR, BT_IO_ERROR_INVALID_ARGS,
|
|
"Unknown BtIO type %d", type);
|
|
return FALSE;
|
|
}
|
|
|
|
gboolean bt_io_accept(GIOChannel *io, BtIOConnect connect, gpointer user_data,
|
|
GDestroyNotify destroy, GError **err)
|
|
{
|
|
int sock;
|
|
char c;
|
|
struct pollfd pfd;
|
|
|
|
sock = g_io_channel_unix_get_fd(io);
|
|
|
|
memset(&pfd, 0, sizeof(pfd));
|
|
pfd.fd = sock;
|
|
pfd.events = POLLOUT;
|
|
|
|
if (poll(&pfd, 1, 0) < 0) {
|
|
ERROR_FAILED(err, "poll", errno);
|
|
return FALSE;
|
|
}
|
|
|
|
if (!(pfd.revents & POLLOUT)) {
|
|
int ret;
|
|
ret = read(sock, &c, 1);
|
|
}
|
|
|
|
accept_add(io, connect, user_data, destroy);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
gboolean bt_io_set(GIOChannel *io, BtIOType type, GError **err,
|
|
BtIOOption opt1, ...)
|
|
{
|
|
va_list args;
|
|
gboolean ret;
|
|
struct set_opts opts;
|
|
int sock;
|
|
|
|
va_start(args, opt1);
|
|
ret = parse_set_opts(&opts, err, opt1, args);
|
|
va_end(args);
|
|
|
|
if (!ret)
|
|
return ret;
|
|
|
|
sock = g_io_channel_unix_get_fd(io);
|
|
|
|
switch (type) {
|
|
case BT_IO_L2RAW:
|
|
case BT_IO_L2CAP:
|
|
return l2cap_set(sock, opts.sec_level, opts.imtu, opts.omtu,
|
|
opts.master, err);
|
|
case BT_IO_RFCOMM:
|
|
return rfcomm_set(sock, opts.sec_level, opts.master, err);
|
|
case BT_IO_SCO:
|
|
return sco_set(sock, opts.mtu, err);
|
|
}
|
|
|
|
g_set_error(err, BT_IO_ERROR, BT_IO_ERROR_INVALID_ARGS,
|
|
"Unknown BtIO type %d", type);
|
|
return FALSE;
|
|
}
|
|
|
|
gboolean bt_io_get(GIOChannel *io, BtIOType type, GError **err,
|
|
BtIOOption opt1, ...)
|
|
{
|
|
va_list args;
|
|
gboolean ret;
|
|
|
|
va_start(args, opt1);
|
|
ret = get_valist(io, type, err, opt1, args);
|
|
va_end(args);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static GIOChannel *create_io(BtIOType type, gboolean server,
|
|
struct set_opts *opts, GError **err)
|
|
{
|
|
int sock;
|
|
GIOChannel *io;
|
|
|
|
switch (type) {
|
|
case BT_IO_L2RAW:
|
|
sock = socket(PF_BLUETOOTH, SOCK_RAW, BTPROTO_L2CAP);
|
|
if (sock < 0) {
|
|
ERROR_FAILED(err, "socket(RAW, L2CAP)", errno);
|
|
return NULL;
|
|
}
|
|
if (l2cap_bind(sock, &opts->src, server ? opts->psm : 0) < 0) {
|
|
ERROR_FAILED(err, "l2cap_bind", errno);
|
|
return NULL;
|
|
}
|
|
if (!l2cap_set(sock, opts->sec_level, 0, 0, -1, err))
|
|
return NULL;
|
|
break;
|
|
case BT_IO_L2CAP:
|
|
sock = socket(PF_BLUETOOTH, SOCK_SEQPACKET, BTPROTO_L2CAP);
|
|
if (sock < 0) {
|
|
ERROR_FAILED(err, "socket(SEQPACKET, L2CAP)", errno);
|
|
return NULL;
|
|
}
|
|
if (l2cap_bind(sock, &opts->src, server ? opts->psm : 0) < 0) {
|
|
ERROR_FAILED(err, "l2cap_bind", errno);
|
|
return NULL;
|
|
}
|
|
if (!l2cap_set(sock, opts->sec_level, opts->imtu, opts->omtu,
|
|
opts->master, err))
|
|
return NULL;
|
|
break;
|
|
case BT_IO_RFCOMM:
|
|
sock = socket(PF_BLUETOOTH, SOCK_STREAM, BTPROTO_RFCOMM);
|
|
if (sock < 0) {
|
|
ERROR_FAILED(err, "socket(STREAM, RFCOMM)", errno);
|
|
return NULL;
|
|
}
|
|
if (rfcomm_bind(sock, &opts->src,
|
|
server ? opts->channel : 0) < 0) {
|
|
ERROR_FAILED(err, "rfcomm_bind", errno);
|
|
return NULL;
|
|
}
|
|
if (!rfcomm_set(sock, opts->sec_level, opts->master, err))
|
|
return NULL;
|
|
break;
|
|
case BT_IO_SCO:
|
|
sock = socket(PF_BLUETOOTH, SOCK_SEQPACKET, BTPROTO_SCO);
|
|
if (sock < 0) {
|
|
ERROR_FAILED(err, "socket(SEQPACKET, SCO)", errno);
|
|
return NULL;
|
|
}
|
|
if (sco_bind(sock, &opts->src) < 0) {
|
|
ERROR_FAILED(err, "sco_bind", errno);
|
|
return NULL;
|
|
}
|
|
if (!sco_set(sock, opts->mtu, err))
|
|
return NULL;
|
|
break;
|
|
default:
|
|
g_set_error(err, BT_IO_ERROR, BT_IO_ERROR_INVALID_ARGS,
|
|
"Unknown BtIO type %d", type);
|
|
return NULL;
|
|
}
|
|
|
|
io = g_io_channel_unix_new(sock);
|
|
|
|
g_io_channel_set_close_on_unref(io, TRUE);
|
|
g_io_channel_set_flags(io, G_IO_FLAG_NONBLOCK, NULL);
|
|
|
|
return io;
|
|
}
|
|
|
|
GIOChannel *bt_io_connect(BtIOType type, BtIOConnect connect,
|
|
gpointer user_data, GDestroyNotify destroy,
|
|
GError **gerr, BtIOOption opt1, ...)
|
|
{
|
|
GIOChannel *io;
|
|
va_list args;
|
|
struct set_opts opts;
|
|
int err, sock;
|
|
gboolean ret;
|
|
|
|
va_start(args, opt1);
|
|
ret = parse_set_opts(&opts, gerr, opt1, args);
|
|
va_end(args);
|
|
|
|
if (ret == FALSE)
|
|
return NULL;
|
|
|
|
io = create_io(type, FALSE, &opts, gerr);
|
|
if (io == NULL)
|
|
return NULL;
|
|
|
|
sock = g_io_channel_unix_get_fd(io);
|
|
|
|
switch (type) {
|
|
case BT_IO_L2RAW:
|
|
err = l2cap_connect(sock, &opts.dst, 0);
|
|
break;
|
|
case BT_IO_L2CAP:
|
|
err = l2cap_connect(sock, &opts.dst, opts.psm);
|
|
break;
|
|
case BT_IO_RFCOMM:
|
|
err = rfcomm_connect(sock, &opts.dst, opts.channel);
|
|
break;
|
|
case BT_IO_SCO:
|
|
err = sco_connect(sock, &opts.dst);
|
|
break;
|
|
default:
|
|
g_set_error(gerr, BT_IO_ERROR, BT_IO_ERROR_INVALID_ARGS,
|
|
"Unknown BtIO type %d", type);
|
|
return NULL;
|
|
}
|
|
|
|
if (err < 0) {
|
|
g_set_error(gerr, BT_IO_ERROR, BT_IO_ERROR_CONNECT_FAILED,
|
|
"connect: %s (%d)", strerror(-err), -err);
|
|
g_io_channel_unref(io);
|
|
return NULL;
|
|
}
|
|
|
|
connect_add(io, connect, user_data, destroy);
|
|
|
|
return io;
|
|
}
|
|
|
|
GIOChannel *bt_io_listen(BtIOType type, BtIOConnect connect,
|
|
BtIOConfirm confirm, gpointer user_data,
|
|
GDestroyNotify destroy, GError **err,
|
|
BtIOOption opt1, ...)
|
|
{
|
|
GIOChannel *io;
|
|
va_list args;
|
|
struct set_opts opts;
|
|
int sock;
|
|
gboolean ret;
|
|
|
|
if (type == BT_IO_L2RAW) {
|
|
g_set_error(err, BT_IO_ERROR, BT_IO_ERROR_INVALID_ARGS,
|
|
"Server L2CAP RAW sockets not supported");
|
|
return NULL;
|
|
}
|
|
|
|
va_start(args, opt1);
|
|
ret = parse_set_opts(&opts, err, opt1, args);
|
|
va_end(args);
|
|
|
|
if (ret == FALSE)
|
|
return NULL;
|
|
|
|
io = create_io(type, TRUE, &opts, err);
|
|
if (io == NULL)
|
|
return NULL;
|
|
|
|
sock = g_io_channel_unix_get_fd(io);
|
|
|
|
if (confirm)
|
|
setsockopt(sock, SOL_BLUETOOTH, BT_DEFER_SETUP, &opts.defer,
|
|
sizeof(opts.defer));
|
|
|
|
if (listen(sock, 5) < 0) {
|
|
ERROR_FAILED(err, "listen", errno);
|
|
g_io_channel_unref(io);
|
|
return NULL;
|
|
}
|
|
|
|
server_add(io, connect, confirm, user_data, destroy);
|
|
|
|
return io;
|
|
}
|
|
|
|
GQuark bt_io_error_quark(void)
|
|
{
|
|
return g_quark_from_static_string("bt-io-error-quark");
|
|
}
|
|
|