diff mbox

[Ada] Fix implementation of the vectored sockets on Windows

Message ID 20100622083653.GA28900@adacore.com
State New
Headers show

Commit Message

Arnaud Charlet June 22, 2010, 8:36 a.m. UTC
Fix a race condition in the way the vectored sockets
emulation was implemented on Windows. Also fix the
way the vectors are filled.

Tested on i686-pc-mingw32, committed on trunk

2010-06-22  Pascal Obry  <obry@adacore.com>

	* g-socthi-mingw.adb: Fix implementation of the vectored sockets on
	Windows.
	(C_Recvmsg): Make sure the routine is atomic. Also fully
	fill vectors in the proper order.
	(C_Sendmsg): Make sure the routine is atomic.
diff mbox

Patch

Index: g-socthi-mingw.adb
===================================================================
--- g-socthi-mingw.adb	(revision 161073)
+++ g-socthi-mingw.adb	(working copy)
@@ -6,7 +6,7 @@ 
 --                                                                          --
 --                                 B o d y                                  --
 --                                                                          --
---                     Copyright (C) 2001-2009, AdaCore                     --
+--                     Copyright (C) 2001-2010, AdaCore                     --
 --                                                                          --
 -- GNAT is free software;  you can  redistribute it  and/or modify it under --
 -- terms of the  GNU General Public License as published  by the Free Soft- --
@@ -37,8 +37,13 @@ 
 
 --  This version is for NT
 
-with Interfaces.C.Strings; use Interfaces.C.Strings;
-with System;               use System;
+with Ada.Streams;             use Ada.Streams;
+with Ada.Unchecked_Conversion;
+with Interfaces.C.Strings;    use Interfaces.C.Strings;
+with System;                  use System;
+with System.Storage_Elements; use System.Storage_Elements;
+
+with GNAT.Task_Lock;
 
 package body GNAT.Sockets.Thin is
 
@@ -273,8 +278,10 @@  package body GNAT.Sockets.Thin is
    is
       use type C.size_t;
 
-      Res   : C.int;
-      Count : C.int := 0;
+      Res    : C.int;
+      Count  : C.int := 0;
+      Locked : Boolean := False;
+      --  Set to false when the lock is activated
 
       MH : Msghdr;
       for MH'Address use Msg;
@@ -283,26 +290,105 @@  package body GNAT.Sockets.Thin is
       for Iovec'Address use MH.Msg_Iov;
       pragma Import (Ada, Iovec);
 
+      Iov_Index     : Integer;
+      Current_Iovec : Vector_Element;
+
+      function To_Access is new Ada.Unchecked_Conversion
+                                  (System.Address, Stream_Element_Reference);
+      pragma Warnings (Off, Stream_Element_Reference);
+
+      Req : Request_Type (Name => N_Bytes_To_Read);
+
    begin
       --  Windows does not provide an implementation of recvmsg(). The spec for
       --  WSARecvMsg() is incompatible with the data types we define, and is
       --  not available in all versions of Windows. So, we use C_Recv instead.
 
-      for J in Iovec'Range loop
+      --  First, wait for some data to be available if socket is blocking
+
+      declare
+         Selector     : Selector_Type;
+         R_Socket_Set : Socket_Set_Type;
+         W_Socket_Set : Socket_Set_Type;
+         Status       : Selector_Status;
+         Req          : Request_Type (Name => Non_Blocking_IO);
+      begin
+         Control_Socket (Socket_Type (S), Req);
+
+         if not Req.Enabled then
+            --  We are in a blocking IO mode
+            Create_Selector (Selector);
+
+            Set (R_Socket_Set, Socket_Type (S));
+
+            Check_Selector (Selector, R_Socket_Set, W_Socket_Set, Status);
+
+            Close_Selector (Selector);
+         end if;
+      end;
+
+      GNAT.Task_Lock.Lock;
+      Locked := True;
+
+      --  Check how much data are available
+
+      Control_Socket (Socket_Type (S), Req);
+
+      --  Fill the vectors
+
+      Iov_Index := -1;
+      Current_Iovec := (Base => null, Length => 0);
+
+      loop
+         if Current_Iovec.Length = 0 then
+            Iov_Index := Iov_Index + 1;
+            exit when Iov_Index > Integer (Iovec'Last);
+            Current_Iovec := Iovec (SOSC.Msg_Iovlen_T (Iov_Index));
+         end if;
+
          Res :=
            C_Recv
             (S,
-             Iovec (J).Base.all'Address,
-             C.int (Iovec (J).Length),
+             Current_Iovec.Base.all'Address,
+             C.int (Current_Iovec.Length),
              Flags);
 
          if Res < 0 then
+            Task_Lock.Unlock;
             return System.CRTL.ssize_t (Res);
+
+         elsif Res = 0 then
+            exit;
+
          else
+            pragma Assert (Stream_Element_Count (Res) <= Current_Iovec.Length);
+
             Count := Count + Res;
+            Current_Iovec.Length :=
+              Current_Iovec.Length - Stream_Element_Count (Res);
+            Current_Iovec.Base :=
+              To_Access (Current_Iovec.Base.all'Address
+                + Storage_Offset (Res));
+
+            --  If we have read all the data that was initially available,
+            --  do not attempt to receive more, since this might block, or
+            --  merge data from successive datagrams in case of a datagram-
+            --  oriented socket.
+
+            exit when Natural (Count) >= Req.Size;
          end if;
       end loop;
+
+      Task_Lock.Unlock;
+
       return System.CRTL.ssize_t (Count);
+
+   exception
+      when others =>
+         if Locked then
+            Task_Lock.Unlock;
+         end if;
+         raise;
    end C_Recvmsg;
 
    --------------
@@ -428,7 +514,10 @@  package body GNAT.Sockets.Thin is
       --  not available in all versions of Windows. So, we'll use C_Sendto
       --  instead.
 
+      Task_Lock.Lock;
+
       for J in Iovec'Range loop
+
          Res :=
            C_Sendto
             (S,
@@ -439,13 +528,20 @@  package body GNAT.Sockets.Thin is
              Tolen => C.int (MH.Msg_Namelen));
 
          if Res < 0 then
+            Task_Lock.Unlock;
             return System.CRTL.ssize_t (Res);
          else
             Count := Count + Res;
          end if;
       end loop;
 
+      Task_Lock.Unlock;
+
       return System.CRTL.ssize_t (Count);
+   exception
+      when others =>
+         Task_Lock.Unlock;
+         raise;
    end C_Sendmsg;
 
    --------------