Изучение архитектуры 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
может ее заблокировать).