OSDN Git Service

2007-04-20 Ed Schonberg <schonberg@adacore.com>
[pf3gnuchains/gcc-fork.git] / gcc / ada / g-expect.adb
index 651b620..fb9d296 100644 (file)
@@ -6,9 +6,7 @@
 --                                                                          --
 --                                 B o d y                                  --
 --                                                                          --
---                            $Revision: 1.7 $
---                                                                          --
---           Copyright (C) 2000-2001 Ada Core Technologies, Inc.            --
+--                     Copyright (C) 2000-2007, 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- --
@@ -18,8 +16,8 @@
 -- or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License --
 -- for  more details.  You should have  received  a copy of the GNU General --
 -- Public License  distributed with GNAT;  see file COPYING.  If not, write --
--- to  the Free Software Foundation,  59 Temple Place - Suite 330,  Boston, --
--- MA 02111-1307, USA.                                                      --
+-- to  the  Free Software Foundation,  51  Franklin  Street,  Fifth  Floor, --
+-- Boston, MA 02110-1301, USA.                                              --
 --                                                                          --
 -- As a special exception,  if other files  instantiate  generics from this --
 -- unit, or you link  this unit with other files  to produce an executable, --
 -- however invalidate  any other reasons why  the executable file  might be --
 -- covered by the  GNU Public License.                                      --
 --                                                                          --
