Как записать строку в STDIN другого процесса в FreePascal - PullRequest
0 голосов
/ 25 марта 2020

Мои наиболее распространенные варианты использования требуют, чтобы отчеты об ошибках и выполнении отправлялись системному администратору по электронной почте. В Bash вы бы использовали оператор Pipe |, как в:

echo "test mail" | mail -s "Mail Command Test" admin@domain.com

Теперь я пытаюсь применить эту концепцию к Free Pascal с классом TProcess.

Как я могу записать строку отчета в подпроцесс mail?

Я нашел информацию в https://wiki.freepascal.org/Executing_External_Programs Но я точно не описываю свой вариант использования, поскольку не существует первого процесса

1 Ответ

0 голосов
/ 31 марта 2020

Изучение архитектуры Free Pascal Я понял, что функциональность | реализована с помощью свойства TProcess.Input, которое действительно является TOutputPipeStream.

. TOutputPipeStream Отрицательная реализация известного pipe https://www.freepascal.org/docs-html/fcl/pipes/toutputpipestream.html

TOutputPipeStream создается вызовом CreatePipeStreams для представления конца записи канала. Это потомок TStream, который не позволяет читать.

Таким образом, TStream Объекты, которые вы можете написать с помощью функции TStream.Write().

Поскольку команда echo в bash просто печатает текст, я просто заменил его на TStringStream, который динамически растет и приводит к Pascal String. Это пригодится для генерации прогрессивных отчетов. Полученный Pascal String вы можете использовать напрямую и записать в TProcess.Input Stream.

Примечание: Вам необходимо позвонить TProcess.Execute, прежде чем вы сможете записывать данные во входной поток, потому что это момент, когда происходит вызов системы fork() и открыт pipe. Но все же TProcess не работает, потому что он все еще ждет ввода STDIN.

program mail_pipe;

uses
Classes, sysutils, process;

var
  mailcommand: TProcess;
  messagestream: TStringStream;
  smessage: String;
  sbuffer: String;
  ReadSize: Integer;
  bwait: Boolean;
begin
  mailcommand := TProcess.Create(nil);
  messagestream := TStringStream.Create('');

  try
    try
      smessage := 'test mail';

      messagestream.WriteString(smessage);

      // this would be the same as 'mail -s "Mail Command Test" admin@domain.com'
      mailcommand.Options    := [poUsePipes, poStderrToOutPut];
      mailcommand.Executable := 'mail';
      mailcommand.Parameters.Add('-s');
      mailcommand.Parameters.Add('Mail Command Test');
      mailcommand.Parameters.Add('user_login@localhost');

      mailcommand.Execute;

      WriteLn('Mail Input: Writing ...');

      WriteLn('Mail Input (Length ', chr(39), messagestream.Size, chr(39), '):');
      WriteLn(chr(39), messagestream.DataString, chr(39));
      mailcommand.Input.Write(messagestream.DataString[1], messagestream.Size);
      //mailcommand.Input.Write(PChar(''), 0);

      // Close the input on the SecondProcess
      // so it finishes processing it's data
      mailcommand.CloseInput;

      // and wait for it to complete

      bwait := mailcommand.WaitOnExit;

      WriteLn('Mail Command WaitOnExit: ', chr(39), bwait, chr(39));

      // that's it! the rest of the program is just so the example
      // is a little 'useful'

      // we will reuse Buffer to output the SecondProcess's
      // output to *this* programs stdout
      WriteLn('Mail Output: Reading ...');

      sbuffer := '';

      ReadSize := mailcommand.Output.NumBytesAvailable;

      WriteLn('Mail Report (Length ', chr(39), ReadSize, chr(39), '):');

      SetLength(sbuffer, ReadSize);

      if ReadSize > 0 then
      begin
        mailcommand.Output.Read(sbuffer[1], ReadSize);

        WriteLn(chr(39), sbuffer, chr(39));
      end;

      WriteLn('Mail Command finished with [', mailcommand.ExitStatus, ']');
    except
      //------------------------
      //Report Exception

      on e : Exception do
      begin
        WriteLn('Mail Command - failed with Exception [', e.HelpContext, ']: '
          , chr(39), e.Message, chr(39));
      end //on E : Exception do
      else
      begin
        WriteLn('Mail Command - failed with Unknown Exception: '
          , chr(39), 'unknown error', chr(39));
      end;  //on e : Exception do
    end;

  finally
    // free our process objects
    messagestream.Free;
    mailcommand.Free;
  end;

end.

На самом деле вы можете наблюдать в журнале strace, что TProcess создает Pipes, выполняет поиск команды mail и разветвляет ее из основного процесса:

