------------------------------------------------------------------------------
--                                                                          --
--                         GNAT COMPILER COMPONENTS                         --
--                                                                          --
--                         G N A T . S O C K E T S                          --
--                                                                          --
--                                 B o d y                                  --
--                                                                          --
--                     Copyright (C) 2001-2019, 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- --
-- ware  Foundation;  either version 3,  or (at your option) any later ver- --
-- sion.  GNAT is distributed in the hope that it will be useful, but WITH- --
-- OUT ANY WARRANTY;  without even the  implied warranty of MERCHANTABILITY --
-- or FITNESS FOR A PARTICULAR PURPOSE.                                     --
--                                                                          --
-- As a special exception under Section 7 of GPL version 3, you are granted --
-- additional permissions described in the GCC Runtime Library Exception,   --
-- version 3.1, as published by the Free Software Foundation.               --
--                                                                          --
-- You should have received a copy of the GNU General Public License and    --
-- a copy of the GCC Runtime Library Exception along with this program;     --
-- see the files COPYING3 and COPYING.RUNTIME respectively.  If not, see    --
-- <http://www.gnu.org/licenses/>.                                          --
--                                                                          --
-- GNAT was originally developed  by the GNAT team at  New York University. --
-- Extensive contributions were provided by Ada Core Technologies Inc.      --
--                                                                          --
------------------------------------------------------------------------------

with Ada.Streams;              use Ada.Streams;
with Ada.Exceptions;           use Ada.Exceptions;
with Ada.Containers.Generic_Array_Sort;
with Ada.Finalization;
with Ada.Unchecked_Conversion;

with GNAT.Sockets.Thin_Common; use GNAT.Sockets.Thin_Common;
with GNAT.Sockets.Thin;        use GNAT.Sockets.Thin;

with GNAT.Sockets.Linker_Options;
pragma Warnings (Off, GNAT.Sockets.Linker_Options);
--  Need to include pragma Linker_Options which is platform dependent

with System;               use System;
with System.Communication; use System.Communication;
with System.CRTL;          use System.CRTL;
with System.Task_Lock;

