Recent

Author Topic: Signal Handling with sigactionhandler_t - Structures are not initialized  (Read 898 times)

domibay_hugo

  • New Member
  • *
  • Posts: 31
I try to write a Signal Handling Procedure that would react to the extenal Signals from the Operating System
as described at:
https://www.freepascal.org/docs-html/rtl/baseunix/fpsigaction.html
I declared and registered a Procedure as required at:
https://www.freepascal.org/docs-html/rtl/baseunix/sigactionhandler_t.html
Code: Pascal  [Select][+][-]
  1. procedure TMyService.OnTerminate(isignal: LongInt; pinfo: psiginfo; pcontext: PSigContext); cdecl;
  2. begin
  3.   WriteLn('TMyService.OnTerminate: go ...');
  4.  
  5.   WriteLn('got signal: ', chr(39), isignal, chr(39));
  6.  
  7.   if pinfo <> Nil then
  8.   begin
  9.     WriteLn('signal details: no: ', chr(39), pinfo^.si_signo, chr(39)
  10.       , '; cd: ', chr(39), pinfo^.si_code, chr(39)
  11.       , '; err: ', chr(39), pinfo^.si_errno, chr(39));
  12.  
  13.   end;
  14.  
  15.   if pcontext <> Nil then
  16.   begin
  17.     WriteLn('signal context: set.');
  18.   end;
  19. end;
  20.  

This Procedure is correctly called at the SigTerm Signal but I find that isignal is not initialized with any value.

Supposedly the Structures tsiginfo and TSigContext would give info about the received Signal, but they don't seem to have any content.

Sending from another Terminal a Signal with kill:

$ ps axuf|grep -i dbg
usr    14045  0.0  0.0   9792  1196 pts/0    S+   15:29   0:00  |           \_ ./my_service.dbg.run
$ kill 14045


I got this output:

$ ./my_service.dbg.run
TMyService.DoRun: new message event waiting ...
TMyService.OnTerminate: go ...
got signal: '1612258544'
signal details: no: '0'; cd: '0'; err: '0'
signal context: set.


Where could I find the actually Information about the received Signal ?

domibay_hugo

  • New Member
  • *
  • Posts: 31
Additional I need to remark even if the OnTerminate Function is defined within the Class it is invoked statically by the System.
So even if Self is not Nil any access to it will result into an Access Violation Exception.

Martin_fr

  • Administrator
  • Hero Member
  • *
  • Posts: 6447
    • wiki
Without looking at any detail....

The docs refer to a procedure. You have a method (procedure of object).

That is a big difference. A method expects an additional parameter for self. But that would not be passed by the caller....



Also (and I understand that may be for testing only): write/ln is not safe to be called in a signal/interrupt handler.


« Last Edit: March 31, 2020, 01:26:32 pm by Martin_fr »

domibay_hugo

  • New Member
  • *
  • Posts: 31
Studying further I found that SigActionRec
https://www.freepascal.org/docs-html/rtl/baseunix/sigactionrec.html
Is not POSIX compliant as it should according to:
http://man7.org/linux/man-pages/man2/sigaction.2.html
Quote
The sigaction structure is defined as something like:

           struct sigaction {
               void     (*sa_handler)(int);
               void     (*sa_sigaction)(int, siginfo_t *, void *);
               sigset_t   sa_mask;
               int        sa_flags;
               void     (*sa_restorer)(void);
           };

The sigaction.sa_sigaction field is missing in
/rtl/linux/signal.inc

So the POSIX documentation states on that:
Quote
On some architectures a union is involved: do not assign to both
       sa_handler and sa_sigaction.

in /rtl/linux/signal.inc:
Code: Pascal  [Select][+][-]
  1.  
  2.   signalhandler_t = procedure(signal: longint); cdecl;
  3.   sigactionhandler_t = procedure(signal: longint; info: psiginfo; context: psigcontext); cdecl;
  4.  
  5.   sigactionrec = record
  6.     sa_handler: sigactionhandler_t;
  7.     sa_flags: culong;
  8.     sa_restorer: sigrestorerhandler_t;
  9.     sa_mask: sigset_t;
  10.   end;
  11.  

sigactionrec.sa_handler is defined as sigactionhandler_t but should rather be a signalhandler_t Pointer.

