المؤلف سيرج ألينيكوف <سالين (في) gmail.com>
تنفيذ والتحكم في عمليات نظام التشغيل من Erlang/OTP.
يقوم هذا المشروع بتطبيق تطبيق Erlang مع برنامج منفذ C ++ الذي يمنح عمليات Erlang خفيفة الوزن تحكمًا في الحبوب الدقيقة في تنفيذ عمليات نظام التشغيل.
الميزات التالية مدعومة:
erlang:monitor/2 . يوفر هذا التطبيق تحكمًا أفضل بشكل ملحوظ في عمليات نظام التشغيل من erlang:open_port/2 الأمر مع خيار {spawn, Command} ، ويقوم بتنظيف عملية تشغيل الطفل المناسبة عندما يخرج المحاكي.
كان تطبيق 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 تأكد من تثبيت حديد التسليح أو حديد التسليح محليًا وأن نص حديد التسليح في المسار.
إذا كنت تقوم بنشر التطبيق على Linux وترغب في الاستفادة من مهام تشغيل المنفذ exec-port باستخدام معرفات المستخدم الفعالة التي تختلف عن معرف المستخدم الحقيقي الذي بدأ منفذ Exec-port ، ثم تأكد من تثبيت Libcap-dev [el] أو التأكد من أن المستخدم الذي يدير برنامج المنافذ له حقوق sudo .
تعليمات تثبيت LIBCAP-DEV الخاصة بـ OS:
$ 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.
حقوق الطبع والنشر (ج) 2003 سيرج ألينيكوف
┌┌
│ ┌ ┌ ┌ ┐ ┌ ┌ │ │
│ │pid1│ │pid2│ │pidn│ │ erlang pids خفيفة الوزن المرتبطة
│ └ └ └ ┘ └-
│ ╲ │ ╱ │
│ ╲ │ ╱ │
│ ╲ │ ╱ (الروابط) │
│ ┌ ┐
│ │ exec │ │ exec application قيد التشغيل في Erlang VM
│ └ ┘
│ erlang vm │ │
└ └ ─
│
┌
exec-port │ برنامج المنفذ (عملية منفصلة لنظام التشغيل)
└
╱ │ ╲
(أنابيب stdin/stdout/stderr اختيارية
╱ │ ╲
┌ ┐ ┌ ┐ ┌
│ospid1│ │ospid2│ │ospidn│ عمليات نظام التشغيل للأطفال
└ ┘ └ ┘ └
انظر وصف الأنواع في {link exec: exec_options ()}.
يتطلب برنامج exec-port تعيين متغير SHELL . إذا كنت تقوم بتشغيل Erlang داخل حاوية Docker ، فقد تحتاج إلى التأكد من تعيين SHELL بشكل صحيح قبل بدء المحاكي.
يدعم exec العديد من خيارات التصحيح التي تم تمريرها إلى exec:start/1 :
{debug, Level} - يقوم بتشغيل تصحيح التصحيح في برنامج المنفذ.verbose - تشغيل الفعل في رمز Erlang.valgrind - يعمل 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 ويحصل على مجموعة البتات (استخدام: 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 مجموعة البتات (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 للبدء في مراقبة عملية نظام التشغيل وإخطار 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 إلى اكتشاف نهاية الإدخال من أجل معالجة البيانات الواردة. عندما يتم التعامل مع أوامر الأنابيب (على سبيل المثال 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