Autor Serge Aleynikov <saleyn (at) gmail.com>
Führen Sie OS -Prozesse aus Erlang/OTP aus und steuern Sie sie.
Dieses Projekt implementiert eine Erlang-Anwendung mit einem C ++-Port-Programm, das leichte Erlang-Prozesse für Feinkornsteuerung über die Ausführung von Betriebssystemprozessen ermöglicht.
Die folgenden Funktionen werden unterstützt:
erlang:monitor/2 . Diese Anwendung bietet eine wesentlich bessere Kontrolle über Betriebssystemprozesse als ein integriertes erlang:open_port/2 Befehl mit einer {spawn, Command} Option und führt die ordnungsgemäße Aufbereitung auf Child-Prozess durch, wenn der Emulator beendet ist.
Die erlexec -Anwendung wurde von Erlang und Elixir -Systemen in der Produktion verwendet und gilt als stabil.
Wenn Sie dieses Projekt nützlich finden, spenden Sie bitte an:
12pt8TcoMWMkF6iY66VJQk95ntdN4pFihg0x268295486F258037CF53E504fcC1E67eba014218 Linux, Solaris, FreeBSD, OpenBSD, macOS x
Siehe https://hexdocs.pm/erlexec/readme.html
rebar.config hinzu: { deps ,
[ % ...
{ erlexec , " ~> 2.0 " }
]}.*.app.src einbeziehen: { applications ,
[ kernel ,
stdlib ,
% ...
erlexec
]} defp deps do
[
# ...
{ :erlexec , "~> 2.0" }
]
end Stellen Sie sicher, dass Sie Rebar oder Rebar3 lokal installiert haben und das Bewehrungsskript auf dem Weg ist.
Wenn Sie die Anwendung unter Linux bereitstellen und die Ausführungsaufgaben mit effektiven Benutzer-IDs, die sich von der realen Benutzer-ID mit dem Start von EXEC-Port unterscheiden, nutzen möchten, stellen Sie entweder sicher, dass die Bibliothek LibCap-Dev [EL] installiert ist, oder stellen Sie sicher, dass das Benutzer, das das Portprogramm ausführt, sudo Rechte hat.
OS-spezifische libcap-dev-Installationsanweisungen:
$ git clone [email protected]:saleyn/erlexec.git
$ make
# NOTE: for disabling optimized build of exec-port, do the following instead:
$ OPTIMIZE=0 make Standardmäßig werden die Implementierung des Programms zur poll(2) für die Demultiplexierung von Ereignissen verwendet. Wenn Sie es vorziehen, select(2) zu verwenden, legen Sie die folgende Umgebungsvariable fest:
$ USE_POLL=0 makeDas Programm wird unter BSD -Lizenz verteilt.
Copyright (C) 2003 Serge Aleynikov
"
│ ┌wort
│ │pid1│ │pid2│ │pidn│ │ Erlang Leichte PIDs Assoziiert
│ └wort
│ ╲ │ ╱ │ │
│ ╲ │ ╱ │ │
│ ╲ │ ╱ (Links) │
│ ┌wort. │ │ │
│ │ Exec │ │ EXEC -Anwendung in Erlang VM
│ └wort. │ │ │
│ Erlang vm │ │ │
"
│
"
│ Exec-Port │ Portprogramm (separater Betriebssystemprozess)
"
╱ │ ╲
(Optionale Stdin/Stdout/Stderr -Rohre)
╱ │ ╲
┌─── Rot
│spid1│ │Spid2│ │Spidn│ Managed Child OS -Prozesse
└─── Rot
Siehe Beschreibung der Typen in {@link exec: exec_options ()}.
Für das exec-port -Programm muss die SHELL Variable festgelegt werden. Wenn Sie Erlang in einem Docker -Container ausführen, müssen Sie möglicherweise sicherstellen, dass SHELL vor dem Starten des Emulators ordnungsgemäß eingestellt ist.
exec unterstützt mehrere an exec:start/1 :
{debug, Level} - Schaltet Debugg -Ausführlichkeit im Portprogramm ein.verbose - Einschalten ausführlich im Erlang -Code eingeschaltet.valgrind - führt Exec unter dem Valgrind -Tool aus, das im Betriebssystem installiert werden muss. Dies erzeugt eine lokale valgrind.YYYYMMDDhhmmss.log -Datei mit Valgrind -Ausgabe. Wenn Sie die Befehlsoptionen von Valgrind anpassen müssen, verwenden Sie {valgrind, "/path/to/valgrind Args ..."} Option. 1 > exec : start (). % Start the port program.
{ ok , < 0.32 . 0 > }
2 > { ok , _ , I } = exec : run_link ( " sleep 1000 " , []). % Run a shell command to sleep for 1000s.
{ ok , < 0.34 . 0 > , 23584 }
3 > exec : stop ( I ). % Kill the shell command.
ok % Note that this could also be accomplished
% by doing exec:stop(pid(0,34,0)).In Elixir:
iex ( 1 ) > :exec . start
{ :ok , #PID<0.112.0>}
iex ( 2 ) > :exec . run ( "echo ok" , [ :sync , :stdout ] )
{ :ok , [ stdout: [ "ok n " ] ] }
% % Clear environment with {env, [clear]} option:
10 > f ( Bin ), { ok , [{ stdout , [ Bin ]}]} = exec : run ( " env " , [ sync , stdout , { env , [ clear ]}]), p ( re : split ( Bin , << " n " >>)).
[<< " PWD=/home/... " >>,<< " SHLVL=0 " >>, << " _=/usr/bin/env " >>,<<>>]
ok
% % Clear env and add a "TEST" env variable:
11 > f ( Bin ), { ok , [{ stdout , [ Bin ]}]} = exec : run ( " env " , [ sync , stdout , { env , [ clear , { " TEST " , " xxx " }]}]), p ( re : split ( Bin , << " n " >>)).
[<< " PWD=/home/... " >>,<< " SHLVL=0 " >>, << " _=/usr/bin/env " >>,<< " TEST=xxx " >>,<<>>]
% % Unset an "EMU" env variable:
11 > f ( Bin ), { ok , [{ stdout , [ Bin ]}]} = exec : run ( " env " , [ sync , stdout , { env , [{ " EMU " , false }]}]), p ( re : split ( Bin , << " n " >>)).
[...]
ok Um diese Funktion verwenden zu können, muss der aktuelle Benutzer entweder sudo Rechte haben oder die exec-port Datei muss von root gehören und das SUID-Bit festgelegt (Verwendung: chown root:root exec-port; chmod 4555 exec-port ):
$ ll priv/x86_64-unknown-linux-gnu/exec-port
-rwsr-xr-x 1 root root 777336 Dec 8 10:02 ./priv/x86_64-unknown-linux-gnu/exec-port Wenn der effektive Benutzer im Verzeichnis des realen Benutzers kein Recht hat, auf das exec-port -Programm zuzugreifen, kann der exec-port an einen gemeinsam genutzten Speicherort kopiert werden, der beim Start mit {portexe, "/path/to/exec-port"} angegeben wird.
$ cp $( find . - name exec - port ) / tmp
$ chmod 755 / tmp / exec - port
$ whoami
serge
$ erl
1 > exec : start ([{ user , " wheel " }, { portexe , " /tmp/exec-port " }]). % Start the port program as effective user "wheel".
{ ok , < 0.32 . 0 > }
$ ps haxo user , comm | grep exec - port
wheel exec - port Um diese Funktion verwenden zu können, muss der aktuelle Benutzer entweder sudo Rechte haben oder die exec-port Datei muss das SUID-Bit festgelegt haben, und in der exec-port Datei muss die Funktionen wie im obigen Abschnitt "Build" beschrieben festgelegt werden.
Das Portprogramm wird zunächst als root gestartet und wechselt dann den effektiven Benutzer auf {user, User} und setzt Prozessfunktionen auf cap_setuid,cap_kill,cap_sys_nice . Danach kann es ermöglicht, untergeordnete Programme unter effektiven Benutzern auszuführen, die in der Option {limit_users, Users} aufgeführt sind.
$ whoami
serge
$ erl
1 > Opts = [ root , { user , " wheel " }, { limit_users , [ " alex " , " guest " ]}],
2 > exec : start ( Opts ). % Start the port program as effective user "wheel"
% and allow it to execute commands as "alex" or "guest".
{ ok , < 0.32 . 0 > }
3 > exec : run ( " whoami " , [ sync , stdout , { user , " alex " }]). % Command is executed under effective user "alex"
{ ok ,[{ stdout ,[<< " alex n " >>]}]}
$ ps haxo user , comm | grep exec - port
wheel exec - portWährend das Ausführen des Portprogramms als Root stark entmutigt ist, da es ein Sicherheitsloch eröffnet, das den Benutzern die Möglichkeit gibt, das System zu schädigen, für diejenigen, die möglicherweise eine solche Option benötigen, können Sie dies erledigen (gehen Sie auf eigenes Risiko aus !!!).
HINWEIS: In diesem Fall würde exec sudo exec-port verwenden, um es als root auszuführen, oder der exec-port muss das SUID-Bit (4555) haben und von root gehören. Die andere (gefährliche und fest entmutigte !!!) ist es, erl als root zu betreiben:
$ whoami
serge
# Make sure the exec - port can run as root :
$ sudo _build / default / lib / erlexec / priv /*/ exec - port -- whoami
root
$ erl
1 > exec : start ([ root , { user , " root " }, { limit_users , [ " root " ]}]).
2 > exec : run ( " whoami " , [ sync , stdout ]).
{ ok , [{ stdout , [<< " root n " >>]}]}
$ ps haxo user , comm | grep exec - port
root exec - portBeachten Sie, dass das Töten eines Prozesses durch Ausführen von Kill (3) -Befehl in einer externen Shell oder durch Ausführung von Exec: Kill/2 erreicht werden kann.
1 > f ( I ), { ok , _ , I } = exec : run_link ( " sleep 1000 " , []).
{ ok , < 0.37 . 0 > , 2350 }
2 > exec : kill ( I , 15 ).
ok
** exception error : { exit_status , 15 } % Our shell died because we linked to the
% killed shell process via exec:run_link/2.
3 > exec : status ( 15 ). % Examine the exit status.
{ signal , 15 , false } % The program got SIGTERM signal and produced
% no core file. 1 > exec : start_link ([]).
{ ok , < 0.35 . 0 > }
2 > exec : run_link ( " sleep 1 " , [{ success_exit_code , 0 }, sync ]).
{ ok ,[]}
3 > exec : run ( " sleep 1 " , [{ success_exit_code , 1 }, sync ]).
{ error ,[{ exit_status , 1 }]} % Note that the command returns exit code 1 7 > f ( I ), { ok , _ , I } = exec : run_link ( " for i in 1 2 3; do echo " Test$i " ; done " ,
[{ stdout , " /tmp/output " }]).
8 > io : format ( " ~s " , [ binary_to_list ( element ( 2 , file : read_file ( " /tmp/output " )))]),
file : delete ( " /tmp/output " ).
Test1
Test2
Test3
ok 9 > exec : run ( " echo Test " , [{ stdout , print }]).
{ ok , < 0.119 . 0 > , 29651 }
Got stdout from 29651 : << " Test n " >>
10 > exec : run ( " for i in 1 2 3; do sleep 1; echo " Iter$i " ; done " ,
[{ stdout , fun ( S , OsPid , D ) -> io : format ( " Got ~w from ~w : ~p n " , [ S , OsPid , D ]) end }]).
{ ok , < 0.121 . 0 > , 29652 }
Got stdout from 29652 : << " Iter1 n " >>
Got stdout from 29652 : << " Iter2 n " >>
Got stdout from 29652 : << " Iter3 n " >>
% Note that stdout/stderr options are equivanet to {stdout, self()}, {stderr, self()}
11 > exec : run ( " echo Hello World!; echo ERR!! 1>&2 " , [ stdout , stderr ]).
{ ok , < 0.244 . 0 > , 18382 }
12 > flush ().
Shell got { stdout , 18382 ,<< " Hello World! n " >>}
Shell got { stderr , 18382 ,<< " ERR!! n " >>}
ok 13 > exec : run ( " for i in 1 2 3; do echo TEST$i; done " ,
[{ stdout , " /tmp/out " , [ append , { mode , 8#600 }]}, sync ]),
file : read_file ( " /tmp/out " ).
{ ok ,<< " TEST1 n TEST2 n TEST3 n " >>}
14 > exec : run ( " echo Test4; done " , [{ stdout , " /tmp/out " , [ append , { mode , 8#600 }]}, sync ]),
file : read_file ( " /tmp/out " ).
{ ok ,<< " TEST1 n TEST2 n TEST3 n Test4 n " >>}
15 > file : delete ( " /tmp/out " ). > f ( I ), f ( P ), { ok , P , I } = exec : run ( " echo ok " , [{ stdout , self ()}, monitor ]).
{ ok , < 0.263 . 0 > , 18950 }
16 > flush ().
Shell got { stdout , 18950 ,<< " ok n " >>}
Shell got { 'DOWN' , 18950 , process , < 0.263 . 0 > , normal }
okMit diesem Befehl ermöglicht es, Erlexec zu unterweisen, den OS -Prozess zu überwachen und Erlang zu benachrichtigen, wenn der Prozess ausgeht. Es ist auch in der Lage, Signale an den Prozess zu senden und zu töten.
% Start an externally managed OS process and retrieve its OS PID:
17 > spawn ( fun () -> os : cmd ( " echo $$ > /tmp/pid; sleep 15 " ) end ).
< 0.330 . 0 >
18 > f ( P ), P = list_to_integer ( lists : reverse ( tl ( lists : reverse ( binary_to_list ( element ( 2 ,
file : read_file ( " /tmp/pid " ))))))).
19355
% Manage the process and get notified by a monitor when it exits:
19 > exec : manage ( P , [ monitor ]).
{ ok , < 0.334 . 0 > , 19355 }
% Wait for monitor notification
20 > f ( M ), receive M -> M end .
{ 'DOWN' , 19355 , process , < 0.334 . 0 > ,{ exit_status , 10 }}
ok
21 > file : delete ( " /tmp/pid " ).
ok % Execute an OS process (script) that blocks SIGTERM with custom kill timeout, and monitor
22 > f ( I ), { ok , _ , I } = exec : run ( " trap '' SIGTERM; sleep 30 " , [{ kill_timeout , 3 }, monitor ]).
{ ok , < 0.399 . 0 > , 26347 }
% Attempt to stop the OS process
23 > exec : stop ( I ).
ok
% Wait for its completion
24 > f ( M ), receive M -> M after 10000 -> timeout end .
{ 'DOWN' , 26347 , process , < 0.403 . 0 > , normal } % Execute an OS process (script) that blocks SIGTERM, and uses a custom kill command,
% which kills it with a SIGINT. Add a monitor so that we can wait for process exit
% notification. Note the use of the special environment variable "CHILD_PID" by the
% kill command. This environment variable is set by the port program before invoking
% the kill command:
2 > f ( I ), { ok , _ , I } = exec : run ( " trap '' SIGTERM; sleep 30 " , [{ kill , " kill -n 2 ${CHILD_PID} " },
{ kill_timeout , 2 }, monitor ]).
{ ok , < 0.399 . 0 > , 26347 }
% Try to kill by SIGTERM. This does nothing, since the process is blocking SIGTERM:
3 > exec : kill ( I , sigterm ), f ( M ), receive M -> M after 0 -> timeout end .
timeout
% Attempt to stop the OS process
4 > exec : stop ( I ).
ok
% Wait for its completion
5 > f ( M ), receive M -> M after 1000 -> timeout end .
{ 'DOWN' , 26347 , process , < 0.403 . 0 > , normal } % Execute an OS process (script) that reads STDIN and echoes it back to Erlang
25 > f ( I ), { ok , _ , I } = exec : run ( " read x; echo " Got: $x " " , [ stdin , stdout , monitor ]).
{ ok , < 0.427 . 0 > , 26431 }
% Send the OS process some data via its stdin
26 > exec : send ( I , << " Test data n " >>).
ok
% Get the response written to processes stdout
27 > f ( M ), receive M -> M after 10000 -> timeout end .
{ stdout , 26431 ,<< " Got: Test data n " >>}
% Confirm that the process exited
28 > f ( M ), receive M -> M after 10000 -> timeout end .
{ 'DOWN' , 26431 , process , < 0.427 . 0 > , normal } Manchmal muss ein von Child OS erhältlicher OS -Prozess, der die STDIN -Eingabe empfängt, das Ende der Eingabe erfassen, um eingehende Daten zu verarbeiten. Bei Rohrleitungsbefehlen (z. B. cat file | tac ) wird dies behandelt, indem das EOF durch das Leseende des Rohrs erfasst wird, wenn das Schreibende des Rohrs geschlossen wird. In erlexec wird dies behandelt, indem das eof -Atom explizit an den OS -Prozess gesendet wird, der Stdin hört:
2 > Watcher = spawn ( fun F () -> receive Msg -> io : format ( " Got: ~p n " , [ Msg ]), F () after 60000 -> ok end end ).
< 0.112 . 0 >
3 > f ( Pid ), f ( OsPid ), { ok , Pid , OsPid } = exec : run ( " tac " , [ stdin , { stdout , Watcher }, { stderr , Watcher }]).
{ ok , < 0.114 . 0 > , 26143 }
4 > exec : send ( Pid , << " foo n " >>).
ok
5 > exec : send ( Pid , << " bar n " >>).
ok
6 > exec : send ( Pid , << " baz n " >>).
ok
7 > exec : send ( Pid , eof ). % % <--- sending the EOF command to the STDIN
ok
Got : { stdout , 26143 ,<< " baz n bar n foo n " >>} % Execute an shell script that blocks for 1 second and return its termination code
29 > exec : run ( " sleep 1; echo Test " , [ sync ]).
% By default all I/O is redirected to /dev/null, so no output is captured
{ ok ,[]}
% 'stdout' option instructs the port program to capture stdout and return it to caller
30 > exec : run ( " sleep 1; echo Test " , [ stdout , sync ]).
{ ok ,[{ stdout , [<< " Test n " >>]}]}
% Execute a non-existing command
31 > exec : run ( " echo1 Test " , [ sync , stdout , stderr ]).
{ error ,[{ exit_status , 32512 },
{ stderr ,[<< " /bin/bash: echo1: command not found n " >>]}]}
% Capture stdout/stderr of the executed command
32 > exec : run ( " echo Test; echo Err 1>&2 " , [ sync , stdout , stderr ]).
{ ok ,[{ stdout ,[<< " Test n " >>]},{ stderr ,[<< " Err n " >>]}]}
% Redirect stderr to stdout
33 > exec : run ( " echo Test 1>&2 " , [{ stderr , stdout }, stdout , sync ]).
{ ok , [{ stdout , [<< " Test n " >>]}]} % Execute a command by an OS shell interpreter
34 > exec : run ( " echo ok " , [ sync , stdout ]).
{ ok , [{ stdout , [<< " ok n " >>]}]}
% Execute an executable without a shell (note that in this case
% the full path to the executable is required):
35 > exec : run ([ " /bin/echo " , " ok " ], [ sync , stdout ])).
{ ok , [{ stdout , [<< " ok n " >>]}]}
% Execute a shell with custom options
36 > exec : run ([ " /bin/bash " , " -c " , " echo ok " ], [ sync , stdout ])).
{ ok , [{ stdout , [<< " ok n " >>]}]} % Execute a command without a pty
37 > exec : run ( " echo hello " , [ sync , stdout ]).
{ ok , [{ stdout ,[<< " hello n " >>]}]}
% Execute a command with a pty
38 > exec : run ( " echo hello " , [ sync , stdout , pty ]).
{ ok ,[{ stdout ,[<< " hello " >>,<< " rn " >>]}]}
% Execute a command with pty echo
39 > { ok , P0 , I0 } = exec : run ( " cat " , [ stdin , stdout , { stderr , stdout }, pty , pty_echo ]).
{ ok , < 0.162 . 0 > , 17086 }
40 > exec : send ( I0 , << " hello " >>).
ok
41 > flush ().
Shell got { stdout , 17086 ,<< " hello " >>}
ok
42 > exec : send ( I0 , << " n " >>).
ok
43 > flush ().
Shell got { stdout , 17086 ,<< " rn " >>}
Shell got { stdout , 17086 ,<< " hello rn " >>}
ok
44 > exec : send ( I , << 3 >>).
ok
45 > flush ().
Shell got { stdout , 17086 ,<< " ^C " >>}
Shell got { 'DOWN' , 17086 , process , < 0.162 . 0 > ,{ exit_status , 2 }}
ok
% Execute a command with custom pty options
46 > { ok , P1 , I1 } = exec : run ( " cat " , [ stdin , stdout , { stderr , stdout }, { pty , [{ vintr , 2 }]}, monitor ]).
{ ok , < 0.199 . 0 > , 16662 }
47 > exec : send ( I1 , << 3 >>).
ok
48 > flush ().
ok
49 > exec : send ( I1 , << 2 >>).
ok
50 > flush ().
Shell got { 'DOWN' , 16662 , process , < 0.199 . 0 > ,{ exit_status , 2 }}
ok % In the following scenario the process P0 will create a new process group
% equal to the OS pid of that process (value = GID). The next two commands
% are assigned to the same process group GID. As soon as the P0 process exits
% P1 and P2 will also get terminated by signal 15 (SIGTERM):
51 > { ok , P2 , GID } = exec : run ( " sleep 10 " , [{ group , 0 }, kill_group ]).
{ ok , < 0.37 . 0 > , 25306 }
52 > { ok , P3 , _ } = exec : run ( " sleep 15 " , [{ group , GID }, monitor ]).
{ ok , < 0.39 . 0 > , 25307 }
53 > { ok , P4 , _ } = exec : run ( " sleep 15 " , [{ group , GID }, monitor ]).
{ ok , < 0.41 . 0 > , 25308 }
54 > flush ().
Shell got { 'DOWN' , 25307 , process , < 0.39 . 0 > ,{ exit_status , 15 }}
Shell got { 'DOWN' , 25308 , process , < 0.41 . 0 > ,{ exit_status , 15 }}
ok