-- Topal: GPG/GnuPG and Alpine/Pine integration
-- Copyright (C) 2001--2022  Phillip J. Brooke
--
-- This program is free software: you can redistribute it and/or modify
-- it under the terms of the GNU General Public License version 3 as
-- published by the Free Software Foundation.
--
-- This program is distributed in the hope that it will be useful,
-- but WITHOUT ANY WARRANTY; without even the implied warranty of
-- MERCHANTABILITY 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
-- along with this program.  If not, see <http://www.gnu.org/licenses/>.

with Ada.Text_IO;
with GNAT.OS_Lib;
with Interfaces.C;
with Interfaces.C.Strings;
with Misc;

package body Externals is

   pragma Linker_Options("externals-c.o");

   procedure C_Perror (S : in Interfaces.C.char_array);
   pragma Import(C, C_Perror, "perror");

   function C_Errno return Interfaces.C.Int;
   pragma Import(C, C_Errno, "errno_wrapper");

   function C_Glob_Actual (Pattern : in Interfaces.C.Char_Array)
     return Interfaces.C.Int;
   pragma Import(C, C_Glob_Actual, "glob_actual");

   function C_Glob_Text (Index : Interfaces.C.Int)
     return Interfaces.C.Strings.Chars_Ptr;
   pragma Import(C, C_Glob_Text, "glob_text");

   procedure C_Glob_Free;
   pragma Import(C, C_Glob_Free, "glob_free");

   function Glob (Pattern : in String) return UBS_Array is
     C   : Natural;
   begin
     Misc.Debug("Glob: Looking for pattern `" & Pattern & "'");
     C := Natural(C_Glob_Actual(Interfaces.C.To_C(Pattern)));
     Misc.Debug("Glob: Count (C) is " & Integer'Image(C));
     declare
        A : UBS_Array(1..C);
     begin
        for I in 1 .. C loop
           A(I) := ToUBS(Interfaces.C.Strings.Value(C_Glob_Text(Interfaces.C.Int(I - 1))));
           Misc.Debug("Glob: Item "
                      & Integer'Image(I)
                      & " is `"
                      & ToStr(A(I))
                      & "'");
        end loop;
        Misc.Debug("Glob: Free'ing...");
        C_Glob_Free;
        Misc.Debug("Glob: A'First = " & Integer'Image(A'First));
        Misc.Debug("Glob: A'Last = " & Integer'Image(A'Last));
        Misc.Debug("Glob: A'Length = " & Integer'Image(A'Length));
        Misc.Debug("Glob: Done");
        return A;
     end;
   exception
      when others =>
         Ada.Text_IO.Put_Line(Ada.Text_IO.Standard_Error,
                              "Exception raised in Externals.Glob");
         raise;
   end Glob;

   -- Execute a command via `execvp'.
   function C_Execvp (File : in Interfaces.C.Char_Array;
                      Argv : in Interfaces.C.Strings.Chars_Ptr_Array)
        return Interfaces.C.int;
   pragma Import(C, C_Execvp, "execvp");

   -- Our wrapper for execvp.
   procedure Execvp (File : in String;
                     Argv : in UBS) is
      AVA : constant UBS_Array := Misc.Split_Arguments(Argv);
   begin
      -- Handoff to Execvp (B).
      Execvp(File, AVA);
   exception
      when others =>
         Ada.Text_IO.Put_Line(Ada.Text_IO.Standard_Error,
                              "Exception raised in Externals.Execvp (A)");
         raise;
   end Execvp;

   -- And another wrapper for execvp.
   procedure Execvp (File : in String;
                     Argv : in UBS_Array) is
      use type Interfaces.C.Size_T;
      use type Interfaces.C.Strings.Chars_Ptr;
      use type UBS;
      LI  : constant Interfaces.C.Size_T := Interfaces.C.Size_T(Argv'Last);
      AVC : Interfaces.C.Strings.Chars_Ptr_Array(0..LI+1);
      -- Last item should be NULL!
      RV  : Integer;
   begin
      Misc.Debug("Execvp (B)");
      if Argv'First /= 0 then
         Misc.Error("Argv first element should be 0.");
      end if;
      Misc.Debug("Argv is 0 to " & Integer'Image(Argv'Last));
      Misc.Debug("Argv length (number of items) is " & Integer'Image(Argv'Length));
      Misc.Debug("The C items are 0 to " & Integer'Image(Integer(LI)));
      for I in 0 .. LI loop
         AVC(I) := Interfaces.C.Strings.New_String(ToStr(Argv(Integer(I))));
         Misc.Debug("Argc["
                    & Integer'Image(Integer(I))
                    & "]=`" & ToStr(Argv(Integer(I))) & "'");
      end loop;
      -- Last item should be null!
      AVC(LI+1) := Interfaces.C.Strings.Null_Ptr;
      -- Check that the executable actually exists.
      declare
         use GNAT.OS_Lib;
         EP : String_Access;
      begin
         EP := Locate_Exec_On_Path(File);
         if EP = null then
            -- It's not there.
            Misc.Error("‘" & File & "’ is either not present or not executable.");
         end if;
         -- Free it.
         Free(EP);
      end;
      -- Actually do the exec.
      RV := Integer(C_Execvp(Interfaces.C.To_C(File), AVC));
      if RV = -1 then
         Misc.Debug("Errno is "
                    & Misc.Trim_Leading_Spaces(Integer'Image(Integer(C_Errno))));
         C_Perror(Interfaces.C.To_C("Topal: problem in Execvp (B)"));
      end if;
      Misc.Debug("Execvp: Command return value "
                 & Misc.Trim_Leading_Spaces(Integer'Image(RV)));
      if RV = -1 then
         raise Exec_Failed;
      end if;
   exception
      when others =>
         Ada.Text_IO.Put_Line(Ada.Text_IO.Standard_Error,
                              "Exception raised in Externals.Execvp (B), File=`" & File &"'");
         raise;
   end Execvp;

   -- Pipe binding.
   type Filedes_Array is array (Positive range 1..2) of Interfaces.C.Int;
   pragma Convention (C, Filedes_Array);
   function C_Pipe (Files : in Filedes_Array) return Interfaces.C.int;
   pragma Import(C, C_Pipe, "pipe");

   procedure Pipe (Reading : out Integer;
                   Writing : out Integer) is
      RV      : Integer;
      Filedes : Filedes_Array;
   begin
      Filedes := (0, 1); -- Suppress that warning.
      RV := Integer(C_Pipe(Filedes));
      if RV = -1 then
         Misc.Debug("Errno is "
                    & Misc.Trim_Leading_Spaces(Integer'Image(Integer(C_Errno))));
         C_Perror(Interfaces.C.To_C("Topal: problem in Pipe"));
      end if;
      Misc.Debug("Pipe: Command return value "
                 & Misc.Trim_Leading_Spaces(Integer'Image(RV)));
      if RV = -1 then
         raise Pipe_Failed;
      end if;
      Reading := Integer(Filedes(1));
      Writing := Integer(Filedes(2));
      Misc.Debug("Pipe created; reading is "
                 & Integer'Image(Reading)
                 & "   writing is "
                 & Integer'Image(Writing));
   exception
      when others =>
         Ada.Text_IO.Put_Line(Ada.Text_IO.Standard_Error,
                              "Exception raised in Externals.Pipe");
         raise;
   end Pipe;

   -- Fork binding.
   function C_Fork return Interfaces.C.int;
   pragma Import(C, C_Fork, "fork");

   function Fork return Integer is
      RV : Integer;
   begin
      RV := Integer(C_Fork);
      if RV = -1 then
         Misc.Debug("Errno is "
                    & Misc.Trim_Leading_Spaces(Integer'Image(Integer(C_Errno))));
         C_Perror(Interfaces.C.To_C("Topal: problem in Fork"));
      end if;
      Misc.Debug("Fork: Command return value "
                 & Misc.Trim_Leading_Spaces(Integer'Image(RV)));
      if RV = -1 then
         raise Fork_Failed;
      end if;
      return RV;
   exception
      when others =>
         Ada.Text_IO.Put_Line(Ada.Text_IO.Standard_Error,
                              "Exception raised in Externals.Fork");
         raise;
   end Fork;

   -- Dup2 binding
   function C_Dup2 (Oldfd, Newfd : Interfaces.C.Int) return Interfaces.C.int;
   pragma Import(C, C_Dup2, "dup2");

   procedure Dup2 (Oldfd, Newfd : in Integer) is
      RV : Integer;
   begin
      RV := Integer(C_Dup2(Interfaces.C.Int(Oldfd),
                           Interfaces.C.Int(Newfd)));
      if RV = -1 then
         Misc.Debug("Errno is "
                    & Misc.Trim_Leading_Spaces(Integer'Image(Integer(C_Errno))));
         C_Perror(Interfaces.C.To_C("Topal: problem in Dup2"));
      end if;
      Misc.Debug("Dup2: Command return value "
                 & Misc.Trim_Leading_Spaces(Integer'Image(RV)));
      if RV = -1 then
         raise Dup2_Failed;
      end if;
   exception
      when others =>
         Ada.Text_IO.Put_Line(Ada.Text_IO.Standard_Error,
                              "Exception raised in Externals.Dup2");
         raise;
   end Dup2;

   -- Binding for C Close.  We'll need this to tidy up nicely.
   function C_Close (FD : Interfaces.C.Int) return Interfaces.C.int;
   pragma Import(C, C_Close, "close");

   procedure CClose (FD : in Integer) is
      RV : Integer;
   begin
      RV := Integer(C_Close(Interfaces.C.Int(FD)));
      if RV = -1 then
         Misc.Debug("Errno is "
                    & Misc.Trim_Leading_Spaces(Integer'Image(Integer(C_Errno))));
         C_Perror(Interfaces.C.To_C("Topal: problem in CClose"));
      end if;
      Misc.Debug("CClose: Command return value "
                 & Misc.Trim_Leading_Spaces(Integer'Image(RV)));
      if RV = -1 then
         raise CClose_Failed;
      end if;
   exception
      when others =>
         Ada.Text_IO.Put_Line(Ada.Text_IO.Standard_Error,
                              "Exception raised in Externals.CClose");
         raise;
   end CClose;

   -- Binding for waitpid.  We return the subprocess exit code.
   function C_Waitpid_Wrapper (PID     : Interfaces.C.Int)
                    return Interfaces.C.int;
   pragma Import(C, C_Waitpid_Wrapper, "waitpid_wrapper");

   function Waitpid (PID : Integer) return Integer is
      RV : Integer;
   begin
      RV := Integer(C_Waitpid_Wrapper(Interfaces.C.Int(PID)));
      if RV = -1 then
         Misc.Debug("Errno is "
                    & Misc.Trim_Leading_Spaces(Integer'Image(Integer(C_Errno))));
         C_Perror(Interfaces.C.To_C("Topal: problem in Waitpid"));
      end if;
      Misc.Debug("Waitpid: Command return value "
                 & Misc.Trim_Leading_Spaces(Integer'Image(RV)));
      if RV = -1 then
         raise Waitpid_Failed;
      end if;
      return RV;
   exception
      when others =>
         Ada.Text_IO.Put_Line(Ada.Text_IO.Standard_Error,
                              "Exception raised in Externals.Waitpid");
         raise;
   end Waitpid;

   -- ForkExec: a replacement for System.
   function ForkExec (File : in String;
                      Argv : in UBS_Array) return Integer is
      P, E : Integer;
   begin
      P := Fork;
      if P = 0 then
         -- Child.
         Execvp(File, Argv);
         return -1;
      else
         -- Parent.  Wait for the child to finish.
         E := Waitpid(P);
         return E;
      end if;
   exception
      when others =>
         Ada.Text_IO.Put_Line(Ada.Text_IO.Standard_Error,
                              "Exception raised in Externals.ForkExec");
         raise;
   end ForkExec;

   function ForkExec (File : in String;
                      Argv : in UBS) return Integer is
      AVA : constant UBS_Array := Misc.Split_Arguments(Argv);
   begin
      return ForkExec(File, AVA);
   exception
      when others =>
         Ada.Text_IO.Put_Line(Ada.Text_IO.Standard_Error,
                              "Exception raised in Externals.ForkExec (B)");
         raise;
   end ForkExec;

   function C_Open_Append_Wrapper (S : Interfaces.C.Char_Array)
                    return Interfaces.C.int;
   pragma Import(C, C_Open_Append_Wrapper, "open_append_wrapper");

   function ForkExec_Append (File   : in String;
                             Argv   : in UBS_Array;
                             Target : in String) return Integer is
      T, P, E : Integer;
   begin
      P := Fork;
      if P = 0 then
         -- Child.
         -- Get new file handle.
         T := Integer(C_Open_Append_Wrapper(Interfaces.C.To_C(Target)));
         if T = -1 then
            Misc.Debug("Errno is "
                    & Misc.Trim_Leading_Spaces(Integer'Image(Integer(C_Errno))));
            C_Perror(Interfaces.C.To_C("Topal: problem in ForkExec_Append"));
            raise Open_Append_Failed;
         end if;
         Dup2(T, 1);
         Execvp(File, Argv);
         return -1;
      else
         -- Parent.  Wait for the child to finish.
         E := Waitpid(P);
         return E;
      end if;
   exception
      when others =>
         Ada.Text_IO.Put_Line(Ada.Text_IO.Standard_Error,
                              "Exception raised in Externals.ForkExec_Append");
         raise;
   end ForkExec_Append;
   
   function C_Open_Out_Wrapper (S : Interfaces.C.Char_Array)
                                return Interfaces.C.int;
   pragma Import(C, C_Open_Out_Wrapper, "open_out_wrapper");

   function ForkExec_Out (File   : in String;
                          Argv   : in UBS_Array;
                          Target : in String) return Integer is
      T, P, E : Integer;
   begin
      P := Fork;
      if P = 0 then
         -- Child.
         -- Get new file handle.
         T := Integer(C_Open_Out_Wrapper(Interfaces.C.To_C(Target)));
         if T = -1 then
            Misc.Debug("Errno is "
                    & Misc.Trim_Leading_Spaces(Integer'Image(Integer(C_Errno))));
            C_Perror(Interfaces.C.To_C("Topal: problem in ForkExec_Out"));
            raise Open_Out_Failed;
         end if;
         Dup2(T, 1);
         Execvp(File, Argv);
         return -1;
      else
         -- Parent.  Wait for the child to finish.
         E := Waitpid(P);
         return E;
      end if;
   exception
      when others =>
         Ada.Text_IO.Put_Line(Ada.Text_IO.Standard_Error,
                              "Exception raised in Externals.ForkExec_Out");
         raise;
   end ForkExec_Out;

   function ForkExec_Out (File   : in String;
                          Argv   : in UBS;
                          Target : in String) return Integer is
      AVA : constant UBS_Array := Misc.Split_Arguments(Argv);
   begin
      return ForkExec_Out(File, AVA, Target);
   exception
      when others =>
         Ada.Text_IO.Put_Line(Ada.Text_IO.Standard_Error,
                              "Exception raised in Externals.ForkExec_Out (B)");
         raise;
   end ForkExec_Out;

   function C_Open_In_Wrapper (S : Interfaces.C.Char_Array)
                                return Interfaces.C.int;
   pragma Import(C, C_Open_In_Wrapper, "open_in_wrapper");

   function ForkExec_In (File   : in String;
			 Argv   : in UBS_Array;
			 Source : in String) return Integer is
      S, P, E : Integer;
   begin
      P := Fork;
      if P = 0 then
         -- Child.
         -- Get new file handle.
         S := Integer(C_Open_In_Wrapper(Interfaces.C.To_C(Source)));
         if S = -1 then
            Misc.Debug("Errno is "
                    & Misc.Trim_Leading_Spaces(Integer'Image(Integer(C_Errno))));
            C_Perror(Interfaces.C.To_C("Topal: problem in ForkExec_In"));
            raise Open_In_Failed;
         end if;
         Dup2(S, 0);
         Execvp(File, Argv);
         return -1;
      else
         -- Parent.  Wait for the child to finish.
         E := Waitpid(P);
         return E;
      end if;
   exception
      when others =>
         Ada.Text_IO.Put_Line(Ada.Text_IO.Standard_Error,
                              "Exception raised in Externals.ForkExec_In");
         raise;
   end ForkExec_In;
   
   function ForkExec_InOut (File   : in String;
                            Argv   : in UBS_Array;
                            Source : in String;
                            Target : in String) return Integer is
      S, T, P, E : Integer;
   begin
      P := Fork;
      if P = 0 then
         -- Child.
         -- Get new file handle.
         S := Integer(C_Open_In_Wrapper(Interfaces.C.To_C(Source)));
         if S = -1 then
            Misc.Debug("Errno is "
                    & Misc.Trim_Leading_Spaces(Integer'Image(Integer(C_Errno))));
            C_Perror(Interfaces.C.To_C("Topal: problem in ForkExec_InOut"));
            raise Open_In_Failed;
         end if;
         Dup2(S, 0);
         -- Get new file handle.
         T := Integer(C_Open_Out_Wrapper(Interfaces.C.To_C(Target)));
         if T = -1 then
            Misc.Debug("Errno is "
                    & Misc.Trim_Leading_Spaces(Integer'Image(Integer(C_Errno))));
            C_Perror(Interfaces.C.To_C("Topal: problem in ForkExec_InOut"));
            raise Open_Out_Failed;
         end if;
         Dup2(T, 1);
         Execvp(File, Argv);
         return -1;
      else
         -- Parent.  Wait for the child to finish.
         E := Waitpid(P);
         return E;
      end if;
   exception
      when others =>
         Ada.Text_IO.Put_Line(Ada.Text_IO.Standard_Error,
                              "Exception raised in Externals.ForkExec_InOut");
         raise;
   end ForkExec_InOut;

   function ForkExec_InOut (File   : in String;
                            Argv   : in UBS;
                            Source : in String;
                            Target : in String) return Integer is
      AVA : constant UBS_Array := Misc.Split_Arguments(Argv);
   begin
      return ForkExec_InOut(File,
                            AVA,
                            Source,
                            Target);
   exception
      when others =>
         Ada.Text_IO.Put_Line(Ada.Text_IO.Standard_Error,
                              "Exception raised in Externals.ForkExec_InOut (B)");
         raise;
   end ForkExec_InOut;

   procedure ForkExec2 (File1         : in  String;
                        Argv1         : in  UBS_Array;
                        Exit1         : out Integer;
                        File2         : in  String;
                        Argv2         : in  UBS_Array;
                        Exit2         : out Integer;
                        Merge_StdErr1 : in  Boolean := False;
                        Report        : in  Boolean := False) is
      R, W, P1, P2 : Integer;
   begin
      if Report then
         Ada.Text_IO.New_Line;
         Ada.Text_IO.Put("Executing `"
                         & File1
                         & "' and `"
                         & File2
                         & "' with arguments ");
         for I in Argv1'First .. Argv1'Last loop
            Ada.Text_IO.Put("`" & ToStr(Argv1(I)) & "' ");
         end loop;
         Ada.Text_IO.Put(" and ");
         for I in Argv2'First .. Argv2'Last loop
            Ada.Text_IO.Put("`" & ToStr(Argv2(I)) & "' ");
         end loop;
         Ada.Text_IO.New_Line(2);
      end if;
      Pipe(R, W);
      P1 := Fork;
      if P1 = 0 then -- child1
         CClose(R);
         Dup2(W, 1);
         if Merge_StdErr1 then
            Dup2(W, 2);
         end if;
         Execvp(File1,
                Argv1);
      else
         P2 := Fork;
         if P2 = 0 then -- child2
            CClose(W);
            Dup2(R, 0);
            Execvp(File2,
                   Argv2);
         else
            CClose(R);
            CClose(W);
            Exit1 := Waitpid(P1);
            Exit2 := Waitpid(P2);
         end if;
      end if;
   exception
      when others =>
         Ada.Text_IO.Put_Line(Ada.Text_IO.Standard_Error,
                              "Exception raised in Externals.ForkExec2");
         raise;
   end ForkExec2;

   procedure ForkExec2 (File1         : in  String;
                        Argv1         : in  UBS;
                        Exit1         : out Integer;
                        File2         : in  String;
                        Argv2         : in  UBS;
                        Exit2         : out Integer;
                        Merge_StdErr1 : in  Boolean := False;
                        Report        : in  Boolean := False) is
      AVA1 : constant UBS_Array := Misc.Split_Arguments(Argv1);
      AVA2 : constant UBS_Array := Misc.Split_Arguments(Argv2);
   begin
      ForkExec2(File1,
                AVA1,
                Exit1,
                File2,
                AVA2,
                Exit2,
                Merge_StdErr1,
                Report);
   exception
      when others =>
         Ada.Text_IO.Put_Line(Ada.Text_IO.Standard_Error,
                              "Exception raised in Externals.ForkExec2 (B)");
         raise;
   end ForkExec2;

   procedure ForkExec2_Out (File1  : in  String;
                            Argv1  : in  UBS_Array;
                            Exit1  : out Integer;
                            File2  : in  String;
                            Argv2  : in  UBS_Array;
                            Exit2  : out Integer;
                            Target : in  String) is
      T, R, W, P1, P2 : Integer;
   begin
      Pipe(R, W);
      P1 := Fork;
      if P1 = 0 then -- child1
         CClose(R);
         Dup2(W, 1);
         Execvp(File1,
                Argv1);
      else
         P2 := Fork;
         if P2 = 0 then -- child2
            CClose(W);
            Dup2(R, 0);
            -- Get new file handle.
            T := Integer(C_Open_Out_Wrapper(Interfaces.C.To_C(Target)));
            if T = -1 then
               Misc.Debug("Errno is "
                          & Misc.Trim_Leading_Spaces(Integer'Image(Integer(C_Errno))));
               C_Perror(Interfaces.C.To_C("Topal: problem in ForkExec2_Out"));
               raise Open_Out_Failed;
            end if;
            Dup2(T, 1);
            Execvp(File2,
                   Argv2);
         else
            CClose(R);
            CClose(W);
            Exit1 := Waitpid(P1);
            Exit2 := Waitpid(P2);
         end if;
      end if;
   exception
      when others =>
         Ada.Text_IO.Put_Line(Ada.Text_IO.Standard_Error,
                              "Exception raised in Externals.ForkExec2_Out");
         raise;
   end ForkExec2_Out;

   procedure ForkExec2_Out (File1  : in  String;
                            Argv1  : in  UBS;
                            Exit1  : out Integer;
                            File2  : in  String;
                            Argv2  : in  UBS_Array;
                            Exit2  : out Integer;
                            Target : in  String) is
      AVA1 : constant UBS_Array := Misc.Split_Arguments(Argv1);
   begin
      ForkExec2_Out(File1, AVA1, Exit1,
                    File2, Argv2, Exit2,
                    Target);
   exception
      when others =>
         Ada.Text_IO.Put_Line(Ada.Text_IO.Standard_Error,
                              "Exception raised in Externals.ForkExec2_Out(B)");
         raise;
   end ForkExec2_Out;



   procedure ForkExec2_InOut (File1  : in  String;
                              Argv1  : in  UBS_Array;
                              Exit1  : out Integer;
                              File2  : in  String;
                              Argv2  : in  UBS_Array;
                              Exit2  : out Integer;
                              Source : in  String;
                              Target : in  String) is
      S, T, R, W, P1, P2 : Integer;
   begin
      Pipe(R, W);
      P1 := Fork;
      if P1 = 0 then -- child1
         CClose(R);
         Dup2(W, 1);
         -- Get new file handle.
         S := Integer(C_Open_In_Wrapper(Interfaces.C.To_C(Source)));
         if S = -1 then
            Misc.Debug("Errno is "
                    & Misc.Trim_Leading_Spaces(Integer'Image(Integer(C_Errno))));
            C_Perror(Interfaces.C.To_C("Topal: problem in ForkExec2_InOut"));
            raise Open_In_Failed;
         end if;
         Dup2(S, 0);
         -- Get new file handle.
         Execvp(File1,
                Argv1);
      else
         P2 := Fork;
         if P2 = 0 then -- child2
            CClose(W);
            Dup2(R, 0);
            -- Get new file handle.
            T := Integer(C_Open_Out_Wrapper(Interfaces.C.To_C(Target)));
            if T = -1 then
               Misc.Debug("Errno is "
                          & Misc.Trim_Leading_Spaces(Integer'Image(Integer(C_Errno))));
               C_Perror(Interfaces.C.To_C("Topal: problem in ForkExec2_InOut"));
               raise Open_Out_Failed;
            end if;
            Dup2(T, 1);
            Execvp(File2,
                   Argv2);
         else
            CClose(R);
            CClose(W);
            Exit1 := Waitpid(P1);
            Exit2 := Waitpid(P2);
         end if;
      end if;
   exception
      when others =>
         Ada.Text_IO.Put_Line(Ada.Text_IO.Standard_Error,
                              "Exception raised in Externals.ForkExec2_InOut");
         raise;
   end ForkExec2_InOut;

   procedure ForkExec3_Out (File1  : in  String;
                            Argv1  : in  UBS;
                            Exit1  : out Integer;
                            File2  : in  String;
                            Argv2  : in  UBS_Array;
                            Exit2  : out Integer;
                            File3  : in  String;
                            Argv3  : in  UBS_Array;
                            Exit3  : out Integer;
                            Target : in  String) is
      T, R1, W1, R2, W2, P1, P2, P3 : Integer;
      AVA1 : constant UBS_Array := Misc.Split_Arguments(Argv1);
   begin
      Pipe(R1, W1);
      Pipe(R2, W2);
      P1 := Fork;
      if P1 = 0 then -- child1
         CClose(R1);
         CClose(R2);
         CClose(W2);
         Dup2(W1, 1);
         Execvp(File1,
                AVA1);
      else
         P2 := Fork;
         if P2 = 0 then -- child2
            CClose(W1);
            CClose(R2);
            Dup2(R1, 0);
            Dup2(W2, 1);
            Execvp(File2,
                   Argv2);
         else
            P3 := Fork;
            if P3 = 0 then -- child3
               CClose(R1);
               CClose(W1);
               CClose(W2);
               Dup2(R2, 0);
               -- Get new file handle.
               T := Integer(C_Open_Out_Wrapper(Interfaces.C.To_C(Target)));
               if T = -1 then
                  Misc.Debug("Errno is "
                             & Misc.Trim_Leading_Spaces(Integer'Image(Integer(C_Errno))));
                  C_Perror(Interfaces.C.To_C("Topal: problem in ForkExec3_Out"));
                  raise Open_Out_Failed;
               end if;
               Dup2(T, 1);
               Execvp(File3,
                      Argv3);
            else
               CClose(R1);
               CClose(W1);
               CClose(R2);
               CClose(W2);
               Exit1 := Waitpid(P1);
               Exit2 := Waitpid(P2);
               Exit3 := Waitpid(P3);
            end if;
         end if;
      end if;
   exception
      when others =>
         Ada.Text_IO.Put_Line(Ada.Text_IO.Standard_Error,
                              "Exception raised in Externals.ForkExec3_Out");
         raise;
   end ForkExec3_Out;
   
   -- More wrappers.
   function Open_Append (S : String) return Integer is
   begin
      return Integer(C_Open_Append_Wrapper(Interfaces.C.To_C(S)));
   exception
      when others =>
         Ada.Text_IO.Put_Line(Ada.Text_IO.Standard_Error,
                              "Exception raised in Externals.Open_Append");
         raise;
   end Open_Append;
   
   function Open_Out    (S : String) return Integer is
   begin
      return Integer(C_Open_Out_Wrapper(Interfaces.C.To_C(S)));
   exception
      when others =>
         Ada.Text_IO.Put_Line(Ada.Text_IO.Standard_Error,
                              "Exception raised in Externals.Open_Out");
         raise;
   end Open_Out;

   function Open_In     (S : String) return Integer is
   begin
      return Integer(C_Open_In_Wrapper(Interfaces.C.To_C(S)));
   exception
      when others =>
         Ada.Text_IO.Put_Line(Ada.Text_IO.Standard_Error,
                              "Exception raised in Externals.Open_In");
         raise;
   end Open_In;
   

end Externals;
