spawn_24.0.0_8f4c2fa8/source/spawn/spawn-internal-monitor__posix.adb

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
--
--  Copyright (C) 2018-2022, AdaCore
--
--  SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
--

with Ada.Containers.Hashed_Maps;
with Ada.Containers.Synchronized_Queue_Interfaces;
with Ada.Containers.Unbounded_Synchronized_Queues;
with Ada.Interrupts.Names;
with Ada.Strings.Unbounded;

with Interfaces.C.Strings;

with GNAT.OS_Lib;

with Spawn.Channels;
with Spawn.Environments.Internal;
with Spawn.Polls.POSIX_Polls;
with Spawn.Posix;

package body Spawn.Internal.Monitor is
   use type Interfaces.C.int;
   use all type Pipe_Kinds;

   type Process_Access is access all Process'Class;

   procedure Start_Process (Self : Process_Access);

   procedure Do_Close_Pipe
     (Self : Process_Access;
      Kind : Common.Standard_Pipe);

   procedure Check_Children;

   package Command_Queue_Interfaces is
     new Ada.Containers.Synchronized_Queue_Interfaces (Command);

   package Command_Queues is new Ada.Containers.Unbounded_Synchronized_Queues
     (Queue_Interfaces => Command_Queue_Interfaces);

   Queue : Command_Queues.Queue;
   Poll  : Spawn.Polls.Poll_Access;
   Wake  : Interfaces.C.int := -1;

   type Wake_Up_Listener is new Spawn.Polls.Listener with null record;

   overriding procedure On_Event
     (Self   : in out Wake_Up_Listener;
      Poll   : Spawn.Polls.Poll_Access;
      Value  : Spawn.Polls.Descriptor;
      Events : Spawn.Polls.Event_Set);
   --  Restart watching of the pipe descriptor.

   function Hash (Value : Interfaces.C.int) return Ada.Containers.Hash_Type;

   package Process_Maps is new Ada.Containers.Hashed_Maps
     (Key_Type        => Interfaces.C.int,
      Element_Type    => Process_Access,
      Hash            => Hash,
      Equivalent_Keys => Interfaces.C."=",
      "="             => "=");

   Map : Process_Maps.Map;

   Pipe_Flags : constant Interfaces.C.int := Posix.O_CLOEXEC;

   protected SIGCHLD is
      entry Wait;

      procedure Handle
        with Interrupt_Handler,
             Attach_Handler => Ada.Interrupts.Names.SIGCHLD;
   private
      Fired : Boolean := False;
   end SIGCHLD;

   protected body SIGCHLD is

      entry Wait when Fired is
      begin
         Fired := False;
      end Wait;

      procedure Handle is
      begin
         Fired := True;
      end Handle;

   end SIGCHLD;

   --------------------
   -- Check_Children --
   --------------------

   procedure Check_Children is

      function WIFEXITED (Status : Interfaces.C.unsigned) return Boolean;

      function WEXITSTATUS
        (Status : Interfaces.C.unsigned) return Interfaces.C.unsigned
           with Import        => True,
                Convention    => C,
                External_Name => "__spawn_WEXITSTATUS";

      function WIFSIGNALED (Status : Interfaces.C.unsigned) return Boolean;

      function WTERMSIG
        (Status : Interfaces.C.unsigned) return Interfaces.C.unsigned
           with Import, Convention => C, External_Name => "__spawn_WTERMSIG";

      ---------------
      -- WIFEXITED --
      ---------------

      function WIFEXITED (Status : Interfaces.C.unsigned) return Boolean is
         function Imported
           (Status : Interfaces.C.unsigned) return Interfaces.C.int
              with Import        => True,
                   Convention    => C,
                   External_Name => "__spawn_WIFEXITED";

      begin
         return Imported (Status) /= 0;
      end WIFEXITED;

      -----------------
      -- WIFSIGNALED --
      -----------------

      function WIFSIGNALED (Status : Interfaces.C.unsigned) return Boolean is
         function Imported
           (Status : Interfaces.C.unsigned) return Interfaces.C.int
              with Import        => True,
                   Convention    => C,
                   External_Name => "__spawn_WIFSIGNALED";

      begin
         return Imported (Status) /= 0;
      end WIFSIGNALED;

      status  : aliased Interfaces.C.unsigned := 0;
      Process : Process_Access;

   begin
      loop
         declare
            pid : constant Interfaces.C.int :=
              Posix.waitpid (-1, status'Unchecked_Access, Posix.WNOHANG);

            Cursor : constant Process_Maps.Cursor := Map.Find (pid);
         begin
            exit when pid <= 0;  --  no more children change state

            if Process_Maps.Has_Element (Cursor) then
               Process := Process_Maps.Element (Cursor);

               Process.Exit_Status :=
                 (if WIFEXITED (status) then Normal else Crash);

               case Process.Exit_Status is
                  when Normal =>
                     Process.Exit_Code :=
                       Process_Exit_Code (WEXITSTATUS (status));

                  when Crash =>
                     Process.Exit_Code :=
                       (if WIFSIGNALED (status)
                        then Process_Exit_Code (WTERMSIG (status))
                        else Process_Exit_Code'Last);
               end case;

               if Spawn.Channels.Is_Active (Process.Channels) then
                  Process.Pending_Finish := True;
               elsif Process.Pending_Error = 0 then
                  Process.Status := Not_Running;
                  Process.Emit_Finished
                    (Process.Exit_Status, Process.Exit_Code);
               else
                  Process.Status := Not_Running;
                  Process.Emit_Error_Occurred (Process.Pending_Error);
               end if;
            end if;
         end;
      end loop;
   end Check_Children;

   -------------------
   -- Do_Close_Pipe --
   -------------------

   procedure Do_Close_Pipe
     (Self : Process_Access;
      Kind : Common.Standard_Pipe) is
   begin
      Spawn.Channels.Close_Parent_Descriptor (Self.Channels, Kind, Poll);
   end Do_Close_Pipe;

   -------------
   -- Enqueue --
   -------------

   procedure Enqueue (Value : Command) is
      Ignore : Interfaces.C.size_t;
   begin
      Queue.Enqueue (Value);
      --  Wake up monitoring tread.
      Ignore := Posix.write (Wake, (1 => 0), 1);
   end Enqueue;

   ----------
   -- Hash --
   ----------

   function Hash (Value : Interfaces.C.int) return Ada.Containers.Hash_Type is
   begin
      return Ada.Containers.Hash_Type (abs Value);
   end Hash;

   ----------------
   -- Loop_Cycle --
   ----------------

   procedure Loop_Cycle (Timeout : Duration) is
      use type Ada.Containers.Count_Type;

      Command : Monitor.Command;
   begin
      select
         SIGCHLD.Wait;
         Check_Children;
      else
         null;
      end select;

      while Queue.Current_Use > 0 loop
         Queue.Dequeue (Command);

         case Command.Kind is
            when Start =>
               Start_Process (Process_Access (Command.Process));
            when Close_Pipe =>
               Do_Close_Pipe (Command.Process, Command.Pipe);
            when Watch_Pipe =>
               Spawn.Channels.Start_Watch
                 (Command.Process.Channels, Command.Pipe, Poll);
         end case;
      end loop;

      Poll.Wait (Timeout);
   end Loop_Cycle;

   -------------------
   -- Start_Process --
   -------------------

   procedure Start_Process (Self : Process_Access) is
      use Ada.Strings.Unbounded;
      use type Interfaces.C.Strings.chars_ptr;
      use type Ada.Streams.Stream_Element_Offset;

      procedure Send_Errno_And_Exit with No_Return;
      --  Put errno into Launch pipe end abort process
      procedure Prepare_Arguments (argv : out Posix.chars_ptr_array);
      --  Allocate argumnets
      procedure Free (argv : out Posix.chars_ptr_array);
      --  Deallocate argumnets

      --------------------
      -- Free_Arguments --
      --------------------

      procedure Free (argv : out Posix.chars_ptr_array) is
      begin
         for J in argv'Range loop
            Interfaces.C.Strings.Free (argv (J));
         end loop;
      end Free;

      -----------------------
      -- Prepare_Arguments --
      -----------------------

      procedure Prepare_Arguments (argv : out Posix.chars_ptr_array) is
      begin
         argv (0) := Interfaces.C.Strings.New_String (Self.Program);

         for J in 1 .. Self.Arguments.Last_Index loop
            argv (J) := Interfaces.C.Strings.New_String
              (Self.Arguments.Element (J));
         end loop;

         argv (argv'Last) := Interfaces.C.Strings.Null_Ptr;
      end Prepare_Arguments;

      Child_Ends : Spawn.Channels.Pipe_Array;

      Dup : constant array (Stdout .. Stdin) of Interfaces.C.int :=
        (Stdin => 0, Stdout => 1, Stderr => 2);

      ----------------
      -- Send_Errno --
      ----------------

      procedure Send_Errno_And_Exit is
         count : Interfaces.C.size_t;
         pragma Unreferenced (count);
         errno : Integer;
         Error_Dump : Ada.Streams.Stream_Element_Array (1 .. errno'Size / 8)
           with Import, Convention => Ada, Address => errno'Address;
      begin
         errno := GNAT.OS_Lib.Errno;
         count := Posix.write
           (Child_Ends (Launch),
            Error_Dump,
            Error_Dump'Length);
         GNAT.OS_Lib.OS_Exit (127);
      end Send_Errno_And_Exit;

      pid  : Interfaces.C.int;
      dir  : Interfaces.C.Strings.chars_ptr :=
        (if Length (Self.Directory) = 0 then Interfaces.C.Strings.Null_Ptr
           else Interfaces.C.Strings.New_String
             (To_String (Self.Directory)));

      argv : Posix.chars_ptr_array (0 .. Natural (Self.Arguments.Length) + 1);
      envp : Posix.chars_ptr_array :=
        Spawn.Environments.Internal.Raw (Self.Environment);

      Ok   : Boolean;
   begin
      --  Create pipes for children's stdio
      Spawn.Channels.Setup_Channels
        (Self.Channels, Self.Use_PTY, Child_Ends, Ok);

      if not Ok then
         Interfaces.C.Strings.Free (dir);
         return;
      end if;

      Prepare_Arguments (argv);

      pid := Posix.fork;

      if pid = -1 then
         --  Fork failed
         Self.Emit_Error_Occurred (GNAT.OS_Lib.Errno);
         Free (argv);
         Free (envp);
         Interfaces.C.Strings.Free (dir);
         return;
      elsif pid = 0 then  --  Child process
         --  Close unused ends
         Spawn.Channels.Close_Parent_Descriptors (Self.Channels, Ok);

         if not Ok then
            Send_Errno_And_Exit;
         --  Copy fd to standard numbers
         elsif (for some X in Dup'Range =>
                  Posix.dup2 (Child_Ends (X), Dup (X)) = -1)
         then
            Send_Errno_And_Exit;
         --  Change directory if needed
         elsif dir /= Interfaces.C.Strings.Null_Ptr
           and then Posix.chdir (dir) /= 0
         then
            Send_Errno_And_Exit;
         else  --  Replace executable
            declare
               Ignore : Interfaces.C.int;
            begin
               Ignore := Posix.execve (argv (0), argv, envp);
               Send_Errno_And_Exit;
            end;
         end if;
      end if;

      --  Parent process
      Free (argv);
      Free (envp);
      Interfaces.C.Strings.Free (dir);

      --  Close unused ends
      Spawn.Channels.Close_Child_Descriptors (Self.Channels, Ok);

      if not Ok then
         Self.Emit_Error_Occurred (GNAT.OS_Lib.Errno);
         return;
      end if;

      Self.pid := pid;
      Map.Insert (pid, Self);

      for Kind in Launch .. Stderr loop
         Spawn.Channels.Start_Watch (Self.Channels, Kind, Poll);
      end loop;
   end Start_Process;

   procedure Initialize;
   --  Do low level initialization if needed

   procedure Dummy is null;
   --  This is to be used in Initialize procedure

   procedure Initialize is separate;

   type POSIX_Poll_Access is access Polls.POSIX_Polls.POSIX_Poll;

   --------------
   -- On_Event --
   --------------

   overriding procedure On_Event
     (Self   : in out Wake_Up_Listener;
      Poll   : Spawn.Polls.Poll_Access;
      Value  : Spawn.Polls.Descriptor;
      Events : Spawn.Polls.Event_Set)
   is
      Byte   : Ada.Streams.Stream_Element_Array (1 .. 1);
      Ignore : Interfaces.C.size_t;
   begin
      if Events (Spawn.Polls.Input) then
         Ignore := Posix.read (Value, Byte, Byte'Length);
         Poll.Watch
           (Value    => Value,
            Events   => Spawn.Polls.Input,
            Listener => Self'Unchecked_Access);
      end if;
   end On_Event;

   WL : aliased Wake_Up_Listener;

begin
   declare
      Object : constant POSIX_Poll_Access := new Polls.POSIX_Polls.POSIX_Poll;
      Value  : Posix.Fd_Pair;
      Result : constant Interfaces.C.int := Posix.pipe2 (Value, Pipe_Flags);
   begin
      pragma Assert (Result = 0, GNAT.OS_Lib.Errno_Message);
      Wake := Value (Posix.Write_End);
      Poll := Spawn.Polls.Poll_Access (Object);
      Poll.Initialize;
      Poll.Watch
        (Value    => Value (Posix.Read_End),
         Events   => Spawn.Polls.Input,
         Listener => WL'Access);
   end;
   Initialize;
end Spawn.Internal.Monitor;