저자 Serge Aleynikov <saleyn (at) gmail.com>
Erlang/OTP에서 OS 프로세스를 실행 및 제어합니다.
이 프로젝트는 C ++ 포트 프로그램을 통해 Erlang 애플리케이션을 구현하여 가벼운 Erlang 프로세스가 OS 프로세스의 실행에 대한 세밀한 곡물 제어를 제공합니다.
다음과 같은 기능이 지원됩니다.
erlang:monitor/2 사용하여 OS 프로세스 종료 모니터. 이 응용 프로그램은 {spawn, Command} 옵션을 사용하여 내장 erlang:open_port/2 명령보다 OS 프로세스에 대한 제어를 훨씬 더 잘 제어하고 에뮬레이터가 종료 될 때 적절한 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) 이벤트 demultiplexing을 요청합니다. select(2) 사용하려면 다음 환경 변수를 설정하십시오.
$ USE_POLL=0 make이 프로그램은 BSD 라이센스에 따라 배포됩니다.
저작권 (C) 2003 Serge Aleynikov
┌ ── 작동사 는신 안정고림입니다. UNTEM 은신 안정장입니다. UNTEM 은신 안정장입니다. UNTER. UNTHER 신안 은신 안정장입니다. UNDER 신안 은림이 있습니다. UNTER. UNTER. UNTER. UNTHERY. UNTHER 신 안타임입니다. UN. 출신입니다. UN
│ │ │ 착수 동안 ─퀴 ─ 안피 ┌ ┌ ┌ ┌ 효율
│ │ │pid1│ │pid2│ │pidn│ │ erlang 가벼운 중량 PIDS 관련
│ │ │ 익 └ └ 효율이 관리되는 물체와 일대일로 일대일
│ │ │ ╲ │
│ │ │ ╲ │
│ │ │ ╲ (링크) │ │
│ │ ───퀴 유장
│ │ │ exec │ │ 엘랑 vm에서 실행되는 exec 응용 프로그램
│ │ ───퀴 유장
Erlang VM │ │
└얼 은신 이신 것에 대한 유장이 신 안에신 신 안타임입니다. UNTER 신안 은신 안정장입니다. UNDER 신 안타임입니다. UN. ─ 안타임입니다. UN
│
┌ ─건고장 은신 안정장입니다. UN
│ exec-port port 포트 프로그램 (별도의 OS 프로세스)
└ ─건고장 은신 안정장입니다. UN
╱ ╱ ╲
(선택적 stdin/stdout/stderr 파이프)
╱ ╱ ╲
┌ ──퀴 유자 뇨 ─ 정전 ─ 안타고 ┌ ┌┐ ┌ ─퀴 유장
│ospid1│ idospid2│ │ospidn│ 관리 아동 OS 프로세스
└ ──퀴 유자 뇨 ─ 정전 ─ 안타고 └ └┘ └ ─퀴 유장
{@link exec : exec_options ()}의 유형에 대한 설명을 참조하십시오.
exec-port 프로그램은 SHELL 변수를 설정해야합니다. Docker 컨테이너 내에서 Erlang을 실행하는 경우 에뮬레이터를 시작하기 전에 SHELL 올바르게 설정되어 있는지 확인해야 할 수도 있습니다.
exec exec:start/1 :
{debug, Level} - 포트 프로그램에서 디버그 Verbosity를 켭니다.verbose Erlang 코드에서 Verbosity를 켭니다.valgrind valgrind 도구 아래에서 실행되며 OS에 설치해야합니다. 이것은 Valgrind의 출력을 포함하는 로컬 valgrind.YYYYMMDDhhmmss.log 파일을 생성합니다. 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)).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 이 기능을 사용할 수 있으려면 현재 사용자는 sudo 권한이 있거나 exec-port 파일은 root 별로 소유해야하며 SUID 비트 세트를 가져야합니다 (사용 : 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루트로서 포트 프로그램을 실행하는 동안 러트 프로그램은 낙담합니다. 왜냐하면 사용자에게 시스템을 손상시킬 수있는 능력을 제공하는 보안 구멍을 엽니 다. 그러한 옵션이 필요한 사람들은 다음과 같은 방법이있다 (여기서는 자신의 위험에 따라 진행하는 방법 !!!).
참고 :이 경우 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킬은 외부 쉘에서 킬 (3) 명령을 실행하거나 exec : 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 atom을 OS 프로세스로 명시 적으로 전송하여 stdin을 듣는 것으로 처리됩니다.
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