ผู้แต่ง Serge Aleynikov <Saleyn (at) gmail.com>
ดำเนินการและควบคุมกระบวนการ OS จาก ERLANG/OTP
โครงการนี้ใช้แอพพลิเคชั่น Erlang ด้วยโปรแกรมพอร์ต C ++ ที่ให้ ERLANG น้ำหนักเบาประมวลผลการควบคุมเกรนอย่างละเอียดเกี่ยวกับการดำเนินการของกระบวนการ OS
รองรับคุณสมบัติต่อไปนี้:
erlang:monitor/2 แอปพลิเคชันนี้ให้การควบคุมกระบวนการ OS ได้ดีกว่า erlang:open_port/2 ด้วยตัวเลือก {spawn, Command} และดำเนินการทำความสะอาดกระบวนการเด็กระบบปฏิบัติการที่เหมาะสมเมื่อ Emulator ออก
แอปพลิเคชัน 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 ตรวจสอบให้แน่ใจว่าคุณติดตั้งเหล็กเส้นหรือ Rebar3 ในพื้นที่และสคริปต์เหล็กเส้นอยู่ในเส้นทาง
หากคุณกำลังปรับใช้แอปพลิเคชันบน Linux และต้องการใช้ประโยชน์จากงานที่ใช้งาน Exec-Port โดยใช้ ID ผู้ใช้ที่มีประสิทธิภาพแตกต่างจาก ID ผู้ใช้จริงที่เริ่มต้น Exec-Port จากนั้นตรวจสอบให้แน่ใจว่า LibCap-Dev [EL] ได้ติดตั้งหรือตรวจสอบให้แน่ใจว่าผู้ใช้ที่ใช้โปรแกรมพอร์ตมีสิทธิ์ sudo
คำแนะนำการติดตั้ง 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
│pid1│pid2│pidn│ erlang pids น้ำหนักเบาที่เกี่ยวข้อง
│└───┘───7
│ (ลิงก์) │
│ EXEC │ EXEC Application │ EXEC ที่ทำงานอยู่ใน ERLANG VM
│ erlang vm ││
│โปรแกรม Exec-Port │พอร์ต (กระบวนการ OS แยกต่างหาก)
(ท่อเสริม stdin/stdout/stderr)
│ospid1││ospid2││ospidn│กระบวนการระบบปฏิบัติการเด็กที่มีการจัดการ
ดูคำอธิบายประเภทใน {@link exec: exec_options ()}
โปรแกรม exec-port ต้องการตัวแปร SHELL ที่จะตั้งค่า หากคุณใช้งาน Erlang ภายในคอนเทนเนอร์ Docker คุณอาจต้องตรวจสอบให้แน่ใจว่า SHELL ถูกตั้งค่าอย่างเหมาะสมก่อนที่จะเริ่มตัวจำลอง
exec รองรับตัวเลือกการดีบักหลายตัวที่ส่งผ่านไปยัง exec:start/1 :
{debug, Level} - เปิด verbosity debug ในโปรแกรมพอร์ตverbose - เปิดใช้คำย้ำในรหัส Erlangvalgrind - เรียกใช้ Exec ภายใต้เครื่องมือ Valgrind ซึ่งจำเป็นต้องติดตั้งในระบบปฏิบัติการ สิ่งนี้จะสร้างไฟล์ 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 (ใช้: 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 จะต้องมีความสามารถที่กำหนดตามที่อธิบายไว้ในส่วน "build" ด้านบน
โปรแกรมพอร์ตจะเริ่มต้นเป็น 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โปรดทราบว่าการฆ่ากระบวนการสามารถทำได้โดยการเรียกใช้คำสั่ง Kill (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 } บางครั้งกระบวนการ OS เด็กที่มีการวางไข่ที่ได้รับอินพุต Stdin จำเป็นต้องตรวจจับการสิ้นสุดของอินพุตเพื่อประมวลผลข้อมูลที่เข้ามา เมื่อคำสั่ง piping (เช่น cat file | tac ) สิ่งนี้จะถูกจัดการโดยการตรวจจับ EOF โดยปลายการอ่านของท่อเมื่อปิดการเขียนของท่อ ใน erlexec สิ่งนี้ได้รับการจัดการโดยการส่งอะตอม eof ไปยังกระบวนการ 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