blob: 3f153af108776a4ed382f19edc45763d021f854c [file] [log] [blame]
------------------------------------------------------------------------------
-- --
-- GNAT COMPILER COMPONENTS --
-- --
-- G N A T . S O C K E T S . P O L L --
-- --
-- S p e c --
-- --
-- Copyright (C) 2020-2021, 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.Calendar;
with GNAT.Sockets.Thin;
package body GNAT.Sockets.Poll is
To_C : constant array (Wait_Event_Type) of Events_Type :=
(Input => SOC.POLLIN or SOC.POLLPRI, Output => SOC.POLLOUT);
-- To convert Wait_Event_Type to C I/O events flags
procedure Set_Mode (Item : out Pollfd; Mode : Wait_Event_Set);
-- Set I/O waiting mode on Item
procedure Set_Event
(Item : out Pollfd; Event : Wait_Event_Type; Value : Boolean);
-- Set or reset waiting state on I/O event
procedure Check_Range (Self : Set; Index : Positive) with Inline;
-- raise Constraint_Error if Index is more than number of sockets in Self
function Status (Item : Pollfd) return Event_Set is
(Input => (Item.REvents and To_C (Input)) /= 0,
Output => (Item.REvents and To_C (Output)) /= 0,
Error => (Item.REvents and SOC.POLLERR) /= 0,
Hang_Up => (Item.REvents and SOC.POLLHUP) /= 0,
Invalid_Request => (Item.REvents and SOC.POLLNVAL) /= 0);
-- Get I/O events from C word
procedure Wait
(Fds : in out Set; Timeout : Interfaces.C.int; Result : out Integer);
-- Waits until one or more of the sockets descriptors become ready for some
-- class of I/O operation or error state occurs on one or more of them.
-- Timeout is in milliseconds. Result mean how many sockets ready for I/O
-- or have error state.
----------
-- Wait --
----------
procedure Wait
(Fds : in out Set; Timeout : Interfaces.C.int; Result : out Integer)
is separate;
------------
-- Create --
------------
function Create (Size : Positive) return Set is
begin
return Result : Set (Size);
end Create;
------------
-- To_Set --
------------
function To_Set
(Socket : Socket_Type;
Events : Wait_Event_Set;
Size : Positive := 1) return Set is
begin
return Result : Set (Size) do
Append (Result, Socket, Events);
end return;
end To_Set;
------------
-- Append --
------------
procedure Append
(Self : in out Set; Socket : Socket_Type; Events : Wait_Event_Set) is
begin
Insert (Self, Socket, Events, Self.Length + 1);
end Append;
------------
-- Insert --
------------
procedure Insert
(Self : in out Set;
Socket : Socket_Type;
Events : Wait_Event_Set;
Index : Positive;
Keep_Order : Boolean := False) is
begin
if Self.Size <= Self.Length then
raise Constraint_Error with "Socket set is full";
elsif Index > Self.Length + 1 then
raise Constraint_Error with "Insert out of range";
end if;
if Socket < 0 then
raise Socket_Error with
"Wrong socket descriptor " & Socket_Type'Image (Socket);
end if;
Self.Length := Self.Length + 1;
if Index /= Self.Length then
if Keep_Order then
Self.Fds (Index + 1 .. Self.Length) :=
Self.Fds (Index .. Self.Length - 1);
else
Self.Fds (Self.Length) := Self.Fds (Index);
end if;
Self.Fds (Index).Events := 0;
end if;
Self.Fds (Index).Socket := FD_Type (Socket);
Set_Mode (Self.Fds (Index), Events);
if FD_Type (Socket) > Self.Max_FD then
Self.Max_FD := FD_Type (Socket);
Self.Max_OK := True;
end if;
end Insert;
-----------------
-- Check_Range --
-----------------
procedure Check_Range (Self : Set; Index : Positive) is
begin
if Index > Self.Length then
raise Constraint_Error;
end if;
end Check_Range;
----------
-- Copy --
----------
procedure Copy (Source : Set; Target : out Set) is
begin
if Target.Size < Source.Length then
raise Constraint_Error with
"Can't copy because size of target less than source length";
end if;
Target.Fds (1 .. Source.Length) := Source.Fds (1 .. Source.Length);
Target.Length := Source.Length;
Target.Max_FD := Source.Max_FD;
Target.Max_OK := Source.Max_OK;
end Copy;
----------------
-- Get_Events --
----------------
function Get_Events
(Self : Set; Index : Positive) return Wait_Event_Set is
begin
Check_Range (Self, Index);
return
(Input => (Self.Fds (Index).Events and To_C (Input)) /= 0,
Output => (Self.Fds (Index).Events and To_C (Output)) /= 0);
end Get_Events;
------------
-- Growth --
------------
function Growth (Self : Set) return Set is
begin
return Resize
(Self,
(case Self.Size is
when 1 .. 20 => 32,
when 21 .. 50 => 64,
when 51 .. 99 => Self.Size + Self.Size / 3,
when others => Self.Size + Self.Size / 4));
end Growth;
------------
-- Remove --
------------
procedure Remove
(Self : in out Set; Index : Positive; Keep_Order : Boolean := False) is
begin
Check_Range (Self, Index);
if Self.Max_FD = Self.Fds (Index).Socket then
Self.Max_OK := False;
end if;
if Index < Self.Length then
if Keep_Order then
Self.Fds (Index .. Self.Length - 1) :=
Self.Fds (Index + 1 .. Self.Length);
else
Self.Fds (Index) := Self.Fds (Self.Length);
end if;
end if;
Self.Length := Self.Length - 1;
end Remove;
------------
-- Resize --
------------
function Resize (Self : Set; Size : Positive) return Set is
begin
return Result : Set (Size) do
Copy (Self, Result);
end return;
end Resize;
---------------
-- Set_Event --
---------------
procedure Set_Event
(Self : in out Set;
Index : Positive;
Event : Wait_Event_Type;
Value : Boolean) is
begin
Check_Range (Self, Index);
Set_Event (Self.Fds (Index), Event, Value);
end Set_Event;
procedure Set_Event
(Item : out Pollfd; Event : Wait_Event_Type; Value : Boolean) is
begin
if Value then
Item.Events := Item.Events or To_C (Event);
else
Item.Events := Item.Events and not To_C (Event);
end if;
end Set_Event;
----------------
-- Set_Events --
----------------
procedure Set_Events
(Self : in out Set;
Index : Positive;
Events : Wait_Event_Set) is
begin
Check_Range (Self, Index);
Set_Mode (Self.Fds (Index), Events);
end Set_Events;
--------------
-- Set_Mode --
--------------
procedure Set_Mode (Item : out Pollfd; Mode : Wait_Event_Set) is
begin
for J in Mode'Range loop
Set_Event (Item, J, Mode (J));
end loop;
end Set_Mode;
------------
-- Socket --
------------
function Socket (Self : Set; Index : Positive) return Socket_Type is
begin
Check_Range (Self, Index);
return Socket_Type (Self.Fds (Index).Socket);
end Socket;
-----------
-- State --
-----------
procedure State
(Self : Set;
Index : Positive;
Socket : out Socket_Type;
Status : out Event_Set) is
begin
Check_Range (Self, Index);
Socket := Socket_Type (Self.Fds (Index).Socket);
Status := Poll.Status (Self.Fds (Index));
end State;
----------
-- Wait --
----------
procedure Wait (Self : in out Set; Timeout : Duration; Count : out Natural)
is
use Ada.Calendar;
-- Used to calculate partially consumed timeout on EINTR.
-- Better to use Ada.Real_Time, but we can't in current GNAT because
-- Ada.Real_Time is in tasking part of runtime.
Result : Integer;
Poll_Timeout : Duration := Timeout;
C_Timeout : Interfaces.C.int;
Errno : Integer;
Stamp : constant Time := Clock;
begin
if Self.Length = 0 then
Count := 0;
return;
end if;
loop
if Poll_Timeout >= Duration (Interfaces.C.int'Last - 8) / 1_000 then
-- Minus 8 is to workaround Linux kernel 2.6.24 bug with close to
-- Integer'Last poll timeout values.
-- syscall (SYS_poll, &ufds, 1, 2147483644); // is waiting
-- syscall (SYS_poll, &ufds, 1, 2147483645); // is not waiting
-- Timeout values close to maximum could be not safe because of
-- possible time conversion boundary errors in the kernel.
-- Use unlimited timeout instead of maximum 24 days timeout for
-- safety reasons.
C_Timeout := -1;
else
C_Timeout := Interfaces.C.int (Poll_Timeout * 1_000);
end if;
Wait (Self, C_Timeout, Result);
exit when Result >= 0;
Errno := Thin.Socket_Errno;
-- In case of EINTR error we have to continue waiting for network
-- events.
if Errno = SOC.EINTR then
if C_Timeout >= 0 then
Poll_Timeout := Timeout - (Clock - Stamp);
if Poll_Timeout < 0.0 then
Poll_Timeout := 0.0;
elsif Poll_Timeout > Timeout then
-- Clock moved back in time. This should not be happen when
-- we use monotonic time.
Poll_Timeout := Timeout;
end if;
end if;
else
Raise_Socket_Error (Errno);
end if;
end loop;
Count := Result;
end Wait;
----------
-- Next --
----------
procedure Next (Self : Set; Index : in out Natural) is
begin
loop
Index := Index + 1;
if Index > Self.Length then
Index := 0;
return;
elsif Self.Fds (Index).REvents /= 0 then
return;
end if;
end loop;
end Next;
------------
-- Status --
------------
function Status (Self : Set; Index : Positive) return Event_Set is
begin
Check_Range (Self, Index);
return Status (Self.Fds (Index));
end Status;
--------------
-- C_Status --
--------------
function C_Status
(Self : Set; Index : Positive) return Interfaces.C.unsigned is
begin
Check_Range (Self, Index);
return Interfaces.C.unsigned (Self.Fds (Index).REvents);
end C_Status;
end GNAT.Sockets.Poll;