Autor Serge Aleynikov <saleyn (at) gmail.com>
Executar e controlar os processos de OS a partir de Erlang/OTP.
Este projeto implementa um aplicativo Erlang com um programa de porta C ++ que fornece processos leves de eRlang processos de grãos finos sobre a execução dos processos do sistema operacional.
Os seguintes recursos são suportados:
erlang:monitor/2 . Este aplicativo fornece um controle significativamente melhor sobre os processos do sistema operacional do que o comando erlang:open_port/2 com uma opção {spawn, Command} e executa a limpeza adequada do processo filho do SO quando o emulador sair.
O aplicativo erlexec tem sido em uso da produção pelos sistemas Erlang e Elixir e é considerado estável.
Se você achar esse projeto útil, doe para:
12pt8TcoMWMkF6iY66VJQk95ntdN4pFihg0x268295486F258037CF53E504fcC1E67eba014218 Linux, Solaris, FreeBSD, OpenBSD, MacOS X
Consulte 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 Certifique -se de ter vergalhão ou vergalhão instalado localmente e o script de vergalhão está no caminho.
Se você estiver implantando o aplicativo no Linux e gostaria de aproveitar as tarefas executadas Exec-Port usando IDs de usuário eficazes diferentes do ID de usuário real que iniciou o EXEC-Port, verifique se a biblioteca Libcap-dev [EL] está instalada ou verifique se o usuário executando o programa portuário possui direitos sudo .
Instruções de instalação específicas do 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 Por padrão, a implementação do programa portuária usa poll(2) Ligue para a desmultiplexação de eventos. Se você preferir usar select(2) , defina a seguinte variável de ambiente:
$ USE_POLL=0 makeO programa é distribuído pela licença BSD.
Copyright (C) 2003 Serge Aleynikov
┌───────periptórios fáceis de fazer
│ ┌sto
│ │pid1│ │pid2│ │pidn│ │ erlang PIDs leves associados
│ └sto
│ ╲ │ ╱ │
│ ╲ │ ╱ │
│ ╲ │ ╱ (links) │
│ ┌───── fare │ │
│ │ Exec │ │ Exec Application em execução em Erlang VM
│ └───── fare │ │
│ erlang vm │ │
└───────perifice───── far uma
│
┌────── fara────┐
│ Exec-port │ Programa de porta (processo separado do sistema operacional)
└────── fara────┘
╱ │ ╲
(tubos opcionais stdin/stdout/stderr)
╱ │ ╲
┌──────┐ ┌───── far uma
│ospid1│ │ospid2│ │ospidn│ processos infantis gerenciados
└──────┘ └───── far uma
Consulte Descrição dos tipos em {@link Exec: Exec_Options ()}.
O programa exec-port exige que a variável SHELL seja definida. Se você estiver executando o Erlang dentro de um contêiner do Docker, pode ser necessário garantir que SHELL esteja definido corretamente antes de iniciar o emulador.
exec suporta várias opções de depuração passadas para exec:start/1 :
{debug, Level} - Liga a verbosidade da depuração no programa Port.verbose - liga a verbosidade no código Erlang.valgrind - executa o EXEC sob a ferramenta Valgrind, que precisa ser instalada no sistema operacional. Isso gera um arquivo local valgrind.YYYYMMDDhhmmss.log contendo saída de Valgrind. Se você precisar personalizar as opções de comando valgrind, use {valgrind, "/path/to/valgrind Args ..."} opção. 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)).Em 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 Para poder usar esse recurso, o usuário atual deve ter direitos sudo ou o arquivo exec-port deve pertencer à root e ter o conjunto de bits suid (use: 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 Se o usuário eficaz não tiver direitos para acessar o programa exec-port no diretório do usuário real, a exec-port poderá ser copiada para algum local compartilhado, que será especificado na inicialização usando {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 Para poder usar esse recurso, o usuário atual deve ter direitos sudo ou o arquivo exec-port deve ter o conjunto de bits SUID, e o arquivo exec-port deve ter os recursos definidos conforme descrito na seção "Build" acima.
O programa da porta será iniciado inicialmente como root e, em seguida, alternará o usuário eficaz para {user, User} e definirá recursos de processo para cap_setuid,cap_kill,cap_sys_nice . Depois disso, permitirá executar programas infantis sob usuários eficazes listados na opção {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 - portEnquanto a execução do programa de portas como root é altamente desencorajada, pois abre um buraco de segurança que oferece aos usuários a capacidade de danificar o sistema, para aqueles que podem precisar dessa opção, aqui está como fazê -lo (prossiga por sua conta e risco !!!).
NOTA: Nesse caso, exec usaria sudo exec-port para executá-lo como root ou o exec-port deve ter o conjunto de bits SUID (4555) e pertencer à root . A outra (perigosa e firmemente desencorajada !!!) alternativa é executar erl como 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 - portObserve que matar um processo pode ser realizado com o comando de Kill (3) em um shell externo ou executando o 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 }
okEste comando permite instruir o ERLEXEC a começar a monitorar o processo do SO e notificar o Erlang quando o processo sair. Também é capaz de enviar sinais para o processo e matá -lo.
% 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 } Às vezes, um processo de OS infantil gerado que recebe a entrada de stdin precisa detectar o final da entrada para processar dados recebidos. Quando os comandos da tubulação (por exemplo, cat file | tac ), isso é tratado detectando o EOF pela extremidade de leitura do tubo quando a extremidade da escrita do tubo é fechada. No erlexec isso é tratado enviando explicitamente o átomo eof para o processo de OS que ouve para 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