diff gcc/ada/libgnat/s-os_lib.adb @ 111:04ced10e8804

gcc 7
author kono
date Fri, 27 Oct 2017 22:46:09 +0900
parents
children 84e7813d76e9
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/gcc/ada/libgnat/s-os_lib.adb	Fri Oct 27 22:46:09 2017 +0900
@@ -0,0 +1,3083 @@
+------------------------------------------------------------------------------
+--                                                                          --
+--                         GNAT COMPILER COMPONENTS                         --
+--                                                                          --
+--                        S Y S T E M . O S _ L I B                         --
+--                                                                          --
+--                                 B o d y                                  --
+--                                                                          --
+--                     Copyright (C) 1995-2017, 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.      --
+--                                                                          --
+------------------------------------------------------------------------------
+
+pragma Compiler_Unit_Warning;
+
+with Ada.Unchecked_Conversion;
+with Ada.Unchecked_Deallocation;
+with System; use System;
+with System.Case_Util;
+with System.CRTL;
+with System.Soft_Links;
+
+package body System.OS_Lib is
+
+   subtype size_t is CRTL.size_t;
+
+   procedure Strncpy (dest, src : System.Address; n : size_t)
+     renames CRTL.strncpy;
+
+   --  Imported procedures Dup and Dup2 are used in procedures Spawn and
+   --  Non_Blocking_Spawn.
+
+   function Dup (Fd : File_Descriptor) return File_Descriptor;
+   pragma Import (C, Dup, "__gnat_dup");
+
+   procedure Dup2 (Old_Fd, New_Fd : File_Descriptor);
+   pragma Import (C, Dup2, "__gnat_dup2");
+
+   function Copy_Attributes
+     (From : System.Address;
+      To   : System.Address;
+      Mode : Integer) return Integer;
+   pragma Import (C, Copy_Attributes, "__gnat_copy_attribs");
+   --  Mode = 0 - copy only time stamps.
+   --  Mode = 1 - copy time stamps and read/write/execute attributes
+   --  Mode = 2 - copy read/write/execute attributes
+
+   On_Windows : constant Boolean := Directory_Separator = '\';
+   --  An indication that we are on Windows. Used in Normalize_Pathname, to
+   --  deal with drive letters in the beginning of absolute paths.
+
+   package SSL renames System.Soft_Links;
+
+   --  The following are used by Create_Temp_File
+
+   First_Temp_File_Name : constant String := "GNAT-TEMP-000000.TMP";
+   --  Used to initialize Current_Temp_File_Name and Temp_File_Name_Last_Digit
+
+   Current_Temp_File_Name : String := First_Temp_File_Name;
+   --  Name of the temp file last created
+
+   Temp_File_Name_Last_Digit : constant Positive :=
+                                 First_Temp_File_Name'Last - 4;
+   --  Position of the last digit in Current_Temp_File_Name
+
+   Max_Attempts : constant := 100;
+   --  The maximum number of attempts to create a new temp file
+
+   -----------------------
+   -- Local Subprograms --
+   -----------------------
+
+   function Args_Length (Args : Argument_List) return Natural;
+   --  Returns total number of characters needed to create a string of all Args
+   --  terminated by ASCII.NUL characters.
+
+   procedure Create_Temp_File_Internal
+     (FD     : out File_Descriptor;
+      Name   : out String_Access;
+      Stdout : Boolean);
+   --  Internal routine to implement two Create_Temp_File routines. If Stdout
+   --  is set to True the created descriptor is stdout-compatible, otherwise
+   --  it might not be depending on the OS. The first two parameters are as
+   --  in Create_Temp_File.
+
+   function C_String_Length (S : Address) return Integer;
+   --  Returns the length of C (null-terminated) string at S, or 0 for
+   --  Null_Address.
+
+   procedure Spawn_Internal
+     (Program_Name : String;
+      Args         : Argument_List;
+      Result       : out Integer;
+      Pid          : out Process_Id;
+      Blocking     : Boolean);
+   --  Internal routine to implement the two Spawn (blocking/non blocking)
+   --  routines. If Blocking is set to True then the spawn is blocking
+   --  otherwise it is non blocking. In this latter case the Pid contains the
+   --  process id number. The first three parameters are as in Spawn. Note that
+   --  Spawn_Internal normalizes the argument list before calling the low level
+   --  system spawn routines (see Normalize_Arguments).
+   --
+   --  Note: Normalize_Arguments is designed to do nothing if it is called more
+   --  than once, so calling Normalize_Arguments before calling one of the
+   --  spawn routines is fine.
+
+   function To_Path_String_Access
+     (Path_Addr : Address;
+      Path_Len  : Integer) return String_Access;
+   --  Converts a C String to an Ada String. We could do this making use of
+   --  Interfaces.C.Strings but we prefer not to import that entire package
+
+   ---------
+   -- "<" --
+   ---------
+
+   function "<"  (X, Y : OS_Time) return Boolean is
+   begin
+      return Long_Integer (X) < Long_Integer (Y);
+   end "<";
+
+   ----------
+   -- "<=" --
+   ----------
+
+   function "<="  (X, Y : OS_Time) return Boolean is
+   begin
+      return Long_Integer (X) <= Long_Integer (Y);
+   end "<=";
+
+   ---------
+   -- ">" --
+   ---------
+
+   function ">"  (X, Y : OS_Time) return Boolean is
+   begin
+      return Long_Integer (X) > Long_Integer (Y);
+   end ">";
+
+   ----------
+   -- ">=" --
+   ----------
+
+   function ">="  (X, Y : OS_Time) return Boolean is
+   begin
+      return Long_Integer (X) >= Long_Integer (Y);
+   end ">=";
+
+   -----------------
+   -- Args_Length --
+   -----------------
+
+   function Args_Length (Args : Argument_List) return Natural is
+      Len : Natural := 0;
+
+   begin
+      for J in Args'Range loop
+         Len := Len + Args (J)'Length + 1; --  One extra for ASCII.NUL
+      end loop;
+
+      return Len;
+   end Args_Length;
+
+   -----------------------------
+   -- Argument_String_To_List --
+   -----------------------------
+
+   function Argument_String_To_List
+     (Arg_String : String) return Argument_List_Access
+   is
+      Max_Args : constant Integer := Arg_String'Length;
+      New_Argv : Argument_List (1 .. Max_Args);
+      Idx      : Integer;
+      New_Argc : Natural := 0;
+
+      Cleaned     : String (1 .. Arg_String'Length);
+      Cleaned_Idx : Natural;
+      --  A cleaned up version of the argument. This function is taking
+      --  backslash escapes when computing the bounds for arguments. It is
+      --  then removing the extra backslashes from the argument.
+
+      Backslash_Is_Sep : constant Boolean := Directory_Separator = '\';
+      --  Whether '\' is a directory separator (as on Windows), or a way to
+      --  quote special characters.
+
+   begin
+      Idx := Arg_String'First;
+
+      loop
+         exit when Idx > Arg_String'Last;
+
+         declare
+            Backqd  : Boolean := False;
+            Quoted  : Boolean := False;
+
+         begin
+            Cleaned_Idx := Cleaned'First;
+
+            loop
+               --  An unquoted space is the end of an argument
+
+               if not (Backqd or Quoted)
+                 and then Arg_String (Idx) = ' '
+               then
+                  exit;
+
+               --  Start of a quoted string
+
+               elsif not (Backqd or Quoted)
+                 and then Arg_String (Idx) = '"'
+               then
+                  Quoted := True;
+                  Cleaned (Cleaned_Idx) := Arg_String (Idx);
+                  Cleaned_Idx := Cleaned_Idx + 1;
+
+               --  End of a quoted string and end of an argument
+
+               elsif (Quoted and not Backqd)
+                 and then Arg_String (Idx) = '"'
+               then
+                  Cleaned (Cleaned_Idx) := Arg_String (Idx);
+                  Cleaned_Idx := Cleaned_Idx + 1;
+                  Idx := Idx + 1;
+                  exit;
+
+               --  Turn off backquoting after advancing one character
+
+               elsif Backqd then
+                  Backqd := False;
+                  Cleaned (Cleaned_Idx) := Arg_String (Idx);
+                  Cleaned_Idx := Cleaned_Idx + 1;
+
+               --  Following character is backquoted
+
+               elsif not Backslash_Is_Sep and then Arg_String (Idx) = '\' then
+                  Backqd := True;
+
+               else
+                  Cleaned (Cleaned_Idx) := Arg_String (Idx);
+                  Cleaned_Idx := Cleaned_Idx + 1;
+               end if;
+
+               Idx := Idx + 1;
+               exit when Idx > Arg_String'Last;
+            end loop;
+
+            --  Found an argument
+
+            New_Argc := New_Argc + 1;
+            New_Argv (New_Argc) :=
+              new String'(Cleaned (Cleaned'First .. Cleaned_Idx - 1));
+
+            --  Skip extraneous spaces
+
+            while Idx <= Arg_String'Last and then Arg_String (Idx) = ' ' loop
+               Idx := Idx + 1;
+            end loop;
+         end;
+      end loop;
+
+      return new Argument_List'(New_Argv (1 .. New_Argc));
+   end Argument_String_To_List;
+
+   ---------------------
+   -- C_String_Length --
+   ---------------------
+
+   function C_String_Length (S : Address) return Integer is
+   begin
+      if S = Null_Address then
+         return 0;
+      else
+         return Integer (CRTL.strlen (S));
+      end if;
+   end C_String_Length;
+
+   -----------
+   -- Close --
+   -----------
+
+   procedure Close (FD : File_Descriptor) is
+      use CRTL;
+      Discard : constant int := close (int (FD));
+   begin
+      null;
+   end Close;
+
+   procedure Close (FD : File_Descriptor; Status : out Boolean) is
+      use CRTL;
+   begin
+      Status := (close (int (FD)) = 0);
+   end Close;
+
+   ---------------
+   -- Copy_File --
+   ---------------
+
+   procedure Copy_File
+     (Name     : String;
+      Pathname : String;
+      Success  : out Boolean;
+      Mode     : Copy_Mode := Copy;
+      Preserve : Attribute := Time_Stamps)
+   is
+      From : File_Descriptor;
+      To   : File_Descriptor;
+
+      Copy_Error : exception;
+      --  Internal exception raised to signal error in copy
+
+      function Build_Path (Dir : String; File : String) return String;
+      --  Returns pathname Dir concatenated with File adding the directory
+      --  separator only if needed.
+
+      procedure Copy (From : File_Descriptor; To : File_Descriptor);
+      --  Read data from From and place them into To. In both cases the
+      --  operations uses the current file position. Raises Constraint_Error
+      --  if a problem occurs during the copy.
+
+      procedure Copy_To (To_Name : String);
+      --  Does a straight copy from source to designated destination file
+
+      ----------------
+      -- Build_Path --
+      ----------------
+
+      function Build_Path (Dir : String; File : String) return String is
+         function Is_Dirsep (C : Character) return Boolean;
+         pragma Inline (Is_Dirsep);
+         --  Returns True if C is a directory separator. On Windows we
+         --  handle both styles of directory separator.
+
+         ---------------
+         -- Is_Dirsep --
+         ---------------
+
+         function Is_Dirsep (C : Character) return Boolean is
+         begin
+            return C = Directory_Separator or else C = '/';
+         end Is_Dirsep;
+
+         --  Local variables
+
+         Base_File_Ptr : Integer;
+         --  The base file name is File (Base_File_Ptr + 1 .. File'Last)
+
+         Res : String (1 .. Dir'Length + File'Length + 1);
+
+      --  Start of processing for Build_Path
+
+      begin
+         --  Find base file name
+
+         Base_File_Ptr := File'Last;
+         while Base_File_Ptr >= File'First loop
+            exit when Is_Dirsep (File (Base_File_Ptr));
+            Base_File_Ptr := Base_File_Ptr - 1;
+         end loop;
+
+         declare
+            Base_File : String renames
+                          File (Base_File_Ptr + 1 .. File'Last);
+
+         begin
+            Res (1 .. Dir'Length) := Dir;
+
+            if Is_Dirsep (Dir (Dir'Last)) then
+               Res (Dir'Length + 1 .. Dir'Length + Base_File'Length) :=
+                 Base_File;
+               return Res (1 .. Dir'Length + Base_File'Length);
+
+            else
+               Res (Dir'Length + 1) := Directory_Separator;
+               Res (Dir'Length + 2 .. Dir'Length + 1 + Base_File'Length) :=
+                 Base_File;
+               return Res (1 .. Dir'Length + 1 + Base_File'Length);
+            end if;
+         end;
+      end Build_Path;
+
+      ----------
+      -- Copy --
+      ----------
+
+      procedure Copy (From : File_Descriptor; To : File_Descriptor) is
+         Buf_Size : constant := 200_000;
+         type Buf is array (1 .. Buf_Size) of Character;
+         type Buf_Ptr is access Buf;
+
+         Buffer : Buf_Ptr;
+         R      : Integer;
+         W      : Integer;
+
+         Status_From : Boolean;
+         Status_To   : Boolean;
+         --  Statuses for the calls to Close
+
+         procedure Free is new Ada.Unchecked_Deallocation (Buf, Buf_Ptr);
+
+      begin
+         --  Check for invalid descriptors, making sure that we do not
+         --  accidentally leave an open file descriptor around.
+
+         if From = Invalid_FD then
+            if To /= Invalid_FD then
+               Close (To, Status_To);
+            end if;
+
+            raise Copy_Error;
+
+         elsif To = Invalid_FD then
+            Close (From, Status_From);
+            raise Copy_Error;
+         end if;
+
+         --  Allocate the buffer on the heap
+
+         Buffer := new Buf;
+
+         loop
+            R := Read (From, Buffer (1)'Address, Buf_Size);
+
+            --  On some systems, the buffer may not be full. So, we need to try
+            --  again until there is nothing to read.
+
+            exit when R = 0;
+
+            W := Write (To, Buffer (1)'Address, R);
+
+            if W < R then
+
+               --  Problem writing data, could be a disk full. Close files
+               --  without worrying about status, since we are raising a
+               --  Copy_Error exception in any case.
+
+               Close (From, Status_From);
+               Close (To, Status_To);
+
+               Free (Buffer);
+
+               raise Copy_Error;
+            end if;
+         end loop;
+
+         Close (From, Status_From);
+         Close (To, Status_To);
+
+         Free (Buffer);
+
+         if not (Status_From and Status_To) then
+            raise Copy_Error;
+         end if;
+      end Copy;
+
+      -------------
+      -- Copy_To --
+      -------------
+
+      procedure Copy_To (To_Name : String) is
+         C_From : String (1 .. Name'Length + 1);
+         C_To   : String (1 .. To_Name'Length + 1);
+
+      begin
+         From := Open_Read (Name, Binary);
+
+         --  Do not clobber destination file if source file could not be opened
+
+         if From /= Invalid_FD then
+            To := Create_File (To_Name, Binary);
+         end if;
+
+         Copy (From, To);
+
+         --  Copy attributes
+
+         C_From (1 .. Name'Length) := Name;
+         C_From (C_From'Last) := ASCII.NUL;
+
+         C_To (1 .. To_Name'Length) := To_Name;
+         C_To (C_To'Last) := ASCII.NUL;
+
+         case Preserve is
+            when Time_Stamps =>
+               if Copy_Attributes (C_From'Address, C_To'Address, 0) = -1 then
+                  raise Copy_Error;
+               end if;
+
+            when Full =>
+               if Copy_Attributes (C_From'Address, C_To'Address, 1) = -1 then
+                  raise Copy_Error;
+               end if;
+
+            when None =>
+               null;
+         end case;
+      end Copy_To;
+
+   --  Start of processing for Copy_File
+
+   begin
+      Success := True;
+
+      --  The source file must exist
+
+      if not Is_Regular_File (Name) then
+         raise Copy_Error;
+      end if;
+
+      --  The source file exists
+
+      case Mode is
+
+         --  Copy case, target file must not exist
+
+         when Copy =>
+
+            --  If the target file exists, we have an error
+
+            if Is_Regular_File (Pathname) then
+               raise Copy_Error;
+
+            --  Case of target is a directory
+
+            elsif Is_Directory (Pathname) then
+               declare
+                  Dest : constant String := Build_Path (Pathname, Name);
+
+               begin
+                  --  If target file exists, we have an error, else do copy
+
+                  if Is_Regular_File (Dest) then
+                     raise Copy_Error;
+                  else
+                     Copy_To (Dest);
+                  end if;
+               end;
+
+            --  Case of normal copy to file (destination does not exist)
+
+            else
+               Copy_To (Pathname);
+            end if;
+
+         --  Overwrite case (destination file may or may not exist)
+
+         when Overwrite =>
+            if Is_Directory (Pathname) then
+               Copy_To (Build_Path (Pathname, Name));
+            else
+               Copy_To (Pathname);
+            end if;
+
+         --  Append case (destination file may or may not exist)
+
+         when Append =>
+
+            --  Appending to existing file
+
+            if Is_Regular_File (Pathname) then
+
+               --  Append mode and destination file exists, append data at the
+               --  end of Pathname. But if we fail to open source file, do not
+               --  touch destination file at all.
+
+               From := Open_Read (Name, Binary);
+               if From /= Invalid_FD then
+                  To := Open_Read_Write (Pathname, Binary);
+               end if;
+
+               Lseek (To, 0, Seek_End);
+
+               Copy (From, To);
+
+            --  Appending to directory, not allowed
+
+            elsif Is_Directory (Pathname) then
+               raise Copy_Error;
+
+            --  Appending when target file does not exist
+
+            else
+               Copy_To (Pathname);
+            end if;
+      end case;
+
+   --  All error cases are caught here
+
+   exception
+      when Copy_Error =>
+         Success := False;
+   end Copy_File;
+
+   procedure Copy_File
+     (Name     : C_File_Name;
+      Pathname : C_File_Name;
+      Success  : out Boolean;
+      Mode     : Copy_Mode := Copy;
+      Preserve : Attribute := Time_Stamps)
+   is
+      Ada_Name     : String_Access :=
+                       To_Path_String_Access
+                         (Name, C_String_Length (Name));
+      Ada_Pathname : String_Access :=
+                       To_Path_String_Access
+                         (Pathname, C_String_Length (Pathname));
+
+   begin
+      Copy_File (Ada_Name.all, Ada_Pathname.all, Success, Mode, Preserve);
+      Free (Ada_Name);
+      Free (Ada_Pathname);
+   end Copy_File;
+
+   --------------------------
+   -- Copy_File_Attributes --
+   --------------------------
+
+   procedure Copy_File_Attributes
+     (From             : String;
+      To               : String;
+      Success          : out Boolean;
+      Copy_Timestamp   : Boolean := True;
+      Copy_Permissions : Boolean := True)
+   is
+      F : aliased String (1 .. From'Length + 1);
+      T : aliased String (1 .. To'Length + 1);
+
+      Mode : Integer;
+
+   begin
+      if Copy_Timestamp then
+         if Copy_Permissions then
+            Mode := 1;
+         else
+            Mode := 0;
+         end if;
+      else
+         if Copy_Permissions then
+            Mode := 2;
+         else
+            Success := True;
+            return;  --  nothing to do
+         end if;
+      end if;
+
+      F (1 .. From'Length) := From;
+      F (F'Last) := ASCII.NUL;
+
+      T (1 .. To'Length) := To;
+      T (T'Last) := ASCII.NUL;
+
+      Success := Copy_Attributes (F'Address, T'Address, Mode) /= -1;
+   end Copy_File_Attributes;
+
+   ----------------------
+   -- Copy_Time_Stamps --
+   ----------------------
+
+   procedure Copy_Time_Stamps
+     (Source  : String;
+      Dest    : String;
+      Success : out Boolean)
+   is
+   begin
+      if Is_Regular_File (Source) and then Is_Writable_File (Dest) then
+         declare
+            C_Source : String (1 .. Source'Length + 1);
+            C_Dest   : String (1 .. Dest'Length + 1);
+
+         begin
+            C_Source (1 .. Source'Length) := Source;
+            C_Source (C_Source'Last)      := ASCII.NUL;
+
+            C_Dest (1 .. Dest'Length) := Dest;
+            C_Dest (C_Dest'Last)      := ASCII.NUL;
+
+            if Copy_Attributes (C_Source'Address, C_Dest'Address, 0) = -1 then
+               Success := False;
+            else
+               Success := True;
+            end if;
+         end;
+
+      else
+         Success := False;
+      end if;
+   end Copy_Time_Stamps;
+
+   procedure Copy_Time_Stamps
+     (Source  : C_File_Name;
+      Dest    : C_File_Name;
+      Success : out Boolean)
+   is
+      Ada_Source : String_Access :=
+                     To_Path_String_Access
+                       (Source, C_String_Length (Source));
+      Ada_Dest   : String_Access :=
+                     To_Path_String_Access
+                       (Dest, C_String_Length (Dest));
+
+   begin
+      Copy_Time_Stamps (Ada_Source.all, Ada_Dest.all, Success);
+      Free (Ada_Source);
+      Free (Ada_Dest);
+   end Copy_Time_Stamps;
+
+   -----------------
+   -- Create_File --
+   -----------------
+
+   function Create_File
+     (Name  : C_File_Name;
+      Fmode : Mode) return File_Descriptor
+   is
+      function C_Create_File
+        (Name  : C_File_Name;
+         Fmode : Mode) return File_Descriptor;
+      pragma Import (C, C_Create_File, "__gnat_open_create");
+   begin
+      return C_Create_File (Name, Fmode);
+   end Create_File;
+
+   function Create_File
+     (Name  : String;
+      Fmode : Mode) return File_Descriptor
+   is
+      C_Name : String (1 .. Name'Length + 1);
+   begin
+      C_Name (1 .. Name'Length) := Name;
+      C_Name (C_Name'Last)      := ASCII.NUL;
+      return Create_File (C_Name (C_Name'First)'Address, Fmode);
+   end Create_File;
+
+   ---------------------
+   -- Create_New_File --
+   ---------------------
+
+   function Create_New_File
+     (Name  : C_File_Name;
+      Fmode : Mode) return File_Descriptor
+   is
+      function C_Create_New_File
+        (Name  : C_File_Name;
+         Fmode : Mode) return File_Descriptor;
+      pragma Import (C, C_Create_New_File, "__gnat_open_new");
+   begin
+      return C_Create_New_File (Name, Fmode);
+   end Create_New_File;
+
+   function Create_New_File
+     (Name  : String;
+      Fmode : Mode) return File_Descriptor
+   is
+      C_Name : String (1 .. Name'Length + 1);
+   begin
+      C_Name (1 .. Name'Length) := Name;
+      C_Name (C_Name'Last)      := ASCII.NUL;
+      return Create_New_File (C_Name (C_Name'First)'Address, Fmode);
+   end Create_New_File;
+
+   -----------------------------
+   -- Create_Output_Text_File --
+   -----------------------------
+
+   function Create_Output_Text_File (Name : String) return File_Descriptor is
+      function C_Create_File (Name : C_File_Name) return File_Descriptor;
+      pragma Import (C, C_Create_File, "__gnat_create_output_file");
+
+      C_Name : String (1 .. Name'Length + 1);
+
+   begin
+      C_Name (1 .. Name'Length) := Name;
+      C_Name (C_Name'Last)      := ASCII.NUL;
+      return C_Create_File (C_Name (C_Name'First)'Address);
+   end Create_Output_Text_File;
+
+   ----------------------
+   -- Create_Temp_File --
+   ----------------------
+
+   procedure Create_Temp_File
+     (FD   : out File_Descriptor;
+      Name : out Temp_File_Name)
+   is
+      function Open_New_Temp
+        (Name  : System.Address;
+         Fmode : Mode) return File_Descriptor;
+      pragma Import (C, Open_New_Temp, "__gnat_open_new_temp");
+
+   begin
+      FD := Open_New_Temp (Name'Address, Binary);
+   end Create_Temp_File;
+
+   procedure Create_Temp_File
+     (FD   : out File_Descriptor;
+      Name : out String_Access)
+   is
+   begin
+      Create_Temp_File_Internal (FD, Name, Stdout => False);
+   end Create_Temp_File;
+
+   -----------------------------
+   -- Create_Temp_Output_File --
+   -----------------------------
+
+   procedure Create_Temp_Output_File
+     (FD   : out File_Descriptor;
+      Name : out String_Access)
+   is
+   begin
+      Create_Temp_File_Internal (FD, Name, Stdout => True);
+   end Create_Temp_Output_File;
+
+   -------------------------------
+   -- Create_Temp_File_Internal --
+   -------------------------------
+
+   procedure Create_Temp_File_Internal
+     (FD     : out File_Descriptor;
+      Name   : out String_Access;
+      Stdout : Boolean)
+   is
+      Pos      : Positive;
+      Attempts : Natural := 0;
+      Current  : String (Current_Temp_File_Name'Range);
+
+      function Create_New_Output_Text_File
+        (Name : String) return File_Descriptor;
+      --  Similar to Create_Output_Text_File, except it fails if the file
+      --  already exists. We need this behavior to ensure we don't accidentally
+      --  open a temp file that has just been created by a concurrently running
+      --  process. There is no point exposing this function, as it's generally
+      --  not particularly useful.
+
+      ---------------------------------
+      -- Create_New_Output_Text_File --
+      ---------------------------------
+
+      function Create_New_Output_Text_File
+        (Name : String) return File_Descriptor
+      is
+         function C_Create_File (Name : C_File_Name) return File_Descriptor;
+         pragma Import (C, C_Create_File, "__gnat_create_output_file_new");
+
+         C_Name : String (1 .. Name'Length + 1);
+
+      begin
+         C_Name (1 .. Name'Length) := Name;
+         C_Name (C_Name'Last)      := ASCII.NUL;
+         return C_Create_File (C_Name (C_Name'First)'Address);
+      end Create_New_Output_Text_File;
+
+   --  Start of processing for Create_Temp_File_Internal
+
+   begin
+      --  Loop until a new temp file can be created
+
+      File_Loop : loop
+         Locked : begin
+
+            --  We need to protect global variable Current_Temp_File_Name
+            --  against concurrent access by different tasks.
+
+            SSL.Lock_Task.all;
+
+            --  Start at the last digit
+
+            Pos := Temp_File_Name_Last_Digit;
+
+            Digit_Loop :
+            loop
+               --  Increment the digit by one
+
+               case Current_Temp_File_Name (Pos) is
+                  when '0' .. '8' =>
+                     Current_Temp_File_Name (Pos) :=
+                       Character'Succ (Current_Temp_File_Name (Pos));
+                     exit Digit_Loop;
+
+                  when '9' =>
+
+                     --  For 9, set the digit to 0 and go to the previous digit
+
+                     Current_Temp_File_Name (Pos) := '0';
+                     Pos := Pos - 1;
+
+                  when others =>
+
+                     --  If it is not a digit, then there are no available
+                     --  temp file names. Return Invalid_FD. There is almost no
+                     --  chance that this code will be ever be executed, since
+                     --  it would mean that there are one million temp files in
+                     --  the same directory.
+
+                     SSL.Unlock_Task.all;
+                     FD := Invalid_FD;
+                     Name := null;
+                     exit File_Loop;
+               end case;
+            end loop Digit_Loop;
+
+            Current := Current_Temp_File_Name;
+
+            --  We can now release the lock, because we are no longer accessing
+            --  Current_Temp_File_Name.
+
+            SSL.Unlock_Task.all;
+
+         exception
+            when others =>
+               SSL.Unlock_Task.all;
+               raise;
+         end Locked;
+
+         --  Attempt to create the file
+
+         if Stdout then
+            FD := Create_New_Output_Text_File (Current);
+         else
+            FD := Create_New_File (Current, Binary);
+         end if;
+
+         if FD /= Invalid_FD then
+            Name := new String'(Current);
+            exit File_Loop;
+         end if;
+
+         if not Is_Regular_File (Current) then
+
+            --  If the file does not already exist and we are unable to create
+            --  it, we give up after Max_Attempts. Otherwise, we try again with
+            --  the next available file name.
+
+            Attempts := Attempts + 1;
+
+            if Attempts >= Max_Attempts then
+               FD := Invalid_FD;
+               Name := null;
+               exit File_Loop;
+            end if;
+         end if;
+      end loop File_Loop;
+   end Create_Temp_File_Internal;
+
+   -------------------------
+   -- Current_Time_String --
+   -------------------------
+
+   function Current_Time_String return String is
+      subtype S23 is String (1 .. 23);
+      --  Holds current time in ISO 8601 format YYYY-MM-DD HH:MM:SS.SS + NUL
+
+      procedure Current_Time_String (Time : System.Address);
+      pragma Import (C, Current_Time_String, "__gnat_current_time_string");
+      --  Puts current time into Time in above ISO 8601 format
+
+      Result23 : aliased S23;
+      --  Current time in ISO 8601 format
+
+   begin
+      Current_Time_String (Result23'Address);
+      return Result23 (1 .. 19);
+   end Current_Time_String;
+
+   -----------------
+   -- Delete_File --
+   -----------------
+
+   procedure Delete_File (Name : Address; Success : out Boolean) is
+      R : Integer;
+   begin
+      R := System.CRTL.unlink (Name);
+      Success := (R = 0);
+   end Delete_File;
+
+   procedure Delete_File (Name : String; Success : out Boolean) is
+      C_Name : String (1 .. Name'Length + 1);
+   begin
+      C_Name (1 .. Name'Length) := Name;
+      C_Name (C_Name'Last)      := ASCII.NUL;
+      Delete_File (C_Name'Address, Success);
+   end Delete_File;
+
+   -------------------
+   -- Errno_Message --
+   -------------------
+
+   function Errno_Message
+     (Err     : Integer := Errno;
+      Default : String  := "") return String
+   is
+      function strerror (errnum : Integer) return System.Address;
+      pragma Import (C, strerror, "strerror");
+
+      C_Msg : constant System.Address := strerror (Err);
+
+   begin
+      if C_Msg = Null_Address then
+         if Default /= "" then
+            return Default;
+
+         else
+            --  Note: for bootstrap reasons, it is impractical
+            --  to use Integer'Image here.
+
+            declare
+               Val   : Integer;
+               First : Integer;
+
+               Buf : String (1 .. 20);
+               --  Buffer large enough to hold image of largest Integer values
+
+            begin
+               Val   := abs Err;
+               First := Buf'Last;
+               loop
+                  Buf (First) :=
+                    Character'Val (Character'Pos ('0') + Val mod 10);
+                  Val := Val / 10;
+                  exit when Val = 0;
+                  First := First - 1;
+               end loop;
+
+               if Err < 0 then
+                  First := First - 1;
+                  Buf (First) := '-';
+               end if;
+
+               return "errno = " & Buf (First .. Buf'Last);
+            end;
+         end if;
+
+      else
+         declare
+            Msg : String (1 .. Integer (CRTL.strlen (C_Msg)));
+            for Msg'Address use C_Msg;
+            pragma Import (Ada, Msg);
+         begin
+            return Msg;
+         end;
+      end if;
+   end Errno_Message;
+
+   ---------------------
+   -- File_Time_Stamp --
+   ---------------------
+
+   function File_Time_Stamp (FD : File_Descriptor) return OS_Time is
+      function File_Time (FD : File_Descriptor) return OS_Time;
+      pragma Import (C, File_Time, "__gnat_file_time_fd");
+   begin
+      return File_Time (FD);
+   end File_Time_Stamp;
+
+   function File_Time_Stamp (Name : C_File_Name) return OS_Time is
+      function File_Time (Name : Address) return OS_Time;
+      pragma Import (C, File_Time, "__gnat_file_time_name");
+   begin
+      return File_Time (Name);
+   end File_Time_Stamp;
+
+   function File_Time_Stamp (Name : String) return OS_Time is
+      F_Name : String (1 .. Name'Length + 1);
+   begin
+      F_Name (1 .. Name'Length) := Name;
+      F_Name (F_Name'Last)      := ASCII.NUL;
+      return File_Time_Stamp (F_Name'Address);
+   end File_Time_Stamp;
+
+   ---------------------------
+   -- Get_Debuggable_Suffix --
+   ---------------------------
+
+   function Get_Debuggable_Suffix return String_Access is
+      procedure Get_Suffix_Ptr (Length, Ptr : Address);
+      pragma Import (C, Get_Suffix_Ptr, "__gnat_get_debuggable_suffix_ptr");
+
+      Result        : String_Access;
+      Suffix_Length : Integer;
+      Suffix_Ptr    : Address;
+
+   begin
+      Get_Suffix_Ptr (Suffix_Length'Address, Suffix_Ptr'Address);
+      Result := new String (1 .. Suffix_Length);
+
+      if Suffix_Length > 0 then
+         Strncpy (Result.all'Address, Suffix_Ptr, size_t (Suffix_Length));
+      end if;
+
+      return Result;
+   end Get_Debuggable_Suffix;
+
+   ---------------------------
+   -- Get_Executable_Suffix --
+   ---------------------------
+
+   function Get_Executable_Suffix return String_Access is
+      procedure Get_Suffix_Ptr (Length, Ptr : Address);
+      pragma Import (C, Get_Suffix_Ptr, "__gnat_get_executable_suffix_ptr");
+
+      Result        : String_Access;
+      Suffix_Length : Integer;
+      Suffix_Ptr    : Address;
+
+   begin
+      Get_Suffix_Ptr (Suffix_Length'Address, Suffix_Ptr'Address);
+      Result := new String (1 .. Suffix_Length);
+
+      if Suffix_Length > 0 then
+         Strncpy (Result.all'Address, Suffix_Ptr, size_t (Suffix_Length));
+      end if;
+
+      return Result;
+   end Get_Executable_Suffix;
+
+   -----------------------
+   -- Get_Object_Suffix --
+   -----------------------
+
+   function Get_Object_Suffix return String_Access is
+      procedure Get_Suffix_Ptr (Length, Ptr : Address);
+      pragma Import (C, Get_Suffix_Ptr, "__gnat_get_object_suffix_ptr");
+
+      Result        : String_Access;
+      Suffix_Length : Integer;
+      Suffix_Ptr    : Address;
+
+   begin
+      Get_Suffix_Ptr (Suffix_Length'Address, Suffix_Ptr'Address);
+      Result := new String (1 .. Suffix_Length);
+
+      if Suffix_Length > 0 then
+         Strncpy (Result.all'Address, Suffix_Ptr, size_t (Suffix_Length));
+      end if;
+
+      return Result;
+   end Get_Object_Suffix;
+
+   ----------------------------------
+   -- Get_Target_Debuggable_Suffix --
+   ----------------------------------
+
+   function Get_Target_Debuggable_Suffix return String_Access is
+      Target_Exec_Ext_Ptr : Address;
+      pragma Import
+        (C, Target_Exec_Ext_Ptr, "__gnat_target_debuggable_extension");
+
+      Result        : String_Access;
+      Suffix_Length : Integer;
+
+   begin
+      Suffix_Length := Integer (CRTL.strlen (Target_Exec_Ext_Ptr));
+      Result := new String (1 .. Suffix_Length);
+
+      if Suffix_Length > 0 then
+         Strncpy
+           (Result.all'Address, Target_Exec_Ext_Ptr, size_t (Suffix_Length));
+      end if;
+
+      return Result;
+   end Get_Target_Debuggable_Suffix;
+
+   ----------------------------------
+   -- Get_Target_Executable_Suffix --
+   ----------------------------------
+
+   function Get_Target_Executable_Suffix return String_Access is
+      Target_Exec_Ext_Ptr : Address;
+      pragma Import
+        (C, Target_Exec_Ext_Ptr, "__gnat_target_executable_extension");
+
+      Result        : String_Access;
+      Suffix_Length : Integer;
+
+   begin
+      Suffix_Length := Integer (CRTL.strlen (Target_Exec_Ext_Ptr));
+      Result := new String (1 .. Suffix_Length);
+
+      if Suffix_Length > 0 then
+         Strncpy
+           (Result.all'Address, Target_Exec_Ext_Ptr, size_t (Suffix_Length));
+      end if;
+
+      return Result;
+   end Get_Target_Executable_Suffix;
+
+   ------------------------------
+   -- Get_Target_Object_Suffix --
+   ------------------------------
+
+   function Get_Target_Object_Suffix return String_Access is
+      Target_Object_Ext_Ptr : Address;
+      pragma Import
+        (C, Target_Object_Ext_Ptr, "__gnat_target_object_extension");
+
+      Result        : String_Access;
+      Suffix_Length : Integer;
+
+   begin
+      Suffix_Length := Integer (CRTL.strlen (Target_Object_Ext_Ptr));
+      Result := new String (1 .. Suffix_Length);
+
+      if Suffix_Length > 0 then
+         Strncpy
+           (Result.all'Address, Target_Object_Ext_Ptr, size_t (Suffix_Length));
+      end if;
+
+      return Result;
+   end Get_Target_Object_Suffix;
+
+   ------------
+   -- Getenv --
+   ------------
+
+   function Getenv (Name : String) return String_Access is
+      procedure Get_Env_Value_Ptr (Name, Length, Ptr : Address);
+      pragma Import (C, Get_Env_Value_Ptr, "__gnat_getenv");
+
+      Env_Value_Ptr    : aliased Address;
+      Env_Value_Length : aliased Integer;
+      F_Name           : aliased String (1 .. Name'Length + 1);
+      Result           : String_Access;
+
+   begin
+      F_Name (1 .. Name'Length) := Name;
+      F_Name (F_Name'Last)      := ASCII.NUL;
+
+      Get_Env_Value_Ptr
+        (F_Name'Address, Env_Value_Length'Address, Env_Value_Ptr'Address);
+
+      Result := new String (1 .. Env_Value_Length);
+
+      if Env_Value_Length > 0 then
+         Strncpy
+           (Result.all'Address, Env_Value_Ptr, size_t (Env_Value_Length));
+      end if;
+
+      return Result;
+   end Getenv;
+
+   ------------
+   -- GM_Day --
+   ------------
+
+   function GM_Day (Date : OS_Time) return Day_Type is
+      D  : Day_Type;
+
+      Y  : Year_Type;
+      Mo : Month_Type;
+      H  : Hour_Type;
+      Mn : Minute_Type;
+      S  : Second_Type;
+      pragma Unreferenced (Y, Mo, H, Mn, S);
+
+   begin
+      GM_Split (Date, Y, Mo, D, H, Mn, S);
+      return D;
+   end GM_Day;
+
+   -------------
+   -- GM_Hour --
+   -------------
+
+   function GM_Hour (Date : OS_Time) return Hour_Type is
+      H  : Hour_Type;
+
+      Y  : Year_Type;
+      Mo : Month_Type;
+      D  : Day_Type;
+      Mn : Minute_Type;
+      S  : Second_Type;
+      pragma Unreferenced (Y, Mo, D, Mn, S);
+
+   begin
+      GM_Split (Date, Y, Mo, D, H, Mn, S);
+      return H;
+   end GM_Hour;
+
+   ---------------
+   -- GM_Minute --
+   ---------------
+
+   function GM_Minute (Date : OS_Time) return Minute_Type is
+      Mn : Minute_Type;
+
+      Y  : Year_Type;
+      Mo : Month_Type;
+      D  : Day_Type;
+      H  : Hour_Type;
+      S  : Second_Type;
+      pragma Unreferenced (Y, Mo, D, H, S);
+
+   begin
+      GM_Split (Date, Y, Mo, D, H, Mn, S);
+      return Mn;
+   end GM_Minute;
+
+   --------------
+   -- GM_Month --
+   --------------
+
+   function GM_Month (Date : OS_Time) return Month_Type is
+      Mo : Month_Type;
+
+      Y  : Year_Type;
+      D  : Day_Type;
+      H  : Hour_Type;
+      Mn : Minute_Type;
+      S  : Second_Type;
+      pragma Unreferenced (Y, D, H, Mn, S);
+
+   begin
+      GM_Split (Date, Y, Mo, D, H, Mn, S);
+      return Mo;
+   end GM_Month;
+
+   ---------------
+   -- GM_Second --
+   ---------------
+
+   function GM_Second (Date : OS_Time) return Second_Type is
+      S  : Second_Type;
+
+      Y  : Year_Type;
+      Mo : Month_Type;
+      D  : Day_Type;
+      H  : Hour_Type;
+      Mn : Minute_Type;
+      pragma Unreferenced (Y, Mo, D, H, Mn);
+
+   begin
+      GM_Split (Date, Y, Mo, D, H, Mn, S);
+      return S;
+   end GM_Second;
+
+   --------------
+   -- GM_Split --
+   --------------
+
+   procedure GM_Split
+     (Date   : OS_Time;
+      Year   : out Year_Type;
+      Month  : out Month_Type;
+      Day    : out Day_Type;
+      Hour   : out Hour_Type;
+      Minute : out Minute_Type;
+      Second : out Second_Type)
+   is
+      procedure To_GM_Time
+        (P_Time_T : Address;
+         P_Year   : Address;
+         P_Month  : Address;
+         P_Day    : Address;
+         P_Hours  : Address;
+         P_Mins   : Address;
+         P_Secs   : Address);
+      pragma Import (C, To_GM_Time, "__gnat_to_gm_time");
+
+      T  : OS_Time := Date;
+      Y  : Integer;
+      Mo : Integer;
+      D  : Integer;
+      H  : Integer;
+      Mn : Integer;
+      S  : Integer;
+
+   begin
+      --  Use the global lock because To_GM_Time is not thread safe
+
+      Locked_Processing : begin
+         SSL.Lock_Task.all;
+         To_GM_Time
+           (P_Time_T => T'Address,
+            P_Year   => Y'Address,
+            P_Month  => Mo'Address,
+            P_Day    => D'Address,
+            P_Hours  => H'Address,
+            P_Mins   => Mn'Address,
+            P_Secs   => S'Address);
+         SSL.Unlock_Task.all;
+
+      exception
+         when others =>
+            SSL.Unlock_Task.all;
+            raise;
+      end Locked_Processing;
+
+      Year   := Y + 1900;
+      Month  := Mo + 1;
+      Day    := D;
+      Hour   := H;
+      Minute := Mn;
+      Second := S;
+   end GM_Split;
+
+   ----------------
+   -- GM_Time_Of --
+   ----------------
+
+   function GM_Time_Of
+     (Year   : Year_Type;
+      Month  : Month_Type;
+      Day    : Day_Type;
+      Hour   : Hour_Type;
+      Minute : Minute_Type;
+      Second : Second_Type) return OS_Time
+   is
+      procedure To_OS_Time
+        (P_Time_T : Address;
+         P_Year   : Integer;
+         P_Month  : Integer;
+         P_Day    : Integer;
+         P_Hours  : Integer;
+         P_Mins   : Integer;
+         P_Secs   : Integer);
+      pragma Import (C, To_OS_Time, "__gnat_to_os_time");
+
+      Result : OS_Time;
+
+   begin
+      To_OS_Time
+        (P_Time_T => Result'Address,
+         P_Year   => Year - 1900,
+         P_Month  => Month - 1,
+         P_Day    => Day,
+         P_Hours  => Hour,
+         P_Mins   => Minute,
+         P_Secs   => Second);
+      return Result;
+   end GM_Time_Of;
+
+   -------------
+   -- GM_Year --
+   -------------
+
+   function GM_Year (Date : OS_Time) return Year_Type is
+      Y  : Year_Type;
+
+      Mo : Month_Type;
+      D  : Day_Type;
+      H  : Hour_Type;
+      Mn : Minute_Type;
+      S  : Second_Type;
+      pragma Unreferenced (Mo, D, H, Mn, S);
+
+   begin
+      GM_Split (Date, Y, Mo, D, H, Mn, S);
+      return Y;
+   end GM_Year;
+
+   ----------------------
+   -- Is_Absolute_Path --
+   ----------------------
+
+   function Is_Absolute_Path (Name : String) return Boolean is
+      function Is_Absolute_Path
+        (Name   : Address;
+         Length : Integer) return Integer;
+      pragma Import (C, Is_Absolute_Path, "__gnat_is_absolute_path");
+   begin
+      return Is_Absolute_Path (Name'Address, Name'Length) /= 0;
+   end Is_Absolute_Path;
+
+   ------------------
+   -- Is_Directory --
+   ------------------
+
+   function Is_Directory (Name : C_File_Name) return Boolean is
+      function Is_Directory (Name : Address) return Integer;
+      pragma Import (C, Is_Directory, "__gnat_is_directory");
+   begin
+      return Is_Directory (Name) /= 0;
+   end Is_Directory;
+
+   function Is_Directory (Name : String) return Boolean is
+      F_Name : String (1 .. Name'Length + 1);
+   begin
+      F_Name (1 .. Name'Length) := Name;
+      F_Name (F_Name'Last)      := ASCII.NUL;
+      return Is_Directory (F_Name'Address);
+   end Is_Directory;
+
+   -----------------------------
+   -- Is_Read_Accessible_File --
+   -----------------------------
+
+   function Is_Read_Accessible_File (Name : String) return Boolean is
+      function Is_Read_Accessible_File (Name : Address) return Integer;
+      pragma Import
+        (C, Is_Read_Accessible_File, "__gnat_is_read_accessible_file");
+      F_Name : String (1 .. Name'Length + 1);
+
+   begin
+      F_Name (1 .. Name'Length) := Name;
+      F_Name (F_Name'Last)      := ASCII.NUL;
+      return Is_Read_Accessible_File (F_Name'Address) /= 0;
+   end Is_Read_Accessible_File;
+
+   ----------------------------
+   -- Is_Owner_Readable_File --
+   ----------------------------
+
+   function Is_Owner_Readable_File (Name : C_File_Name) return Boolean is
+      function Is_Readable_File (Name : Address) return Integer;
+      pragma Import (C, Is_Readable_File, "__gnat_is_readable_file");
+   begin
+      return Is_Readable_File (Name) /= 0;
+   end Is_Owner_Readable_File;
+
+   function Is_Owner_Readable_File (Name : String) return Boolean is
+      F_Name : String (1 .. Name'Length + 1);
+   begin
+      F_Name (1 .. Name'Length) := Name;
+      F_Name (F_Name'Last)      := ASCII.NUL;
+      return Is_Owner_Readable_File (F_Name'Address);
+   end Is_Owner_Readable_File;
+
+   ------------------------
+   -- Is_Executable_File --
+   ------------------------
+
+   function Is_Executable_File (Name : C_File_Name) return Boolean is
+      function Is_Executable_File (Name : Address) return Integer;
+      pragma Import (C, Is_Executable_File, "__gnat_is_executable_file");
+   begin
+      return Is_Executable_File (Name) /= 0;
+   end Is_Executable_File;
+
+   function Is_Executable_File (Name : String) return Boolean is
+      F_Name : String (1 .. Name'Length + 1);
+   begin
+      F_Name (1 .. Name'Length) := Name;
+      F_Name (F_Name'Last)      := ASCII.NUL;
+      return Is_Executable_File (F_Name'Address);
+   end Is_Executable_File;
+
+   ---------------------
+   -- Is_Regular_File --
+   ---------------------
+
+   function Is_Regular_File (Name : C_File_Name) return Boolean is
+      function Is_Regular_File (Name : Address) return Integer;
+      pragma Import (C, Is_Regular_File, "__gnat_is_regular_file");
+   begin
+      return Is_Regular_File (Name) /= 0;
+   end Is_Regular_File;
+
+   function Is_Regular_File (Name : String) return Boolean is
+      F_Name : String (1 .. Name'Length + 1);
+   begin
+      F_Name (1 .. Name'Length) := Name;
+      F_Name (F_Name'Last)      := ASCII.NUL;
+      return Is_Regular_File (F_Name'Address);
+   end Is_Regular_File;
+
+   ----------------------
+   -- Is_Symbolic_Link --
+   ----------------------
+
+   function Is_Symbolic_Link (Name : C_File_Name) return Boolean is
+      function Is_Symbolic_Link (Name : Address) return Integer;
+      pragma Import (C, Is_Symbolic_Link, "__gnat_is_symbolic_link");
+   begin
+      return Is_Symbolic_Link (Name) /= 0;
+   end Is_Symbolic_Link;
+
+   function Is_Symbolic_Link (Name : String) return Boolean is
+      F_Name : String (1 .. Name'Length + 1);
+   begin
+      F_Name (1 .. Name'Length) := Name;
+      F_Name (F_Name'Last)      := ASCII.NUL;
+      return Is_Symbolic_Link (F_Name'Address);
+   end Is_Symbolic_Link;
+
+   ------------------------------
+   -- Is_Write_Accessible_File --
+   ------------------------------
+
+   function Is_Write_Accessible_File (Name : String) return Boolean is
+      function Is_Write_Accessible_File (Name : Address) return Integer;
+      pragma Import
+        (C, Is_Write_Accessible_File, "__gnat_is_write_accessible_file");
+      F_Name : String (1 .. Name'Length + 1);
+
+   begin
+      F_Name (1 .. Name'Length) := Name;
+      F_Name (F_Name'Last)      := ASCII.NUL;
+      return Is_Write_Accessible_File (F_Name'Address) /= 0;
+   end Is_Write_Accessible_File;
+
+   ----------------------------
+   -- Is_Owner_Writable_File --
+   ----------------------------
+
+   function Is_Owner_Writable_File (Name : C_File_Name) return Boolean is
+      function Is_Writable_File (Name : Address) return Integer;
+      pragma Import (C, Is_Writable_File, "__gnat_is_writable_file");
+   begin
+      return Is_Writable_File (Name) /= 0;
+   end Is_Owner_Writable_File;
+
+   function Is_Owner_Writable_File (Name : String) return Boolean is
+      F_Name : String (1 .. Name'Length + 1);
+   begin
+      F_Name (1 .. Name'Length) := Name;
+      F_Name (F_Name'Last)      := ASCII.NUL;
+      return Is_Owner_Writable_File (F_Name'Address);
+   end Is_Owner_Writable_File;
+
+   ----------
+   -- Kill --
+   ----------
+
+   procedure Kill (Pid : Process_Id; Hard_Kill : Boolean := True) is
+      SIGKILL : constant := 9;
+      SIGINT  : constant := 2;
+
+      procedure C_Kill (Pid : Process_Id; Sig_Num : Integer; Close : Integer);
+      pragma Import (C, C_Kill, "__gnat_kill");
+
+   begin
+      if Hard_Kill then
+         C_Kill (Pid, SIGKILL, 1);
+      else
+         C_Kill (Pid, SIGINT, 1);
+      end if;
+   end Kill;
+
+   -----------------------
+   -- Kill_Process_Tree --
+   -----------------------
+
+   procedure Kill_Process_Tree
+     (Pid : Process_Id; Hard_Kill : Boolean := True)
+   is
+      SIGKILL : constant := 9;
+      SIGINT  : constant := 2;
+
+      procedure C_Kill_PT (Pid : Process_Id; Sig_Num : Integer);
+      pragma Import (C, C_Kill_PT, "__gnat_killprocesstree");
+
+   begin
+      if Hard_Kill then
+         C_Kill_PT (Pid, SIGKILL);
+      else
+         C_Kill_PT (Pid, SIGINT);
+      end if;
+   end Kill_Process_Tree;
+
+   -------------------------
+   -- Locate_Exec_On_Path --
+   -------------------------
+
+   function Locate_Exec_On_Path
+     (Exec_Name : String) return String_Access
+   is
+      function Locate_Exec_On_Path (C_Exec_Name : Address) return Address;
+      pragma Import (C, Locate_Exec_On_Path, "__gnat_locate_exec_on_path");
+
+      C_Exec_Name  : String (1 .. Exec_Name'Length + 1);
+      Path_Addr    : Address;
+      Path_Len     : Integer;
+      Result       : String_Access;
+
+   begin
+      C_Exec_Name (1 .. Exec_Name'Length)   := Exec_Name;
+      C_Exec_Name (C_Exec_Name'Last)        := ASCII.NUL;
+
+      Path_Addr := Locate_Exec_On_Path (C_Exec_Name'Address);
+      Path_Len  := C_String_Length (Path_Addr);
+
+      if Path_Len = 0 then
+         return null;
+
+      else
+         Result := To_Path_String_Access (Path_Addr, Path_Len);
+         CRTL.free (Path_Addr);
+
+         --  Always return an absolute path name
+
+         if not Is_Absolute_Path (Result.all) then
+            declare
+               Absolute_Path : constant String :=
+                 Normalize_Pathname (Result.all, Resolve_Links => False);
+            begin
+               Free (Result);
+               Result := new String'(Absolute_Path);
+            end;
+         end if;
+
+         return Result;
+      end if;
+   end Locate_Exec_On_Path;
+
+   -------------------------
+   -- Locate_Regular_File --
+   -------------------------
+
+   function Locate_Regular_File
+     (File_Name : C_File_Name;
+      Path      : C_File_Name) return String_Access
+   is
+      function Locate_Regular_File
+        (C_File_Name, Path_Val : Address) return Address;
+      pragma Import (C, Locate_Regular_File, "__gnat_locate_regular_file");
+
+      Path_Addr    : Address;
+      Path_Len     : Integer;
+      Result       : String_Access;
+
+   begin
+      Path_Addr := Locate_Regular_File (File_Name, Path);
+      Path_Len  := C_String_Length (Path_Addr);
+
+      if Path_Len = 0 then
+         return null;
+
+      else
+         Result := To_Path_String_Access (Path_Addr, Path_Len);
+         CRTL.free (Path_Addr);
+         return Result;
+      end if;
+   end Locate_Regular_File;
+
+   function Locate_Regular_File
+     (File_Name : String;
+      Path      : String) return String_Access
+   is
+      C_File_Name : String (1 .. File_Name'Length + 1);
+      C_Path      : String (1 .. Path'Length + 1);
+      Result      : String_Access;
+
+   begin
+      C_File_Name (1 .. File_Name'Length)   := File_Name;
+      C_File_Name (C_File_Name'Last)        := ASCII.NUL;
+
+      C_Path    (1 .. Path'Length)          := Path;
+      C_Path    (C_Path'Last)               := ASCII.NUL;
+
+      Result := Locate_Regular_File (C_File_Name'Address, C_Path'Address);
+
+      --  Always return an absolute path name
+
+      if Result /= null and then not Is_Absolute_Path (Result.all) then
+         declare
+            Absolute_Path : constant String := Normalize_Pathname (Result.all);
+         begin
+            Free (Result);
+            Result := new String'(Absolute_Path);
+         end;
+      end if;
+
+      return Result;
+   end Locate_Regular_File;
+
+   ------------------------
+   -- Non_Blocking_Spawn --
+   ------------------------
+
+   function Non_Blocking_Spawn
+     (Program_Name : String;
+      Args         : Argument_List) return Process_Id
+   is
+      Junk : Integer;
+      pragma Warnings (Off, Junk);
+      Pid  : Process_Id;
+
+   begin
+      Spawn_Internal (Program_Name, Args, Junk, Pid, Blocking => False);
+      return Pid;
+   end Non_Blocking_Spawn;
+
+   function Non_Blocking_Spawn
+     (Program_Name           : String;
+      Args                   : Argument_List;
+      Output_File_Descriptor : File_Descriptor;
+      Err_To_Out             : Boolean := True) return Process_Id
+   is
+      Pid          : Process_Id;
+      Saved_Error  : File_Descriptor := Invalid_FD; -- prevent warning
+      Saved_Output : File_Descriptor;
+
+   begin
+      if Output_File_Descriptor = Invalid_FD then
+         return Invalid_Pid;
+      end if;
+
+      --  Set standard output and, if specified, error to the temporary file
+
+      Saved_Output := Dup (Standout);
+      Dup2 (Output_File_Descriptor, Standout);
+
+      if Err_To_Out then
+         Saved_Error  := Dup (Standerr);
+         Dup2 (Output_File_Descriptor, Standerr);
+      end if;
+
+      --  Spawn the program
+
+      Pid := Non_Blocking_Spawn (Program_Name, Args);
+
+      --  Restore the standard output and error
+
+      Dup2 (Saved_Output, Standout);
+
+      if Err_To_Out then
+         Dup2 (Saved_Error, Standerr);
+      end if;
+
+      --  And close the saved standard output and error file descriptors
+
+      Close (Saved_Output);
+
+      if Err_To_Out then
+         Close (Saved_Error);
+      end if;
+
+      return Pid;
+   end Non_Blocking_Spawn;
+
+   function Non_Blocking_Spawn
+     (Program_Name : String;
+      Args         : Argument_List;
+      Output_File  : String;
+      Err_To_Out   : Boolean := True) return Process_Id
+   is
+      Output_File_Descriptor : constant File_Descriptor :=
+                                 Create_Output_Text_File (Output_File);
+      Result : Process_Id;
+
+   begin
+      --  Do not attempt to spawn if the output file could not be created
+
+      if Output_File_Descriptor = Invalid_FD then
+         return Invalid_Pid;
+
+      else
+         Result :=
+           Non_Blocking_Spawn
+             (Program_Name, Args, Output_File_Descriptor, Err_To_Out);
+
+         --  Close the file just created for the output, as the file descriptor
+         --  cannot be used anywhere, being a local value. It is safe to do
+         --  that, as the file descriptor has been duplicated to form
+         --  standard output and error of the spawned process.
+
+         Close (Output_File_Descriptor);
+
+         return Result;
+      end if;
+   end Non_Blocking_Spawn;
+
+   function Non_Blocking_Spawn
+     (Program_Name : String;
+      Args         : Argument_List;
+      Stdout_File  : String;
+      Stderr_File  : String) return Process_Id
+   is
+      Stderr_FD : constant File_Descriptor :=
+                    Create_Output_Text_File (Stderr_File);
+      Stdout_FD : constant File_Descriptor :=
+                    Create_Output_Text_File (Stdout_File);
+
+      Result       : Process_Id;
+      Saved_Error  : File_Descriptor;
+      Saved_Output : File_Descriptor;
+
+      Dummy_Status : Boolean;
+
+   begin
+      --  Do not attempt to spawn if the output files could not be created
+
+      if Stdout_FD = Invalid_FD or else Stderr_FD = Invalid_FD then
+         return Invalid_Pid;
+      end if;
+
+      --  Set standard output and error to the specified files
+
+      Saved_Output := Dup (Standout);
+      Dup2 (Stdout_FD, Standout);
+
+      Saved_Error  := Dup (Standerr);
+      Dup2 (Stderr_FD, Standerr);
+
+      Set_Close_On_Exec (Saved_Output, True, Dummy_Status);
+      Set_Close_On_Exec (Saved_Error,  True, Dummy_Status);
+
+      --  Close the files just created for the output, as the file descriptors
+      --  cannot be used anywhere, being local values. It is safe to do that,
+      --  as the file descriptors have been duplicated to form standard output
+      --  and standard error of the spawned process.
+
+      Close (Stdout_FD);
+      Close (Stderr_FD);
+
+      --  Spawn the program
+
+      Result := Non_Blocking_Spawn (Program_Name, Args);
+
+      --  Restore the standard output and error
+
+      Dup2 (Saved_Output, Standout);
+      Dup2 (Saved_Error, Standerr);
+
+      --  And close the saved standard output and error file descriptors
+
+      Close (Saved_Output);
+      Close (Saved_Error);
+
+      return Result;
+   end Non_Blocking_Spawn;
+
+   -------------------------------
+   -- Non_Blocking_Wait_Process --
+   -------------------------------
+
+   procedure Non_Blocking_Wait_Process
+     (Pid : out Process_Id; Success : out Boolean)
+   is
+      Status : Integer;
+
+      function Portable_No_Block_Wait (S : Address) return Process_Id;
+      pragma Import
+        (C, Portable_No_Block_Wait, "__gnat_portable_no_block_wait");
+
+   begin
+      Pid := Portable_No_Block_Wait (Status'Address);
+      Success := (Status = 0);
+
+      if Pid = 0 then
+         Pid := Invalid_Pid;
+      end if;
+   end Non_Blocking_Wait_Process;
+
+   -------------------------
+   -- Normalize_Arguments --
+   -------------------------
+
+   procedure Normalize_Arguments (Args : in out Argument_List) is
+      procedure Quote_Argument (Arg : in out String_Access);
+      --  Add quote around argument if it contains spaces (or HT characters)
+
+      C_Argument_Needs_Quote : Integer;
+      pragma Import (C, C_Argument_Needs_Quote, "__gnat_argument_needs_quote");
+      Argument_Needs_Quote : constant Boolean := C_Argument_Needs_Quote /= 0;
+
+      --------------------
+      -- Quote_Argument --
+      --------------------
+
+      procedure Quote_Argument (Arg : in out String_Access) is
+         J            : Positive := 1;
+         Quote_Needed : Boolean  := False;
+         Res          : String (1 .. Arg'Length * 2);
+
+      begin
+         if Arg (Arg'First) /= '"' or else Arg (Arg'Last) /= '"' then
+
+            --  Starting quote
+
+            Res (J) := '"';
+
+            for K in Arg'Range loop
+
+               J := J + 1;
+
+               if Arg (K) = '"' then
+                  Res (J) := '\';
+                  J := J + 1;
+                  Res (J) := '"';
+                  Quote_Needed := True;
+
+               elsif Arg (K) = ' ' or else Arg (K) = ASCII.HT then
+                  Res (J) := Arg (K);
+                  Quote_Needed := True;
+
+               else
+                  Res (J) := Arg (K);
+               end if;
+            end loop;
+
+            if Quote_Needed then
+
+               --  Case of null terminated string
+
+               if Res (J) = ASCII.NUL then
+
+                  --  If the string ends with \, double it
+
+                  if Res (J - 1) = '\' then
+                     Res (J) := '\';
+                     J := J + 1;
+                  end if;
+
+                  --  Put a quote just before the null at the end
+
+                  Res (J) := '"';
+                  J := J + 1;
+                  Res (J) := ASCII.NUL;
+
+               --  If argument is terminated by '\', then double it. Otherwise
+               --  the ending quote will be taken as-is. This is quite strange
+               --  spawn behavior from Windows, but this is what we see.
+
+               else
+                  if Res (J) = '\' then
+                     J := J + 1;
+                     Res (J) := '\';
+                  end if;
+
+                  --  Ending quote
+
+                  J := J + 1;
+                  Res (J) := '"';
+               end if;
+
+               declare
+                  Old : String_Access := Arg;
+
+               begin
+                  Arg := new String'(Res (1 .. J));
+                  Free (Old);
+               end;
+            end if;
+
+         end if;
+      end Quote_Argument;
+
+   --  Start of processing for Normalize_Arguments
+
+   begin
+      if Argument_Needs_Quote then
+         for K in Args'Range loop
+            if Args (K) /= null and then Args (K)'Length /= 0 then
+               Quote_Argument (Args (K));
+            end if;
+         end loop;
+      end if;
+   end Normalize_Arguments;
+
+   ------------------------
+   -- Normalize_Pathname --
+   ------------------------
+
+   function Normalize_Pathname
+     (Name           : String;
+      Directory      : String  := "";
+      Resolve_Links  : Boolean := True;
+      Case_Sensitive : Boolean := True) return String
+   is
+      procedure Get_Current_Dir
+        (Dir    : System.Address;
+         Length : System.Address);
+      pragma Import (C, Get_Current_Dir, "__gnat_get_current_dir");
+
+      function Get_File_Names_Case_Sensitive return Integer;
+      pragma Import
+        (C, Get_File_Names_Case_Sensitive,
+         "__gnat_get_file_names_case_sensitive");
+
+      Max_Path : Integer;
+      pragma Import (C, Max_Path, "__gnat_max_path_len");
+      --  Maximum length of a path name
+
+      function Readlink
+        (Path   : System.Address;
+         Buf    : System.Address;
+         Bufsiz : size_t) return Integer;
+      pragma Import (C, Readlink, "__gnat_readlink");
+
+      function To_Canonical_File_Spec
+        (Host_File : System.Address) return System.Address;
+      pragma Import
+        (C, To_Canonical_File_Spec, "__gnat_to_canonical_file_spec");
+      --  Convert possible foreign file syntax to canonical form
+
+      Fold_To_Lower_Case : constant Boolean :=
+                             not Case_Sensitive
+                               and then Get_File_Names_Case_Sensitive = 0;
+
+      function Final_Value (S : String) return String;
+      --  Make final adjustment to the returned string. This function strips
+      --  trailing directory separators, and folds returned string to lower
+      --  case if required.
+
+      function Get_Directory  (Dir : String) return String;
+      --  If Dir is not empty, return it, adding a directory separator
+      --  if not already present, otherwise return current working directory
+      --  with terminating directory separator.
+
+      -----------------
+      -- Final_Value --
+      -----------------
+
+      function Final_Value (S : String) return String is
+         S1 : String := S;
+         --  We may need to fold S to lower case, so we need a variable
+
+         Last : Natural;
+
+      begin
+         if Fold_To_Lower_Case then
+            System.Case_Util.To_Lower (S1);
+         end if;
+
+         --  Remove trailing directory separator, if any
+
+         Last := S1'Last;
+
+         if Last > 1
+           and then (S1 (Last) = '/'
+                       or else
+                     S1 (Last) = Directory_Separator)
+         then
+            --  Special case for Windows: C:\
+
+            if Last = 3
+              and then S1 (1) /= Directory_Separator
+              and then S1 (2) = ':'
+            then
+               null;
+
+            else
+               Last := Last - 1;
+            end if;
+         end if;
+
+         return S1 (1 .. Last);
+      end Final_Value;
+
+      -------------------
+      -- Get_Directory --
+      -------------------
+
+      function Get_Directory (Dir : String) return String is
+      begin
+         --  Directory given, add directory separator if needed
+
+         if Dir'Length > 0 then
+            declare
+               Result : String   :=
+                          Normalize_Pathname
+                            (Dir, "", Resolve_Links, Case_Sensitive) &
+                             Directory_Separator;
+               Last   : Positive := Result'Last - 1;
+
+            begin
+               --  On Windows, change all '/' to '\'
+
+               if On_Windows then
+                  for J in Result'First .. Last - 1 loop
+                     if Result (J) = '/' then
+                        Result (J) := Directory_Separator;
+                     end if;
+                  end loop;
+               end if;
+
+               --  Include additional directory separator, if needed
+
+               if Result (Last) /= Directory_Separator then
+                  Last := Last + 1;
+               end if;
+
+               return Result (Result'First .. Last);
+            end;
+
+         --  Directory name not given, get current directory
+
+         else
+            declare
+               Buffer   : String (1 .. Max_Path + 2);
+               Path_Len : Natural := Max_Path;
+
+            begin
+               Get_Current_Dir (Buffer'Address, Path_Len'Address);
+
+               if Path_Len = 0 then
+                  raise Program_Error;
+               end if;
+
+               if Buffer (Path_Len) /= Directory_Separator then
+                  Path_Len := Path_Len + 1;
+                  Buffer (Path_Len) := Directory_Separator;
+               end if;
+
+               --  By default, the drive letter on Windows is in upper case
+
+               if On_Windows
+                 and then Path_Len >= 2
+                 and then Buffer (2) = ':'
+               then
+                  System.Case_Util.To_Upper (Buffer (1 .. 1));
+               end if;
+
+               return Buffer (1 .. Path_Len);
+            end;
+         end if;
+      end Get_Directory;
+
+      --  Local variables
+
+      Max_Iterations : constant := 500;
+
+      Canonical_File_Addr : System.Address;
+      Canonical_File_Len  : Integer;
+
+      End_Path    : Natural := 0;
+      Finish      : Positive;
+      Last        : Positive;
+      Link_Buffer : String (1 .. Max_Path + 2);
+      Path_Buffer : String (1 .. Max_Path + Max_Path + 2);
+      Start       : Natural;
+      Status      : Integer;
+      The_Name    : String (1 .. Name'Length + 1);
+
+   --  Start of processing for Normalize_Pathname
+
+   begin
+      --  Special case, return null if name is null, or if it is bigger than
+      --  the biggest name allowed.
+
+      if Name'Length = 0 or else Name'Length > Max_Path then
+         return "";
+      end if;
+
+      --  First, convert possible foreign file spec to Unix file spec. If no
+      --  conversion is required, all this does is put Name at the beginning
+      --  of Path_Buffer unchanged.
+
+      File_Name_Conversion : begin
+         The_Name (1 .. Name'Length) := Name;
+         The_Name (The_Name'Last) := ASCII.NUL;
+
+         Canonical_File_Addr := To_Canonical_File_Spec (The_Name'Address);
+         Canonical_File_Len  := Integer (CRTL.strlen (Canonical_File_Addr));
+
+         --  If syntax conversion has failed, return an empty string to
+         --  indicate the failure.
+
+         if Canonical_File_Len = 0 then
+            return "";
+         end if;
+
+         declare
+            subtype Path_String is String (1 .. Canonical_File_Len);
+            Canonical_File : Path_String;
+            for Canonical_File'Address use Canonical_File_Addr;
+            pragma Import (Ada, Canonical_File);
+
+         begin
+            Path_Buffer (1 .. Canonical_File_Len) := Canonical_File;
+            End_Path := Canonical_File_Len;
+            Last := 1;
+         end;
+      end File_Name_Conversion;
+
+      --  Replace all '/' by Directory Separators (this is for Windows)
+
+      if Directory_Separator /= '/' then
+         for Index in 1 .. End_Path loop
+            if Path_Buffer (Index) = '/' then
+               Path_Buffer (Index) := Directory_Separator;
+            end if;
+         end loop;
+      end if;
+
+      --  Resolve directory names for Windows
+
+      if On_Windows then
+
+         --  On Windows, if we have an absolute path starting with a directory
+         --  separator, we need to have the drive letter appended in front.
+
+         --  On Windows, Get_Current_Dir will return a suitable directory name
+         --  (path starting with a drive letter on Windows). So we take this
+         --  drive letter and prepend it to the current path.
+
+         if Path_Buffer (1) = Directory_Separator
+           and then Path_Buffer (2) /= Directory_Separator
+         then
+            declare
+               Cur_Dir : constant String := Get_Directory ("");
+               --  Get the current directory to get the drive letter
+
+            begin
+               if Cur_Dir'Length > 2
+                 and then Cur_Dir (Cur_Dir'First + 1) = ':'
+               then
+                  Path_Buffer (3 .. End_Path + 2) :=
+                    Path_Buffer (1 .. End_Path);
+                  Path_Buffer (1 .. 2) :=
+                    Cur_Dir (Cur_Dir'First .. Cur_Dir'First + 1);
+                  End_Path := End_Path + 2;
+               end if;
+            end;
+
+         --  We have a drive letter, ensure it is upper-case
+
+         elsif Path_Buffer (1) in 'a' .. 'z'
+           and then Path_Buffer (2) = ':'
+         then
+            System.Case_Util.To_Upper (Path_Buffer (1 .. 1));
+         end if;
+      end if;
+
+      --  On Windows, remove all double-quotes that are possibly part of the
+      --  path but can cause problems with other methods.
+
+      if On_Windows then
+         declare
+            Index : Natural;
+
+         begin
+            Index := Path_Buffer'First;
+            for Current in Path_Buffer'First .. End_Path loop
+               if Path_Buffer (Current) /= '"' then
+                  Path_Buffer (Index) := Path_Buffer (Current);
+                  Index := Index + 1;
+               end if;
+            end loop;
+
+            End_Path := Index - 1;
+         end;
+      end if;
+
+      --  Start the conversions
+
+      --  If this is not finished after Max_Iterations, give up and return an
+      --  empty string.
+
+      for J in 1 .. Max_Iterations loop
+
+         --  If we don't have an absolute pathname, prepend the directory
+         --  Reference_Dir.
+
+         if Last = 1
+           and then not Is_Absolute_Path (Path_Buffer (1 .. End_Path))
+         then
+            declare
+               Reference_Dir : constant String  := Get_Directory (Directory);
+               Ref_Dir_Len   : constant Natural := Reference_Dir'Length;
+               --  Current directory name specified and its length
+
+            begin
+               Path_Buffer (Ref_Dir_Len + 1 .. Ref_Dir_Len + End_Path) :=
+                 Path_Buffer (1 .. End_Path);
+               End_Path := Ref_Dir_Len + End_Path;
+               Path_Buffer (1 .. Ref_Dir_Len) := Reference_Dir;
+               Last := Ref_Dir_Len;
+            end;
+         end if;
+
+         Start  := Last + 1;
+         Finish := Last;
+
+         --  Ensure that Windows network drives are kept, e.g: \\server\drive-c
+
+         if Start = 2
+           and then Directory_Separator = '\'
+           and then Path_Buffer (1 .. 2) = "\\"
+         then
+            Start := 3;
+         end if;
+
+         --  If we have traversed the full pathname, return it
+
+         if Start > End_Path then
+            return Final_Value (Path_Buffer (1 .. End_Path));
+         end if;
+
+         --  Remove duplicate directory separators
+
+         while Path_Buffer (Start) = Directory_Separator loop
+            if Start = End_Path then
+               return Final_Value (Path_Buffer (1 .. End_Path - 1));
+
+            else
+               Path_Buffer (Start .. End_Path - 1) :=
+                 Path_Buffer (Start + 1 .. End_Path);
+               End_Path := End_Path - 1;
+            end if;
+         end loop;
+
+         --  Find the end of the current field: last character or the one
+         --  preceding the next directory separator.
+
+         while Finish < End_Path
+           and then Path_Buffer (Finish + 1) /= Directory_Separator
+         loop
+            Finish := Finish + 1;
+         end loop;
+
+         --  Remove "." field
+
+         if Start = Finish and then Path_Buffer (Start) = '.' then
+            if Start = End_Path then
+               if Last = 1 then
+                  return (1 => Directory_Separator);
+               else
+                  if Fold_To_Lower_Case then
+                     System.Case_Util.To_Lower (Path_Buffer (1 .. Last - 1));
+                  end if;
+
+                  return Path_Buffer (1 .. Last - 1);
+               end if;
+            else
+               Path_Buffer (Last + 1 .. End_Path - 2) :=
+                 Path_Buffer (Last + 3 .. End_Path);
+               End_Path := End_Path - 2;
+            end if;
+
+         --  Remove ".." fields
+
+         elsif Finish = Start + 1
+           and then Path_Buffer (Start .. Finish) = ".."
+         then
+            Start := Last;
+            loop
+               Start := Start - 1;
+               exit when Start < 1
+                 or else Path_Buffer (Start) = Directory_Separator;
+            end loop;
+
+            if Start <= 1 then
+               if Finish = End_Path then
+                  return (1 => Directory_Separator);
+
+               else
+                  Path_Buffer (1 .. End_Path - Finish) :=
+                    Path_Buffer (Finish + 1 .. End_Path);
+                  End_Path := End_Path - Finish;
+                  Last := 1;
+               end if;
+
+            else
+               if Finish = End_Path then
+                  return Final_Value (Path_Buffer (1 .. Start - 1));
+
+               else
+                  Path_Buffer (Start + 1 .. Start + End_Path - Finish - 1) :=
+                    Path_Buffer (Finish + 2 .. End_Path);
+                  End_Path := Start + End_Path - Finish - 1;
+                  Last := Start;
+               end if;
+            end if;
+
+         --  Check if current field is a symbolic link
+
+         elsif Resolve_Links then
+            declare
+               Saved : constant Character := Path_Buffer (Finish + 1);
+
+            begin
+               Path_Buffer (Finish + 1) := ASCII.NUL;
+               Status :=
+                 Readlink
+                   (Path   => Path_Buffer'Address,
+                    Buf    => Link_Buffer'Address,
+                    Bufsiz => Link_Buffer'Length);
+               Path_Buffer (Finish + 1) := Saved;
+            end;
+
+            --  Not a symbolic link, move to the next field, if any
+
+            if Status <= 0 then
+               Last := Finish + 1;
+
+            --  Replace symbolic link with its value
+
+            else
+               if Is_Absolute_Path (Link_Buffer (1 .. Status)) then
+                  Path_Buffer (Status + 1 .. End_Path - (Finish - Status)) :=
+                  Path_Buffer (Finish + 1 .. End_Path);
+                  End_Path := End_Path - (Finish - Status);
+                  Path_Buffer (1 .. Status) := Link_Buffer (1 .. Status);
+                  Last := 1;
+
+               else
+                  Path_Buffer
+                    (Last + Status + 1 .. End_Path - Finish + Last + Status) :=
+                    Path_Buffer (Finish + 1 .. End_Path);
+                  End_Path := End_Path - Finish + Last + Status;
+                  Path_Buffer (Last + 1 .. Last + Status) :=
+                    Link_Buffer (1 .. Status);
+               end if;
+            end if;
+
+         else
+            Last := Finish + 1;
+         end if;
+      end loop;
+
+      --  Too many iterations: give up
+
+      --  This can happen when there is a circularity in the symbolic links: A
+      --  is a symbolic link for B, which itself is a symbolic link, and the
+      --  target of B or of another symbolic link target of B is A. In this
+      --  case, we return an empty string to indicate failure to resolve.
+
+      return "";
+   end Normalize_Pathname;
+
+   -----------------
+   -- Open_Append --
+   -----------------
+
+   function Open_Append
+     (Name  : C_File_Name;
+      Fmode : Mode) return File_Descriptor
+   is
+      function C_Open_Append
+        (Name  : C_File_Name;
+         Fmode : Mode) return File_Descriptor;
+      pragma Import (C, C_Open_Append, "__gnat_open_append");
+   begin
+      return C_Open_Append (Name, Fmode);
+   end Open_Append;
+
+   function Open_Append
+     (Name  : String;
+      Fmode : Mode) return File_Descriptor
+   is
+      C_Name : String (1 .. Name'Length + 1);
+   begin
+      C_Name (1 .. Name'Length) := Name;
+      C_Name (C_Name'Last)      := ASCII.NUL;
+      return Open_Append (C_Name (C_Name'First)'Address, Fmode);
+   end Open_Append;
+
+   ---------------
+   -- Open_Read --
+   ---------------
+
+   function Open_Read
+     (Name  : C_File_Name;
+      Fmode : Mode) return File_Descriptor
+   is
+      function C_Open_Read
+        (Name  : C_File_Name;
+         Fmode : Mode) return File_Descriptor;
+      pragma Import (C, C_Open_Read, "__gnat_open_read");
+   begin
+      return C_Open_Read (Name, Fmode);
+   end Open_Read;
+
+   function Open_Read
+     (Name  : String;
+      Fmode : Mode) return File_Descriptor
+   is
+      C_Name : String (1 .. Name'Length + 1);
+   begin
+      C_Name (1 .. Name'Length) := Name;
+      C_Name (C_Name'Last)      := ASCII.NUL;
+      return Open_Read (C_Name (C_Name'First)'Address, Fmode);
+   end Open_Read;
+
+   ---------------------
+   -- Open_Read_Write --
+   ---------------------
+
+   function Open_Read_Write
+     (Name  : C_File_Name;
+      Fmode : Mode) return File_Descriptor
+   is
+      function C_Open_Read_Write
+        (Name  : C_File_Name;
+         Fmode : Mode) return File_Descriptor;
+      pragma Import (C, C_Open_Read_Write, "__gnat_open_rw");
+   begin
+      return C_Open_Read_Write (Name, Fmode);
+   end Open_Read_Write;
+
+   function Open_Read_Write
+     (Name  : String;
+      Fmode : Mode) return File_Descriptor
+   is
+      C_Name : String (1 .. Name'Length + 1);
+   begin
+      C_Name (1 .. Name'Length) := Name;
+      C_Name (C_Name'Last)      := ASCII.NUL;
+      return Open_Read_Write (C_Name (C_Name'First)'Address, Fmode);
+   end Open_Read_Write;
+
+   -------------
+   -- OS_Exit --
+   -------------
+
+   procedure OS_Exit (Status : Integer) is
+   begin
+      OS_Exit_Ptr (Status);
+      raise Program_Error;
+   end OS_Exit;
+
+   ---------------------
+   -- OS_Exit_Default --
+   ---------------------
+
+   procedure OS_Exit_Default (Status : Integer) is
+      procedure GNAT_OS_Exit (Status : Integer);
+      pragma Import (C, GNAT_OS_Exit, "__gnat_os_exit");
+      pragma No_Return (GNAT_OS_Exit);
+   begin
+      GNAT_OS_Exit (Status);
+   end OS_Exit_Default;
+
+   --------------------
+   -- Pid_To_Integer --
+   --------------------
+
+   function Pid_To_Integer (Pid : Process_Id) return Integer is
+   begin
+      return Integer (Pid);
+   end Pid_To_Integer;
+
+   ----------
+   -- Read --
+   ----------
+
+   function Read
+     (FD : File_Descriptor;
+      A  : System.Address;
+      N  : Integer) return Integer
+   is
+   begin
+      return
+        Integer (System.CRTL.read
+                   (System.CRTL.int (FD),
+                    System.CRTL.chars (A),
+                    System.CRTL.size_t (N)));
+   end Read;
+
+   -----------------
+   -- Rename_File --
+   -----------------
+
+   procedure Rename_File
+     (Old_Name : C_File_Name;
+      New_Name : C_File_Name;
+      Success  : out Boolean)
+   is
+      function rename (From, To : Address) return Integer;
+      pragma Import (C, rename, "__gnat_rename");
+      R : Integer;
+
+   begin
+      R := rename (Old_Name, New_Name);
+      Success := (R = 0);
+   end Rename_File;
+
+   procedure Rename_File
+     (Old_Name : String;
+      New_Name : String;
+      Success  : out Boolean)
+   is
+      C_Old_Name : String (1 .. Old_Name'Length + 1);
+      C_New_Name : String (1 .. New_Name'Length + 1);
+
+   begin
+      C_Old_Name (1 .. Old_Name'Length) := Old_Name;
+      C_Old_Name (C_Old_Name'Last)      := ASCII.NUL;
+      C_New_Name (1 .. New_Name'Length) := New_Name;
+      C_New_Name (C_New_Name'Last)      := ASCII.NUL;
+      Rename_File (C_Old_Name'Address, C_New_Name'Address, Success);
+   end Rename_File;
+
+   -----------------------
+   -- Set_Close_On_Exec --
+   -----------------------
+
+   procedure Set_Close_On_Exec
+     (FD            : File_Descriptor;
+      Close_On_Exec : Boolean;
+      Status        : out Boolean)
+   is
+      function C_Set_Close_On_Exec
+        (FD : File_Descriptor; Close_On_Exec : System.CRTL.int)
+         return System.CRTL.int;
+      pragma Import (C, C_Set_Close_On_Exec, "__gnat_set_close_on_exec");
+   begin
+      Status := C_Set_Close_On_Exec (FD, Boolean'Pos (Close_On_Exec)) = 0;
+   end Set_Close_On_Exec;
+
+   --------------------
+   -- Set_Executable --
+   --------------------
+
+   procedure Set_Executable (Name : String; Mode : Positive := S_Owner) is
+      procedure C_Set_Executable (Name : C_File_Name; Mode : Integer);
+      pragma Import (C, C_Set_Executable, "__gnat_set_executable");
+      C_Name : aliased String (Name'First .. Name'Last + 1);
+
+   begin
+      C_Name (Name'Range)  := Name;
+      C_Name (C_Name'Last) := ASCII.NUL;
+      C_Set_Executable (C_Name (C_Name'First)'Address, Mode);
+   end Set_Executable;
+
+   -------------------------------------
+   -- Set_File_Last_Modify_Time_Stamp --
+   -------------------------------------
+
+   procedure Set_File_Last_Modify_Time_Stamp (Name : String; Time : OS_Time) is
+      procedure C_Set_File_Time (Name : C_File_Name; Time : OS_Time);
+      pragma Import (C, C_Set_File_Time, "__gnat_set_file_time_name");
+      C_Name : aliased String (Name'First .. Name'Last + 1);
+
+   begin
+      C_Name (Name'Range)  := Name;
+      C_Name (C_Name'Last) := ASCII.NUL;
+      C_Set_File_Time (C_Name'Address, Time);
+   end Set_File_Last_Modify_Time_Stamp;
+
+   ----------------------
+   -- Set_Non_Readable --
+   ----------------------
+
+   procedure Set_Non_Readable (Name : String) is
+      procedure C_Set_Non_Readable (Name : C_File_Name);
+      pragma Import (C, C_Set_Non_Readable, "__gnat_set_non_readable");
+      C_Name : aliased String (Name'First .. Name'Last + 1);
+
+   begin
+      C_Name (Name'Range)  := Name;
+      C_Name (C_Name'Last) := ASCII.NUL;
+      C_Set_Non_Readable (C_Name (C_Name'First)'Address);
+   end Set_Non_Readable;
+
+   ----------------------
+   -- Set_Non_Writable --
+   ----------------------
+
+   procedure Set_Non_Writable (Name : String) is
+      procedure C_Set_Non_Writable (Name : C_File_Name);
+      pragma Import (C, C_Set_Non_Writable, "__gnat_set_non_writable");
+      C_Name : aliased String (Name'First .. Name'Last + 1);
+
+   begin
+      C_Name (Name'Range)  := Name;
+      C_Name (C_Name'Last) := ASCII.NUL;
+      C_Set_Non_Writable (C_Name (C_Name'First)'Address);
+   end Set_Non_Writable;
+
+   ------------------
+   -- Set_Readable --
+   ------------------
+
+   procedure Set_Readable (Name : String) is
+      procedure C_Set_Readable (Name : C_File_Name);
+      pragma Import (C, C_Set_Readable, "__gnat_set_readable");
+      C_Name : aliased String (Name'First .. Name'Last + 1);
+
+   begin
+      C_Name (Name'Range)  := Name;
+      C_Name (C_Name'Last) := ASCII.NUL;
+      C_Set_Readable (C_Name (C_Name'First)'Address);
+   end Set_Readable;
+
+   --------------------
+   -- Set_Writable --
+   --------------------
+
+   procedure Set_Writable (Name : String) is
+      procedure C_Set_Writable (Name : C_File_Name);
+      pragma Import (C, C_Set_Writable, "__gnat_set_writable");
+      C_Name : aliased String (Name'First .. Name'Last + 1);
+
+   begin
+      C_Name (Name'Range)  := Name;
+      C_Name (C_Name'Last) := ASCII.NUL;
+      C_Set_Writable (C_Name (C_Name'First)'Address);
+   end Set_Writable;
+
+   ------------
+   -- Setenv --
+   ------------
+
+   procedure Setenv (Name : String; Value : String) is
+      F_Name  : String (1 .. Name'Length + 1);
+      F_Value : String (1 .. Value'Length + 1);
+
+      procedure Set_Env_Value (Name, Value : System.Address);
+      pragma Import (C, Set_Env_Value, "__gnat_setenv");
+
+   begin
+      F_Name (1 .. Name'Length) := Name;
+      F_Name (F_Name'Last)      := ASCII.NUL;
+
+      F_Value (1 .. Value'Length) := Value;
+      F_Value (F_Value'Last)      := ASCII.NUL;
+
+      Set_Env_Value (F_Name'Address, F_Value'Address);
+   end Setenv;
+
+   -----------
+   -- Spawn --
+   -----------
+
+   function Spawn
+     (Program_Name : String;
+      Args         : Argument_List) return Integer
+   is
+      Junk   : Process_Id;
+      pragma Warnings (Off, Junk);
+      Result : Integer;
+
+   begin
+      Spawn_Internal (Program_Name, Args, Result, Junk, Blocking => True);
+      return Result;
+   end Spawn;
+
+   procedure Spawn
+     (Program_Name : String;
+      Args         : Argument_List;
+      Success      : out Boolean)
+   is
+   begin
+      Success := (Spawn (Program_Name, Args) = 0);
+   end Spawn;
+
+   procedure Spawn
+     (Program_Name           : String;
+      Args                   : Argument_List;
+      Output_File_Descriptor : File_Descriptor;
+      Return_Code            : out Integer;
+      Err_To_Out             : Boolean := True)
+   is
+      Saved_Error  : File_Descriptor := Invalid_FD; -- prevent compiler warning
+      Saved_Output : File_Descriptor;
+
+   begin
+      --  Set standard output and error to the temporary file
+
+      Saved_Output := Dup (Standout);
+      Dup2 (Output_File_Descriptor, Standout);
+
+      if Err_To_Out then
+         Saved_Error  := Dup (Standerr);
+         Dup2 (Output_File_Descriptor, Standerr);
+      end if;
+
+      --  Spawn the program
+
+      Return_Code := Spawn (Program_Name, Args);
+
+      --  Restore the standard output and error
+
+      Dup2 (Saved_Output, Standout);
+
+      if Err_To_Out then
+         Dup2 (Saved_Error, Standerr);
+      end if;
+
+      --  And close the saved standard output and error file descriptors
+
+      Close (Saved_Output);
+
+      if Err_To_Out then
+         Close (Saved_Error);
+      end if;
+   end Spawn;
+
+   procedure Spawn
+     (Program_Name : String;
+      Args         : Argument_List;
+      Output_File  : String;
+      Success      : out Boolean;
+      Return_Code  : out Integer;
+      Err_To_Out   : Boolean := True)
+   is
+      FD : File_Descriptor;
+
+   begin
+      Success := True;
+      Return_Code := 0;
+
+      FD := Create_Output_Text_File (Output_File);
+
+      if FD = Invalid_FD then
+         Success := False;
+         return;
+      end if;
+
+      Spawn (Program_Name, Args, FD, Return_Code, Err_To_Out);
+
+      Close (FD, Success);
+   end Spawn;
+
+   --------------------
+   -- Spawn_Internal --
+   --------------------
+
+   procedure Spawn_Internal
+     (Program_Name : String;
+      Args         : Argument_List;
+      Result       : out Integer;
+      Pid          : out Process_Id;
+      Blocking     : Boolean)
+   is
+      procedure Spawn (Args : Argument_List);
+      --  Call Spawn with given argument list
+
+      N_Args : Argument_List (Args'Range);
+      --  Normalized arguments
+
+      -----------
+      -- Spawn --
+      -----------
+
+      procedure Spawn (Args : Argument_List) is
+         type Chars is array (Positive range <>) of aliased Character;
+         type Char_Ptr is access constant Character;
+
+         Command_Len  : constant Positive :=
+                          Program_Name'Length + 1 + Args_Length (Args);
+         Command_Last : Natural := 0;
+         Command      : aliased Chars (1 .. Command_Len);
+         --  Command contains all characters of the Program_Name and Args, all
+         --  terminated by ASCII.NUL characters.
+
+         Arg_List_Len  : constant Positive := Args'Length + 2;
+         Arg_List_Last : Natural := 0;
+         Arg_List      : aliased array (1 .. Arg_List_Len) of Char_Ptr;
+         --  List with pointers to NUL-terminated strings of the Program_Name
+         --  and the Args and terminated with a null pointer. We rely on the
+         --  default initialization for the last null pointer.
+
+         procedure Add_To_Command (S : String);
+         --  Add S and a NUL character to Command, updating Last
+
+         function Portable_Spawn (Args : Address) return Integer;
+         pragma Import (C, Portable_Spawn, "__gnat_portable_spawn");
+
+         function Portable_No_Block_Spawn (Args : Address) return Process_Id;
+         pragma Import
+           (C, Portable_No_Block_Spawn, "__gnat_portable_no_block_spawn");
+
+         --------------------
+         -- Add_To_Command --
+         --------------------
+
+         procedure Add_To_Command (S : String) is
+            First : constant Natural := Command_Last + 1;
+
+         begin
+            Command_Last := Command_Last + S'Length;
+
+            --  Move characters one at a time, because Command has aliased
+            --  components.
+
+            --  But not volatile, so why is this necessary ???
+
+            for J in S'Range loop
+               Command (First + J - S'First) := S (J);
+            end loop;
+
+            Command_Last := Command_Last + 1;
+            Command (Command_Last) := ASCII.NUL;
+
+            Arg_List_Last := Arg_List_Last + 1;
+            Arg_List (Arg_List_Last) := Command (First)'Access;
+         end Add_To_Command;
+
+      --  Start of processing for Spawn
+
+      begin
+         Add_To_Command (Program_Name);
+
+         for J in Args'Range loop
+            Add_To_Command (Args (J).all);
+         end loop;
+
+         if Blocking then
+            Pid    := Invalid_Pid;
+            Result := Portable_Spawn (Arg_List'Address);
+         else
+            Pid    := Portable_No_Block_Spawn (Arg_List'Address);
+            Result := Boolean'Pos (Pid /= Invalid_Pid);
+         end if;
+      end Spawn;
+
+   --  Start of processing for Spawn_Internal
+
+   begin
+      --  Copy arguments into a local structure
+
+      for K in N_Args'Range loop
+         N_Args (K) := new String'(Args (K).all);
+      end loop;
+
+      --  Normalize those arguments
+
+      Normalize_Arguments (N_Args);
+
+      --  Call spawn using the normalized arguments
+
+      Spawn (N_Args);
+
+      --  Free arguments list
+
+      for K in N_Args'Range loop
+         Free (N_Args (K));
+      end loop;
+   end Spawn_Internal;
+
+   ---------------------------
+   -- To_Path_String_Access --
+   ---------------------------
+
+   function To_Path_String_Access
+     (Path_Addr : Address;
+      Path_Len  : Integer) return String_Access
+   is
+      subtype Path_String is String (1 .. Path_Len);
+      type    Path_String_Access is access Path_String;
+
+      function Address_To_Access is new Ada.Unchecked_Conversion
+        (Source => Address, Target => Path_String_Access);
+
+      Path_Access : constant Path_String_Access :=
+                      Address_To_Access (Path_Addr);
+
+      Return_Val  : String_Access;
+
+   begin
+      Return_Val := new String (1 .. Path_Len);
+
+      for J in 1 .. Path_Len loop
+         Return_Val (J) := Path_Access (J);
+      end loop;
+
+      return Return_Val;
+   end To_Path_String_Access;
+
+   ------------------
+   -- Wait_Process --
+   ------------------
+
+   procedure Wait_Process (Pid : out Process_Id; Success : out Boolean) is
+      Status : Integer;
+
+      function Portable_Wait (S : Address) return Process_Id;
+      pragma Import (C, Portable_Wait, "__gnat_portable_wait");
+
+   begin
+      Pid := Portable_Wait (Status'Address);
+      Success := (Status = 0);
+   end Wait_Process;
+
+   -----------
+   -- Write --
+   -----------
+
+   function Write
+     (FD : File_Descriptor;
+      A  : System.Address;
+      N  : Integer) return Integer
+   is
+   begin
+      return
+        Integer (System.CRTL.write
+                   (System.CRTL.int (FD),
+                    System.CRTL.chars (A),
+                    System.CRTL.size_t (N)));
+   end Write;
+
+end System.OS_Lib;