diff options
Diffstat (limited to 'net')
-rw-r--r-- | net/bluetooth/l2cap_sock.c | 44 |
1 files changed, 27 insertions, 17 deletions
diff --git a/net/bluetooth/l2cap_sock.c b/net/bluetooth/l2cap_sock.c index ca5598d6a201..d06fb54082aa 100644 --- a/net/bluetooth/l2cap_sock.c +++ b/net/bluetooth/l2cap_sock.c @@ -1111,6 +1111,8 @@ static int l2cap_sock_shutdown(struct socket *sock, int how) if (!sk) return 0; + lock_sock(sk); + if (sk->sk_shutdown) goto shutdown_already; @@ -1122,25 +1124,37 @@ static int l2cap_sock_shutdown(struct socket *sock, int how) chan = l2cap_pi(sk)->chan; /* prevent chan structure from being freed whilst unlocked */ l2cap_chan_hold(chan); - conn = chan->conn; BT_DBG("chan %p state %s", chan, state_to_string(chan->state)); - if (conn) - mutex_lock(&conn->chan_lock); - - l2cap_chan_lock(chan); - lock_sock(sk); - if (chan->mode == L2CAP_MODE_ERTM && chan->unacked_frames > 0 && chan->state == BT_CONNECTED) err = __l2cap_wait_ack(sk, chan); sk->sk_shutdown = SHUTDOWN_MASK; - release_sock(sk); + + l2cap_chan_lock(chan); + conn = chan->conn; + if (conn) + /* prevent conn structure from being freed */ + l2cap_conn_get(conn); + l2cap_chan_unlock(chan); + + if (conn) + /* mutex lock must be taken before l2cap_chan_lock() */ + mutex_lock(&conn->chan_lock); + + l2cap_chan_lock(chan); l2cap_chan_close(chan, 0); + l2cap_chan_unlock(chan); + + if (conn) { + mutex_unlock(&conn->chan_lock); + l2cap_conn_put(conn); + } + lock_sock(sk); if (sock_flag(sk, SOCK_LINGER) && sk->sk_lingertime && @@ -1148,20 +1162,16 @@ static int l2cap_sock_shutdown(struct socket *sock, int how) err = bt_sock_wait_state(sk, BT_CLOSED, sk->sk_lingertime); + l2cap_chan_put(chan); + sock_put(sk); + +shutdown_already: if (!err && sk->sk_err) err = -sk->sk_err; release_sock(sk); - l2cap_chan_unlock(chan); - - if (conn) - mutex_unlock(&conn->chan_lock); - l2cap_chan_put(chan); - sock_put(sk); - -shutdown_already: - BT_DBG("err: %d", err); + BT_DBG("Sock shutdown complete err: %d", err); return err; } |