Auteur Serge Aleynikov <Saleyn (AT) gmail.com>
Exécuter et contrôler les processus du système d'exploitation à partir d'Erlang / OTP.
Ce projet met en œuvre une application Erlang avec un programme de port C ++ qui donne des processus Erlang légers de contrôle des grains fins sur l'exécution des processus OS.
Les fonctionnalités suivantes sont prises en charge:
erlang:monitor/2 . Cette application fournit un contrôle nettement meilleur sur les processus du système d'exploitation que la commande erlang:open_port/2 intégrée avec une option {spawn, Command} et effectue un nettoyage de processus d'enfant OS approprié lorsque l'émulateur quitte.
L'application erlexec a été en production par les systèmes Erlang et Elixir, et est considéré comme stable.
Si vous trouvez ce projet utile, veuillez faire un don à:
12pt8TcoMWMkF6iY66VJQk95ntdN4pFihg0x268295486F258037CF53E504fcC1E67eba014218 Linux, Solaris, FreeBSD, OpenBSD, macOS X
Voir 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 Assurez-vous que les barres d'armature ou la barre de Rebar3 sont installées localement et que le script de barre d'arme est sur le chemin.
Si vous déploiez l'application sur Linux et que vous souhaitez profiter des tâches d'exécution Exec-Port en utilisant des ID utilisateur efficaces différents de l'ID utilisateur réel qui a commencé Exec-Port, assurez-vous que la bibliothèque LibCap-Dev [El] est installée ou assurez-vous que l'utilisateur exécutant le programme de port a des droits sudo .
Instructions d'installation de LiBCAP-DEV spécifiques du système d'exploitation:
$ git clone [email protected]:saleyn/erlexec.git
$ make
# NOTE: for disabling optimized build of exec-port, do the following instead:
$ OPTIMIZE=0 make Par défaut, l'implémentation du programme de port utilise poll(2) Appel pour le démultiplexage des événements. Si vous préférez utiliser select(2) , définissez la variable d'environnement suivante:
$ USE_POLL=0 makeLe programme est distribué sous licence BSD.
Copyright (C) 2003 Serge Aleynikov
┌─ase
│ ┌sé
│ │pid1│ │pid2│ │pidn│ │ Erlang Light-Weight Pids associé
│ └fique
│ ╲ │ ╱ │
│ ╲ │ ╱ │
│ ╲ │ ╱ (liens) │
│ ┌fique
│ │ EXEC │ │ Application exécutive exécutée dans Erlang VM
│ └fique
│ Erlang VM │ │
└fiques
│
┌──formation
│ Exec-Port │ Programme de port (processus de système d'exploitation séparé)
└──formation
╱ │ ╲
(Pipes stdin / stdout / stderr en option)
╱ │ ╲
┌─ase
│OSPID1│ │oshipid2│ │ososhipidn│ Processus de système d'exploitation gérés
└─ase
Voir Description des types dans {@link exec: exec_options ()}.
Le programme exec-port exige que la variable SHELL soit définie. Si vous exécutez Erlang dans un conteneur Docker, vous devrez peut-être vous assurer que SHELL est correctement défini avant le démarrage de l'émulateur.
exec prend en charge plusieurs options de débogage passées à exec:start/1 :
{debug, Level} - excite la verbosité de débogage dans le programme portuaire.verbose - Active la verbosité dans le code Erlang.valgrind - exécute EXEC sous l'outil Valgrind, qui doit être installé dans le système d'exploitation. Cela génère un fichier valgrind.YYYYMMDDhhmmss.log contenant la sortie de Valgrind. Si vous avez besoin de personnaliser les options de commande Valgrind, utilisez {valgrind, "/path/to/valgrind Args ..."} Option. 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)).Dans 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 Afin de pouvoir utiliser cette fonctionnalité, l'utilisateur actuel doit avoir des droits sudo ou le fichier exec-port doit appartenir à root et avoir le SUID BIT SET (Utiliser: 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 Si l'utilisateur effectif n'a pas le droit d'accéder au programme exec-port dans le répertoire de l'utilisateur réel, le exec-port peut être copié dans un emplacement partagé, qui sera spécifié au démarrage à l'aide de {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 Afin de pouvoir utiliser cette fonctionnalité, l'utilisateur actuel doit avoir des droits sudo ou le fichier exec-port doit avoir le jeu de bits SUID, et le fichier exec-port doit avoir les capacités définies comme décrit dans la section "build" ci-dessus.
Le programme de port sera initialement démarré comme root , puis il passera l'utilisateur efficace à {user, User} et définira les capacités de processus sur cap_setuid,cap_kill,cap_sys_nice . Après cela, il permettra d'exécuter des programmes d'enfants sous des utilisateurs efficaces répertoriés dans l'option {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 - portLors de l'exécution du programme de port en tant que racine est très découragée, car elle ouvre un trou de sécurité qui donne aux utilisateurs la possibilité d'endommager le système, pour ceux qui pourraient avoir besoin d'une telle option, voici comment le faire (procéder à vos propres risques !!!).
Remarque: Dans ce cas, exec utiliserait sudo exec-port pour l'exécuter en tant que root ou le exec-port doit avoir le SUID Bit Set (4555) et appartenir à root . L'autre alternative (dangereuse et fermement découragée !!!) est de gérer erl comme 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 - portNotez que la mort d'un processus peut être accomplie en exécutant la commande kill (3) dans un shell externe, ou en exécutant 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 }
okCette commande permet de demander à Erlexec de commencer à surveiller le processus de système d'exploitation donné et à informer Erlang lorsque le processus sort. Il est également en mesure d'envoyer des signaux au processus et de le tuer.
% 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 } Parfois, un processus de système d'exploitation enfant engendré recevant l'entrée STDIN doit détecter la fin de l'entrée afin de traiter les données entrantes. Lors des commandes de tuyauterie (par exemple, cat file | tac ), cela est géré en détectant l'EOF par l'extrémité de lecture du tuyau lorsque l'extrémité d'écriture du tuyau est fermée. Dans erlexec cela est géré en envoyant explicitement l'atome eof au processus OS qui écoute 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