Current section
Files
Jump to
Current section
Files
src/overview.edoc
Exec - OS Process Manager for Erlang VM.
@author Serge Aleynikov <saleyn at gmail dot com>
@version {@vsn}
@title Exec - OS Process Manager for Erlang VM.
@doc This application implements a manager of OS processes.
It's designed to address the shortcomings of Erlang's
`os:cmd/1' and `erlang:open_port/2' that allow to execute external
OS processes.
== Contents ==
<ol>
<li>{@section Download}</li>
<li>{@section Features}</li>
<li>{@section Supported Platforms}</li>
<li>{@section Architecture}</li>
<li>{@section Configuration Options}</li>
<li>{@section Examples}</li>
</ol>
== Download ==
<ul>
<li>Project's repository: [https://github.com/saleyn/erlexec]</li>
<li>Git clone command: `git clone https://github.com/saleyn/erlexec.git'</li>
</ul>
== Features ==
<ol>
<li>Starting, stopping OS commands and getting their OS process IDs.</li>
<li>Setting OS command working directory, environment, effective user,
process priority.</li>
<li>Providing custom termination command for killing a process or relying
on default SIGTERM/SIGKILL behavior. Specifying custom timeout
for SIGKILL after the termination command or SIGTERM was executed.</li>
<li>Terminating all processing beloging to a process group</li>
<li>Ability to link Erlang processes to OS processes (via intermediate
Erlang Pids that are linked to an associated OS process).</li>
<li>Ability to monitor the termination of OS processes.</li>
<li>Ability to execute OS processes synchronously and asynchronously.</li>
<li>Proper cleanup of OS processes at port program termination time.</li>
<li>Communicating with an OS process via its STDIN.</li>
<li>Redirecting STDOUT and STDERR of an OS process to a file, erlang process,
or a custom function. When redirected to a file, the file can be
open in append/truncate mode, and given creation access mask.</li>
<li>Running interactive processes with psudo-terminal pty support.</li>
</ol>
== Supported Platforms ==
Linux, Solaris, and MacOS X.
== Architecture ==
```
*-------------------------*
| +----+ +----+ +----+ |
| |Pid1| |Pid2| |PidN| | Erlang light-weight Pids associated
| +----+ +----+ +----+ | one-to-one with managed OsPids
| \ | / |
| \ | / |
| \ | / (links) |
| +------+ |
| | exec | | Exec application running in Erlang VM
| +------+ |
| Erlang VM | |
*-------------+-----------*
|
+-----------+
| exec-port | Port program (separate OS process)
+-----------+
/ | \
(optional stdin/stdout/stderr pipes)
/ | \
+------+ +------+ +------+
|OsPid1| |OsPid2| |OsPidN| Managed Child OS processes
+------+ +------+ +------+
'''
== Configuration Options ==
See description of types in {@link exec:exec_options()}.
== Examples ==
=== Starting/stopping an OS process ===
```
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)).
'''
=== Killing an OS process ===
Note that killing a process can be accomplished by running kill(3) command
in an external shell, or by executing 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.
'''
=== Using a custom success return code ===
```
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
'''
=== Redirecting OS process stdout to a file ===
```
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
'''
=== Redirecting OS process stdout to screen, an Erlang process or a custom function ===
```
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
'''
=== Appending OS process stdout to a file ===
```
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\nTEST2\nTEST3\n">>}
14> exec:run("echo Test4; done", [{stdout, "/tmp/out", [append, {mode, 8#600}]}, sync]),
file:read_file("/tmp/out").
{ok,<<"TEST1\nTEST2\nTEST3\nTest4\n">>}
15> file:delete("/tmp/out").
'''
=== Setting up a monitor for the OS process ===
```
> 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',#Ref<0.0.0.1651>,process,<0.263.0>,normal}
ok
'''
=== Managing an externally started OS process ===
This command allows to instruct erlexec to begin monitoring given OS process
and notify Erlang when the process exits. It is also able to send signals to
the process and kill it.
```
% 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',#Ref<0.0.0.2205>,process,<0.334.0>,{exit_status,10}}
ok
21> file:delete("/tmp/pid").
ok
'''
=== Specifying custom process shutdown delay in seconds ===
```
% 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',#Ref<0.0.0.1671>,process,<0.403.0>,normal}
'''
=== Communicating with an OS process via STDIN ===
```
% 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',#Ref<0.0.0.1837>,process,<0.427.0>,normal}
'''
=== Running OS commands synchronously ===
```
% 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">>]}]}
'''
=== Running OS commands with/without shell ===
```
% Execute a command by an OS shell interpreter
34> exec:run("/bin/echo ok", [sync, stdout]).
{ok, [{stdout, [<<"ok\n">>]}]}
% Execute an executable without a shell
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">>]}]}
'''
=== Running OS commands with pseudo terminal (pty) ===
```
% 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">>,<<"\r\n">>]}]}
'''
=== Kill a process group at process exit ===
```
% 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):
39> {ok, P0, GID} = exec:run("sleep 10", [{group, 0}, kill_group]).
{ok,<0.37.0>,25306}
40> {ok, P1, _} = exec:run("sleep 15", [{group, GID}, monitor]).
{ok,<0.39.0>,25307}
41> {ok, P2, _} = exec:run("sleep 15", [{group, GID}, monitor]).
{ok,<0.41.0>,25308}
42> flush().
Shell got {'DOWN',#Ref<0.0.0.42>,process,<0.39.0>,{exit_status,15}}
Shell got {'DOWN',#Ref<0.0.0.48>,process,<0.41.0>,{exit_status,15}}
ok
'''
@end