作者Serge Aleynikov <saleyn(at)gmail.com>
从ERLANG/OTP执行和控制OS进程。
该项目使用C ++端口程序实现ERLANG应用程序,该应用程序可为轻巧的Erlang进程对操作系统的执行进行细粒度控制。
支持以下功能:
erlang:monitor/2 。与内置的ERLANG相比,该应用程序对OS进程的控制要好于使用{spawn, Command}选项的erlang:open_port/2命令,并在仿真器退出时执行适当的OS子进程清理。
erlexec应用程序一直在Erlang和Elixir Systems的生产中使用,被认为是稳定的。
如果您觉得这个项目有用,请捐赠给:
12pt8TcoMWMkF6iY66VJQk95ntdN4pFihg0x268295486F258037CF53E504fcC1E67eba014218 Linux,Solaris,FreeBSD,OpenBSD,MacOS X
请参阅https://hexdocs.pm/erlexec/readme.html
rebar.config中添加依赖性: { deps ,
[ % ...
{ erlexec , " ~> 2.0 " }
]}.*.app.src中包含: { applications ,
[ kernel ,
stdlib ,
% ...
erlexec
]} defp deps do
[
# ...
{ :erlexec , "~> 2.0" }
]
end 确保您在本地安装了Rebar或Rebar3,并且Rebar脚本在路径中。
如果您将应用程序部署在Linux上,并希望使用与启动Exec-PORT的真实用户ID不同的有效用户ID来利用Exec-port运行任务,请确保已安装LibCap-Dev [El]库,或确保运行端口程序的用户具有sudo权利。
特定于OS的LIBCAP-DEV安装说明:
$ git clone [email protected]:saleyn/erlexec.git
$ make
# NOTE: for disabling optimized build of exec-port, do the following instead:
$ OPTIMIZE=0 make默认情况下,端口程序的实现使用poll(2)呼叫事件删除。如果您喜欢使用select(2) ,请设置以下环境变量:
$ USE_POLL=0 make该程序根据BSD许可分配。
版权(C)2003 Serge Aleynikov
┌┌前往┌┌┌┌邮编 - ─-─-─-─-─-─-─-─-─-─-─-─-─-─-─-─-┐
│││┌┌┌┐┐届┌┌┌┌邮费┐邮寄
PID2│PID2│PIDN│PIDN│ERLANG轻巧PIDS相关
│││└└└┘┘┘┘┘届└┘┘┘┘届 - ─┘
│╲╲╱
│╲╲╱
│││(链接)│
│││前达基 - ─-┐┐
在ERLANG VM中运行的Exec应用程序
│││前达基 - ─-┘┘
Erlang VM│││
└└└届└└邮编 - ─-─-─-─-─-─-─-┼前达教至期达c至─..- - ─达队 - ─-─-┘
│
┌┌前培养基─-─-─-�—─-┐
│exec-port│端口程序(单独的OS进程)
└└前培养基─-─-─-�—─-┘
╱│
(可选的stdin/stdout/stderr管道)
╱│
┌┌前培来 - ─┐┐┐届┌┐┐届─┐┐邮邮费邮邮费
│ospid1│ospid2││ospidn│托管儿童操作系统流程
└└前培来 - ─┘┘┘届└┘┘届─┘┘邮邮费邮邮费
请参阅{@link exec:exec_options()}中的类型描述。
exec-port程序需要设置SHELL变量。如果您在Docker容器中运行Erlang,则可能需要确保在启动模拟器之前正确设置SHELL 。
exec支持传递给exec:start/1 :
{debug, Level} - 打开端口程序中的调试详细性。verbose - 在Erlang代码中打开详细性。valgrind在Valgrind工具下运行EXEC,该工具需要安装在OS中。这将生成一个本地valgrind.YYYYMMDDhhmmss.log文件,其中包含Valgrind的输出。如果您需要自定义valgrind命令选项,请使用{valgrind, "/path/to/valgrind Args ..."}选项。 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)).在长生不老药中:
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为了能够使用此功能,当前用户必须具有sudo权利,否则exec-port文件必须由root拥有并具有suid bit设置(使用: 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如果有效用户没有权利访问真实用户目录中的exec-port程序,则可以将exec-port复制到某些共享位置,该位置将使用{portexe, "/path/to/exec-port"}在启动时指定。
$ 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为了能够使用此功能,当前用户必须具有sudo权利,否则exec-port文件必须具有SUID位设置,并且exec-port文件必须具有上述“构建”部分中所述的功能设置。
端口程序最初将以root启动,然后将有效的用户切换为{user, User}并将过程功能设置为cap_setuid,cap_kill,cap_sys_nice 。之后,它将允许在{limit_users, Users}选项中列出的有效用户下运行子程序。
$ 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 - port当将端口程序运行为root时,这是高度灰心的,因为它打开了一个安全孔,可以使用户能够损坏系统的能力,而对于那些可能需要这种选择的人来说,以下是如何完成该系统(按照您的风险进行操作!!!)。
注意:在这种情况下, exec将使用sudo exec-port作为root运行,或者exec-port必须具有SUID位设置(4555)并由root拥有。另一个(危险且坚定地灰心!!!)的替代方法是将erl作为root :
$ 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 - port请注意,可以通过在外壳中或执行执行执行:kill/2来实现杀死过程。
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 }
ok此命令允许指示Erlexec开始监视给定的OS进程并在进程退出时通知Erlang。它还能够将信号发送到该过程并杀死它。
% 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 }有时,接收STDIN输入的产卵子OS过程需要检测输入的结束,以处理传入的数据。当管道命令(例如cat file | tac )时,当管道的写入末端关闭时,通过在管道的读取端检测到EOF来处理。在erlexec中,通过将eof原子发送到Listing to stdin的OS进程来处理:
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