Penulis Serge Aleynikov <Saleeyn (at) gmail.com>
Jalankan dan kontrol proses OS dari Erlang/OTP.
Proyek ini mengimplementasikan aplikasi Erlang dengan program port C ++ yang memberikan proses pengendalian biji-bijian yang ringan atas eksekusi proses OS.
Fitur -fitur berikut didukung:
erlang:monitor/2 . Aplikasi ini memberikan kontrol yang jauh lebih baik atas proses OS daripada perintah erlang:open_port/2 dengan opsi {spawn, Command} , dan melakukan pembersihan proses anak OS yang tepat saat emulator keluar.
Aplikasi erlexec telah digunakan produksi oleh sistem Erlang dan Elixir, dan dianggap stabil.
Jika Anda menemukan proyek ini bermanfaat, silakan donasi ke:
12pt8TcoMWMkF6iY66VJQk95ntdN4pFihg0x268295486F258037CF53E504fcC1E67eba014218 Linux, Solaris, Freebsd, OpenBSD, MacOS X
Lihat https://hexdocs.pm/erlexec/readme.html
rebar.config : { deps ,
[ % ...
{ erlexec , " ~> 2.0 " }
]}.*.app.src Anda: { applications ,
[ kernel ,
stdlib ,
% ...
erlexec
]} defp deps do
[
# ...
{ :erlexec , "~> 2.0" }
]
end Pastikan Anda memiliki rebar atau rebar3 yang diinstal secara lokal dan skrip rebar ada di jalur.
Jika Anda menggunakan aplikasi di Linux dan ingin memanfaatkan tugas menjalankan exec-port menggunakan ID pengguna yang efektif berbeda dari ID pengguna nyata yang memulai Exec-port, maka pastikan bahwa perpustakaan libcap-dev [El] diinstal atau pastikan bahwa pengguna yang menjalankan program port memiliki hak sudo .
Instruksi Instalasi LibCap-Dev Specific OS:
$ git clone [email protected]:saleyn/erlexec.git
$ make
# NOTE: for disabling optimized build of exec-port, do the following instead:
$ OPTIMIZE=0 make Implementasi Program Port Default menggunakan poll(2) Panggilan untuk acara demultiplexing. Jika Anda lebih suka menggunakan select(2) , atur variabel lingkungan berikut:
$ USE_POLL=0 makeProgram ini didistribusikan di bawah lisensi BSD.
Hak Cipta (C) 2003 Serge Aleynikov
┌────── Chasan 4irim 4 "" tolasanaskanarmaskanirim "" ""onggol uranding" ilangan ilangan ilangan Phalisasi olak ilangan ans,
│ ┌─────┐ ┌────┐ ┌acam "" "│ │ │ │ │
│ │PID1│ │PID2│ │PIDN│ │ erlang light-weight pids terkait
│ └─────┘ └────┘ └acam "" "│ │ │ satu-ke-satu dengan rajawali yang dikelola
│ ╲ │ ╱ │
│ ╲ │ ╱ │
│ ╲ │ ╱ (tautan) │
│ ┌───── Churat │ │
│ │ exec │ │ Exec application running in Erlang VM
│ └───── Churat │ │
│ erlang vm │ │
└────── Chasan 4irim 4 "" tolasanasanaskan tolasan ilangan ilangan ilangan harga> ilangan ilangan harga> ─ ─┘
│
┌────── Chasan 4irimansirim "" " -
│ Program Port Exec-Port │ (Proses OS terpisah)
└────── Chasan 4irimansirim "" " -
╱ │ ╲
(Pipa Stdin/Stdout/Stderr opsional)
╱ │ ╲
┌───── Churat ┌ ┌───── ┌ ┌acam "" "4 olak -ilangan
│ospid1│ │ospid2│ │ospidn│ proses OS anak yang dikelola
└───── Churat └ └───── └ └acam "" "4 olak -ilangan
Lihat deskripsi tipe di {@link exec: exec_options ()}.
Program exec-port mengharuskan variabel SHELL untuk ditetapkan. Jika Anda menjalankan Erlang di dalam wadah Docker, Anda mungkin perlu memastikan bahwa SHELL diatur dengan benar sebelum memulai emulator.
exec mendukung beberapa opsi debugging yang diteruskan ke exec:start/1 :
{debug, Level} - Mengaktifkan verbositas debug dalam program port.verbose - Mengaktifkan verbositas dalam kode Erlang.valgrind - menjalankan EXEC di bawah alat Valgrind, yang perlu diinstal di OS. Ini menghasilkan file valgrind.YYYYMMDDhhmmss.log lokal yang berisi output Valgrind. Jika Anda perlu menyesuaikan opsi perintah valgrind, gunakan {valgrind, "/path/to/valgrind Args ..."} opsi. 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)).Di 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 Agar dapat menggunakan fitur ini, pengguna saat ini harus memiliki hak sudo atau file exec-port harus dimiliki oleh root dan memiliki set bit suid (gunakan: 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 Jika pengguna yang efektif tidak memiliki hak untuk mengakses program exec-port di direktori pengguna nyata, maka exec-port dapat disalin ke beberapa lokasi bersama, yang akan ditentukan saat startup menggunakan {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 Agar dapat menggunakan fitur ini, pengguna saat ini harus memiliki hak sudo atau file exec-port harus memiliki bit bit suid, dan file exec-port harus memiliki kemampuan set seperti yang dijelaskan di bagian "Bangun" di atas.
Program port awalnya akan dimulai sebagai root , dan kemudian akan mengalihkan pengguna yang efektif ke {user, User} dan mengatur kemampuan proses ke cap_setuid,cap_kill,cap_sys_nice . Setelah itu akan memungkinkan untuk menjalankan program anak di bawah pengguna yang efektif yang terdaftar di opsi {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 - portSaat menjalankan program port sebagai root sangat berkecil hati, karena membuka lubang keamanan yang memberi pengguna kemampuan untuk merusak sistem, bagi mereka yang mungkin membutuhkan opsi seperti itu, berikut adalah cara menyelesaikannya (lanjutkan dengan risiko Anda sendiri !!!).
Catatan: Dalam hal ini exec akan menggunakan sudo exec-port untuk menjalankannya sebagai root atau exec-port harus memiliki bit bit suid (4555) dan dimiliki oleh root . Alternatif lainnya (berbahaya dan berkecil hati !!!) adalah menjalankan erl sebagai 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 - portPerhatikan bahwa membunuh suatu proses dapat dicapai dengan menjalankan perintah Kill (3) dalam cangkang eksternal, atau dengan mengeksekusi eksekutif: 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 }
okPerintah ini memungkinkan untuk menginstruksikan Erlexec untuk mulai memantau proses OS yang diberikan dan memberi tahu Erlang ketika proses keluar. Itu juga dapat mengirim sinyal ke proses dan membunuhnya.
% 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 } Kadang -kadang proses OS anak yang melahirkan yang menerima input STDIN perlu mendeteksi akhir input untuk memproses data yang masuk. Saat Perintah Piping (misalnya cat file | tac ) Ini ditangani dengan mendeteksi EOF dengan ujung bacaan pipa ketika ujung penulisan pipa ditutup. Di erlexec ini ditangani dengan secara eksplisit mengirimkan atom eof ke proses OS yang mendengarkan 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