I understand that is why I find all Parameters uninitialized because the memory mapping is wrong.
« Last Edit: March 31, 2020, 01:50:14 pm by domibay_hugo »

domibay_hugo

  • New Member
  • *
  • Posts: 31
Without looking at any detail....

The docs refer to a procedure. You have a method (procedure of object).

That is a big difference. A method expects an additional parameter for self. But that would not be passed by the caller....



Also (and I understand that may be for testing only): write/ln is not safe to be called in a signal/interrupt handler.

Additionally the Documentation is not conform with the actual FPC Source as defined in /rtl/unix/signal.inc
As the added Examples are as well, which reference the signalhandler_t Signal Handler style.
https://www.freepascal.org/docs-html/rtl/baseunix/fpsignal.html
« Last Edit: March 31, 2020, 01:50:59 pm by domibay_hugo »

PascalDragon

  • Hero Member
  • *****
  • Posts: 1922
  • Compiler Developer
I understand that is why I find all Parameters uninitialized because the memory mapping is wrong.

The existance of both sa_handler and sa_sigaction would be wrong, because - as the POSIX documentation states and as you have quoted - the two fields might be a union. Also for the kernel it does not matter whether you pass in a signalhandler_t or a sigactionhandler_t. For the kernel it's only a pointer. And it will happily call it with the correct parameters depending on whether you set up the signal using signal or sigaction.

What is your problem is that you're passing in a method. You need to either use a global function or a class function with static modifier, because otherwise the function signature will be ($Self: Pointer; signal: longint; info: psiginfo; context: psigcontext) and that results in bogus fields, because the parameters will be mixed up.

Also the sigactionrec is not declared in rtl/unix/signal.inc, but in rtl/<os>/signal.inc and as such is declared fitting for the corresponding OS. So if there would be one that explicitly has both sa_handler and sa_sigaction as separate fields then it would be declared accordingly there.

If you still have problems then please provide a full reproducible example.

domibay_hugo

  • New Member
  • *
  • Posts: 31
Thank you for looking into this issue.

I modified my code according to the hints and suggestions I received.
I built a Protype to demonstrate the observed behaviour.