package body GNAT.Sockets is

   package C renames Interfaces.C;

   type IPV6_Mreq is record
      ipv6mr_multiaddr : In6_Addr;
      ipv6mr_interface : C.unsigned;
   end record with Convention => C;
   --  Record to Add/Drop_Membership for multicast in IPv6

   ENOERROR : constant := 0;

   Netdb_Buffer_Size : constant := SOSC.Need_Netdb_Buffer * 1024;
   Need_Netdb_Lock   : constant Boolean := SOSC.Need_Netdb_Lock /= 0;
   --  The network database functions gethostbyname, gethostbyaddr,
   --  getservbyname and getservbyport can either be guaranteed task safe by
   --  the operating system, or else return data through a user-provided buffer
   --  to ensure concurrent uses do not interfere.

   --  Correspondence tables

   Levels : constant array (Level_Type) of C.int :=
              (Socket_Level               => SOSC.SOL_SOCKET,
               IP_Protocol_For_IP_Level   => SOSC.IPPROTO_IP,
               IP_Protocol_For_IPv6_Level => SOSC.IPPROTO_IPV6,
               IP_Protocol_For_UDP_Level  => SOSC.IPPROTO_UDP,
               IP_Protocol_For_TCP_Level  => SOSC.IPPROTO_TCP,
               IP_Protocol_For_ICMP_Level => SOSC.IPPROTO_ICMP,
               IP_Protocol_For_IGMP_Level => SOSC.IPPROTO_IGMP,
               IP_Protocol_For_RAW_Level  => SOSC.IPPROTO_RAW);

   Modes : constant array (Mode_Type) of C.int :=
             (Socket_Stream   => SOSC.SOCK_STREAM,
              Socket_Datagram => SOSC.SOCK_DGRAM,
              Socket_Raw      => SOSC.SOCK_RAW);

   Shutmodes : constant array (Shutmode_Type) of C.int :=
                 (Shut_Read       => SOSC.SHUT_RD,
                  Shut_Write      => SOSC.SHUT_WR,
                  Shut_Read_Write => SOSC.SHUT_RDWR);

   Requests : constant array (Request_Name) of SOSC.IOCTL_Req_T :=
                (Non_Blocking_IO => SOSC.FIONBIO,
                 N_Bytes_To_Read => SOSC.FIONREAD);

   Options : constant array (Specific_Option_Name) of C.int :=
               (Keep_Alive          => SOSC.SO_KEEPALIVE,
                Reuse_Address       => SOSC.SO_REUSEADDR,
                Broadcast           => SOSC.SO_BROADCAST,
                Send_Buffer         => SOSC.SO_SNDBUF,
                Receive_Buffer      => SOSC.SO_RCVBUF,
                Linger              => SOSC.SO_LINGER,
                Error               => SOSC.SO_ERROR,
                No_Delay            => SOSC.TCP_NODELAY,
                Add_Membership_V4   => SOSC.IP_ADD_MEMBERSHIP,
                Drop_Membership_V4  => SOSC.IP_DROP_MEMBERSHIP,
                Multicast_If_V4     => SOSC.IP_MULTICAST_IF,
                Multicast_Loop_V4   => SOSC.IP_MULTICAST_LOOP,
                Receive_Packet_Info => SOSC.IP_PKTINFO,
                Multicast_TTL       => SOSC.IP_MULTICAST_TTL,
                Add_Membership_V6   => SOSC.IPV6_ADD_MEMBERSHIP,
                Drop_Membership_V6  => SOSC.IPV6_DROP_MEMBERSHIP,
                Multicast_If_V6     => SOSC.IPV6_MULTICAST_IF,
                Multicast_Loop_V6   => SOSC.IPV6_MULTICAST_LOOP,
                Multicast_Hops      => SOSC.IPV6_MULTICAST_HOPS,
                IPv6_Only           => SOSC.IPV6_V6ONLY,
                Send_Timeout        => SOSC.SO_SNDTIMEO,
                Receive_Timeout     => SOSC.SO_RCVTIMEO,
                Busy_Polling        => SOSC.SO_BUSY_POLL);
   --  ??? Note: for OpenSolaris, Receive_Packet_Info should be IP_RECVPKTINFO,
   --  but for Linux compatibility this constant is the same as IP_PKTINFO.

   Flags : constant array (0 .. 3) of C.int :=
             (0 => SOSC.MSG_OOB,     --  Process_Out_Of_Band_Data
              1 => SOSC.MSG_PEEK,    --  Peek_At_Incoming_Data
              2 => SOSC.MSG_WAITALL, --  Wait_For_A_Full_Reception
              3 => SOSC.MSG_EOR);    --  Send_End_Of_Record

   Socket_Error_Id : constant Exception_Id := Socket_Error'Identity;
   Host_Error_Id   : constant Exception_Id := Host_Error'Identity;

   type In_Addr_Union (Family : Family_Inet_4_6) is record
      case Family is
         when Family_Inet =>
            In4 : In_Addr;
         when Family_Inet6 =>
            In6 : In6_Addr;
      end case;
   end record with Unchecked_Union;

   -----------------------
   -- Local subprograms --
   -----------------------

   function Resolve_Error
     (Error_Value : Integer;
      From_Errno  : Boolean := True) return Error_Type;
   --  Associate an enumeration value (error_type) to an error value (errno).
   --  From_Errno prevents from mixing h_errno with errno.

   function To_Name   (N  : String) return Name_Type;
   function To_String (HN : Name_Type) return String;
   --  Conversion functions

   function To_Int (F : Request_Flag_Type) return C.int;
   --  Return the int value corresponding to the specified flags combination

   function Set_Forced_Flags (F : C.int) return C.int;
   --  Return F with the bits from SOSC.MSG_Forced_Flags forced set

   procedure Netdb_Lock;
   pragma Inline (Netdb_Lock);
   procedure Netdb_Unlock;
   pragma Inline (Netdb_Unlock);
   --  Lock/unlock operation used to protect netdb access for platforms that
   --  require such protection.

   function To_Host_Entry (E : Hostent_Access) return Host_Entry_Type;
   --  Conversion function

   function To_Service_Entry (E : Servent_Access) return Service_Entry_Type;
   --  Conversion function

   function Value (S : System.Address) return String;
   --  Same as Interfaces.C.Strings.Value but taking a System.Address

   function To_Timeval (Val : Timeval_Duration) return Timeval;
   --  Separate Val in seconds and microseconds

   function To_Duration (Val : Timeval) return Timeval_Duration;
   --  Reconstruct a Duration value from a Timeval record (seconds and
   --  microseconds).

   function Dedot (Value : String) return String
   is (if Value /= "" and then Value (Value'Last) = '.'
       then Value (Value'First .. Value'Last - 1)
       else Value);
   --  Removes dot at the end of error message

   procedure Raise_Socket_Error (Error : Integer);
   --  Raise Socket_Error with an exception message describing the error code
   --  from errno.

   procedure Raise_Host_Error (H_Error : Integer; Name : String);
   --  Raise Host_Error exception with message describing error code (note
   --  hstrerror seems to be obsolete) from h_errno. Name is the name
   --  or address that was being looked up.

   procedure Raise_GAI_Error (RC : C.int; Name : String);
   --  Raise Host_Error with exception message in case of errors in
   --  getaddrinfo and getnameinfo.

   function Is_Windows return Boolean with Inline;
   --  Returns True on Windows platform

   procedure Narrow (Item : in out Socket_Set_Type);
   --  Update Last as it may be greater than the real last socket

   procedure Check_For_Fd_Set (Fd : Socket_Type);
   pragma Inline (Check_For_Fd_Set);
   --  Raise Constraint_Error if Fd is less than 0 or greater than or equal to
   --  FD_SETSIZE, on platforms where fd_set is a bitmap.

   function Connect_Socket
     (Socket : Socket_Type;
      Server : Sock_Addr_Type) return C.int;
   pragma Inline (Connect_Socket);
   --  Underlying implementation for the Connect_Socket procedures

   --  Types needed for Datagram_Socket_Stream_Type

   type Datagram_Socket_Stream_Type is new Root_Stream_Type with record
      Socket : Socket_Type;
      To     : Sock_Addr_Type;
      From   : Sock_Addr_Type;
   end record;

   type Datagram_Socket_Stream_Access is
     access all Datagram_Socket_Stream_Type;

   procedure Read
     (Stream : in out Datagram_Socket_Stream_Type;
      Item   : out Ada.Streams.Stream_Element_Array;
      Last   : out Ada.Streams.Stream_Element_Offset);

   procedure Write
     (Stream : in out Datagram_Socket_Stream_Type;
      Item   : Ada.Streams.Stream_Element_Array);

   --  Types needed for Stream_Socket_Stream_Type

   type Stream_Socket_Stream_Type is new Root_Stream_Type with record
      Socket : Socket_Type;
   end record;

   type Stream_Socket_Stream_Access is
     access all Stream_Socket_Stream_Type;

   procedure Read
     (Stream : in out Stream_Socket_Stream_Type;
      Item   : out Ada.Streams.Stream_Element_Array;
      Last   : out Ada.Streams.Stream_Element_Offset);

   procedure Write
     (Stream : in out Stream_Socket_Stream_Type;
      Item   : Ada.Streams.Stream_Element_Array);

   procedure Wait_On_Socket
     (Socket   : Socket_Type;
      For_Read : Boolean;
      Timeout  : Selector_Duration;
      Selector : access Selector_Type := null;
      Status   : out Selector_Status);
   --  Common code for variants of socket operations supporting a timeout:
   --  block in Check_Selector on Socket for at most the indicated timeout.
   --  If For_Read is True, Socket is added to the read set for this call, else
   --  it is added to the write set. If no selector is provided, a local one is
   --  created for this call and destroyed prior to returning.

   type Sockets_Library_Controller is new Ada.Finalization.Limited_Controlled
     with null record;
   --  This type is used to generate automatic calls to Initialize and Finalize
   --  during the elaboration and finalization of this package. A single object
   --  of this type must exist at library level.

   function Err_Code_Image (E : Integer) return String;
   --  Return the value of E surrounded with brackets

   procedure Initialize (X : in out Sockets_Library_Controller);
   procedure Finalize   (X : in out Sockets_Library_Controller);

   procedure Normalize_Empty_Socket_Set (S : in out Socket_Set_Type);
   --  If S is the empty set (detected by Last = No_Socket), make sure its
   --  fd_set component is actually cleared. Note that the case where it is
   --  not can occur for an uninitialized Socket_Set_Type object.

   function Is_Open (S : Selector_Type) return Boolean;
   --  Return True for an "open" Selector_Type object, i.e. one for which
   --  Create_Selector has been called and Close_Selector has not been called,
   --  or the null selector.

   function Create_Address
     (Family : Family_Inet_4_6; Bytes : Inet_Addr_Bytes) return Inet_Addr_Type
     with Inline;
   --  Creates address from family and Inet_Addr_Bytes array.

   function Get_Bytes (Addr : Inet_Addr_Type) return Inet_Addr_Bytes
     with Inline;
   --  Extract bytes from address

   ---------
   -- "+" --
   ---------

   function "+" (L, R : Request_Flag_Type) return Request_Flag_Type is
   begin
      return L or R;
   end "+";

   --------------------
   -- Abort_Selector --
   --------------------

   procedure Abort_Selector (Selector : Selector_Type) is
      Res : C.int;

   begin
      if not Is_Open (Selector) then
         raise Program_Error with "closed selector";

      elsif Selector.Is_Null then
         raise Program_Error with "null selector";

      end if;

      --  Send one byte to unblock select system call

      Res := Signalling_Fds.Write (C.int (Selector.W_Sig_Socket));

      if Res = Failure then
         Raise_Socket_Error (Socket_Errno);
      end if;
   end Abort_Selector;

   -------------------
   -- Accept_Socket --
   -------------------

   procedure Accept_Socket
     (Server  : Socket_Type;
      Socket  : out Socket_Type;
      Address : out Sock_Addr_Type)
   is
      Res : C.int;
      Sin : aliased Sockaddr;
      Len : aliased C.int := Sin'Size / 8;

   begin
      Res := C_Accept (C.int (Server), Sin'Address, Len'Access);

      if Res = Failure then
         Raise_Socket_Error (Socket_Errno);
      end if;

      Socket := Socket_Type (Res);
      Address := Get_Address (Sin, Len);
   end Accept_Socket;

   -------------------
   -- Accept_Socket --
   -------------------

   procedure Accept_Socket
     (Server   : Socket_Type;
      Socket   : out Socket_Type;
      Address  : out Sock_Addr_Type;
      Timeout  : Selector_Duration;
      Selector : access Selector_Type := null;
      Status   : out Selector_Status)
   is
   begin
      if Selector /= null and then not Is_Open (Selector.all) then
         raise Program_Error with "closed selector";
      end if;

      --  Wait for socket to become available for reading

      Wait_On_Socket
        (Socket    => Server,
         For_Read  => True,
         Timeout   => Timeout,
         Selector  => Selector,
         Status    => Status);

      --  Accept connection if available

      if Status = Completed then
         Accept_Socket (Server, Socket, Address);
      else
         Socket := No_Socket;
      end if;
   end Accept_Socket;

   ---------------
   -- Addresses --
   ---------------

   function Addresses
     (E : Host_Entry_Type;
      N : Positive := 1) return Inet_Addr_Type
   is
   begin
      return E.Addresses (N);
   end Addresses;

   ----------------------
   -- Addresses_Length --
   ----------------------

   function Addresses_Length (E : Host_Entry_Type) return Natural is
   begin
      return E.Addresses_Length;
   end Addresses_Length;

   -------------
   -- Aliases --
   -------------

   function Aliases
     (E : Host_Entry_Type;
      N : Positive := 1) return String
   is
   begin
      return To_String (E.Aliases (N));
   end Aliases;

   -------------
   -- Aliases --
   -------------

   function Aliases
     (S : Service_Entry_Type;
      N : Positive := 1) return String
   is
   begin
      return To_String (S.Aliases (N));
   end Aliases;

   --------------------
   -- Aliases_Length --
   --------------------

   function Aliases_Length (E : Host_Entry_Type) return Natural is
   begin
      return E.Aliases_Length;
   end Aliases_Length;

   --------------------
   -- Aliases_Length --
   --------------------

   function Aliases_Length (S : Service_Entry_Type) return Natural is
   begin
      return S.Aliases_Length;
   end Aliases_Length;

   -----------------
   -- Bind_Socket --
   -----------------

   procedure Bind_Socket
     (Socket  : Socket_Type;
      Address : Sock_Addr_Type)
   is
      Res : C.int;
      Sin : aliased Sockaddr;
      Len : C.int;

   begin
      Set_Address (Sin'Unchecked_Access, Address, Len);

      Res := C_Bind (C.int (Socket), Sin'Address, Len);

      if Res = Failure then
         Raise_Socket_Error (Socket_Errno);
      end if;
   end Bind_Socket;

   ----------------------
   -- Check_For_Fd_Set --
   ----------------------

   procedure Check_For_Fd_Set (Fd : Socket_Type) is
   begin
      --  On Windows, fd_set is a FD_SETSIZE array of socket ids:
      --  no check required. Warnings suppressed because condition
      --  is known at compile time.

      if Is_Windows then

         return;

      --  On other platforms, fd_set is an FD_SETSIZE bitmap: check
      --  that Fd is within range (otherwise behavior is undefined).

      elsif Fd < 0 or else Fd >= SOSC.FD_SETSIZE then
         raise Constraint_Error
           with "invalid value for socket set: " & Image (Fd);
      end if;
   end Check_For_Fd_Set;

   --------------------
   -- Check_Selector --
   --------------------

   procedure Check_Selector
     (Selector     : Selector_Type;
      R_Socket_Set : in out Socket_Set_Type;
      W_Socket_Set : in out Socket_Set_Type;
      Status       : out Selector_Status;
      Timeout      : Selector_Duration := Forever)
   is
      E_Socket_Set : Socket_Set_Type;
   begin
      Check_Selector
        (Selector, R_Socket_Set, W_Socket_Set, E_Socket_Set, Status, Timeout);
   end Check_Selector;

   procedure Check_Selector
     (Selector     : Selector_Type;
      R_Socket_Set : in out Socket_Set_Type;
      W_Socket_Set : in out Socket_Set_Type;
      E_Socket_Set : in out Socket_Set_Type;
      Status       : out Selector_Status;
      Timeout      : Selector_Duration := Forever)
   is
      Res  : C.int;
      Last : C.int;
      RSig : Socket_Type := No_Socket;
      TVal : aliased Timeval;
      TPtr : Timeval_Access;

   begin
      if not Is_Open (Selector) then
         raise Program_Error with "closed selector";
      end if;

      Status := Completed;

      --  No timeout or Forever is indicated by a null timeval pointer

      if Timeout = Forever then
         TPtr := null;
      else
         TVal := To_Timeval (Timeout);
         TPtr := TVal'Unchecked_Access;
      end if;

      --  Add read signalling socket, if present

      if not Selector.Is_Null then
         RSig := Selector.R_Sig_Socket;
         Set (R_Socket_Set, RSig);
      end if;

      Last := C.int'Max (C.int'Max (C.int (R_Socket_Set.Last),
                                    C.int (W_Socket_Set.Last)),
                                    C.int (E_Socket_Set.Last));

      --  Zero out fd_set for empty Socket_Set_Type objects

      Normalize_Empty_Socket_Set (R_Socket_Set);
      Normalize_Empty_Socket_Set (W_Socket_Set);
      Normalize_Empty_Socket_Set (E_Socket_Set);

      Res :=
        C_Select
         (Last + 1,
          R_Socket_Set.Set'Access,
          W_Socket_Set.Set'Access,
          E_Socket_Set.Set'Access,
          TPtr);

      if Res = Failure then
         Raise_Socket_Error (Socket_Errno);
      end if;

      --  If Select was resumed because of read signalling socket, read this
      --  data and remove socket from set.

      if RSig /= No_Socket and then Is_Set (R_Socket_Set, RSig) then
         Clear (R_Socket_Set, RSig);

         Res := Signalling_Fds.Read (C.int (RSig));

         if Res = Failure then
            Raise_Socket_Error (Socket_Errno);
         end if;

         Status := Aborted;

      elsif Res = 0 then
         Status := Expired;
      end if;

      --  Update socket sets in regard to their new contents

      Narrow (R_Socket_Set);
      Narrow (W_Socket_Set);
      Narrow (E_Socket_Set);
   end Check_Selector;

   -----------
   -- Clear --
   -----------

   procedure Clear
     (Item   : in out Socket_Set_Type;
      Socket : Socket_Type)
   is
      Last : aliased C.int := C.int (Item.Last);

   begin
      Check_For_Fd_Set (Socket);

      if Item.Last /= No_Socket then
         Remove_Socket_From_Set (Item.Set'Access, C.int (Socket));
         Last_Socket_In_Set (Item.Set'Access, Last'Unchecked_Access);
         Item.Last := Socket_Type (Last);
      end if;
   end Clear;

   --------------------
   -- Close_Selector --
   --------------------

   procedure Close_Selector (Selector : in out Selector_Type) is
   begin
      --  Nothing to do if selector already in closed state

      if Selector.Is_Null or else not Is_Open (Selector) then
         return;
      end if;

      --  Close the signalling file descriptors used internally for the
      --  implementation of Abort_Selector.

      Signalling_Fds.Close (C.int (Selector.R_Sig_Socket));
      Signalling_Fds.Close (C.int (Selector.W_Sig_Socket));

      --  Reset R_Sig_Socket and W_Sig_Socket to No_Socket to ensure that any
      --  (erroneous) subsequent attempt to use this selector properly fails.

      Selector.R_Sig_Socket := No_Socket;
      Selector.W_Sig_Socket := No_Socket;
   end Close_Selector;

   ------------------
   -- Close_Socket --
   ------------------

   procedure Close_Socket (Socket : Socket_Type) is
      Res : C.int;

   begin
      Res := C_Close (C.int (Socket));

      if Res = Failure then
         Raise_Socket_Error (Socket_Errno);
      end if;
   end Close_Socket;

   --------------------
   -- Connect_Socket --
   --------------------

   function Connect_Socket
     (Socket : Socket_Type;
      Server : Sock_Addr_Type) return C.int
   is
      Sin : aliased Sockaddr;
      Len : C.int;
   begin
      Set_Address (Sin'Unchecked_Access, Server, Len);

      return C_Connect (C.int (Socket), Sin'Address, Len);
   end Connect_Socket;

   procedure Connect_Socket
     (Socket : Socket_Type;
      Server : Sock_Addr_Type)
   is
   begin
      if Connect_Socket (Socket, Server) = Failure then
         Raise_Socket_Error (Socket_Errno);
      end if;
   end Connect_Socket;

   procedure Connect_Socket
     (Socket   : Socket_Type;
      Server   : Sock_Addr_Type;
      Timeout  : Selector_Duration;
      Selector : access Selector_Type := null;
      Status   : out Selector_Status)
   is
      Req : Request_Type;
      --  Used to set Socket to non-blocking I/O

      Conn_Err : aliased Integer;
      --  Error status of the socket after completion of select(2)

      Res           : C.int;
      Conn_Err_Size : aliased C.int := Conn_Err'Size / 8;
      --  For getsockopt(2) call

   begin
      if Selector /= null and then not Is_Open (Selector.all) then
         raise Program_Error with "closed selector";
      end if;

      --  Set the socket to non-blocking I/O

      Req := (Name => Non_Blocking_IO, Enabled => True);
      Control_Socket (Socket, Request => Req);

      --  Start operation (non-blocking), will return Failure with errno set
      --  to EINPROGRESS.

      Res := Connect_Socket (Socket, Server);
      if Res = Failure then
         Conn_Err := Socket_Errno;
         if Conn_Err /= SOSC.EINPROGRESS then
            Raise_Socket_Error (Conn_Err);
         end if;
      end if;

      --  Wait for socket to become available for writing (unless the Timeout
      --  is zero, in which case we consider that it has already expired, and
      --  we do not need to wait at all).

      if Timeout = 0.0 then
         Status := Expired;

      else
         Wait_On_Socket
           (Socket   => Socket,
            For_Read => False,
            Timeout  => Timeout,
            Selector => Selector,
            Status   => Status);
      end if;

      --  Check error condition (the asynchronous connect may have terminated
      --  with an error, e.g. ECONNREFUSED) if select(2) completed.

      if Status = Completed then
         Res := C_Getsockopt
           (C.int (Socket), SOSC.SOL_SOCKET, SOSC.SO_ERROR,
            Conn_Err'Address, Conn_Err_Size'Access);

         if Res /= 0 then
            Conn_Err := Socket_Errno;
         end if;

      else
         Conn_Err := 0;
      end if;

      --  Reset the socket to blocking I/O

      Req := (Name => Non_Blocking_IO, Enabled => False);
      Control_Socket (Socket, Request => Req);

      --  Report error condition if any

      if Conn_Err /= 0 then
         Raise_Socket_Error (Conn_Err);
      end if;
   end Connect_Socket;

   --------------------
   -- Control_Socket --
   --------------------

   procedure Control_Socket
     (Socket  : Socket_Type;
      Request : in out Request_Type)
   is
      Arg : aliased C.int;
      Res : C.int;

   begin
      case Request.Name is
         when Non_Blocking_IO =>
            Arg := C.int (Boolean'Pos (Request.Enabled));

         when N_Bytes_To_Read =>
            null;
      end case;

      Res := Socket_Ioctl
               (C.int (Socket), Requests (Request.Name), Arg'Unchecked_Access);

      if Res = Failure then
         Raise_Socket_Error (Socket_Errno);
      end if;

      case Request.Name is
         when Non_Blocking_IO =>
            null;

         when N_Bytes_To_Read =>
            Request.Size := Natural (Arg);
      end case;
   end Control_Socket;

   ----------
   -- Copy --
   ----------

   procedure Copy
     (Source : Socket_Set_Type;
      Target : out Socket_Set_Type)
   is
   begin
      Target := Source;
   end Copy;

   ---------------------
   -- Create_Selector --
   ---------------------

   procedure Create_Selector (Selector : out Selector_Type) is
      Two_Fds : aliased Fd_Pair;
      Res     : C.int;

   begin
      if Is_Open (Selector) then
         --  Raise exception to prevent socket descriptor leak

         raise Program_Error with "selector already open";
      end if;

      --  We open two signalling file descriptors. One of them is used to send
      --  data to the other, which is included in a C_Select socket set. The
      --  communication is used to force a call to C_Select to complete, and
      --  the waiting task to resume its execution.

      Res := Signalling_Fds.Create (Two_Fds'Access);

      if Res = Failure then
         Raise_Socket_Error (Socket_Errno);
      end if;

      Selector.R_Sig_Socket := Socket_Type (Two_Fds (Read_End));
      Selector.W_Sig_Socket := Socket_Type (Two_Fds (Write_End));
   end Create_Selector;

   -------------------
   -- Create_Socket --
   -------------------

   procedure Create_Socket
     (Socket : out Socket_Type;
      Family : Family_Type := Family_Inet;
      Mode   : Mode_Type   := Socket_Stream;
      Level  : Level_Type  := IP_Protocol_For_IP_Level)
   is
      Res : C.int;

   begin
      Res := C_Socket (Families (Family), Modes (Mode), Levels (Level));

      if Res = Failure then
         Raise_Socket_Error (Socket_Errno);
      end if;

      Socket := Socket_Type (Res);
   end Create_Socket;

   ------------------------
   -- Create_Socket_Pair --
   ------------------------

   procedure Create_Socket_Pair
     (Left   : out Socket_Type;
      Right  : out Socket_Type;
      Family : Family_Type := Family_Unspec;
      Mode   : Mode_Type   := Socket_Stream;
      Level  : Level_Type  := IP_Protocol_For_IP_Level)
   is
      Res  : C.int;
      Pair : aliased Thin_Common.Fd_Pair;

   begin
      Res := C_Socketpair
        ((if Family = Family_Unspec then Default_Socket_Pair_Family
          else Families (Family)),
         Modes (Mode), Levels (Level), Pair'Access);

      if Res = Failure then
         Raise_Socket_Error (Socket_Errno);
      end if;

      Left  := Socket_Type (Pair (Pair'First));
      Right := Socket_Type (Pair (Pair'Last));
   end Create_Socket_Pair;

   -----------
   -- Empty --
   -----------

   procedure Empty (Item : out Socket_Set_Type) is
   begin
      Reset_Socket_Set (Item.Set'Access);
      Item.Last := No_Socket;
   end Empty;

   --------------------
   -- Err_Code_Image --
   --------------------

   function Err_Code_Image (E : Integer) return String is
      Msg : String := E'Img & "] ";
   begin
      Msg (Msg'First) := '[';
      return Msg;
   end Err_Code_Image;

   --------------
   -- Finalize --
   --------------

   procedure Finalize (X : in out Sockets_Library_Controller) is
      pragma Unreferenced (X);

   begin
      --  Finalization operation for the GNAT.Sockets package

      Thin.Finalize;
   end Finalize;

   --------------
   -- Finalize --
   --------------

   procedure Finalize is
   begin
      --  This is a dummy placeholder for an obsolete API.
      --  The real finalization actions are in Initialize primitive operation
      --  of Sockets_Library_Controller.

      null;
   end Finalize;

   ---------
   -- Get --
   ---------

   procedure Get
     (Item   : in out Socket_Set_Type;
      Socket : out Socket_Type)
   is
      S : aliased C.int;
      L : aliased C.int := C.int (Item.Last);

   begin
      if Item.Last /= No_Socket then
         Get_Socket_From_Set
           (Item.Set'Access, Last => L'Access, Socket => S'Access);
         Item.Last := Socket_Type (L);
         Socket    := Socket_Type (S);
      else
         Socket := No_Socket;
      end if;
   end Get;

   -----------------
   -- Get_Address --
   -----------------

   function Get_Address
     (Stream : not null Stream_Access) return Sock_Addr_Type
   is
   begin
      if Stream.all in Datagram_Socket_Stream_Type then
         return Datagram_Socket_Stream_Type (Stream.all).From;
      else
         return Get_Peer_Name (Stream_Socket_Stream_Type (Stream.all).Socket);
      end if;
   end Get_Address;

   ---------------------
   -- Raise_GAI_Error --
   ---------------------

   procedure Raise_GAI_Error (RC : C.int; Name : String) is
   begin
      if RC = SOSC.EAI_SYSTEM then
         declare
            Errcode : constant Integer := Socket_Errno;
         begin
            raise Host_Error with Err_Code_Image (Errcode)
              & Dedot (Socket_Error_Message (Errcode)) & ": " & Name;
         end;
      else
         raise Host_Error with Err_Code_Image (Integer (RC))
           & Dedot (CS.Value (C_GAI_Strerror (RC))) & ": " & Name;
      end if;
   end Raise_GAI_Error;

   ----------------------
   -- Get_Address_Info --
   ----------------------

   function Get_Address_Info
     (Host         : String;
      Service      : String;
      Family       : Family_Type := Family_Unspec;
      Mode         : Mode_Type   := Socket_Stream;
      Level        : Level_Type  := IP_Protocol_For_IP_Level;
      Numeric_Host : Boolean     := False;
      Passive      : Boolean     := False;
      Unknown      : access procedure
        (Family, Mode, Level, Length : Integer) := null)
      return Address_Info_Array
   is
      A : aliased Addrinfo_Access;
      N : aliased C.char_array := C.To_C (Host);
      S : aliased C.char_array := C.To_C (if Service = "" then "0"
                                          else Service);
      Hints : aliased constant Addrinfo :=
        (ai_family   => Families (Family),
         ai_socktype => Modes (Mode),
         ai_protocol => Levels (Level),
         ai_flags    => (if Numeric_Host then SOSC.AI_NUMERICHOST else 0) +
                        (if Passive then SOSC.AI_PASSIVE else 0),
         ai_addrlen  => 0,
         others      => <>);

      R     : C.int;
      Iter  : Addrinfo_Access;
      Found : Boolean;

      function To_Array return Address_Info_Array;
      --  Convert taken from OS addrinfo list A into Address_Info_Array

      --------------
      -- To_Array --
      --------------

      function To_Array return Address_Info_Array is
         Result : Address_Info_Array (1 .. 8);

         procedure Unsupported;
         --  Calls Unknown callback if defiend

         -----------------
         -- Unsupported --
         -----------------

         procedure Unsupported is
         begin
            if Unknown /= null then
               Unknown
                 (Integer (Iter.ai_family),
                  Integer (Iter.ai_socktype),
                  Integer (Iter.ai_protocol),
                  Integer (Iter.ai_addrlen));
            end if;
         end Unsupported;

      --  Start of processing for To_Array

      begin
         for J in Result'Range loop
            Look_For_Supported : loop
               if Iter = null then
                  pragma Warnings
                    (Off, "may be referenced before it has a value");

                  return Result (1 .. J - 1);

                  pragma Warnings
                    (On, "may be referenced before it has a value");
               end if;

               Result (J).Addr :=
                 Get_Address (Iter.ai_addr.all, C.int (Iter.ai_addrlen));

               if Result (J).Addr.Family = Family_Unspec then
                  Unsupported;
               else
                  for M in Modes'Range loop
                     Found := False;
                     if Modes (M) = Iter.ai_socktype then
                        Result (J).Mode := M;
                        Found := True;
                        exit;
                     end if;
                  end loop;

                  if Found then
                     for L in Levels'Range loop
                        if Levels (L) = Iter.ai_protocol then
                           Result (J).Level := L;
                           exit;
                        end if;
                     end loop;

                     exit Look_For_Supported;
                  else
                     Unsupported;
                  end if;
               end if;

               Iter := Iter.ai_next;
            end loop Look_For_Supported;

            Iter := Iter.ai_next;
         end loop;

         return Result & To_Array;
      end To_Array;

   --  Start of processing for Get_Address_Info

   begin
      R := C_Getaddrinfo
        (Node    => (if Host = "" then null else N'Unchecked_Access),
         Service => S'Unchecked_Access,
         Hints   => Hints'Unchecked_Access,
         Res     => A'Access);

      if R /= 0 then
         Raise_GAI_Error
           (R, Host & (if Service = "" then "" else ':' & Service));
      end if;

      Iter := A;

      return Result : constant Address_Info_Array := To_Array do
         C_Freeaddrinfo (A);
      end return;
   end Get_Address_Info;

   ----------
   -- Sort --
   ----------

   procedure Sort
     (Addr_Info : in out Address_Info_Array;
      Compare   : access function (Left, Right : Address_Info) return Boolean)
   is
      function Comp (Left, Right : Address_Info) return Boolean is
         (Compare (Left, Right));
      procedure Sorter is new Ada.Containers.Generic_Array_Sort
        (Positive, Address_Info, Address_Info_Array, Comp);
   begin
      Sorter (Addr_Info);
   end Sort;

   ------------------------
   -- IPv6_TCP_Preferred --
   ------------------------

   function IPv6_TCP_Preferred (Left, Right : Address_Info) return Boolean is
   begin
      pragma Assert (Family_Inet < Family_Inet6);
      --  To be sure that Family_Type enumeration has appropriate elements
      --  order

      if Left.Addr.Family /= Right.Addr.Family then
         return Left.Addr.Family > Right.Addr.Family;
      end if;

      pragma Assert (Socket_Stream < Socket_Datagram);
      --  To be sure that Mode_Type enumeration has appropriate elements order

      return Left.Mode < Right.Mode;
   end IPv6_TCP_Preferred;

   -------------------
   -- Get_Name_Info --
   -------------------

   function Get_Name_Info
     (Addr         : Sock_Addr_Type;
      Numeric_Host : Boolean := False;
      Numeric_Serv : Boolean := False) return Host_Service
   is
      SA  : aliased Sockaddr;
      H   : aliased C.char_array := (1 .. SOSC.NI_MAXHOST => C.nul);
      S   : aliased C.char_array := (1 .. SOSC.NI_MAXSERV => C.nul);
      RC  : C.int;
      Len : C.int;
   begin
      Set_Address (SA'Unchecked_Access, Addr, Len);

      RC := C_Getnameinfo
        (SA'Unchecked_Access, socklen_t (Len),
         H'Unchecked_Access, H'Length,
         S'Unchecked_Access, S'Length,
         (if Numeric_Host then SOSC.NI_NUMERICHOST else 0) +
             (if Numeric_Serv then SOSC.NI_NUMERICSERV else 0));

      if RC /= 0 then
         Raise_GAI_Error (RC, Image (Addr));
      end if;

      declare
         HR : constant String := C.To_Ada (H);
         SR : constant String := C.To_Ada (S);
      begin
         return (HR'Length, SR'Length, HR, SR);
      end;
   end Get_Name_Info;

   -------------------------
   -- Get_Host_By_Address --
   -------------------------

   function Get_Host_By_Address
     (Address : Inet_Addr_Type;
      Family  : Family_Type := Family_Inet) return Host_Entry_Type
   is
      pragma Unreferenced (Family);

      HA     : aliased In_Addr_Union (Address.Family);
      Buflen : constant C.int := Netdb_Buffer_Size;
      Buf    : aliased C.char_array (1 .. Netdb_Buffer_Size);
      Res    : aliased Hostent;
      Err    : aliased C.int;

   begin
      case Address.Family is
         when Family_Inet =>
            HA.In4 := To_In_Addr (Address);
         when Family_Inet6 =>
            HA.In6 := To_In6_Addr (Address);
      end case;

      Netdb_Lock;

      if C_Gethostbyaddr
        (HA'Address,
         (case Address.Family is
             when Family_Inet => HA.In4'Size,
             when Family_Inet6 => HA.In6'Size) / 8,
         Families (Address.Family),
         Res'Access, Buf'Address, Buflen, Err'Access) /= 0
      then
         Netdb_Unlock;
         Raise_Host_Error (Integer (Err), Image (Address));
      end if;

      begin
         return H : constant Host_Entry_Type :=
                      To_Host_Entry (Res'Unchecked_Access)
         do
            Netdb_Unlock;
         end return;
      exception
         when others =>
            Netdb_Unlock;
            raise;
      end;
   end Get_Host_By_Address;

   ----------------------
   -- Get_Host_By_Name --
   ----------------------

   function Get_Host_By_Name (Name : String) return Host_Entry_Type is
   begin
      --  If the given name actually is the string representation of
      --  an IP address, use Get_Host_By_Address instead.

      if Is_IPv4_Address (Name) or else Is_IPv6_Address (Name) then
         return Get_Host_By_Address (Inet_Addr (Name));
      end if;

      declare
         HN     : constant C.char_array := C.To_C (Name);
         Buflen : constant C.int := Netdb_Buffer_Size;
         Buf    : aliased C.char_array (1 .. Netdb_Buffer_Size);
         Res    : aliased Hostent;
         Err    : aliased C.int;

      begin
         Netdb_Lock;

         if C_Gethostbyname
           (HN, Res'Access, Buf'Address, Buflen, Err'Access) /= 0
         then
            Netdb_Unlock;
            Raise_Host_Error (Integer (Err), Name);
         end if;

         return H : constant Host_Entry_Type :=
                      To_Host_Entry (Res'Unchecked_Access)
         do
            Netdb_Unlock;
         end return;
      end;
   end Get_Host_By_Name;

   -------------------
   -- Get_Peer_Name --
   -------------------

   function Get_Peer_Name (Socket : Socket_Type) return Sock_Addr_Type is
      Sin : aliased Sockaddr;
      Len : aliased C.int := Sin'Size / 8;
   begin
      if C_Getpeername (C.int (Socket), Sin'Address, Len'Access) = Failure then
         Raise_Socket_Error (Socket_Errno);
      end if;

      return Get_Address (Sin, Len);
   end Get_Peer_Name;

   -------------------------
   -- Get_Service_By_Name --
   -------------------------

   function Get_Service_By_Name
     (Name     : String;
      Protocol : String) return Service_Entry_Type
   is
      SN     : constant C.char_array := C.To_C (Name);
      SP     : constant C.char_array := C.To_C (Protocol);
      Buflen : constant C.int := Netdb_Buffer_Size;
      Buf    : aliased C.char_array (1 .. Netdb_Buffer_Size);
      Res    : aliased Servent;

   begin
      Netdb_Lock;

      if C_Getservbyname (SN, SP, Res'Access, Buf'Address, Buflen) /= 0 then
         Netdb_Unlock;
         raise Service_Error with "Service not found";
      end if;

      --  Translate from the C format to the API format

      return S : constant Service_Entry_Type :=
                   To_Service_Entry (Res'Unchecked_Access)
      do
         Netdb_Unlock;
      end return;
   end Get_Service_By_Name;

   -------------------------
   -- Get_Service_By_Port --
   -------------------------

   function Get_Service_By_Port
     (Port     : Port_Type;
      Protocol : String) return Service_Entry_Type
   is
      SP     : constant C.char_array := C.To_C (Protocol);
      Buflen : constant C.int := Netdb_Buffer_Size;
      Buf    : aliased C.char_array (1 .. Netdb_Buffer_Size);
      Res    : aliased Servent;

   begin
      Netdb_Lock;

      if C_Getservbyport
        (C.int (Short_To_Network (C.unsigned_short (Port))), SP,
         Res'Access, Buf'Address, Buflen) /= 0
      then
         Netdb_Unlock;
         raise Service_Error with "Service not found";
      end if;

      --  Translate from the C format to the API format

      return S : constant Service_Entry_Type :=
                   To_Service_Entry (Res'Unchecked_Access)
      do
         Netdb_Unlock;
      end return;
   end Get_Service_By_Port;

   ---------------------
   -- Get_Socket_Name --
   ---------------------

   function Get_Socket_Name
     (Socket : Socket_Type) return Sock_Addr_Type
   is
      Sin : aliased Sockaddr;
      Len : aliased C.int := Sin'Size / 8;
      Res : C.int;
   begin
      Res := C_Getsockname (C.int (Socket), Sin'Address, Len'Access);

      if Res = Failure then
         return No_Sock_Addr;
      end if;

      return Get_Address (Sin, Len);
   end Get_Socket_Name;

   -----------------------
   -- Get_Socket_Option --
   -----------------------

   function Get_Socket_Option
     (Socket  : Socket_Type;
      Level   : Level_Type;
      Name    : Option_Name;
      Optname : Interfaces.C.int := -1) return Option_Type
   is
      use type C.unsigned;
      use type C.unsigned_char;

      V8  : aliased Two_Ints;
      V4  : aliased C.int;
      U4  : aliased C.unsigned;
      V1  : aliased C.unsigned_char;
      VT  : aliased Timeval;
      Len : aliased C.int;
      Add : System.Address;
      Res : C.int;
      Opt : Option_Type (Name);
      Onm : Interfaces.C.int;

   begin
      if Name in Specific_Option_Name then
         Onm := Options (Name);

      elsif Optname = -1 then
         raise Socket_Error with "optname must be specified";

      else
         Onm := Optname;
      end if;

      case Name is
         when Multicast_TTL
            | Receive_Packet_Info
         =>
            Len := V1'Size / 8;
            Add := V1'Address;

         when Broadcast
            | Busy_Polling
            | Error
            | Generic_Option
            | Keep_Alive
            | Multicast_If_V4
            | Multicast_If_V6
            | Multicast_Loop_V4
            | Multicast_Loop_V6
            | Multicast_Hops
            | No_Delay
            | Receive_Buffer
            | Reuse_Address
            | Send_Buffer
            | IPv6_Only
         =>
            Len := V4'Size / 8;
            Add := V4'Address;

         when Receive_Timeout
            | Send_Timeout
         =>
            --  The standard argument for SO_RCVTIMEO and SO_SNDTIMEO is a
            --  struct timeval, but on Windows it is a milliseconds count in
            --  a DWORD.

            if Is_Windows then
               Len := U4'Size / 8;
               Add := U4'Address;
            else
               Len := VT'Size / 8;
               Add := VT'Address;
            end if;

         when Add_Membership_V4
            | Add_Membership_V6
            | Drop_Membership_V4
            | Drop_Membership_V6
         =>
            raise Socket_Error with
              "Add/Drop membership valid only for Set_Socket_Option";

         when Linger
         =>
            Len := V8'Size / 8;
            Add := V8'Address;
      end case;

      Res :=
        C_Getsockopt
          (C.int (Socket),
           Levels (Level),
           Onm,
           Add, Len'Access);

      if Res = Failure then
         Raise_Socket_Error (Socket_Errno);
      end if;

      case Name is
         when Generic_Option =>
            Opt.Optname := Onm;
            Opt.Optval  := V4;

         when Broadcast
            | Keep_Alive
            | No_Delay
            | Reuse_Address
            | Multicast_Loop_V4
            | Multicast_Loop_V6
            | IPv6_Only
         =>
            Opt.Enabled := (V4 /= 0);

         when Busy_Polling =>
            Opt.Microseconds := Natural (V4);

         when Linger =>
            Opt.Enabled := (V8 (V8'First) /= 0);
            Opt.Seconds := Natural (V8 (V8'Last));

         when Receive_Buffer
            | Send_Buffer
         =>
            Opt.Size := Natural (V4);

         when Error =>
            Opt.Error := Resolve_Error (Integer (V4));

         when Add_Membership_V4
            | Add_Membership_V6
            | Drop_Membership_V4
            | Drop_Membership_V6
         =>
            --  No way to be here. Exception raised in the first case Name
            --  expression.
            null;

         when Multicast_If_V4 =>
            To_Inet_Addr (To_In_Addr (V4), Opt.Outgoing_If);

         when Multicast_If_V6 =>
            Opt.Outgoing_If_Index := Natural (V4);

         when Multicast_TTL =>
            Opt.Time_To_Live := Integer (V1);

         when Multicast_Hops =>
            Opt.Hop_Limit := Integer (V4);

         when Receive_Packet_Info
         =>
            Opt.Enabled := (V1 /= 0);

         when Receive_Timeout
            | Send_Timeout
         =>
            if Is_Windows then

               --  Timeout is in milliseconds, actual value is 500 ms +
               --  returned value (unless it is 0).

               if U4 = 0 then
                  Opt.Timeout := 0.0;
               else
                  Opt.Timeout :=  Duration (U4) / 1000 + 0.500;
               end if;

            else
               Opt.Timeout := To_Duration (VT);
            end if;
      end case;

      return Opt;
   end Get_Socket_Option;

   ---------------
   -- Host_Name --
   ---------------

   function Host_Name return String is
      Name : aliased C.char_array (1 .. 64);
      Res  : C.int;

   begin
      Res := C_Gethostname (Name'Address, Name'Length);

      if Res = Failure then
         Raise_Socket_Error (Socket_Errno);
      end if;

      return C.To_Ada (Name);
   end Host_Name;

   -----------
   -- Image --
   -----------

   function Image (Value : Inet_Addr_Type) return String is
      use type CS.char_array_access;
      Size : constant socklen_t :=
        (case Value.Family is
            when Family_Inet   => 4 * Value.Sin_V4'Length,
            when Family_Inet6  => 6 * 5 + 4 * 4);
            --  1234:1234:1234:1234:1234:1234:123.123.123.123
      Dst : aliased C.char_array := (1 .. C.size_t (Size) => C.nul);
      Ia  : aliased In_Addr_Union (Value.Family);
   begin
      case Value.Family is
         when Family_Inet6 =>
            Ia.In6 := To_In6_Addr (Value);
         when Family_Inet =>
            Ia.In4 := To_In_Addr (Value);
      end case;

      if Inet_Ntop
        (Families (Value.Family), Ia'Address,
         Dst'Unchecked_Access, Size) = null
      then
         Raise_Socket_Error (Socket_Errno);
      end if;

      return C.To_Ada (Dst);
   end Image;

   -----------
   -- Image --
   -----------

   function Image (Value : Sock_Addr_Type) return String is
      function Ipv6_Brackets (S : String) return String is
        (if Value.Family = Family_Inet6 then "[" & S & "]" else S);
   begin
      case Value.Family is
         when Family_Unix =>
            if ASU.Length (Value.Name) > 0
              and then ASU.Element (Value.Name, 1) = ASCII.NUL
            then
               return '@' & ASU.Slice (Value.Name, 2, ASU.Length (Value.Name));
            else
               return ASU.To_String (Value.Name);
            end if;

         when Family_Inet_4_6 =>
            declare
               Port : constant String := Value.Port'Img;
            begin
               return Ipv6_Brackets (Image (Value.Addr)) & ':'
                 & Port (2 .. Port'Last);
            end;

         when Family_Unspec =>
            return "";
      end case;
   end Image;

   -----------
   -- Image --
   -----------

   function Image (Socket : Socket_Type) return String is
   begin
      return Socket'Img;
   end Image;

   -----------
   -- Image --
   -----------

   function Image (Item : Socket_Set_Type) return String is
      Socket_Set : Socket_Set_Type := Item;

   begin
      declare
         Last_Img : constant String := Socket_Set.Last'Img;
         Buffer   : String
                      (1 .. (Integer (Socket_Set.Last) + 1) * Last_Img'Length);
         Index    : Positive := 1;
         Socket   : Socket_Type;

      begin
         while not Is_Empty (Socket_Set) loop
            Get (Socket_Set, Socket);

            declare
               Socket_Img : constant String := Socket'Img;
            begin
               Buffer (Index .. Index + Socket_Img'Length - 1) := Socket_Img;
               Index := Index + Socket_Img'Length;
            end;
         end loop;

         return "[" & Last_Img & "]" & Buffer (1 .. Index - 1);
      end;
   end Image;

   ---------------
   -- Inet_Addr --
   ---------------

   function Inet_Addr (Image : String) return Inet_Addr_Type is
      use Interfaces.C;

      Img    : aliased char_array := To_C (Image);
      Res    : C.int;
      Result : Inet_Addr_Type;
      IPv6   : constant Boolean := Is_IPv6_Address (Image);
      Ia     : aliased In_Addr_Union
                 (if IPv6 then Family_Inet6 else Family_Inet);
   begin
      --  Special case for an empty Image as on some platforms (e.g. Windows)
      --  calling Inet_Addr("") will not return an error.

      if Image = "" then
         Raise_Socket_Error (SOSC.EINVAL);
      end if;

      Res := Inet_Pton
        ((if IPv6 then SOSC.AF_INET6 else SOSC.AF_INET), Img'Address,
         Ia'Address);

      if Res < 0 then
         Raise_Socket_Error (Socket_Errno);

      elsif Res = 0 then
         Raise_Socket_Error (SOSC.EINVAL);
      end if;

      if IPv6 then
         To_Inet_Addr (Ia.In6, Result);
      else
         To_Inet_Addr (Ia.In4, Result);
      end if;

      return Result;
   end Inet_Addr;

   ----------------
   -- Initialize --
   ----------------

   procedure Initialize (X : in out Sockets_Library_Controller) is
      pragma Unreferenced (X);

   begin
      Thin.Initialize;
   end Initialize;

   ----------------
   -- Initialize --
   ----------------

   procedure Initialize (Process_Blocking_IO : Boolean) is
      Expected : constant Boolean := not SOSC.Thread_Blocking_IO;

   begin
      if Process_Blocking_IO /= Expected then
         raise Socket_Error with
           "incorrect Process_Blocking_IO setting, expected " & Expected'Img;
      end if;

      --  This is a dummy placeholder for an obsolete API

      --  Real initialization actions are in Initialize primitive operation
      --  of Sockets_Library_Controller.

      null;
   end Initialize;

   ----------------
   -- Initialize --
   ----------------

   procedure Initialize is
   begin
      --  This is a dummy placeholder for an obsolete API

      --  Real initialization actions are in Initialize primitive operation
      --  of Sockets_Library_Controller.

      null;
   end Initialize;

   ----------------
   -- Is_Windows --
   ----------------

   function Is_Windows return Boolean is
      use SOSC;
   begin
      return Target_OS = Windows;
   end Is_Windows;

   --------------
   -- Is_Empty --
   --------------

   function Is_Empty (Item : Socket_Set_Type) return Boolean is
   begin
      return Item.Last = No_Socket;
   end Is_Empty;

   ---------------------
   -- Is_IPv6_Address --
   ---------------------

   function Is_IPv6_Address (Name : String) return Boolean is
      Prev_Colon   : Natural := 0;
      Double_Colon : Boolean := False;
      Colons       : Natural := 0;
   begin
      for J in Name'Range loop
         if Name (J) = ':' then
            Colons := Colons + 1;

            if Prev_Colon > 0 and then J = Prev_Colon + 1 then
               if Double_Colon then
                  --  Only one double colon allowed
                  return False;
               end if;

               Double_Colon := True;

            elsif J = Name'Last then
               --  Single colon at the end is not allowed
               return False;
            end if;

            Prev_Colon := J;

         elsif Prev_Colon = Name'First then
            --  Single colon at start is not allowed
            return False;

         elsif Name (J) = '.' then
            return Prev_Colon > 0
              and then Is_IPv4_Address (Name (Prev_Colon + 1 .. Name'Last));

         elsif Name (J) not in '0' .. '9' | 'A' .. 'F' | 'a' .. 'f' then
            return False;

         end if;
      end loop;

      return Colons in 2 .. 8;
   end Is_IPv6_Address;

   ---------------------
   -- Is_IPv4_Address --
   ---------------------

   function Is_IPv4_Address (Name : String) return Boolean is
      Dots : Natural := 0;

   begin
      --  Perform a cursory check for a dotted quad: we must have 1 to 3 dots,
      --  and there must be at least one digit around each.

      for J in Name'Range loop
         if Name (J) = '.' then

            --  Check that the dot is not in first or last position, and that
            --  it is followed by a digit. Note that we already know that it is
            --  preceded by a digit, or we would have returned earlier on.

            if J in Name'First + 1 .. Name'Last - 1
              and then Name (J + 1) in '0' .. '9'
            then
               Dots := Dots + 1;

            --  Definitely not a proper dotted quad

            else
               return False;
            end if;

         elsif Name (J) not in '0' .. '9' then
            return False;
         end if;
      end loop;

      return Dots in 1 .. 3;
   end Is_IPv4_Address;

   -------------
   -- Is_Open --
   -------------

   function Is_Open (S : Selector_Type) return Boolean is
   begin
      if S.Is_Null then
         return True;

      else
         --  Either both controlling socket descriptors are valid (case of an
         --  open selector) or neither (case of a closed selector).

         pragma Assert ((S.R_Sig_Socket /= No_Socket)
                          =
                        (S.W_Sig_Socket /= No_Socket));

         return S.R_Sig_Socket /= No_Socket;
      end if;
   end Is_Open;

   ------------
   -- Is_Set --
   ------------

   function Is_Set
     (Item   : Socket_Set_Type;
      Socket : Socket_Type) return Boolean
   is
   begin
      Check_For_Fd_Set (Socket);

      return Item.Last /= No_Socket
        and then Socket <= Item.Last
        and then Is_Socket_In_Set (Item.Set'Access, C.int (Socket)) /= 0;
   end Is_Set;

   -------------------
   -- Listen_Socket --
   -------------------

   procedure Listen_Socket
     (Socket : Socket_Type;
      Length : Natural := 15)
   is
      Res : constant C.int := C_Listen (C.int (Socket), C.int (Length));
   begin
      if Res = Failure then
         Raise_Socket_Error (Socket_Errno);
      end if;
   end Listen_Socket;

   ------------
   -- Narrow --
   ------------

   procedure Narrow (Item : in out Socket_Set_Type) is
      Last : aliased C.int := C.int (Item.Last);
   begin
      if Item.Last /= No_Socket then
         Last_Socket_In_Set (Item.Set'Access, Last'Unchecked_Access);
         Item.Last := Socket_Type (Last);
      end if;
   end Narrow;

   ----------------
   -- Netdb_Lock --
   ----------------

   procedure Netdb_Lock is
   begin
      if Need_Netdb_Lock then
         System.Task_Lock.Lock;
      end if;
   end Netdb_Lock;

   ------------------
   -- Netdb_Unlock --
   ------------------

   procedure Netdb_Unlock is
   begin
      if Need_Netdb_Lock then
         System.Task_Lock.Unlock;
      end if;
   end Netdb_Unlock;

   ----------------------------
   -- Network_Socket_Address --
   ----------------------------

   function Network_Socket_Address
     (Addr : Inet_Addr_Type; Port : Port_Type) return Sock_Addr_Type is
   begin
      return Result : Sock_Addr_Type (Addr.Family) do
         Result.Addr := Addr;
         Result.Port := Port;
      end return;
   end Network_Socket_Address;

   --------------------------------
   -- Normalize_Empty_Socket_Set --
   --------------------------------

   procedure Normalize_Empty_Socket_Set (S : in out Socket_Set_Type) is
   begin
      if S.Last = No_Socket then
         Reset_Socket_Set (S.Set'Access);
      end if;
   end Normalize_Empty_Socket_Set;

   -------------------
   -- Official_Name --
   -------------------

   function Official_Name (E : Host_Entry_Type) return String is
   begin
      return To_String (E.Official);
   end Official_Name;

   -------------------
   -- Official_Name --
   -------------------

   function Official_Name (S : Service_Entry_Type) return String is
   begin
      return To_String (S.Official);
   end Official_Name;

   --------------------
   -- Wait_On_Socket --
   --------------------

   procedure Wait_On_Socket
     (Socket   : Socket_Type;
      For_Read : Boolean;
      Timeout  : Selector_Duration;
      Selector : access Selector_Type := null;
      Status   : out Selector_Status)
   is
      type Local_Selector_Access is access Selector_Type;
      for Local_Selector_Access'Storage_Size use Selector_Type'Size;

      S : Selector_Access;
      --  Selector to use for waiting

      R_Fd_Set : Socket_Set_Type;
      W_Fd_Set : Socket_Set_Type;

   begin
      --  Create selector if not provided by the user

      if Selector = null then
         declare
            Local_S : constant Local_Selector_Access := new Selector_Type;
         begin
            S := Local_S.all'Unchecked_Access;
            Create_Selector (S.all);
         end;

      else
         S := Selector.all'Access;
      end if;

      if For_Read then
         Set (R_Fd_Set, Socket);
      else
         Set (W_Fd_Set, Socket);
      end if;

      Check_Selector (S.all, R_Fd_Set, W_Fd_Set, Status, Timeout);

      if Selector = null then
         Close_Selector (S.all);
      end if;
   end Wait_On_Socket;

   -----------------
   -- Port_Number --
   -----------------

   function Port_Number (S : Service_Entry_Type) return Port_Type is
   begin
      return S.Port;
   end Port_Number;

   -------------------
   -- Protocol_Name --
   -------------------

   function Protocol_Name (S : Service_Entry_Type) return String is
   begin
      return To_String (S.Protocol);
   end Protocol_Name;

   ----------------------
   -- Raise_Host_Error --
   ----------------------

   procedure Raise_Host_Error (H_Error : Integer; Name : String) is
   begin
      raise Host_Error with
        Err_Code_Image (H_Error)
          & Dedot (Host_Error_Messages.Host_Error_Message (H_Error))
          & ": " & Name;
   end Raise_Host_Error;

   ------------------------
   -- Raise_Socket_Error --
   ------------------------

   procedure Raise_Socket_Error (Error : Integer) is
   begin
      raise Socket_Error with
        Err_Code_Image (Error) & Socket_Error_Message (Error);
   end Raise_Socket_Error;

   ----------
   -- Read --
   ----------

   procedure Read
     (Stream : in out Datagram_Socket_Stream_Type;
      Item   : out Ada.Streams.Stream_Element_Array;
      Last   : out Ada.Streams.Stream_Element_Offset)
   is
   begin
      Receive_Socket
        (Stream.Socket,
         Item,
         Last,
         Stream.From);
   end Read;

   ----------
   -- Read --
   ----------

   procedure Read
     (Stream : in out Stream_Socket_Stream_Type;
      Item   : out Ada.Streams.Stream_Element_Array;
      Last   : out Ada.Streams.Stream_Element_Offset)
   is
      First : Ada.Streams.Stream_Element_Offset          := Item'First;
      Index : Ada.Streams.Stream_Element_Offset          := First - 1;
      Max   : constant Ada.Streams.Stream_Element_Offset := Item'Last;

   begin
      loop
         Receive_Socket (Stream.Socket, Item (First .. Max), Index);
         Last  := Index;

         --  Exit when all or zero data received. Zero means that the socket
         --  peer is closed.

         exit when Index < First or else Index = Max;

         First := Index + 1;
      end loop;
   end Read;

   --------------------
   -- Receive_Socket --
   --------------------

   procedure Receive_Socket
     (Socket : Socket_Type;
      Item   : out Ada.Streams.Stream_Element_Array;
      Last   : out Ada.Streams.Stream_Element_Offset;
      Flags  : Request_Flag_Type := No_Request_Flag)
   is
      Res : C.int;

   begin
      Res :=
        C_Recv (C.int (Socket), Item'Address, Item'Length, To_Int (Flags));

      if Res = Failure then
         Raise_Socket_Error (Socket_Errno);
      end if;

      Last := Last_Index (First => Item'First, Count => size_t (Res));
   end Receive_Socket;

   --------------------
   -- Receive_Socket --
   --------------------

   procedure Receive_Socket
     (Socket : Socket_Type;
      Item   : out Ada.Streams.Stream_Element_Array;
      Last   : out Ada.Streams.Stream_Element_Offset;
      From   : out Sock_Addr_Type;
      Flags  : Request_Flag_Type := No_Request_Flag)
   is
      Res : C.int;
      Sin : aliased Sockaddr;
      Len : aliased C.int := Sin'Size / 8;

   begin
      Res :=
        C_Recvfrom
          (C.int (Socket),
           Item'Address,
           Item'Length,
           To_Int (Flags),
           Sin'Address,
           Len'Access);

      if Res = Failure then
         Raise_Socket_Error (Socket_Errno);
      end if;

      Last := Last_Index (First => Item'First, Count => size_t (Res));

      From := Get_Address (Sin, Len);
   end Receive_Socket;

   --------------------
   -- Receive_Vector --
   --------------------

   procedure Receive_Vector
     (Socket : Socket_Type;
      Vector : Vector_Type;
      Count  : out Ada.Streams.Stream_Element_Count;
      Flags  : Request_Flag_Type := No_Request_Flag)
   is
      Res : ssize_t;

      Msg : Msghdr :=
              (Msg_Name       => System.Null_Address,
               Msg_Namelen    => 0,
               Msg_Iov        => Vector'Address,

               --  recvmsg(2) returns EMSGSIZE on Linux (and probably on other
               --  platforms) when the supplied vector is longer than IOV_MAX,
               --  so use minimum of the two lengths.

               Msg_Iovlen     => SOSC.Msg_Iovlen_T'Min
                                   (Vector'Length, SOSC.IOV_MAX),

               Msg_Control    => System.Null_Address,
               Msg_Controllen => 0,
               Msg_Flags      => 0);

   begin
      Res :=
        C_Recvmsg
          (C.int (Socket),
           Msg'Address,
           To_Int (Flags));

      if Res = ssize_t (Failure) then
         Raise_Socket_Error (Socket_Errno);
      end if;

      Count := Ada.Streams.Stream_Element_Count (Res);
   end Receive_Vector;

   -------------------
   -- Resolve_Error --
   -------------------

   function Resolve_Error
     (Error_Value : Integer;
      From_Errno  : Boolean := True) return Error_Type
   is
      use GNAT.Sockets.SOSC;

   begin
      if not From_Errno then
         case Error_Value is
            when SOSC.HOST_NOT_FOUND => return Unknown_Host;
            when SOSC.TRY_AGAIN      => return Host_Name_Lookup_Failure;
            when SOSC.NO_RECOVERY    => return Non_Recoverable_Error;
            when SOSC.NO_DATA        => return Unknown_Server_Error;
            when others              => return Cannot_Resolve_Error;
         end case;
      end if;

      --  Special case: EAGAIN may be the same value as EWOULDBLOCK, so we
      --  can't include it in the case statement below.

      pragma Warnings (Off);
      --  Condition "EAGAIN /= EWOULDBLOCK" is known at compile time

      if EAGAIN /= EWOULDBLOCK and then Error_Value = EAGAIN then
         return Resource_Temporarily_Unavailable;
      end if;

      --  This is not a case statement because if a particular error
      --  number constant is not defined, s-oscons-tmplt.c defines
      --  it to -1.  If multiple constants are not defined, they
      --  would each be -1 and result in a "duplicate value in case" error.
      --
      --  But we have to leave warnings off because the compiler is also
      --  smart enough to note that when two errnos have the same value,
      --  the second if condition is useless.
      if Error_Value = ENOERROR then
         return Success;
      elsif Error_Value = EACCES then
         return Permission_Denied;
      elsif Error_Value = EADDRINUSE then
         return Address_Already_In_Use;
      elsif Error_Value = EADDRNOTAVAIL then
         return Cannot_Assign_Requested_Address;
      elsif Error_Value = EAFNOSUPPORT then
         return Address_Family_Not_Supported_By_Protocol;
      elsif Error_Value = EALREADY then
         return Operation_Already_In_Progress;
      elsif Error_Value = EBADF then
         return Bad_File_Descriptor;
      elsif Error_Value = ECONNABORTED then
         return Software_Caused_Connection_Abort;
      elsif Error_Value = ECONNREFUSED then
         return Connection_Refused;
      elsif Error_Value = ECONNRESET then
         return Connection_Reset_By_Peer;
      elsif Error_Value = EDESTADDRREQ then
         return Destination_Address_Required;
      elsif Error_Value = EFAULT then
         return Bad_Address;
      elsif Error_Value = EHOSTDOWN then
         return Host_Is_Down;
      elsif Error_Value = EHOSTUNREACH then
         return No_Route_To_Host;
      elsif Error_Value = EINPROGRESS then
         return Operation_Now_In_Progress;
      elsif Error_Value = EINTR then
         return Interrupted_System_Call;
      elsif Error_Value = EINVAL then
         return Invalid_Argument;
      elsif Error_Value = EIO then
         return Input_Output_Error;
      elsif Error_Value = EISCONN then
         return Transport_Endpoint_Already_Connected;
      elsif Error_Value = ELOOP then
         return Too_Many_Symbolic_Links;
      elsif Error_Value = EMFILE then
         return Too_Many_Open_Files;
      elsif Error_Value = EMSGSIZE then
         return Message_Too_Long;
      elsif Error_Value = ENAMETOOLONG then
         return File_Name_Too_Long;
      elsif Error_Value = ENETDOWN then
         return Network_Is_Down;
      elsif Error_Value = ENETRESET then
         return Network_Dropped_Connection_Because_Of_Reset;
      elsif Error_Value = ENETUNREACH then
         return Network_Is_Unreachable;
      elsif Error_Value = ENOBUFS then
         return No_Buffer_Space_Available;
      elsif Error_Value = ENOPROTOOPT then
         return Protocol_Not_Available;
      elsif Error_Value = ENOTCONN then
         return Transport_Endpoint_Not_Connected;
      elsif Error_Value = ENOTSOCK then
         return Socket_Operation_On_Non_Socket;
      elsif Error_Value = EOPNOTSUPP then
         return Operation_Not_Supported;
      elsif Error_Value = EPFNOSUPPORT then
         return Protocol_Family_Not_Supported;
      elsif Error_Value = EPIPE then
         return Broken_Pipe;
      elsif Error_Value = EPROTONOSUPPORT then
         return Protocol_Not_Supported;
      elsif Error_Value = EPROTOTYPE then
         return Protocol_Wrong_Type_For_Socket;
      elsif Error_Value = ESHUTDOWN then
         return Cannot_Send_After_Transport_Endpoint_Shutdown;
      elsif Error_Value = ESOCKTNOSUPPORT then
         return Socket_Type_Not_Supported;
      elsif Error_Value = ETIMEDOUT then
         return Connection_Timed_Out;
      elsif Error_Value = ETOOMANYREFS then
         return Too_Many_References;
      elsif Error_Value = EWOULDBLOCK then
         return Resource_Temporarily_Unavailable;
      else
         return Cannot_Resolve_Error;
      end if;
      pragma Warnings (On);

   end Resolve_Error;

   -----------------------
   -- Resolve_Exception --
   -----------------------

   function Resolve_Exception
     (Occurrence : Exception_Occurrence) return Error_Type
   is
      Id    : constant Exception_Id := Exception_Identity (Occurrence);
      Msg   : constant String       := Exception_Message (Occurrence);
      First : Natural;
      Last  : Natural;
      Val   : Integer;

   begin
      First := Msg'First;
      while First <= Msg'Last
        and then Msg (First) not in '0' .. '9'
      loop
         First := First + 1;
      end loop;

      if First > Msg'Last then
         return Cannot_Resolve_Error;
      end if;

      Last := First;
      while Last < Msg'Last
        and then Msg (Last + 1) in '0' .. '9'
      loop
         Last := Last + 1;
      end loop;

      Val := Integer'Value (Msg (First .. Last));

      if Id = Socket_Error_Id then
         return Resolve_Error (Val);

      elsif Id = Host_Error_Id then
         return Resolve_Error (Val, False);

      else
         return Cannot_Resolve_Error;
      end if;
   end Resolve_Exception;

   -----------------
   -- Send_Socket --
   -----------------

   procedure Send_Socket
     (Socket : Socket_Type;
      Item   : Ada.Streams.Stream_Element_Array;
      Last   : out Ada.Streams.Stream_Element_Offset;
      Flags  : Request_Flag_Type := No_Request_Flag)
   is
   begin
      Send_Socket (Socket, Item, Last, To => null, Flags => Flags);
   end Send_Socket;

   -----------------
   -- Send_Socket --
   -----------------

   procedure Send_Socket
     (Socket : Socket_Type;
      Item   : Ada.Streams.Stream_Element_Array;
      Last   : out Ada.Streams.Stream_Element_Offset;
      To     : Sock_Addr_Type;
      Flags  : Request_Flag_Type := No_Request_Flag)
   is
   begin
      Send_Socket
        (Socket, Item, Last, To => To'Unrestricted_Access, Flags => Flags);
   end Send_Socket;

   -----------------
   -- Send_Socket --
   -----------------

   procedure Send_Socket
     (Socket : Socket_Type;
      Item   : Ada.Streams.Stream_Element_Array;
      Last   : out Ada.Streams.Stream_Element_Offset;
      To     : access Sock_Addr_Type;
      Flags  : Request_Flag_Type := No_Request_Flag)
   is
      Res  : C.int;

      Sin  : aliased Sockaddr;
      C_To : System.Address;
      Len  : C.int;

   begin
      if To /= null then
         Set_Address (Sin'Unchecked_Access, To.all, Len);
         C_To := Sin'Address;

      else
         C_To := System.Null_Address;
         Len := 0;
      end if;

      Res := C_Sendto
        (C.int (Socket),
         Item'Address,
         Item'Length,
         Set_Forced_Flags (To_Int (Flags)),
         C_To,
         Len);

      if Res = Failure then
         Raise_Socket_Error (Socket_Errno);
      end if;

      Last := Last_Index (First => Item'First, Count => size_t (Res));
   end Send_Socket;

   -----------------
   -- Send_Vector --
   -----------------

   procedure Send_Vector
     (Socket : Socket_Type;
      Vector : Vector_Type;
      Count  : out Ada.Streams.Stream_Element_Count;
      Flags  : Request_Flag_Type := No_Request_Flag)
   is
      use Interfaces.C;

      Res            : ssize_t;
      Iov_Count      : SOSC.Msg_Iovlen_T;
      This_Iov_Count : SOSC.Msg_Iovlen_T;
      Msg            : Msghdr;

   begin
      Count := 0;
      Iov_Count := 0;
      while Iov_Count < Vector'Length loop

         pragma Warnings (Off);
         --  Following test may be compile time known on some targets

         This_Iov_Count :=
           (if Vector'Length - Iov_Count > SOSC.IOV_MAX
            then SOSC.IOV_MAX
            else Vector'Length - Iov_Count);

         pragma Warnings (On);

         Msg :=
           (Msg_Name       => System.Null_Address,
            Msg_Namelen    => 0,
            Msg_Iov        => Vector
                                (Vector'First + Integer (Iov_Count))'Address,
            Msg_Iovlen     => This_Iov_Count,
            Msg_Control    => System.Null_Address,
            Msg_Controllen => 0,
            Msg_Flags      => 0);

         Res :=
           C_Sendmsg
             (C.int (Socket),
              Msg'Address,
              Set_Forced_Flags (To_Int (Flags)));

         if Res = ssize_t (Failure) then
            Raise_Socket_Error (Socket_Errno);
         end if;

         Count := Count + Ada.Streams.Stream_Element_Count (Res);
         Iov_Count := Iov_Count + This_Iov_Count;
      end loop;
   end Send_Vector;

   ---------
   -- Set --
   ---------

   procedure Set (Item : in out Socket_Set_Type; Socket : Socket_Type) is
   begin
      Check_For_Fd_Set (Socket);

      if Item.Last = No_Socket then

         --  Uninitialized socket set, make sure it is properly zeroed out

         Reset_Socket_Set (Item.Set'Access);
         Item.Last := Socket;

      elsif Item.Last < Socket then
         Item.Last := Socket;
      end if;

      Insert_Socket_In_Set (Item.Set'Access, C.int (Socket));
   end Set;

   -----------------------
   -- Set_Close_On_Exec --
   -----------------------

   procedure Set_Close_On_Exec
     (Socket        : Socket_Type;
      Close_On_Exec : Boolean;
      Status        : out Boolean)
   is
      function C_Set_Close_On_Exec
        (Socket : Socket_Type; Close_On_Exec : C.int) return C.int;
      pragma Import (C, C_Set_Close_On_Exec, "__gnat_set_close_on_exec");
   begin
      Status := C_Set_Close_On_Exec (Socket, Boolean'Pos (Close_On_Exec)) = 0;
   end Set_Close_On_Exec;

   ----------------------
   -- Set_Forced_Flags --
   ----------------------

   function Set_Forced_Flags (F : C.int) return C.int is
      use type C.unsigned;
      function To_unsigned is
        new Ada.Unchecked_Conversion (C.int, C.unsigned);
      function To_int is
        new Ada.Unchecked_Conversion (C.unsigned, C.int);
   begin
      return To_int (To_unsigned (F) or SOSC.MSG_Forced_Flags);
   end Set_Forced_Flags;

   -----------------------
   -- Set_Socket_Option --
   -----------------------

   procedure Set_Socket_Option
     (Socket : Socket_Type;
      Level  : Level_Type;
      Option : Option_Type)
   is
      use type C.unsigned;

      MR  : aliased IPV6_Mreq;
      V8  : aliased Two_Ints;
      V4  : aliased C.int;
      U4  : aliased C.unsigned;
      V1  : aliased C.unsigned_char;
      VT  : aliased Timeval;
      Len : C.int;
      Add : System.Address := Null_Address;
      Res : C.int;
      Onm : C.int;

   begin
      case Option.Name is
         when Generic_Option =>
            V4  := Option.Optval;
            Len := V4'Size / 8;
            Add := V4'Address;

         when Broadcast
            | Keep_Alive
            | No_Delay
            | Reuse_Address
            | Multicast_Loop_V4
            | Multicast_Loop_V6
            | IPv6_Only
         =>
            V4  := C.int (Boolean'Pos (Option.Enabled));
            Len := V4'Size / 8;
            Add := V4'Address;

         when Busy_Polling =>
            V4  := C.int (Option.Microseconds);
            Len := V4'Size / 8;
            Add := V4'Address;

         when Linger =>
            V8 (V8'First) := C.int (Boolean'Pos (Option.Enabled));
            V8 (V8'Last)  := C.int (Option.Seconds);
            Len := V8'Size / 8;
            Add := V8'Address;

         when Receive_Buffer
            | Send_Buffer
         =>
            V4  := C.int (Option.Size);
            Len := V4'Size / 8;
            Add := V4'Address;

         when Error =>
            V4  := C.int (Boolean'Pos (True));
            Len := V4'Size / 8;
            Add := V4'Address;

         when Add_Membership_V4
            | Drop_Membership_V4
         =>
            V8 (V8'First) := To_Int (To_In_Addr (Option.Multicast_Address));
            V8 (V8'Last)  := To_Int (To_In_Addr (Option.Local_Interface));
            Len := V8'Size / 8;
            Add := V8'Address;

         when Add_Membership_V6
            | Drop_Membership_V6 =>
            MR.ipv6mr_multiaddr := To_In6_Addr (Option.Multicast_Address);
            MR.ipv6mr_interface := C.unsigned (Option.Interface_Index);
            Len := MR'Size / 8;
            Add := MR'Address;

         when Multicast_If_V4 =>
            V4  := To_Int (To_In_Addr (Option.Outgoing_If));
            Len := V4'Size / 8;
            Add := V4'Address;

         when Multicast_If_V6 =>
            V4  := C.int (Option.Outgoing_If_Index);
            Len := V4'Size / 8;
            Add := V4'Address;

         when Multicast_TTL =>
            V1  := C.unsigned_char (Option.Time_To_Live);
            Len := V1'Size / 8;
            Add := V1'Address;

         when Multicast_Hops =>
            V4  := C.int (Option.Hop_Limit);
            Len := V4'Size / 8;
            Add := V4'Address;

         when Receive_Packet_Info
         =>
            V1  := C.unsigned_char (Boolean'Pos (Option.Enabled));
            Len := V1'Size / 8;
            Add := V1'Address;

         when Receive_Timeout
            | Send_Timeout
         =>
            if Is_Windows then

               --  On Windows, the timeout is a DWORD in milliseconds

               Len := U4'Size / 8;
               Add := U4'Address;

               U4 := C.unsigned (Option.Timeout / 0.001);

               if Option.Timeout > 0.0 and then U4 = 0 then
                  --  Avoid round to zero. Zero timeout mean unlimited.
                  U4 := 1;
               end if;

               --  Old windows versions actual timeout is 500 ms + the given
               --  value (unless it is 0).

               if Minus_500ms_Windows_Timeout /= 0 then
                  if U4 > 500 then
                     U4 := U4 - 500;

                  elsif U4 > 0 then
                     U4 := 1;
                  end if;
               end if;

            else
               VT  := To_Timeval (Option.Timeout);
               Len := VT'Size / 8;
               Add := VT'Address;
            end if;
      end case;

      if Option.Name in Specific_Option_Name then
         Onm := Options (Option.Name);

      elsif Option.Optname = -1 then
         raise Socket_Error with "optname must be specified";

      else
         Onm := Option.Optname;
      end if;

      Res := C_Setsockopt
        (C.int (Socket),
         Levels (Level),
         Onm,
         Add, Len);

      if Res = Failure then
         Raise_Socket_Error (Socket_Errno);
      end if;
   end Set_Socket_Option;

   ---------------------
   -- Shutdown_Socket --
   ---------------------

   procedure Shutdown_Socket
     (Socket : Socket_Type;
      How    : Shutmode_Type := Shut_Read_Write)
   is
      Res : C.int;

   begin
      Res := C_Shutdown (C.int (Socket), Shutmodes (How));

      if Res = Failure then
         Raise_Socket_Error (Socket_Errno);
      end if;
   end Shutdown_Socket;

   ------------
   -- Stream --
   ------------

   function Stream
     (Socket  : Socket_Type;
      Send_To : Sock_Addr_Type) return Stream_Access
   is
      S : Datagram_Socket_Stream_Access;

   begin
      S        := new Datagram_Socket_Stream_Type;
      S.Socket := Socket;
      S.To     := Send_To;
      S.From   := Get_Socket_Name (Socket);
      return Stream_Access (S);
   end Stream;

   ------------
   -- Stream --
   ------------

   function Stream (Socket : Socket_Type) return Stream_Access is
      S : Stream_Socket_Stream_Access;
   begin
      S := new Stream_Socket_Stream_Type;
      S.Socket := Socket;
      return Stream_Access (S);
   end Stream;

   ------------
   -- To_Ada --
   ------------

   function To_Ada (Fd : Integer) return Socket_Type is
   begin
      return Socket_Type (Fd);
   end To_Ada;

   ----------
   -- To_C --
   ----------

   function To_C (Socket : Socket_Type) return Integer is
   begin
      return Integer (Socket);
   end To_C;

   -----------------
   -- To_Duration --
   -----------------

   function To_Duration (Val : Timeval) return Timeval_Duration is
      Max_D : constant Long_Long_Integer := Long_Long_Integer (Forever - 0.5);
      Tv_sec_64 : constant Boolean := SOSC.SIZEOF_tv_sec = 8;
      --  Need to separate this condition into the constant declaration to
      --  avoid GNAT warning about "always true" or "always false".
   begin
      if Tv_sec_64 then
         --  Check for possible Duration overflow when Tv_Sec field is 64 bit
         --  integer.

         if Val.Tv_Sec > time_t (Max_D) or else
            (Val.Tv_Sec = time_t (Max_D) and then
             Val.Tv_Usec > suseconds_t ((Forever - Duration (Max_D)) * 1E6))
         then
            return Forever;
         end if;
      end if;

      return Duration (Val.Tv_Sec) + Duration (Val.Tv_Usec) * 1.0E-6;
   end To_Duration;

   -------------------
   -- To_Host_Entry --
   -------------------

   function To_Host_Entry (E : Hostent_Access) return Host_Entry_Type is
      Aliases_Count, Addresses_Count : Natural;

      Family : constant Family_Type :=
                 (case Hostent_H_Addrtype (E) is
                     when SOSC.AF_INET  => Family_Inet,
                     when SOSC.AF_INET6 => Family_Inet6,
                     when others        => Family_Unspec);

      Addr_Len : constant C.size_t := C.size_t (Hostent_H_Length (E));

   begin
      if Family = Family_Unspec then
         Raise_Socket_Error (SOSC.EPFNOSUPPORT);
      end if;

      Aliases_Count := 0;
      while Hostent_H_Alias (E, C.int (Aliases_Count)) /= Null_Address loop
         Aliases_Count := Aliases_Count + 1;
      end loop;

      Addresses_Count := 0;
      while Hostent_H_Addr (E, C.int (Addresses_Count)) /= Null_Address loop
         Addresses_Count := Addresses_Count + 1;
      end loop;

      return Result : Host_Entry_Type
                        (Aliases_Length   => Aliases_Count,
                         Addresses_Length => Addresses_Count)
      do
         Result.Official := To_Name (Value (Hostent_H_Name (E)));

         for J in Result.Aliases'Range loop
            Result.Aliases (J) :=
              To_Name (Value (Hostent_H_Alias
                                (E, C.int (J - Result.Aliases'First))));
         end loop;

         for J in Result.Addresses'Range loop
            declare
               Ia : In_Addr_Union (Family);

               --  Hostent_H_Addr (E, <index>) may return an address that is
               --  not correctly aligned for In_Addr, so we need to use
               --  an intermediate copy operation on a type with an alignment
               --  of 1 to recover the value.

               subtype Addr_Buf_T is C.char_array (1 .. Addr_Len);
               Unaligned_Addr : Addr_Buf_T;
               for Unaligned_Addr'Address
                 use Hostent_H_Addr (E, C.int (J - Result.Addresses'First));
               pragma Import (Ada, Unaligned_Addr);

               Aligned_Addr : Addr_Buf_T;
               for Aligned_Addr'Address use Ia'Address;
               pragma Import (Ada, Aligned_Addr);

            begin
               Aligned_Addr := Unaligned_Addr;
               if Family = Family_Inet6 then
                  To_Inet_Addr (Ia.In6, Result.Addresses (J));
               else
                  To_Inet_Addr (Ia.In4, Result.Addresses (J));
               end if;
            end;
         end loop;
      end return;
   end To_Host_Entry;

   ------------
   -- To_Int --
   ------------

   function To_Int (F : Request_Flag_Type) return C.int
   is
      Current : Request_Flag_Type := F;
      Result  : C.int := 0;

   begin
      for J in Flags'Range loop
         exit when Current = 0;

         if Current mod 2 /= 0 then
            if Flags (J) = -1 then
               Raise_Socket_Error (SOSC.EOPNOTSUPP);
            end if;

            Result := Result + Flags (J);
         end if;

         Current := Current / 2;
      end loop;

      return Result;
   end To_Int;

   -------------
   -- To_Name --
   -------------

   function To_Name (N : String) return Name_Type is
   begin
      return Name_Type'(N'Length, N);
   end To_Name;

   ----------------------
   -- To_Service_Entry --
   ----------------------

   function To_Service_Entry (E : Servent_Access) return Service_Entry_Type is
      Aliases_Count : Natural;

   begin
      Aliases_Count := 0;
      while Servent_S_Alias (E, C.int (Aliases_Count)) /= Null_Address loop
         Aliases_Count := Aliases_Count + 1;
      end loop;

      return Result : Service_Entry_Type (Aliases_Length   => Aliases_Count) do
         Result.Official := To_Name (Value (Servent_S_Name (E)));

         for J in Result.Aliases'Range loop
            Result.Aliases (J) :=
              To_Name (Value (Servent_S_Alias
                                (E, C.int (J - Result.Aliases'First))));
         end loop;

         Result.Protocol := To_Name (Value (Servent_S_Proto (E)));
         Result.Port :=
           Port_Type (Network_To_Short (Servent_S_Port (E)));
      end return;
   end To_Service_Entry;

   ---------------
   -- To_String --
   ---------------

   function To_String (HN : Name_Type) return String is
   begin
      return HN.Name (1 .. HN.Length);
   end To_String;

   ----------------
   -- To_Timeval --
   ----------------

   function To_Timeval (Val : Timeval_Duration) return Timeval is
      S  : time_t;
      uS : suseconds_t;

   begin
      --  If zero, set result as zero (otherwise it gets rounded down to -1)

      if Val = 0.0 then
         S  := 0;
         uS := 0;

      --  Normal case where we do round down

      else
         S  := time_t (Val - 0.5);
         uS := suseconds_t (1_000_000 * (Val - Selector_Duration (S)) - 0.5);

         if uS = -1 then
            --  It happen on integer duration
            uS := 0;
         end if;
      end if;

      return (S, uS);
   end To_Timeval;

   -----------
   -- Value --
   -----------

   function Value (S : System.Address) return String is
      Str : String (1 .. Positive'Last);
      for Str'Address use S;
      pragma Import (Ada, Str);

      Terminator : Positive := Str'First;

   begin
      while Str (Terminator) /= ASCII.NUL loop
         Terminator := Terminator + 1;
      end loop;

      return Str (1 .. Terminator - 1);
   end Value;

   -----------
   -- Write --
   -----------

   procedure Write
     (Stream : in out Datagram_Socket_Stream_Type;
      Item   : Ada.Streams.Stream_Element_Array)
   is
      Last : Stream_Element_Offset;

   begin
      Send_Socket
        (Stream.Socket,
         Item,
         Last,
         Stream.To);

      --  It is an error if not all of the data has been sent

      if Last /= Item'Last then
         Raise_Socket_Error (Socket_Errno);
      end if;
   end Write;

   -----------
   -- Write --
   -----------

   procedure Write
     (Stream : in out Stream_Socket_Stream_Type;
      Item   : Ada.Streams.Stream_Element_Array)
   is
      First : Ada.Streams.Stream_Element_Offset;
      Index : Ada.Streams.Stream_Element_Offset;
      Max   : constant Ada.Streams.Stream_Element_Offset := Item'Last;

   begin
      First := Item'First;
      Index := First - 1;
      while First <= Max loop
         Send_Socket (Stream.Socket, Item (First .. Max), Index, null);

         --  Exit when all or zero data sent. Zero means that the socket has
         --  been closed by peer.

         exit when Index < First or else Index = Max;

         First := Index + 1;
      end loop;

      --  For an empty array, we have First > Max, and hence Index >= Max (no
      --  error, the loop above is never executed). After a successful send,
      --  Index = Max. The only remaining case, Index < Max, is therefore
      --  always an actual send failure.

      if Index < Max then
         Raise_Socket_Error (Socket_Errno);
      end if;
   end Write;

   Sockets_Library_Controller_Object : Sockets_Library_Controller;
   pragma Unreferenced (Sockets_Library_Controller_Object);
   --  The elaboration and finalization of this object perform the required
   --  initialization and cleanup actions for the sockets library.

   --------------------
   -- Create_Address --
   --------------------

   function Create_Address
     (Family : Family_Inet_4_6; Bytes : Inet_Addr_Bytes) return Inet_Addr_Type
   is
     (case Family is
         when Family_Inet => (Family_Inet, Bytes),
         when Family_Inet6 => (Family_Inet6, Bytes));

   ---------------
   -- Get_Bytes --
   ---------------

   function Get_Bytes (Addr : Inet_Addr_Type) return Inet_Addr_Bytes is
     (case Addr.Family is
         when Family_Inet => Addr.Sin_V4,
         when Family_Inet6 => Addr.Sin_V6);

   ----------
   -- Mask --
   ----------

   function Mask
     (Family : Family_Inet_4_6;
      Length : Natural;
      Host   : Boolean := False) return Inet_Addr_Type
   is
      Addr_Len : constant Natural := Inet_Addr_Bytes_Length (Family);
   begin
      if Length > 8 * Addr_Len then
         raise Constraint_Error with
           "invalid mask length for address family " & Family'Img;
      end if;

      declare
         B    : Inet_Addr_Bytes (1 ..  Addr_Len);
         Part : Inet_Addr_Comp_Type;
      begin
         for J in 1 .. Length / 8 loop
            B (J) := (if Host then 0 else 255);
         end loop;

         if Length < 8 * Addr_Len then
            Part := 2 ** (8 - Length mod 8) - 1;
            B (Length / 8 + 1) := (if Host then Part else not Part);

            for J in Length / 8 + 2 .. B'Last loop
               B (J) := (if Host then 255 else 0);
            end loop;
         end if;

         return Create_Address (Family, B);
      end;
   end Mask;

   -------------------------
   -- Unix_Socket_Address --
   -------------------------

   function Unix_Socket_Address (Addr : String) return Sock_Addr_Type is
   begin
      return Sock_Addr_Type'(Family_Unix, ASU.To_Unbounded_String (Addr));
   end Unix_Socket_Address;

   -----------
   -- "and" --
   -----------

   function "and" (Addr, Mask : Inet_Addr_Type) return Inet_Addr_Type is
   begin
      if Addr.Family /= Mask.Family then
         raise Constraint_Error with "incompatible address families";
      end if;

      declare
         A : constant Inet_Addr_Bytes := Get_Bytes (Addr);
         M : constant Inet_Addr_Bytes := Get_Bytes (Mask);
         R : Inet_Addr_Bytes (A'Range);

      begin
         for J in A'Range loop
            R (J) := A (J) and M (J);
         end loop;
         return Create_Address (Addr.Family, R);
      end;
   end "and";

   ----------
   -- "or" --
   ----------

   function "or" (Net, Host : Inet_Addr_Type) return Inet_Addr_Type is
   begin
      if Net.Family /= Host.Family then
         raise Constraint_Error with "incompatible address families";
      end if;

      declare
         N : constant Inet_Addr_Bytes := Get_Bytes (Net);
         H : constant Inet_Addr_Bytes := Get_Bytes (Host);
         R : Inet_Addr_Bytes (N'Range);

      begin
         for J in N'Range loop
            R (J) := N (J) or H (J);
         end loop;
         return Create_Address (Net.Family, R);
      end;
   end "or";

   -----------
   -- "not" --
   -----------

   function "not" (Mask : Inet_Addr_Type) return Inet_Addr_Type is
      M : constant Inet_Addr_Bytes := Get_Bytes (Mask);
      R : Inet_Addr_Bytes (M'Range);
   begin
      for J in R'Range loop
         R (J) := not M (J);
      end loop;
      return Create_Address (Mask.Family, R);
   end "not";

end GNAT.Sockets;
