hv_sock: Suppress bogus "may be used uninitialized" warnings
[linux-2.6-microblaze.git] / net / vmw_vsock / hyperv_transport.c
index a827547..e4801c7 100644 (file)
@@ -35,6 +35,9 @@
 /* The MTU is 16KB per the host side's design */
 #define HVS_MTU_SIZE           (1024 * 16)
 
+/* How long to wait for graceful shutdown of a connection */
+#define HVS_CLOSE_TIMEOUT (8 * HZ)
+
 struct vmpipe_proto_header {
        u32 pkt_type;
        u32 data_size;
@@ -305,19 +308,32 @@ static void hvs_channel_cb(void *ctx)
                sk->sk_write_space(sk);
 }
 
-static void hvs_close_connection(struct vmbus_channel *chan)
+static void hvs_do_close_lock_held(struct vsock_sock *vsk,
+                                  bool cancel_timeout)
 {
-       struct sock *sk = get_per_channel_state(chan);
-       struct vsock_sock *vsk = vsock_sk(sk);
-
-       lock_sock(sk);
+       struct sock *sk = sk_vsock(vsk);
 
-       sk->sk_state = TCP_CLOSE;
        sock_set_flag(sk, SOCK_DONE);
-       vsk->peer_shutdown |= SEND_SHUTDOWN | RCV_SHUTDOWN;
-
+       vsk->peer_shutdown = SHUTDOWN_MASK;
+       if (vsock_stream_has_data(vsk) <= 0)
+               sk->sk_state = TCP_CLOSING;
        sk->sk_state_change(sk);
+       if (vsk->close_work_scheduled &&
+           (!cancel_timeout || cancel_delayed_work(&vsk->close_work))) {
+               vsk->close_work_scheduled = false;
+               vsock_remove_sock(vsk);
 
+               /* Release the reference taken while scheduling the timeout */
+               sock_put(sk);
+       }
+}
+
+static void hvs_close_connection(struct vmbus_channel *chan)
+{
+       struct sock *sk = get_per_channel_state(chan);
+
+       lock_sock(sk);
+       hvs_do_close_lock_held(vsock_sk(sk), true);
        release_sock(sk);
 }
 
@@ -328,8 +344,8 @@ static void hvs_open_connection(struct vmbus_channel *chan)
 
        struct sockaddr_vm addr;
        struct sock *sk, *new = NULL;
-       struct vsock_sock *vnew;
-       struct hvsock *hvs, *hvs_new;
+       struct vsock_sock *vnew = NULL;
+       struct hvsock *hvs, *hvs_new = NULL;
        int ret;
 
        if_type = &chan->offermsg.offer.if_type;
@@ -452,50 +468,80 @@ static int hvs_connect(struct vsock_sock *vsk)
        return vmbus_send_tl_connect_request(&h->vm_srv_id, &h->host_srv_id);
 }
 
+static void hvs_shutdown_lock_held(struct hvsock *hvs, int mode)
+{
+       struct vmpipe_proto_header hdr;
+
+       if (hvs->fin_sent || !hvs->chan)
+               return;
+
+       /* It can't fail: see hvs_channel_writable_bytes(). */
+       (void)hvs_send_data(hvs->chan, (struct hvs_send_buf *)&hdr, 0);
+       hvs->fin_sent = true;
+}
+
 static int hvs_shutdown(struct vsock_sock *vsk, int mode)
 {
        struct sock *sk = sk_vsock(vsk);
-       struct vmpipe_proto_header hdr;
-       struct hvs_send_buf *send_buf;
-       struct hvsock *hvs;
 
        if (!(mode & SEND_SHUTDOWN))
                return 0;
 
        lock_sock(sk);
+       hvs_shutdown_lock_held(vsk->trans, mode);
+       release_sock(sk);
+       return 0;
+}
 
-       hvs = vsk->trans;
-       if (hvs->fin_sent)
-               goto out;
-
-       send_buf = (struct hvs_send_buf *)&hdr;
+static void hvs_close_timeout(struct work_struct *work)
+{
+       struct vsock_sock *vsk =
+               container_of(work, struct vsock_sock, close_work.work);
+       struct sock *sk = sk_vsock(vsk);
 
-       /* It can't fail: see hvs_channel_writable_bytes(). */
-       (void)hvs_send_data(hvs->chan, send_buf, 0);
+       sock_hold(sk);
+       lock_sock(sk);
+       if (!sock_flag(sk, SOCK_DONE))
+               hvs_do_close_lock_held(vsk, false);
 
-       hvs->fin_sent = true;
-out:
+       vsk->close_work_scheduled = false;
        release_sock(sk);
-       return 0;
+       sock_put(sk);
 }
 
-static void hvs_release(struct vsock_sock *vsk)
+/* Returns true, if it is safe to remove socket; false otherwise */
+static bool hvs_close_lock_held(struct vsock_sock *vsk)
 {
        struct sock *sk = sk_vsock(vsk);
-       struct hvsock *hvs = vsk->trans;
-       struct vmbus_channel *chan;
 
-       lock_sock(sk);
+       if (!(sk->sk_state == TCP_ESTABLISHED ||
+             sk->sk_state == TCP_CLOSING))
+               return true;
 
-       sk->sk_state = TCP_CLOSING;
-       vsock_remove_sock(vsk);
+       if ((sk->sk_shutdown & SHUTDOWN_MASK) != SHUTDOWN_MASK)
+               hvs_shutdown_lock_held(vsk->trans, SHUTDOWN_MASK);
 
-       release_sock(sk);
+       if (sock_flag(sk, SOCK_DONE))
+               return true;
 
-       chan = hvs->chan;
-       if (chan)
-               hvs_shutdown(vsk, RCV_SHUTDOWN | SEND_SHUTDOWN);
+       /* This reference will be dropped by the delayed close routine */
+       sock_hold(sk);
+       INIT_DELAYED_WORK(&vsk->close_work, hvs_close_timeout);
+       vsk->close_work_scheduled = true;
+       schedule_delayed_work(&vsk->close_work, HVS_CLOSE_TIMEOUT);
+       return false;
+}
 
+static void hvs_release(struct vsock_sock *vsk)
+{
+       struct sock *sk = sk_vsock(vsk);
+       bool remove_sock;
+
+       lock_sock(sk);
+       remove_sock = hvs_close_lock_held(vsk);
+       release_sock(sk);
+       if (remove_sock)
+               vsock_remove_sock(vsk);
 }
 
 static void hvs_destruct(struct vsock_sock *vsk)