strace: Process 11860 attached
restart_syscall(<... resuming interrupted nanosleep ...>) = 0
mmap(NULL, 32768, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f7e8e3c0000
pipe([3, 4])                            = 0
pipe([5, 6])                            = 0
access("mail", F_OK)                    = -1 ENOENT (No such file or directory)
mmap(NULL, 32768, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f7e8e3b8000
access("/usr/lib64/qt-3.3/bin/mail", F_OK) = -1 ENOENT (No such file or directory)
access("/usr/local/bin/mail", F_OK)     = -1 ENOENT (No such file or directory)
access("/usr/local/sbin/mail", F_OK)    = -1 ENOENT (No such file or directory)
access("/usr/bin/mail", F_OK)           = 0
fork()                                  = 13921
close(4)                                = 0
close(5)                                = 0
write(1, "Mail Input: Writing ...\n", 24) = 24
write(1, "Mail Input (Length '9'):\n", 25) = 25
write(1, "'test mail'\n", 12)           = 12
write(6, "test mail", 9)                = 9
close(6)                                = 0
wait4(13921, [{WIFEXITED(s) && WEXITSTATUS(s) == 0}], 0, NULL) = 13921
--- SIGCHLD {si_signo=SIGCHLD, si_code=CLD_EXITED, si_pid=13921, si_uid=1000, si_status=0, si_utime=0, si_stime=0} ---
write(1, "Mail Command WaitOnExit: 'TRUE'\n", 32) = 32
write(1, "Mail Output: Reading ...\n", 25) = 25
ioctl(3, FIONREAD, [0])                 = 0
write(1, "Mail Report (Length '0'):\n", 26) = 26
write(1, "Mail Command finished with [0]\n", 31) = 31
close(3)                                = 0
munmap(0x7f7e8e3e0000, 32768)           = 0
munmap(0x7f7e8e3d8000, 32768)           = 0
munmap(0x7f7e8e3d0000, 32768)           = 0
munmap(0x7f7e8e3e8000, 32768)           = 0
munmap(0x7f7e8e3c8000, 32768)           = 0
munmap(0x7f7e8e3c0000, 32768)           = 0
munmap(0x7f7e8e3b8000, 32768)           = 0
exit_group(0)                           = ?
+++ exited with 0 +++

Вывод из программы mail_pipe:

$ ./mail_pipe
Mail Input: Writing ...
Mail Input (Length '9'):
'test mail'
Mail Command WaitOnExit: 'TRUE'
Mail Output: Reading ...
Mail Report (Length '0'):
Mail Command finished with [0]

Это нормальный и ожидаемый вывод, начиная с mail только выдаст результат, если он потерпит неудачу. Более подробную информацию об обработке электронной почты вы можете найти в вашем местном maillog.

В результате вы получите электронное письмо в своем Локальном почтовом ящике :

Delivered-To: user_login@localhost
Received: by user-workstation (Postfix, from userid 1000)
    id E977140439D4C; Tue, 31 Mar 2020 08:31:42 +0100 (WEST)
Date: Tue, 31 Mar 2020 08:31:42 +0100
To: user_login@localhost
Subject: Mail Command Test
User-Agent: Heirloom mailx 12.5 7/5/10
MIME-Version: 1.0
Content-Type: text/plain; charset=us-ascii
Content-Transfer-Encoding: quoted-printable
Message-Id: <20200331073142.E977140439D4C@user-workstation>
From: user_login (User Name)

test mail=

Важно, когда запись Pascal String с в поток такова, что из-за затрат памяти * из Pascal String с, данные начинаются с позиция 1 как:

SecondProcess.Input.Write(messagestream.DataString[1], messagestream.Size);

Возможные ошибки:

  • Если TProcess.Executable не существует и будет выдано Exception:
$ ./mail_pipe
Mail Command - failed with Exception [0]: 'Executable not found: "no_script.pl"'

Это может произойти, если в системе не установлена ​​команда mail.

  • Если TProcess.Executable не является исполняемым a SIGCHLD Будет сгенерирован сигнал: (И если Приложение переходит к записи в затем прерванный сигнал pipe a SIGPIPE.)
strace: Process 24911 attached restart_syscall(<...
resuming interrupted nanosleep ...>) = 0 mmap(NULL, 32768,
PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) =
0x7ff970770000 pipe([3, 4])                            = 0 pipe([5,
6])                            = 0 access("noexec_script.pl", F_OK)  
= 0 fork()                                  = 27068 close(4)                                = 0
--- SIGCHLD {si_signo=SIGCHLD, si_code=CLD_EXITED, si_pid=27068, si_uid=1000, si_status=127, si_utime=0, si_stime=0} --- close(5)     
= 0 write(1, "Mail Input: Writing ...\n", 24) = 24 write(1, "Mail Input (Length '9'):\n", 25) = 25 write(1, "'test mail'\n", 12)       
= 12 write(6, "test mail", 9)                = -1 EPIPE (Broken pipe)
--- SIGPIPE {si_signo=SIGPIPE, si_code=SI_USER, si_pid=24911, si_uid=1000} ---
+++ killed by SIGPIPE +++ 

Это может произойти, если у приложения нет разрешения на взаимодействие с этой командой (возможно, SELinux может ее заблокировать).

...