A Prototype from what I try to achieve:
Code: Pascal  [Select][+][-]
  1. program signal_handler;
  2.  
  3. uses
  4.   SysUtils, BaseUnix;
  5.  
  6.  
  7. type
  8.   TMyService = class
  9.   protected
  10.     ierr: Integer;
  11.     brun: Boolean;
  12.     procedure ShutDown;
  13.     procedure InstallSignalHandler;
  14.   public
  15.     function DoRun: Integer;
  16.   end;
  17.  
  18.  
  19.   TServiceTerminateException = class(Exception)
  20.   end;
  21.  
  22.  
  23.  
  24.  
  25.  
  26.   procedure RaiseTerminateException(isignal: LongInt; pinfo: PSigInfo; pcontext: PSigContext); cdecl;
  27.   const
  28.     SMETHOD = 'RaiseTerminateException';
  29.   begin
  30.     WriteLn(SMETHOD, ': go ...');
  31.  
  32.     WriteLn('got signal: ', chr(39), isignal, chr(39));
  33.  
  34.     if pinfo <> Nil then
  35.     begin
  36.       WriteLn('signal info: no: ', chr(39), pinfo^.si_signo, chr(39)
  37.         , '; cd: ', chr(39), pinfo^.si_code, chr(39)
  38.         , '; err: ', chr(39), pinfo^.si_errno, chr(39));
  39.     end;
  40.  
  41.     if pcontext <> Nil then
  42.     begin
  43.       WriteLn('signal context: set.');
  44.     end;
  45.  
  46.     raise TServiceTerminateException.CreateHelp('Terminate Signal received', 4);
  47.  
  48.   end;
  49.  
  50.  
  51.   function TMyService.DoRun: Integer;
  52.   const
  53.     SMETHOD = 'TMyService.DoRun';
  54.   begin
  55.     Self.ierr := 0;
  56.     Self.brun := True;
  57.  
  58.     InstallSignalHandler;
  59.  
  60.     Result := Self.ierr;
  61.  
  62.     if Self.ierr <> 0 then Self.brun := False;
  63.  
  64.     if Self.brun then
  65.     begin
  66.       try  //except
  67.         while Self.brun do
  68.         begin
  69.           WriteLn(SMETHOD, ': sleeping ...');
  70.  
  71.           Sleep(15000);
  72.  
  73.           WriteLn(SMETHOD, ': sleeping done.');
  74.         end;  //while Self.brun do
  75.       except
  76.         on te: TServiceTerminateException do
  77.         begin
  78.           WriteLn(SMETHOD, ': TServiceTerminateException received.');
  79.           WriteLn('Message [', te.HelpContext, ']: ', te.Message);
  80.         end;
  81.       end;  //except
  82.  
  83.       WriteLn(SMETHOD, ': done.');
  84.  
  85.       ShutDown;
  86.  
  87.       Result := Self.ierr;
  88.  
  89.     end;  //if Self.brun then
  90.   end;
  91.  
  92. procedure TMyService.ShutDown;
  93. const
  94.   SMETHOD = 'TMyService.ShutDown';
  95. begin
  96.   WriteLn(SMETHOD, ': go ...');
  97.  
  98.  
  99.   WriteLn(SMETHOD, ': done.');
  100. end;
  101.  
  102.  
  103.  
  104. procedure TMyService.InstallSignalHandler;
  105. const
  106.   SMETHOD = 'TMyService.InstallSignalHandler';
  107. var
  108.   narec, oarec: SigActionRec;
  109. begin
  110.   narec.sa_handler := sigactionhandler(@RaiseTerminateException);
  111.   FillChar(narec.sa_mask, SizeOf(narec.sa_mask), #0);
  112.   narec.sa_flags := 0 or SA_SIGINFO;
  113.   {$ifdef Linux}               // Linux specific
  114.     narec.sa_restorer := Nil;
  115.   {$endif}
  116.  
  117.   if FPSigAction(SigTerm, @narec, @oarec) <> 0 then
  118.   begin
  119.     WriteLn(SMETHOD, ': Error: ', chr(39), fpgeterrno, chr(39), '.');
  120.     //Self.brun := False;
  121.  
  122.     Self.ierr := 1;
  123.   end;
  124. end;
  125.  
  126.  
  127.  
  128. var
  129.   service: TMyService;
  130. begin
  131.   service := TMyService.Create;
  132.  
  133.   try
  134.     try
  135.       service.DoRun;
  136.     except
  137.       on e: Exception do
  138.       begin
  139.         WriteLn('Main: Exception caught.');
  140.         WriteLn('Message [', e.HelpContext, ']: ', e.Message);
  141.       end;
  142.     end;
  143.   finally
  144.     service.Free;
  145.   end;
  146.  
  147. end.
  148.  
  149.  

I run it with the sudo command

[2020-03-31 15:51:34 - root@dev-lan27 cacti]# sudo -u upload1 ./signal_handler
TMyService.DoRun: sleeping ...
TMyService.DoRun: sleeping done.
TMyService.DoRun: sleeping ...
TMyService.DoRun: sleeping done.
TMyService.DoRun: sleeping ...
RaiseTerminateException: go ...
got signal: '15'
signal info: no: '15'; cd: '0'; err: '0'
signal context: set.
TMyService.DoRun: TServiceTerminateException received.
Message [4]: Terminate Signal received
TMyService.DoRun: done.
TMyService.ShutDown: go ...
TMyService.ShutDown: done.
Heap dump by heaptrc unit
10 memory blocks allocated : 927/936
10 memory blocks freed     : 927/936
0 unfreed memory blocks : 0
True heap size : 360448
True free heap : 360448



[2020-03-31 15:52:06 - root@dev-lan27 cacti]# strace -p 7172 > sig-hdl_strace_7172_2020-03-31-1.log 2>&1
[2020-03-31 15:51:34 - root@dev-lan27 cacti]# kill 7171


so this produces this dump:

Process 7172 attached
restart_syscall(<... resuming interrupted call ...>) = 0
write(1, "TMyService.DoRun: sleeping done."..., 33) = 33
write(1, "TMyService.DoRun: sleeping ...\n", 31) = 31
nanosleep({15, 0}, {7, 736493484})      = ? ERESTART_RESTARTBLOCK (Interrupted by signal)
--- SIGTERM {si_signo=SIGTERM, si_code=SI_USER, si_pid=7171, si_uid=0} ---
write(1, "RaiseTerminateException: go ...\n", 32) = 32
write(1, "got signal: '15'\n", 17)      = 17
write(1, "signal info: no: '15'; cd: '0'; "..., 41) = 41
write(1, "signal context: set.\n", 21)  = 21
write(1, "TMyService.DoRun: TServiceTermin"..., 55) = 55
write(1, "Message [4]: Terminate Signal re"..., 39) = 39
write(1, "TMyService.DoRun: done.\n", 24) = 24
write(1, "TMyService.ShutDown: go ...\n", 28) = 28
write(1, "TMyService.ShutDown: done.\n", 27) = 27
write(2, "Heap dump by heaptrc unit\n", 26) = 26
write(2, "10 memory blocks allocated : 927"..., 37) = 37
write(2, "10 memory blocks freed     : 927"..., 37) = 37
write(2, "0 unfreed memory blocks : 0\n", 28) = 28
write(2, "True heap size : 360448", 23) = 23
write(2, "\n", 1)                       = 1
write(2, "True free heap : 360448\n", 24) = 24
munmap(0x7f1f17ecd000, 32768)           = 0
munmap(0x7f1f17ed5000, 32768)           = 0
munmap(0x7f1f17edd000, 262144)          = 0
munmap(0x7f1f17ec5000, 32768)           = 0
exit_group(0)                           = ?
+++ exited with 0 +++


the information about where the signal came from is missing

--- SIGTERM {si_signo=SIGTERM, si_code=SI_USER, si_pid=7171, si_uid=0} ---


only If I knew the si_code I could find that the signal came from the parent.

this is relevant according to the information:
https://www.mkssoftware.com/docs/man5/siginfo_t.5.asp#Signal_Codes
« Last Edit: March 31, 2020, 05:04:43 pm by domibay_hugo »

domibay_hugo

  • New Member
  • *
  • Posts: 31
There is another Ceveat to take into account as the POSIX documentation explains:
http://man7.org/linux/man-pages/man2/sigaction.2.html
Quote
The siginfo_t argument to a SA_SIGINFO handler
       When the SA_SIGINFO flag is specified in act.sa_flags, the signal
       handler address is passed via the act.sa_sigaction field.

So I modified the TMyService.InstallSignalHandler method to set the SA_SIGINFO Flag in SigActionRec.sa_flags:
Code: Pascal  [Select][+][-]
  1.  
  2.   narec.sa_flags := 0 or SA_SIGINFO;
  3.  

Setting this Flag I can observe in the strace log that the values are correctly passed to the rt_sigaction Call:

rt_sigaction(SIGTERM, {sa_handler=0x401080, sa_mask=[], sa_flags=SA_RESTORER|SA_SIGINFO, sa_restorer=0x402d60}, {sa_handler=SIG_DFL, sa_mask=[], sa_flags=0}, 8 ) = 0


Now when I debug the example signal_handler Application I can see in the Debugger that the Fields TSigInfo._sifields._kill._pid and TSigInfo._sifields._kill._uid are set.

But as the POSIX Documentation states:
Quote
  si_signo, si_errno and si_code are defined for all signals. (si_errno is generally unused on Linux.) 
The rest of the struct may be a union, so that one should read only the fields that are meaningful for the given signal

I need TSigInfo.si_signo and TSigInfo.si_code to faithfully parse the TSigInfo structure as commented above.
« Last Edit: April 01, 2020, 12:12:49 pm by domibay_hugo »

domibay_hugo

  • New Member
  • *
  • Posts: 31
I looked into the Linux Kernel and found the si_code Definitions at:
https://github.com/torvalds/linux/blob/master/include/uapi/asm-generic/siginfo.h

include/uapi/asm-generic/siginfo.h:
168:#define SI_USER      0      /* sent by kill, sigsend, raise */


But not all si_code Definitions are included in FPC


If I try to write a clean code like:
Code: Pascal  [Select][+][-]
  1.     if pinfo^.si_code = SI_USER then
  2.     begin
  3.       ipppid := LongInt(pinfo^._sifields._kill._pid);
  4.       ippuid := LongInt(pinfo^._sifields._kill._uid);
  5.  
  6.       WriteLn('signal info: pid: ', chr(39), ipppid, chr(39)
  7.       , '; uid: ', chr(39), ippuid, chr(39));
  8.     end;  //if pinfo^.si_code = SI_USER then
  9.  

I get a Compiler Error:

Error: Identifier not found "SI_USER"

« Last Edit: April 01, 2020, 12:18:15 pm by domibay_hugo »

domibay_hugo

  • New Member
  • *
  • Posts: 31
I found another Use Case that demonstrate the vital Necessity to handle System Signals correctly in FreePascal Applications:

  • The TProcess.Execute generates a SIGCHLD Signal with si_code=CLD_EXITED when TProcess.Executable does not have the correct Permissions
  • Writing to the Pipe of the Exited Child Process generates a SIGPIPE Signal which is fatal to the Main Process

So it is vital to convert these Signals into Exceptions to be able to handle them in the correct context.

My Prototype to demonstrate this behaviour is:
Code: Pascal  [Select][+][-]
  1. program command_launcher;
  2.  
  3. uses
  4. Classes, sysutils, process, BaseUnix;
  5.  
  6.  
  7. type
  8.   TChildException = class(Exception)
  9.   end;
  10.  
  11.   TPipeException = class(Exception)
  12.   end;
  13.  
  14.  
  15. procedure RaiseChildException(isignal: LongInt; pinfo: PSigInfo; pcontext: PSigContext); cdecl;
  16. const
  17.   SMETHOD = 'RaiseChildException';
  18.   CLD_EXITED = 1;
  19. var
  20.   ichldpid, ichlduid, ichldstt: LongInt;
  21. begin
  22.   WriteLn(SMETHOD, ': go ...');
  23.  
  24.   ichldpid := -1;
  25.   ichlduid := -1;
  26.   ichldstt := -1;
  27.  
  28.   WriteLn('got signal: ', chr(39), isignal, chr(39));
  29.  
  30.   if pinfo <> Nil then
  31.   begin
  32.     WriteLn('signal info: no: ', chr(39), pinfo^.si_signo, chr(39)
  33.       , '; cd: ', chr(39), pinfo^.si_code, chr(39)
  34.       , '; err: ', chr(39), pinfo^.si_errno, chr(39));
  35.  
  36.     if pinfo^.si_code = CLD_EXITED then
  37.     begin
  38.       ichldpid := LongInt(pinfo^._sifields._sigchld._pid);
  39.       ichlduid := LongInt(pinfo^._sifields._sigchld._uid);
  40.       ichldstt := pinfo^._sifields._sigchld._status;
  41.  
  42.       WriteLn('signal info: pid: ', chr(39), ichldpid, chr(39)
  43.       , '; uid: ', chr(39), ichlduid, chr(39)
  44.       , '; stt: ', chr(39), ichldstt, chr(39));
  45.     end;  //if pinfo^.si_code = CLD_EXITED then
  46.   end;  //if pinfo <> Nil then
  47.  
  48.   if pcontext <> Nil then
  49.   begin
  50.     WriteLn('signal context: set.');
  51.   end;
  52.  
  53.   raise TChildException.CreateHelp('Child [' + IntToStr(ichldpid)
  54.     + ']: User (uid: ' + chr(39) + IntToStr(ichlduid) + chr(39)
  55.     + '): Launch failed with [' + IntToStr(ichldstt) + ']', ichldstt);
  56.  
  57. end;
  58.  
  59. procedure RaisePipeException(isignal: LongInt; pinfo: PSigInfo; pcontext: PSigContext); cdecl;
  60. const
  61.   SMETHOD = 'RaisePipeException';
  62.   SI_USER = 0;
  63. var
  64.   ipppid, ippuid: LongInt;
  65. begin
  66.   WriteLn(SMETHOD, ': go ...');
  67.  
  68.   ipppid := -1;
  69.   ippuid := -1;
  70.  
  71.   WriteLn('got signal: ', chr(39), isignal, chr(39));
  72.  
  73.   if pinfo <> Nil then
  74.   begin
  75.     WriteLn('signal info: no: ', chr(39), pinfo^.si_signo, chr(39)
  76.       , '; cd: ', chr(39), pinfo^.si_code, chr(39)
  77.       , '; err: ', chr(39), pinfo^.si_errno, chr(39));
  78.  
  79.     if pinfo^.si_code = SI_USER then
  80.     begin
  81.       ipppid := LongInt(pinfo^._sifields._kill._pid);
  82.       ippuid := LongInt(pinfo^._sifields._kill._uid);
  83.  
  84.       WriteLn('signal info: pid: ', chr(39), ipppid, chr(39)
  85.       , '; uid: ', chr(39), ippuid, chr(39));
  86.     end;  //if pinfo^.si_code = SI_USER then
  87.   end;  //if pinfo <> Nil then
  88.  
  89.   if pcontext <> Nil then
  90.   begin
  91.     WriteLn('signal context: set.');
  92.   end;
  93.  
  94.   raise TPipeException.CreateHelp('Process [' + IntToStr(ipppid)
  95.     + ']: User (uid: ' + chr(39) + IntToStr(ippuid) + chr(39)
  96.     + '): Pipe failed', isignal);
  97.  
  98. end;
  99.  
  100.  
  101. function InstallSignalHandler: Integer;
  102. const
  103.   SMETHOD = 'InstallSignalHandler';
  104. var
  105.   nchldrec, ochldrec, npprec, opprec: SigActionRec;
  106. begin
  107.   Result := 0;
  108.  
  109.   nchldrec.sa_handler := sigactionhandler(@RaiseChildException);
  110.   FillChar(nchldrec.sa_mask, SizeOf(nchldrec.sa_mask), #0);
  111.   nchldrec.sa_flags := 0 or SA_SIGINFO;
  112.   {$ifdef Linux}               // Linux specific
  113.     nchldrec.sa_restorer := Nil;
  114.   {$endif}
  115.  
  116.   if FPSigAction(SIGCHLD, @nchldrec, @ochldrec) <> 0 then
  117.   begin
  118.     WriteLn(SMETHOD, ': FPSigAction(SIGCHLD): failed with [', fpgeterrno, '].');
  119.     //Self.brun := False;
  120.  
  121.     Result := 1;
  122.   end;
  123.  
  124.   npprec.sa_handler := sigactionhandler(@RaisePipeException);
  125.   FillChar(npprec.sa_mask, SizeOf(npprec.sa_mask), #0);
  126.   npprec.sa_flags := 0 or SA_SIGINFO;
  127.   {$ifdef Linux}               // Linux specific
  128.     npprec.sa_restorer := Nil;
  129.   {$endif}
  130.  
  131.   if FPSigAction(SIGPIPE, @npprec, @opprec) <> 0 then
  132.   begin
  133.     WriteLn(SMETHOD, ': FPSigAction(SIGPIPE): failed with [', fpgeterrno, '].');
  134.     //Self.brun := False;
  135.  
  136.     Result := 1;
  137.   end;
  138. end;
  139.  
  140.  
  141. var
  142.   command: TProcess;
  143.   messagestream: TStringStream;
  144.   smessage: String;
  145.   sbuffer: String;
  146.   ReadSize: Integer;
  147.   bwait: Boolean;
  148. begin
  149.   command := TProcess.Create(nil);
  150.   messagestream := TStringStream.Create('');
  151.  
  152.   InstallSignalHandler;
  153.  
  154.   try
  155.     try
  156.       smessage := 'test input';
  157.  
  158.       messagestream.WriteString(smessage);
  159.  
  160.       command.Options    := [poUsePipes, poStderrToOutPut];
  161.       command.Executable := 'noexec_script.pl';
  162.  
  163.       command.Execute;
  164.  
  165.       WriteLn('Command Input: Writing ...');
  166.  
  167.       WriteLn('Command Input (Length ', chr(39), messagestream.Size, chr(39), '):');
  168.       WriteLn(chr(39), messagestream.DataString, chr(39));
  169.       command.Input.Write(messagestream.DataString[1], messagestream.Size);
  170.  
  171.       // Close the input on the SecondProcess
  172.       // so it finishes processing it's data
  173.       command.CloseInput;
  174.  
  175.       // and wait for it to complete
  176.  
  177.       bwait := command.WaitOnExit;
  178.  
  179.       WriteLn('Command WaitOnExit: ', chr(39), bwait, chr(39));
  180.  
  181.       // that's it! the rest of the program is just so the example
  182.       // is a little 'useful'
  183.  
  184.       WriteLn('Command Output: Reading ...');
  185.  
  186.       sbuffer := '';
  187.  
  188.       ReadSize := command.Output.NumBytesAvailable;
  189.  
  190.       WriteLn('Command Report (Length ', chr(39), ReadSize, chr(39), '):');
  191.  
  192.       SetLength(sbuffer, ReadSize);
  193.  
  194.       if ReadSize > 0 then
  195.       begin
  196.         command.Output.Read(sbuffer[1], ReadSize);
  197.  
  198.         WriteLn(chr(39), sbuffer, chr(39));
  199.       end;
  200.  
  201.       WriteLn('Command finished with [', command.ExitStatus, ']');
  202.     except
  203.       //------------------------
  204.       //Report Exception
  205.  
  206.       on e : Exception do
  207.       begin
  208.         WriteLn('Command - failed with Exception [', e.HelpContext, ']: '
  209.           , chr(39), e.Message, chr(39));
  210.       end //on E : Exception do
  211.       else
  212.       begin
  213.         WriteLn('Command - failed with Unknown Exception: '
  214.           , chr(39), 'unknown error', chr(39));
  215.       end;  //on e : Exception do
  216.     end;
  217.  
  218.   finally
  219.     // free our process objects
  220.     messagestream.Free;
  221.     command.Free;
  222.   end;
  223.  
  224. end.                                              
  225.  

With Uncaptured Signals the Output of the command_launcher Application is:

$ ./command_launcher
RaiseChildException: go ...
got signal: '17'
signal info: no: '17'; cd: '1'; err: '0'
signal info: pid: '3057'; cd: '1000'; stt: '127'
signal context: set.
Command Input: Writing ...
Command Input (Length '10'):
'test input'


The strace log documents:

strace: Process 3048 attached
restart_syscall(<... resuming interrupted nanosleep ...>) = 0
mmap(NULL, 32768, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7fc83b586000
rt_sigaction(SIGCHLD, {sa_handler=0x401080, sa_mask=[], sa_flags=SA_RESTORER|SA_SIGINFO, sa_restorer=0x402710}, {sa_handler=SIG_DFL, sa_mask=[], sa_flags=0}, 8 ) = 0
pipe([3, 4])                            = 0
pipe([5, 6])                            = 0
access("noexec_script.pl", F_OK)        = 0
fork()                                  = 3057
close(4)                                = 0
close(5)                                = 0
--- SIGCHLD {si_signo=SIGCHLD, si_code=CLD_EXITED, si_pid=3057, si_uid=1000, si_status=127, si_utime=0, si_stime=0} ---
write(1, "RaiseChildException: go ...\n", 28) = 28
write(1, "got signal: '17'\n", 17)      = 17
write(1, "signal info: no: '17'; cd: '1'; "..., 41) = 41
write(1, "signal info: pid: '3057'; cd: '1"..., 49) = 49
write(1, "signal context: set.\n", 21)  = 21
rt_sigreturn({mask=[]})                 = 0
write(1, "Command Input: Writing ...\n", 27) = 27
write(1, "Command Input (Length '10'):\n", 29) = 29
write(1, "'test input'\n", 13)          = 13
write(6, "test input", 10)              = -1 EPIPE (Tubería rota)
--- SIGPIPE {si_signo=SIGPIPE, si_code=SI_USER, si_pid=3048, si_uid=1000} ---
+++ killed by SIGPIPE +++


To recognize the SIGCHLD Signal correctly the POSIX Documentation explains:
http://man7.org/linux/man-pages/man2/sigaction.2.html
Quote
* SIGCHLD fills in si_pid, si_uid, si_status, si_utime, and si_stime,
         providing information about the child.  The si_pid field is the
         process ID of the child; si_uid is the child's real user ID.  The
         si_status field contains the exit status of the child (if si_code is CLD_EXITED), or the signal number that caused the process to change state.

So I need to match si_code against  CLD_EXITED.
Unfortunately this Symbol is not defined in the FPC either.

I looked it up in the Linux Kernel and found it in:

include/uapi/asm-generic/siginfo.h:
264:#define CLD_EXITED   1   /* child has exited */


So I added those Definitions as local Constants for the Signal Handler Procedures.

Now with the correct Signal Handler Procedure the Behaviour of the command_launcher Prototype Application changed to:

$ ./command_launcher
RaiseChildException: go ...
got signal: '17'
signal info: no: '17'; cd: '1'; err: '0'
signal info: pid: '3432'; uid: '1000'; stt: '127'
signal context: set.
Command - failed with Exception [127]: 'Child [3432]: User (uid: '1000'): Launch failed with [127]'
Heap dump by heaptrc unit
42 memory blocks allocated : 2780/2832
42 memory blocks freed     : 2780/2832
0 unfreed memory blocks : 0
True heap size : 131072
True free heap : 131072


And the strace log documents the correct Signal Handling:

strace: Process 3418 attached
restart_syscall(<... resuming interrupted nanosleep ...>) = 0
mmap(NULL, 32768, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f757ed26000
rt_sigaction(SIGCHLD, {sa_handler=0x401080, sa_mask=[], sa_flags=SA_RESTORER|SA_SIGINFO, sa_restorer=0x402d60}, {sa_handler=SIG_DFL, sa_mask=[], sa_flags=0}, 8 ) = 0
rt_sigaction(SIGPIPE, {sa_handler=0x401620, sa_mask=[], sa_flags=SA_RESTORER|SA_SIGINFO, sa_restorer=0x402d60}, {sa_handler=SIG_DFL, sa_mask=[], sa_flags=0}, 8 ) = 0
pipe([3, 4])                            = 0
pipe([5, 6])                            = 0
mmap(NULL, 32768, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f757ed1e000
access("noexec_script.pl", F_OK)        = 0
fork()                                  = 3432
close(4)                                = 0
close(5)                                = 0
--- SIGCHLD {si_signo=SIGCHLD, si_code=CLD_EXITED, si_pid=3432, si_uid=1000, si_status=127, si_utime=0, si_stime=0} ---
write(1, "RaiseChildException: go ...\n", 28) = 28
write(1, "got signal: '17'\n", 17)      = 17
write(1, "signal info: no: '17'; cd: '1'; "..., 41) = 41
write(1, "signal info: pid: '3432'; uid: '"..., 50) = 50
write(1, "signal context: set.\n", 21)  = 21
write(1, "Command - failed with Exception "..., 100) = 100
munmap(0x7f757ed4e000, 262144)          = 0
munmap(0x7f757ed46000, 32768)           = 0
munmap(0x7f757ed3e000, 32768)           = 0
write(2, "Heap dump by heaptrc unit\n", 26) = 26
write(2, "42 memory blocks allocated : 278"..., 39) = 39
write(2, "42 memory blocks freed     : 278"..., 39) = 39
write(2, "0 unfreed memory blocks : 0\n", 28) = 28
write(2, "True heap size : 131072", 23) = 23
write(2, "\n", 1)                       = 1
write(2, "True free heap : 131072\n", 24) = 24
munmap(0x7f757ed36000, 32768)           = 0
munmap(0x7f757ed2e000, 32768)           = 0
munmap(0x7f757ed26000, 32768)           = 0
munmap(0x7f757ed1e000, 32768)           = 0
exit_group(0)                           = ?
+++ exited with 0 +++
« Last Edit: April 01, 2020, 01:03:36 pm by domibay_hugo »

trev

  • Hero Member
  • *****
  • Posts: 814
  • Former Delphi 1-7 and 10.2 User
It would be helpful to log a bug for the missing defines or they may never be fixed. See How do I create a bug report.
o Lazarus v2.1.0 r63598, FPC v3.3.1 r45778, macOS 10.14.6 (with sup update), Xcode 11.3.1
o Lazarus v2.1.0 r61574, FPC v3.3.1 r42318, FreeBSD 12.1 amd64 (Parallels VM)
o FPC 3.0.4, FreeBSD 12-STABLE r361007 amd64
o Lazarus v2.1.0 r61574, FPC v3.0.4, Ubuntu 18.04 (Parallels VM)

marcov

  • Global Moderator
  • Hero Member
  • *****
  • Posts: 8555
  • FPC developer.
Afaik the siginfo is a more BSD solution. Iirc Linux only switched later on it when it got more architectures, so it might only do something on non-x86.

 

TinyPortal © 2005-2018