--- GNAT is maintained by Ada Core Technologies Inc (http://www.gnat.com).   --
+-- GNAT was originally developed  by the GNAT team at  New York University. --
+-- Extensive contributions were provided by Ada Core Technologies Inc.      --
 --                                                                          --
 ------------------------------------------------------------------------------
 
+with System;       use System;
+with Ada.Calendar; use Ada.Calendar;
+
 with GNAT.IO;
-with GNAT.OS_Lib;   use GNAT.OS_Lib;
-with GNAT.Regpat;   use GNAT.Regpat;
-with System;        use System;
-with Unchecked_Conversion;
-with Unchecked_Deallocation;
-with Ada.Calendar;  use Ada.Calendar;
+with GNAT.OS_Lib;  use GNAT.OS_Lib;
+with GNAT.Regpat;  use GNAT.Regpat;
 
-package body GNAT.Expect is
+with Ada.Unchecked_Deallocation;
 
-   function To_Pid is new
-     Unchecked_Conversion (OS_Lib.Process_Id, Process_Id);
+package body GNAT.Expect is
 
    type Array_Of_Pd is array (Positive range <>) of Process_Descriptor_Access;
 
@@ -69,9 +66,12 @@ package body GNAT.Expect is
    --  Reinitialize the internal buffer.
    --  The buffer is deleted up to the end of the last match.
 
-   procedure Free is new Unchecked_Deallocation
+   procedure Free is new Ada.Unchecked_Deallocation
      (Pattern_Matcher, Pattern_Matcher_Access);
 
+   procedure Free is new Ada.Unchecked_Deallocation
+     (Filter_List_Elem, Filter_List);
+
    procedure Call_Filters
      (Pid       : Process_Descriptor'Class;
       Str       : String;
@@ -89,32 +89,13 @@ package body GNAT.Expect is
    procedure Dup2 (Old_Fd, New_Fd : File_Descriptor);
    pragma Import (C, Dup2);
 
-   procedure Kill (Pid : Process_Id; Sig_Num : Integer);
-   pragma Import (C, Kill);
+   procedure Kill (Pid : Process_Id; Sig_Num : Integer; Close : Integer);
+   pragma Import (C, Kill, "__gnat_kill");
+   --  if Close is set to 1 all OS resources used by the Pid must be freed
 
-   function Create_Pipe (Pipe : access Pipe_Type) return Integer;
+   function Create_Pipe (Pipe : not null access Pipe_Type) return Integer;
    pragma Import (C, Create_Pipe, "__gnat_pipe");
 
-   function Read
-     (Fd : File_Descriptor;
-      A  : System.Address;
-      N  : Integer) return Integer;
-   pragma Import (C, Read, "read");
-   --  Read N bytes to address A from file referenced by FD. Returned value
-   --  is count of bytes actually read, which can be less than N at EOF.
-
-   procedure Close (Fd : File_Descriptor);
-   pragma Import (C, Close);
-   --  Close a file given its file descriptor.
-
-   function Write
-     (Fd : File_Descriptor;
-      A  : System.Address;
-      N  : Integer) return Integer;
-   pragma Import (C, Write, "write");
-   --  Read N bytes to address A from file referenced by FD. Returned value
-   --  is count of bytes actually read, which can be less than N at EOF.
-
    function Poll
      (Fds     : System.Address;
       Num_Fds : Integer;
@@ -128,6 +109,10 @@ package body GNAT.Expect is
    --
    --  Out_Is_Set is set to 1 if data was available, 0 otherwise.
 
+   function Waitpid (Pid : Process_Id) return Integer;
+   pragma Import (C, Waitpid, "__gnat_waitpid");
+   --  Wait for a specific process id, and return its exit code
+
    ---------
    -- "+" --
    ---------
@@ -142,8 +127,7 @@ package body GNAT.Expect is
    ---------
 
    function "+"
-     (P    : GNAT.Regpat.Pattern_Matcher)
-      return Pattern_Matcher_Access
+     (P : GNAT.Regpat.Pattern_Matcher) return Pattern_Matcher_Access
    is
    begin
       return new GNAT.Regpat.Pattern_Matcher'(P);
@@ -171,8 +155,8 @@ package body GNAT.Expect is
          if Current = null then
             Descriptor.Filters :=
               new Filter_List_Elem'
-              (Filter => Filter, Filter_On => Filter_On,
-               User_Data => User_Data, Next => null);
+               (Filter => Filter, Filter_On => Filter_On,
+                User_Data => User_Data, Next => null);
          else
             Current.Next :=
               new Filter_List_Elem'
@@ -218,9 +202,12 @@ package body GNAT.Expect is
    -- Close --
    -----------
 
-   procedure Close (Descriptor : in out Process_Descriptor) is
-      Success : Boolean;
-      Pid     : OS_Lib.Process_Id;
+   procedure Close
+     (Descriptor : in out Process_Descriptor;
+      Status     : out Integer)
+   is
+      Current_Filter : Filter_List;
+      Next_Filter    : Filter_List;
 
    begin
       Close (Descriptor.Input_Fd);
@@ -231,14 +218,38 @@ package body GNAT.Expect is
 
       Close (Descriptor.Output_Fd);
 
-      --  ??? Should have timeouts for different signals, see ddd
-      Kill (Descriptor.Pid, 9);
+      --  ??? Should have timeouts for different signals
+
+      if Descriptor.Pid > 0 then  --  see comment in Send_Signal
+         Kill (Descriptor.Pid, Sig_Num => 9, Close => 0);
+      end if;
 
       GNAT.OS_Lib.Free (Descriptor.Buffer);
       Descriptor.Buffer_Size := 0;
 
-      Wait_Process (Pid, Success);
-      Descriptor.Pid := To_Pid (Pid);
+      Current_Filter := Descriptor.Filters;
+
+      while Current_Filter /= null loop
+         Next_Filter := Current_Filter.Next;
+         Free (Current_Filter);
+         Current_Filter := Next_Filter;
+      end loop;
+
+      Descriptor.Filters := null;
+
+      --  Check process id (see comment in Send_Signal)
+
+      if Descriptor.Pid > 0 then
+         Status := Waitpid (Descriptor.Pid);
+      else
+         raise Invalid_Process;
+      end if;
+   end Close;
+
+   procedure Close (Descriptor : in out Process_Descriptor) is
+      Status : Integer;
+   begin
+      Close (Descriptor, Status);
    end Close;
 
    ------------
@@ -303,7 +314,7 @@ package body GNAT.Expect is
    is
       N           : Expect_Match;
       Descriptors : Array_Of_Pd := (1 => Descriptor'Unrestricted_Access);
-      Try_Until   : Time := Clock + Duration (Timeout) / 1000.0;
+      Try_Until   : constant Time := Clock + Duration (Timeout) / 1000.0;
       Timeout_Tmp : Integer := Timeout;
 
    begin
@@ -336,10 +347,11 @@ package body GNAT.Expect is
             return;
          end if;
 
-         --  Calculate the timeout for the next turn.
+         --  Calculate the timeout for the next turn
+
          --  Note that Timeout is, from the caller's perspective, the maximum
          --  time until a match, not the maximum time until some output is
-         --  read, and thus can not be reused as is for Expect_Internal.
+         --  read, and thus cannot be reused as is for Expect_Internal.
 
          if Timeout /= -1 then
             Timeout_Tmp := Integer (Try_Until - Clock) * 1000;
@@ -545,7 +557,7 @@ package body GNAT.Expect is
       Num_Descriptors : Integer;
       Buffer_Size     : Integer := 0;
 
-      N               : Integer;
+      N : Integer;
 
       type File_Descriptor_Array is
         array (Descriptors'Range) of File_Descriptor;
@@ -669,7 +681,7 @@ package body GNAT.Expect is
                                    Descriptors (J).Buffer_Size - N;
                               end if;
 
-                              --  Keep what we read in the buffer.
+                              --  Keep what we read in the buffer
 
                               Descriptors (J).Buffer
                                 (Descriptors (J).Buffer_Index + 1 ..
@@ -722,10 +734,10 @@ package body GNAT.Expect is
      (Descriptor : in out Process_Descriptor;
       Timeout    : Integer := 0)
    is
+      Buffer_Size     : constant Integer := 8192;
       Num_Descriptors : Integer;
       N               : Integer;
       Is_Set          : aliased Integer;
-      Buffer_Size     : Integer := 8192;
       Buffer          : aliased String (1 .. Buffer_Size);
 
    begin
@@ -767,16 +779,98 @@ package body GNAT.Expect is
                end if;
          end case;
       end loop;
-
    end Flush;
 
+   ------------------------
+   -- Get_Command_Output --
+   ------------------------
+
+   function Get_Command_Output
+     (Command    : String;
+      Arguments  : GNAT.OS_Lib.Argument_List;
+      Input      : String;
+      Status     : not null access Integer;
+      Err_To_Out : Boolean := False) return String
+   is
+      use GNAT.Expect;
+
+      Process : Process_Descriptor;
+
+      Output : String_Access := new String (1 .. 1024);
+      --  Buffer used to accumulate standard output from the launched
+      --  command, expanded as necessary during execution.
+
+      Last : Integer := 0;
+      --  Index of the last used character within Output
+
+   begin
+      Non_Blocking_Spawn
+        (Process, Command, Arguments, Err_To_Out => Err_To_Out);
+
+      if Input'Length > 0 then
+         Send (Process, Input);
+      end if;
+
+      GNAT.OS_Lib.Close (Get_Input_Fd (Process));
+
+      declare
+         Result : Expect_Match;
+
+      begin
+         --  This loop runs until the call to Expect raises Process_Died
+
+         loop
+            Expect (Process, Result, ".+");
+
+            declare
+               NOutput : String_Access;
+               S       : constant String := Expect_Out (Process);
+               pragma Assert (S'Length > 0);
+
+            begin
+               --  Expand buffer if we need more space. Note here that we add
+               --  S'Length to ensure that S will fit in the new buffer size.
+
+               if Last + S'Length > Output'Last then
+                  NOutput := new String (1 .. 2 * Output'Last + S'Length);
+                  NOutput (Output'Range) := Output.all;
+                  Free (Output);
+
+                  --  Here if current buffer size is OK
+
+               else
+                  NOutput := Output;
+               end if;
+
+               NOutput (Last + 1 .. Last + S'Length) := S;
+               Last := Last + S'Length;
+               Output := NOutput;
+            end;
+         end loop;
+
+      exception
+         when Process_Died =>
+            Close (Process, Status.all);
+      end;
+
+      if Last = 0 then
+         return "";
+      end if;
+
+      declare
+         S : constant String := Output (1 .. Last);
+      begin
+         Free (Output);
+         return S;
+      end;
+   end Get_Command_Output;
+
    ------------------
    -- Get_Error_Fd --
    ------------------
 
    function Get_Error_Fd
-     (Descriptor : Process_Descriptor)
-      return       GNAT.OS_Lib.File_Descriptor
+     (Descriptor : Process_Descriptor) return GNAT.OS_Lib.File_Descriptor
    is
    begin
       return Descriptor.Error_Fd;
@@ -787,8 +881,7 @@ package body GNAT.Expect is
    ------------------
 
    function Get_Input_Fd
-     (Descriptor : Process_Descriptor)
-      return       GNAT.OS_Lib.File_Descriptor
+     (Descriptor : Process_Descriptor) return GNAT.OS_Lib.File_Descriptor
    is
    begin
       return Descriptor.Input_Fd;
@@ -799,8 +892,7 @@ package body GNAT.Expect is
    -------------------
 
    function Get_Output_Fd
-     (Descriptor : Process_Descriptor)
-      return       GNAT.OS_Lib.File_Descriptor
+     (Descriptor : Process_Descriptor) return GNAT.OS_Lib.File_Descriptor
    is
    begin
       return Descriptor.Output_Fd;
@@ -811,8 +903,7 @@ package body GNAT.Expect is
    -------------
 
    function Get_Pid
-     (Descriptor : Process_Descriptor)
-      return       Process_Id
+     (Descriptor : Process_Descriptor) return Process_Id
    is
    begin
       return Descriptor.Pid;
@@ -824,7 +915,6 @@ package body GNAT.Expect is
 
    procedure Interrupt (Descriptor : in out Process_Descriptor) is
       SIGINT : constant := 2;
-
    begin
       Send_Signal (Descriptor, SIGINT);
    end Interrupt;
@@ -851,15 +941,16 @@ package body GNAT.Expect is
    is
       function Fork return Process_Id;
       pragma Import (C, Fork, "__gnat_expect_fork");
-      --  Starts a new process if possible.
-      --  See the Unix command fork for more information. On systems that
-      --  don't support this capability (Windows...), this command does
-      --  nothing, and Fork will return Null_Pid.
+      --  Starts a new process if possible. See the Unix command fork for more
+      --  information. On systems that do not support this capability (such as
+      --  Windows...), this command does nothing, and Fork will return
+      --  Null_Pid.
 
       Pipe1, Pipe2, Pipe3 : aliased Pipe_Type;
 
-      Arg      : String_Access;
-      Arg_List : aliased array (1 .. Args'Length + 2) of System.Address;
+      Arg        : String_Access;
+      Arg_List   : String_List (1 .. Args'Length + 2);
+      C_Arg_List : aliased array (1 .. Args'Length + 2) of System.Address;
 
       Command_With_Path : String_Access;
 
@@ -869,6 +960,12 @@ package body GNAT.Expect is
       Set_Up_Communications
         (Descriptor, Err_To_Out, Pipe1'Access, Pipe2'Access, Pipe3'Access);
 
+      Command_With_Path := Locate_Exec_On_Path (Command);
+
+      if Command_With_Path = null then
+         raise Invalid_Process;
+      end if;
+
       --  Fork a new process
 
       Descriptor.Pid := Fork;
@@ -877,37 +974,49 @@ package body GNAT.Expect is
       --  process).
 
       if Descriptor.Pid = Null_Pid then
-
-         Command_With_Path := Locate_Exec_On_Path (Command);
-
          --  Prepare an array of arguments to pass to C
-         Arg   := new String (1 .. Command_With_Path'Length + 1);
+
+         Arg := new String (1 .. Command_With_Path'Length + 1);
          Arg (1 .. Command_With_Path'Length) := Command_With_Path.all;
-         Arg (Arg'Last)        := ASCII.Nul;
-         Arg_List (1)          := Arg.all'Address;
+         Arg (Arg'Last)        := ASCII.NUL;
+         Arg_List (1)          := Arg;
 
          for J in Args'Range loop
             Arg                     := new String (1 .. Args (J)'Length + 1);
-            Arg (1 .. Args (J)'Length)  := Args (J).all;
-            Arg (Arg'Last)              := ASCII.Nul;
-            Arg_List (J + 2 - Args'First) := Arg.all'Address;
+            Arg (1 .. Args (J)'Length)    := Args (J).all;
+            Arg (Arg'Last)                := ASCII.NUL;
+            Arg_List (J + 2 - Args'First) := Arg.all'Access;
          end loop;
 
-         Arg_List (Arg_List'Last) := System.Null_Address;
+         Arg_List (Arg_List'Last) := null;
+
+         --  Make sure all arguments are compatible with OS conventions
+
+         Normalize_Arguments (Arg_List);
+
+         --  Prepare low-level argument list from the normalized arguments
+
+         for K in Arg_List'Range loop
+            if Arg_List (K) /= null then
+               C_Arg_List (K) := Arg_List (K).all'Address;
+            else
+               C_Arg_List (K) := System.Null_Address;
+            end if;
+         end loop;
 
          --  This does not return on Unix systems
 
          Set_Up_Child_Communications
            (Descriptor, Pipe1, Pipe2, Pipe3, Command_With_Path.all,
-            Arg_List'Address);
-
-         Free (Command_With_Path);
+            C_Arg_List'Address);
       end if;
 
+      Free (Command_With_Path);
+
       --  Did we have an error when spawning the child ?
 
       if Descriptor.Pid < Null_Pid then
-         null;
+         raise Invalid_Process;
       else
          --  We are now in the parent process
 
@@ -921,6 +1030,10 @@ package body GNAT.Expect is
       if Buffer_Size /= 0 then
          Descriptor.Buffer := new String (1 .. Positive (Buffer_Size));
       end if;
+
+      --  Initialize the filters
+
+      Descriptor.Filters := null;
    end Non_Blocking_Spawn;
 
    -------------------------
@@ -998,24 +1111,25 @@ package body GNAT.Expect is
    ----------
 
    procedure Send
-     (Descriptor : in out Process_Descriptor;
-      Str        : String;
-      Add_LF     : Boolean := True;
+     (Descriptor   : in out Process_Descriptor;
+      Str          : String;
+      Add_LF       : Boolean := True;
       Empty_Buffer : Boolean := False)
    is
-      N           : Natural;
-      Full_Str    : constant String := Str & ASCII.LF;
-      Last        : Natural;
+      Line_Feed   : aliased constant String := (1 .. 1 => ASCII.LF);
       Result      : Expect_Match;
       Descriptors : Array_Of_Pd := (1 => Descriptor'Unrestricted_Access);
 
+      Discard : Natural;
+      pragma Warnings (Off, Discard);
+
    begin
       if Empty_Buffer then
 
-         --  Force a read on the process if there is anything waiting.
+         --  Force a read on the process if there is anything waiting
 
-         Expect_Internal (Descriptors, Result,
-                          Timeout => 0, Full_Buffer => False);
+         Expect_Internal
+           (Descriptors, Result, Timeout => 0, Full_Buffer => False);
          Descriptor.Last_Match_End := Descriptor.Buffer_Index;
 
          --  Empty the buffer
@@ -1023,17 +1137,15 @@ package body GNAT.Expect is
          Reinitialize_Buffer (Descriptor);
       end if;
 
+      Call_Filters (Descriptor, Str, Input);
+      Discard :=
+        Write (Descriptor.Input_Fd, Str'Address, Str'Last - Str'First + 1);
+
       if Add_LF then
-         Last := Full_Str'Last;
-      else
-         Last := Full_Str'Last - 1;
+         Call_Filters (Descriptor, Line_Feed, Input);
+         Discard :=
+           Write (Descriptor.Input_Fd, Line_Feed'Address, 1);
       end if;
-
-      Call_Filters (Descriptor, Full_Str (Full_Str'First .. Last), Input);
-
-      N := Write (Descriptor.Input_Fd,
-                  Full_Str'Address,
-                  Last - Full_Str'First + 1);
    end Send;
 
    -----------------
@@ -1045,8 +1157,19 @@ package body GNAT.Expect is
       Signal     : Integer)
    is
    begin
-      Kill (Descriptor.Pid, Signal);
-      --  ??? Need to check process status here.
+      --  A nonpositive process id passed to kill has special meanings. For
+      --  example, -1 means kill all processes in sight, including self, in
+      --  POSIX and Windows (and something slightly different in Linux). See
+      --  man pages for details. In any case, we don't want to do that. Note
+      --  that Descriptor.Pid will be -1 if the process was not successfully
+      --  started; we don't want to kill ourself in that case.
+
+      if Descriptor.Pid > 0 then
+         Kill (Descriptor.Pid, Signal, Close => 1);
+         --  ??? Need to check process status here
+      else
+         raise Invalid_Process;
+      end if;
    end Send_Signal;
 
    ---------------------------------
@@ -1058,10 +1181,14 @@ package body GNAT.Expect is
       Pipe1 : in out Pipe_Type;
       Pipe2 : in out Pipe_Type;
       Pipe3 : in out Pipe_Type;
-      Cmd   : in String;
-      Args  : in System.Address)
+      Cmd   : String;
+      Args  : System.Address)
    is
-      Input, Output, Error : File_Descriptor;
+      pragma Warnings (Off, Pid);
+
+      Input  : File_Descriptor;
+      Output : File_Descriptor;
+      Error  : File_Descriptor;
 
    begin
       --  Since Windows does not have a separate fork/exec, we need to
@@ -1084,7 +1211,7 @@ package body GNAT.Expect is
       Dup2 (Pipe2.Output, GNAT.OS_Lib.Standout);
       Dup2 (Pipe3.Output, GNAT.OS_Lib.Standerr);
 
-      Portable_Execvp (Cmd & ASCII.Nul, Args);
+      Portable_Execvp (Pid.Pid'Access, Cmd & ASCII.Nul, Args);
 
       --  The following commands are not executed on Unix systems, and are
       --  only required for Windows systems. We are now in the parent process.
@@ -1093,7 +1220,7 @@ package body GNAT.Expect is
 
       Dup2 (Input,  GNAT.OS_Lib.Standin);
       Dup2 (Output, GNAT.OS_Lib.Standout);
-      Dup2 (Error, GNAT.OS_Lib.Standerr);
+      Dup2 (Error,  GNAT.OS_Lib.Standerr);
       Close (Input);
       Close (Output);
       Close (Error);
@@ -1106,9 +1233,12 @@ package body GNAT.Expect is
    procedure Set_Up_Communications
      (Pid        : in out Process_Descriptor;
       Err_To_Out : Boolean;
-      Pipe1      : access Pipe_Type;
-      Pipe2      : access Pipe_Type;
-      Pipe3      : access Pipe_Type) is
+      Pipe1      : not null access Pipe_Type;
+      Pipe2      : not null access Pipe_Type;
+      Pipe3      : not null access Pipe_Type)
+   is
+      Status : Boolean;
+
    begin
       --  Create the pipes
 
@@ -1120,18 +1250,35 @@ package body GNAT.Expect is
          return;
       end if;
 
+      --  Record the 'parent' end of the two pipes in Pid:
+      --    Child stdin  is connected to the 'write' end of Pipe1;
+      --    Child stdout is connected to the 'read'  end of Pipe2.
+      --  We do not want these descriptors to remain open in the child
+      --  process, so we mark them close-on-exec/non-inheritable.
+
       Pid.Input_Fd  := Pipe1.Output;
+      Set_Close_On_Exec (Pipe1.Output, True, Status);
       Pid.Output_Fd := Pipe2.Input;
+      Set_Close_On_Exec (Pipe2.Input, True, Status);
 
       if Err_To_Out then
+
+         --  Reuse the standard output pipe for standard error
+
          Pipe3.all := Pipe2.all;
       else
+
+         --  Create a separate pipe for standard error
+
          if Create_Pipe (Pipe3) /= 0 then
             return;
          end if;
       end if;
 
+      --  As above, record the proper fd for the child's standard error stream
+
       Pid.Error_Fd := Pipe3.Input;
+      Set_Close_On_Exec (Pipe3.Input, True, Status);
    end Set_Up_Communications;
 
    ----------------------------------
@@ -1144,6 +1291,7 @@ package body GNAT.Expect is
       Pipe2 : in out Pipe_Type;
       Pipe3 : in out Pipe_Type)
    is
+      pragma Warnings (Off, Pid);
    begin
       Close (Pipe1.Input);
       Close (Pipe2.Output);
@@ -1159,6 +1307,8 @@ package body GNAT.Expect is
       Str        : String;
       User_Data  : System.Address := System.Null_Address)
    is
+      pragma Warnings (Off, Descriptor);
+      pragma Warnings (Off, User_Data);
    begin
       GNAT.IO.Put (Str);
    end Trace_Filter;