Coder Social home page Coder Social logo

saleyn / erlexec Goto Github PK

View Code? Open in Web Editor NEW
519.0 25.0 139.0 919 KB

Execute and control OS processes from Erlang/OTP

Home Page: https://hexdocs.pm/erlexec/readme.html

License: Other

Makefile 2.13% C++ 59.80% Erlang 37.15% CSS 0.92%
erlexec erlang-processes otp erlang os linux

erlexec's Introduction

Erlexec - OS Process Manager for the Erlang VM

build Hex.pm Hex.pm

Author Serge Aleynikov <saleyn(at)gmail.com>

Summary

Execute and control OS processes from Erlang/OTP.

This project implements an Erlang application with a C++ port program that gives light-weight Erlang processes fine-grain control over execution of OS processes.

The following features are supported:

  • Start/stop OS commands and get their OS process IDs, and termination reason (exit code, signal number, core dump status).
  • Support OS commands with unicode characters.
  • Manage/monitor externally started OS processes.
  • Execute OS processes synchronously and asynchronously.
  • Set OS command's working directory, environment, process group, effective user, process priority.
  • Provide custom termination command for killing a process or relying on default SIGTERM/SIGKILL behavior.
  • Specify custom timeout for SIGKILL after the termination command or SIGTERM was executed and the running OS child process is still alive.
  • Link Erlang processes to OS processes (via intermediate Erlang Pids that are linked to an associated OS process), so that the termination of an OS process results in termination of an Erlang Pid, and vice versa.
  • Monitor termination of OS processes using erlang:monitor/2.
  • Terminate all processes belonging to an OS process group.
  • Kill processes belonging to an OS process group at process exit.
  • Communicate with an OS process via its STDIN.
  • Redirect 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 at creation an access mask.
  • Run interactive processes with psudo-terminal pty support.
  • Support all RFC4254 pty psudo-terminal options defined in section-8 of the spec.
  • Execute OS processes under different user credentials (using Linux capabilities).
  • Perform proper cleanup of OS child processes at port program termination time.

This application provides significantly better control over OS processes than built-in erlang:open_port/2 command with a {spawn, Command} option, and performs proper OS child process cleanup when the emulator exits.

The erlexec application has been in production use by Erlang and Elixir systems, and is considered stable.

Donations

If you find this project useful, please donate to:

  • Bitcoin: 12pt8TcoMWMkF6iY66VJQk95ntdN4pFihg
  • Ethereum: 0x268295486F258037CF53E504fcC1E67eba014218

Supported Platforms

Linux, Solaris, FreeBSD, OpenBSD, MacOS X

DOCUMENTATION

See https://hexdocs.pm/erlexec/readme.html

USAGE

Erlang: import as a dependency

  • Add dependency in rebar.config:
{deps,
 [% ...
  {erlexec, "~> 2.0"}
  ]}.
  • Include in your *.app.src:
{applications,
   [kernel,
    stdlib,
    % ...
    erlexec
   ]}

Elixir: import as a dependency

defp deps do
  [
    # ...
    {:erlexec, "~> 2.0"}
  ]
end

BUILDING FROM SOURCE

Make sure you have rebar or rebar3 installed locally and the rebar script is in the path.

If you are deploying the application on Linux and would like to take advantage of exec-port running tasks using effective user IDs different from the real user ID that started exec-port, then either make sure that libcap-dev[el] library is installed or make sure that the user running the port program has sudo rights.

OS-specific libcap-dev installation instructions:

  • Fedora, CentOS: "yum install libcap-devel"
  • Ubuntu: "apt-get install 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

By default port program's implementation uses poll(2) call for event demultiplexing. If you prefer to use select(2), set the following environment variable:

$ USE_POLL=0 make

LICENSE

The program is distributed under BSD license.

Copyright (c) 2003 Serge Aleynikov

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()}.

The exec-port program requires the SHELL variable to be set. If you are running Erlang inside a docker container, you might need to ensure that SHELL is properly set prior to starting the emulator.

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)).

In Elixir:

iex(1)> :exec.start
{:ok, #PID<0.112.0>}
iex(2)> :exec.run("echo ok", [:sync, :stdout])
{:ok, [stdout: ["ok\n"]]}

Clearing environment or unsetting an env variable of the child process

%% 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

Running exec-port as another effective user

In order to be able to use this feature the current user must either have sudo rights or the exec-port file must be owned by root and have the SUID bit set (use: 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

If the effective user doesn't have rights to access the exec-port program in the real user's directory, then the exec-port can be copied to some shared location, which will be specified at startup using {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

Allowing exec-port to run commands as other effective users

In order to be able to use this feature the current user must either have sudo rights or the exec-port file must have the SUID bit set, and the exec-port file must have the capabilities set as described in the "Build" section above.

The port program will initially be started as root, and then it will switch the effective user to {user, User} and set process capabilities to cap_setuid,cap_kill,cap_sys_nice. After that it'll allow to run child programs under effective users listed in the {limit_users, Users} option.

$ 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

Running the port program as root

While running the port program as root is highly discouraged, since it opens a security hole that gives users an ability to damage the system, for those who might need such an option, here is how to get it done (PROCEED AT YOUR OWN RISK!!!).

Note: in this case exec would use sudo exec-port to run it as root or the exec-port must have the SUID bit set (4555) and be owned by root. The other (DANGEROUS and firmly DISCOURAGED!!!) alternative is to run erl as 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

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',18950,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',19355,process,<0.334.0>,{exit_status,10}}
ok
21> file:delete("/tmp/pid").
ok

Specifying a 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',26347,process,<0.403.0>,normal}

Specifying a custom kill command for a process

% 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}

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',26431,process,<0.427.0>,normal}

Communicating with an OS process via STDIN and sending end-of-file

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).
ok
Got: {stdout,26143,<<"baz\nbar\nfoo\n">>}

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("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">>]}]}

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">>]}]}

% 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,<<"\r\n">>}
Shell got {stdout,17086,<<"hello\r\n">>}
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

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):
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

erlexec's People

Contributors

ccrusius avatar chaitanyapandit avatar chrta avatar cieplak avatar darkkey avatar davidw avatar egobrain avatar erlanger avatar ikuz avatar jbalint avatar joaohf avatar kape1395 avatar kianmeng avatar lpgauth avatar noelbk avatar oliv3 avatar pichi avatar pragma1ce avatar puzza007 avatar rikettsie avatar saleyn avatar sanmiguel avatar steffende avatar timclassic avatar tylerhunt avatar ushitora-anqou avatar vinoski avatar whitfin avatar yozhig avatar zenkj avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

erlexec's Issues

Stdout to stderr redirect option is not working

When using {stdout, stderr} redirection option, output is printed to screen instead of being delivered to the calling process:

6> f(I), {ok, _, I} = exec:run("echo TEST", [stderr, {stdout, stderr}]).         
{ok,<0.1396.0>,11509}
7> TEST

7> flush().
ok

Workaround: add 1>&2 redirect on command line:

9> f(I), {ok, _, I} = exec:run("echo TEST 1>&2", [stderr]).
{ok,<0.1399.0>,11510}
10> flush().                                                                  
Shell got {stderr,11510,<<"TEST\n">>}
ok

This issue doesn't affect the {stderr, stdout} redirection, which works correctly.

PTY mode STDOUT/STDERR capture issue

When PTY mode is enabled, it looks like there some mixup happening between STDOUT and STDERR buffers. Here's how it looks like:

Cmd = "for i in `seq 1 400`; do echo $i; done",
{ok, [{stdout, Stdout}, {stderr, Stderr}]} = exec:run(Cmd, [sync,pty,stdout,stderr]).

Results in:

{ok,[{stdout,[<<"1\r\n2\r\n3\r\n">>,<<"6\r\n">>,<<"7\r\n">>,
              <<"8\r\n">>,<<"9\r\n10\r\n">>,<<"11\r\n">>,<<"12\r\n">>,
              <<"13\r\n">>,<<"14\r\n">>,<<"15\r\n">>,<<"16\r\n">>,
              <<"17\r\n">>,<<"18\r\n">>,<<"19\r\n">>,<<"21\r\n22\r\n">>,
              <<"23\r\n">>,<<"24\r\n">>,<<"25\r\n">>,<<"27\r\n">>,
              <<"30\r\n">>,<<"31\r\n">>,<<"32\r\n33\r\n">>,<<"35\r\n">>,
              <<...>>|...]},
     {stderr,[<<"4\r\n5\r\n">>,<<"20\r\n">>,<<"26\r\n">>,
              <<"28\r\n29\r\n">>,<<"34\r\n">>,<<"41\r\n">>,<<"45\r\n">>,
              <<"49\r\n">>,<<"56\r\n">>,<<"63\r\n">>,<<"68\r\n">>,
              <<"70\r\n">>,<<"77\r\n">>,<<"85\r\n">>,<<"88\r\n">>,
              <<"91\r\n">>,<<"97\r\n">>,<<"101\r\n">>,<<"111\r\n">>,
              <<"116\r\n">>,<<"120\r\n">>,<<"123\r"...>>,<<...>>|...]}]}

Please note that the command produces no STDERR at all. As far as I understand it, in pseudo TTY mode there can be no STDERR/STDOUT separation, I'm not sure why it puts some of the read from STDOUT into STDERR.

OS: Linux, Mac.
erlexec version at commit 9178aec

Any help or advice on how to debug this is highly appreciated.

exec leaving defunct processes

Under a high number of child proceses erlexec starts leaving defunct processes which eventually fill up the process table space in the kernel if the user has no process limits. Rather dangerous.
To replicate, run this in the shell:

[ exec:run("echo ho",[stdout]) || T <- lists:seq(1,10000) ].

You may need more than 10000 processes if you have a faster machine. I tried it on a quad core AMD 6, it always leaves defunct processes. I think that the problem is that the port program does not call wait() in some extreme cases when the child process is barely able to start (and dies immediately because of lack of resources?) . But I am not sure. You should be able to replicate it, use more than 10,000 if you have a bigger machine.

Sync run fails when mailbox is non empty

Sync run always fails with non empty process mailbox box due to this piece of code.

Example

1> exec:run("ls", [sync]).
{ok,[]}
2> self() ! test.         
test
3> exec:run("ls", [sync]).
{error,[{reason,test}]}

Inheriting the environment should be the default

The default behavior should be for child processes to inherit the environment - that's what people expect when exec'ing.

A workaround is to pass in the environment every time:

[{env, os:getenv()}]

But that's a pain in the neck.

Fails to compile on Linux Mint 15

c_src/exec.cpp: In function ‘pid_t start_child(const char*, const char*, char* const*, int, int)’:
c_src/exec.cpp:581:48: error: ‘PRIO_PROCESS’ was not declared in this scope
c_src/exec.cpp:581:71: error: ‘setpriority’ was not declared in this scope

Adding #include <sys/resource.h> fixes it.

Could we have an option to send a different signal to external process

It would be nice if we could issues a SIGINT, then a SIGTERM and then a SIGKILL so our python processes can clean up their resources nicely. Or alternatively just choose the signal we send before sending the SIGKILL.

{kill_timeout, Sec::integer()}
Number of seconds to wait after issueing a SIGTERM or executing the custom kill command (if specified) before killing the process with the SIGKILL signal

Interface Lag and console input being passed to started process

There is an interface lag when large applications I run through erlexec are run while in console mode.

First the commands, set up an environment in an empty directory with:

[ -d erlexec ] || git clone https://github.com/saleyn/erlexec.git
cd erlexec
rebar compile
[ -f 'minecraft_server.1.6.2.jar' ] || wget 'https://s3.amazonaws.com/Minecraft.Download/versions/1.6.2/minecraft_server.1.6.2.jar'
erl -pa $PWD/ebin

The erlang console will now be running, I have the same result on both R15B01 and R16B01 on two different systems, both Debian based of differing versions, the main shell used is bash but also tried dash and zsh, both through ssh to remote servers and local with no change.

In the erlang shell now run:

exec:start([debug]).
{_, P, _} = exec:run_link("java -Xms1G -Xmx1G -jar minecraft_server.1.6.2.jar nogui", [{stdout, self()}, {stderr, self()}]).

Now try typing, a large amount of the key presses vanish at this point, press and hold, say, the q key and you will, say, none for 3 seconds, then 1, then none for a second, then like 10, then none for another second, then 1, then none for 5 seconds and so forth.

This does not happen with erlang ports.

It stops when the above launched server process dies, whether with exec:kill(P, 9) or on another shell with kill pidof java 9 or whatever, at which point the erlang shell becomes perfectly responsive again.

It also happens if it is linked to another spawned process and not the shell process, though I have not tested launching over a remote erlang shell.

Something else noticed, the above server java program prints, say '2013-08-11 04:05:59 [INFO] Unknown command. Try /help for a list of commands\n' for any unknown text command sent it it, and if I 'flush().' after running the above command to start the server it will also print out a few (random?) lines of the above, of which if I run 'flush().' again I get more, and if I run 'flush().' again then I get more of the 'Unknown command', thus meaning it is getting an input, and I have not attempted to start using stdin yet.

And yet I have just noticed that when I copied in "flush().\n" to my clipboard and paste it into the console terminal, the number of times that I tap Ctrl+Alt+V and it does nothing equals the number of times that the 'Unknown command' message is received, thus indicating that a large number of randomish input that is typed into the erlang console window is actually passed to the started process. Further testing seems to confirm this. Just tested again, this does not happen with erlang ports.

It did do all of the above as of a week ago as well, prior to the recently made changes and added stdin support.

pty support

it would be great to support pty for the spawned children. for example,

exec:run("ed", [stdin, stdout, pty]).

then it will be easy to interact with some line-buffer program correctly.

Does not compile on macosx 10.8.4 - patch included.

diff --git a/c_src/exec.cpp b/c_src/exec.cpp
index f948daa..025ac2f 100644
--- a/c_src/exec.cpp
+++ b/c_src/exec.cpp
@@ -137,7 +137,7 @@ typedef std::map <pid_t, CmdInfo>           MapChildrenT;
 typedef std::pair<kill_cmd_pid_t, pid_t>    KillPidStatusT;
 typedef std::map <kill_cmd_pid_t, pid_t>    MapKillPidT;
 typedef std::map<std::string, std::string>  MapEnv;
-typedef typename MapEnv::iterator           MapEnvIterator;
+typedef MapEnv::iterator           MapEnvIterator;

 MapChildrenT children;              // Map containing all managed processes started by this port program.
 MapKillPidT  transient_pids;        // Map of pids of custom kill commands.
@@ -972,7 +972,15 @@ pid_t start_child(CmdOptions& op, std::string& error)
             close(i);

         #if !defined(__CYGWIN__) && !defined(__WIN32)
-        if (op.user() != INT_MAX && setresuid(op.user(), op.user(), op.user()) < 0) {
+        if (op.user() != INT_MAX &&
+#ifdef HAVE_SETRESUID
+               setresuid(op.user(), op.user(), op.user())
+#elif HAVE_SETREUID
+               setreuid(op.user(), op.user())
+#else
+#error setresuid(3) not supported!
+#endif
+       < 0) {
             err.write("Cannot set effective user to %d", op.user());
             perror(err.c_str());
             return EXIT_FAILURE;

Port crash if cmdline length is equal 265 bytes.

I got port crash when command line length is equal 256 bytes (255 or 257 working fine). Look like some allocation problem. Cmd params was just [sync].

Error: glibc detected *** /app/deps/erlexec/priv/x86_64-unknown-linux-gnu/exec-port: double free or corruption (out): 0x00007fffe535ef00

{stdout, {append, "/tmp/foobar"}} failing

The lines after

check_cmd_options([{Std, I}|T], State) when Std=:=stderr, I=/=Std; Std=:=stdout, I=/=Std ->

in exec.erl include

element(1,I)=:="append"

When the docs describe it as an atom. In the C code, it appears to be treated as an atom as well. Changing that to 'append' generates this error, though:

{error, "Atom, string or {'append', Name} tuple required for option stdout"}

Unfortunately I'm out of time for today.

Infinite loop when calling exec:run/2 with nonexistent working directory

Hello,

Steps to replicate the issue:

06:38 PM  ~
tim@gil$ erl
Erlang/OTP 17 [erts-6.2] [source] [64-bit] [smp:8:8] [async-threads:10] [hipe] [kernel-poll:false]

Eshell V6.2  (abort with ^G)
(grout@gil)1> exec:start([debug]).
{ok,<0.50.0>}
(grout@gil)2> exec:run("ls -l", [ sync, {cd, "/non/existent/directory"} ]).
  Redirecting [stdin -> null]
  Redirecting [stdout -> null]
  Redirecting [stderr -> null]
Starting child: 'ls -l'
  child  = (stdin=null(fd:5), stdout=null(fd:5), stderr=null(fd:5))
  parent = (stdin=none, stdout=none, stderr=none)
  Args[0]: /bin/bash
  Args[1]: -c
  Args[2]: ls -l

and the shell sits there, waiting, and a core loops at 100%.

Then I type Ctrl-G q to exit:

^G
User switch command
 --> q
Broken Erlang command pipe (0): Success
Setting alarm to 12 seconds
Got signal: 15 (oktojump=0)
Called kill(pid=0, sig=15) -> 0
Called kill(pid=11058, sig=15) -> 0
Sent SIGTERM to pid 11058 (timeout=5s)
Got signal: 15 (oktojump=1)

06:38 PM  ~
tim@gil$ 

... some time passes, and then ...

Called kill(pid=11058, sig=9) -> 0
Child process 11058 exited
* Process 11058 (ret=-1, status=0, sig=17, oktojump=0, exited_count=0)
Got signal: 13 (oktojump=0)
Exiting (0)

On another run, before exiting the Erlang VM, I attached gdb to the exec-port process while it was spinning and gdb stopped it inside of the check_pending() function:

tim@gil$ gdb ./exec-port 9916
GNU gdb (GDB) 7.4.1-debian
Copyright (C) 2012 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.  Type "show copying"
and "show warranty" for details.
This GDB was configured as "x86_64-linux-gnu".
For bug reporting instructions, please see:
<http://www.gnu.org/software/gdb/bugs/>...
Reading symbols from /home/tim/erl-libs/erlexec/priv/x86_64-unknown-linux-gnu/exec-port...done.
Attaching to program: /home/tim/erl-libs/erlexec/priv/x86_64-unknown-linux-gnu/exec-port, process 9916
Reading symbols from /usr/lib/x86_64-linux-gnu/libstdc++.so.6...(no debugging symbols found)...done.
Loaded symbols for /usr/lib/x86_64-linux-gnu/libstdc++.so.6
Reading symbols from /lib/x86_64-linux-gnu/libm.so.6...(no debugging symbols found)...done.
Loaded symbols for /lib/x86_64-linux-gnu/libm.so.6
Reading symbols from /lib/x86_64-linux-gnu/libgcc_s.so.1...(no debugging symbols found)...done.
Loaded symbols for /lib/x86_64-linux-gnu/libgcc_s.so.1
Reading symbols from /lib/x86_64-linux-gnu/libc.so.6...(no debugging symbols found)...done.
Loaded symbols for /lib/x86_64-linux-gnu/libc.so.6
Reading symbols from /lib64/ld-linux-x86-64.so.2...(no debugging symbols found)...done.
Loaded symbols for /lib64/ld-linux-x86-64.so.2
0x00007f7605ec04bc in sigpending () from /lib/x86_64-linux-gnu/libc.so.6
(gdb) bt
#0  0x00007f7605ec04bc in sigpending () from /lib/x86_64-linux-gnu/libc.so.6
#1  0x0000000000405bb2 in check_pending () at c_src/exec.cpp:498
#2  0x0000000000406533 in main (argc=2, argv=0x7fff8bb3e9d8) at c_src/exec.cpp:629
(gdb) 

If this is not easily replicated on another system I can dig further.

test.cpp should not be included in compilation of exec-port

Hi,

I'm running into some issues with compiling v1.1.1 of erlexec from hex.pm for x86_64-alpine-linux-musl. Specifically, it appears that the test.cpp file is included in the compilation process even though it should not be (defines a second main, has #include <ei++.hpp> at the top which causes compilation to fail, etc.). Should this file be removed, or excluded from compilation somehow? I'm on 1.1.0 for now which is fine, but would like to upgrade to v1.1.1.

Thanks!

`mix deps.compile` fails on Mac OSX El Capitan + HomeBrew

Not sure what's behind this. It appears the the -m64 option to g++ is what's breaking it. The errors I get are:

ld: warning: ld: warning: ignoring file c_src/exec.o, file was built for unsupported file format ( 0x7F 0x45 0x4C 0x46 0x02 0x01 0x01 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 ) which is not the architecture being linked (x86_64): c_src/exec.oignoring file c_src/ei++.o, file was built for unsupported file format ( 0x7F 0x45 0x4C 0x46 0x02 0x01 0x01 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 ) which is not the architecture being linked (x86_64): c_src/ei++.o

Undefined symbols for architecture x86_64:
  "_main", referenced from:
     implicit entry/start for main executable
ld: symbol(s) not found for architecture x86_64
clang: error: linker command failed with exit code 1 (use -v to see invocation)

Removing the -m64 from the build seems to have fixed it. I'm not sure if this is the right way to do it, but you can see a working build at jvantuyl/erlexec@ffa8bc2.

Crash while calling `exec:stop_and_wait(I, 5000)`

Symptom:

In my code I have a call to

exec:stop_and_wait(I, 5000),

This crashes with an error {badmatch, []} when called, because of this match in exec:

[{_, Pid}] = ets:lookup(exec_mon, OsPid),

Expected output:

I would probably have expected the code to return an error term, something like {error, not_found} in this case. The mon referenced by I is gone when the call is made, but I'm doing it to clean up in the case it is not dead yet.

Question:

Is this intended behavior or an error? Am I using the API incorrectly?

Sample script demonstrating a few erlexec issues

First, using erlangs ports instead of erlexec does not have the issues described below, it is fast, responsive, get each line in a message and so forth

The program below when run will start printing information to stderr immediately (it does not use stdout, no do not know why, but that part is not related, add 2>&1 to the call and still the same issue), however the erlang process does not receive it until I send the kill command, then instead of receiving them one message per line like I do with erlang ports I get it all at once along with the message that it was killed, though I do get those messages as soon as it was killed.

In the same program another issue is demonstratable, remove the kill call and everything after it and try using the erl shell. On all 3 computers I tested a lot of my typed characters are seemingly silently dropped while the called process is running, though when killed then everything is responsive again.

Occasionally and often when the erlang program is quit (with say q().) then the erlexec program and associated process it started does not die either and they continue to live as zombies until I kill them from bash.

All of the above issues that is shown with the below program are identically shown on 3 very different computer running 3 very different debian version with two different Erlang versions (15B and 16B).

Demonstration script: https://gist.github.com/OvermindDL1/1d8ca244db81d9000050

Result:

timeout {1375,893135,404052}
timeout {1375,893136,405023}
timeout {1375,893137,406024}
timeout {1375,893138,407660}
timeout {1375,893139,408163}
timeout {1375,893141,410052}
timeout {1375,893142,411054}
timeout {1375,893143,412156}
timeout {1375,893144,413055}
timeout {1375,893145,414090}
timeout {1375,893156,420051}
timeout {1375,893157,421057}
timeout {1375,893158,422124}
timeout {1375,893159,423055}
timeout {1375,893160,424056}
Message {1375,893160,451161}: {stderr,24122,
                                      <<"2013-08-07 10:32:19 [INFO] Starting minecraft server version 1.6.2\n2013-08-07 10:32:19 [INFO] Loading properties\n2013-08-07 10:32:19 [INFO] Default game type: SURVIVAL\n2013-08-07 10:32:19 [INFO] Generating keypair\n2013-08-07 10:32:20 [INFO] Starting Minecraft server on *:25565\n2013-08-07 10:32:21 [INFO] Preparing level \"world\"\n2013-08-07 10:32:21 [INFO] Preparing start region for level 0\n2013-08-07 10:32:22 [INFO] Preparing spawn area: 13%\n2013-08-07 10:32:23 [INFO] Preparing spawn area: 33%\n2013-08-07 10:32:24 [INFO] Preparing spawn area: 56%\n2013-08-07 10:32:25 [INFO] Preparing spawn area: 78%\n2013-08-07 10:32:26 [INFO] Preparing spawn area: 99%\n2013-08-07 10:32:26 [INFO] Done (5.284s)! For help, type \"help\" or \"?\"\n">>}
Message {1375,893160,452038}: {'EXIT',<0.47.0>,{exit_status,9}}
timeout {1375,893161,453052}
timeout {1375,893162,454024}
timeout {1375,893163,455053}
timeout {1375,893165,457055}
timeout {1375,893166,458387}
timeout {1375,893167,459703}
timeout {1375,893168,460936}
timeout {1375,893169,462051}

Dialyzer issues

exec.erl:322: Function run_link/2 has no local return
exec.erl:537: Function init/1 has no local return
exec.erl:539: The call proplists:expand([{'debug', {'debug', 1}}, {'root', {'root', 'true'}}, {'verbose', {'verbose', 'true'}}],Options::any()) breaks the contract (Expansions,ListIn) -> ListOut when is_subtype(Expansions,[{Property::property(),Expansion::[term()]}]), is_subtype(ListIn,[term()]), is_subtype(ListOut,[term()])

Capture stdout and stderr output programmatically

Hi,

My reading of the source code indicates you can't capture stderr and stdout programmatically, by, say, having them sent to Erlang as messages, like open_port does.

It'd be nice to have some options along the lines of

{stdout, Pid}, {stderr, Pid}

So that you could have erlexec send the output back to Erlang for processing.

Any incoming message while waiting for sync subprocess treated as error

(kerl:r17) ~/p/erlexec (master|✔)                                                                                                                                       
> erl -pa ebin
Erlang/OTP 17 [erts-6.4] [source] [64-bit] [smp:8:8] [async-threads:10] [kernel-poll:false]

Eshell V6.4  (abort with ^G)
1> application:start(exec).
ok
2> application:which_applications().
[{exec,"OS Process Manager","v1.0-156-g05edfd2"},
 {stdlib,"ERTS  CXC 138 10","2.4"},
 {kernel,"ERTS  CXC 138 10","3.2"}]
3> spawn(fun () -> io:format("~p~n", [exec:run("sleep 3 && echo done", [sync, stdout])]) end) ! test.
test
{error,[{reason,test}]}

As I can see, there is a clause in exec:wait_for_ospid_exit which treats any incoming message as an error message. Are you sure this behaviour is correct?

Something is wrong with the stop_child stuff

I'm not sure entirely what yet, but there is something wrong with the stop_child code.

I've been doing some debugging, and get very strange result for the time difference. I think it has something to do with this:

    uint32_t sec()      const   { return m_tv.tv_sec;  }

By returning it unsigned, it's causing problems, because in reality the difference is a negative number.

exec doesn't killed linked processes properly

When a process that has called exec:run_link dies, the exec process just exits with owner_died. I was under the assumption that this should cause exec to run the kill_cmd / kill_group or at least kill the linked OS processes.

Allowing root user

Under some circumstances, it is necessary to use the root user. For example, I'm developing a remote manager for docker instances, and I want to do things like using iptables. For sure there are other ways to do this, but they are much more complex. I don't see any problem in running as root a program I have the source code and it is not setuid.

I have commented out the root-checking lines from exec.cpp and it seems to work perfectly. Would it be interesting to add an option to exec.cpp and exec.erl to allow root access? Do you see any problem in that?

If it is ok I will contribute it.
Thanks

Non-zero exit codes returned corrupted

Here's what I see consistently with latest version from master branch:

%% 0 exit code is correct
exec:run("ls", [sync, stdout]).
{ok,[{stdout,[<<"AUTHORS\nLICENSE\nMakefile\nREADME\nTODO\nc_src\nebin\ninclude\npriv\nrebar.config\nrebar.config.scrip"...>>]}]}

%% Any non-0 exit code is broken
exec:run("blah", [sync, stdout]).
{error,[{exit_status,32512}]}

%% Real exit code
exec:run("blah; echo \$?", [sync, stdout]).
{ok,[{stdout,[<<"127\n">>]}]}

Happens both on x86_64-unknown-linux-gnu and x86_64-apple-darwin13.1.0.

Versions are:

Linux precise64 3.8.0-35-generic #52~precise1-Ubuntu SMP Thu Jan 30 17:24:40 UTC 2014 x86_64 x86_64 x86_64 GNU/Linux

Mac is: OS X 10.10 preview 2

Close stdin

None of the examples cover how to close stdin after providing custom generated input. For example calling tac.

1> application:start(erlexec).
ok
2> Watcher = spawn(fun F() -> receive Msg -> io:format("Got: ~p~n", [Msg]), F() end end).
<0.103.0>
3> {ok, Pid, OsPid} = exec:run("tac", [stdin, {stdout, Watcher}, {stderr, Watcher}]).
{ok,<0.105.0>,15922}
4> exec:send(Pid, <<"foo\n">>).                                                      
ok
5> exec:send(Pid, <<"bar\n">>).                                                      
ok
6> exec:send(Pid, <<"baz\n">>).                                                      
ok

And what I am supposed to do now? exec:manage/2 doesn't do the trick:

7> exec:manage(OsPid, [{stdin, close}]).
{ok,<0.110.0>,15922}

I am missing command for closing stdin of the existing and opened stdin of the existing command.

Compare with exec:run("echo -e 'foo\\nbar\\nbaz' | tac", [{stdout, Watcher}, {stderr, Watcher}]).

Including erlexec as a dependency fails

Trying to include {erlexec, "1.2.1"} as a dependency via rebar3 fails with an uncaught error in rebar_core.

From the crash dump:

Error: {badmatch,{error,{35,file,
                         {error,{badmatch,{error,enoent}},
                                [{erl_eval,expr,3,[]}]}}}}

Stdout to stderr redirect is broken

When using {stdout, stderr} redirection option, output is printed to screen instead of being delivered to the calling process:

6> f(I), {ok, _, I} = exec:run("echo TEST", [stderr, {stdout, stderr}]).         
{ok,<0.1396.0>,11509}
7> TEST

7> flush().
ok

Workaround - add 1>&2 redirect on command line:

9> f(I), {ok, _, I} = exec:run("echo TEST 1>&2", [stderr, {stdout, stderr}]).
{ok,<0.1399.0>,11510}
10> flush().                                                                  
Shell got {stderr,11510,<<"TEST\n">>}
ok

application:get_env is not properly working

Hello,

I just added erlexec to my application dependencies and then in the sys.config I added:

    {exec,[
        {root, true}
    ]},

Once the application is starting is not finding any environment variable. I suppose because the name of the app is erlexec and not exec.

So changing the code to case application:get_env(erlexec, Option) of into the following function

add_option(Option, Acc) ->
    case application:get_env(exec, Option) of
    {ok, Value} -> [{Option, Value} | Acc];
    undefined   -> Acc
    end.

And also the app name into erlexec into sys.config fixed my issue.

What do you think ?

Redirecting shell arguments to temp file

Assuming a program called foo which takes a file as an argument, how do I do something like this e.g. in bash I could redirect an argument's contents into a temp file with something like foo <(echo "hello world"). Is there a way to do something similar in pure Erlang without having to shell out to bash?

Closing stdin

Lots of apps wait for stdin to be closed as an indication it's time to do something meaningful (like exit). Without the ability to close stdin, one needs to create an intermediary wrapper that knows uses some inbound signal to close stdin (e.g. two \n, EOF, etc.)

The test example conveniently reads one line and exits.

A trivial example is to use write to cat's stdin.

Need a way to ensure a process is dead

I need some kind of call so that I can ensure that when it returns, or sends me a message or something, the underlying process is really and truly dead.

Since I'm calling this from terminate() of a gen_server, I think something that just plain blocks is probably what I'm after.

Stop linked process without exception

It'd be nice to be able to do exec:stop(Pid) without crashing whatever has linked to the Pid. My use case: I have a gen_server that starts and stops programs at various times, and it'd be nice to differentiate between "oh no, the external program has crashed" and "I wanted to stop it".

I think it might be sufficient to do something like

unlink_and_stop(Pid) ->
    erlang:unlink(Pid),
    stop(Pid).

build fails on Mac OS Lion

This may be because Lion is not only 64 bit but also uses LLVM instead of the gcc tool chain. I will install gcc et al via MacPorts and try that way as well, but it's probably worth being aware that this fails on a default Lion install. Here's the debug output from rebar:

$ rebar compile -v 4
DEBUG: Evaluating config script "/usr/local/src/erlang/erlexec/rebar.config.script"
DEBUG: Consult config file "/usr/local/src/erlang/erlexec/rebar.config"
DEBUG: Rebar location: "/Users/t4/bin/rebar"
DEBUG: Available deps: []
DEBUG: Missing deps  : []
DEBUG: Predirs: []
==> erlexec (compile)
DEBUG: Matched required ERTS version: 5.9.1 -> .*
DEBUG: Matched required OTP release: R15B01 -> .*
DEBUG: erl_opts [debug_info,warnings_as_errors,warn_export_all]
DEBUG: Starting 3 compile worker(s)
DEBUG: Worker exited cleanly
INFO:  Skipped src/exec_app.erl
DEBUG: Worker exited cleanly
INFO:  Skipped src/exec.erl
DEBUG: Worker exited cleanly
INFO:  Skipping c_src/ei++.cpp
INFO:  sh info:
    cwd: "/usr/local/src/erlang/erlexec"
    cmd: g++ -c $CXXFLAGS -g -Wall -fPIC  -I/Users/t4/Library/Erlang/Versions/R15B01-l64/lib/erlang/lib/erl_interface-3.7.7/include -I/Users/t4/Library/Erlang/Versions/R15B01-l64/lib/erlang/erts-5.9.1/include   c_src/exec.cpp -o c_src/exec.o
DEBUG:  opts: [{env,[{"ABBOT_HOME","/usr/local/src/web/abbot"},
                     {"Apple_PubSub_Socket_Render",
                      "/tmp/launch-mSCKGl/Render"},
                     {"Apple_Ubiquity_Message",
                      "/tmp/launch-JFoaqK/Apple_Ubiquity_Message"},
                     {"BINDIR",
                      "/Users/t4/Library/Erlang/Versions/R15B01-l64/lib/erlang/erts-5.9.1/bin"},
                     {"CABAL_BIN","/Users/t4/.cabal/bin"},
                     {"CATALINA_HOME","/usr/local/tomcat"},
                     {"CATALINA_OPTS",
                      "-Xms512m -Xmx2046m -XX:MaxPermSize=512m"},
                     {"CC","g++"},
                     {"CLICOLOR","1"},
                     {"COLOR_BLACK","\\e[0;30m"},
                     {"COLOR_BLUE","\\e[0;34m"},
                     {"COLOR_BROWN","\\e[0;33m"},
                     {"COLOR_CYAN","\\e[0;36m"},
                     {"COLOR_GRAY","\\e[0;30m"},
                     {"COLOR_GREEN","\\e[0;32m"},
                     {"COLOR_LIGHT_BLUE","\\e[1;34m"},
                     {"COLOR_LIGHT_CYAN","\\e[1;36m"},
                     {"COLOR_LIGHT_GRAY","\\e[0;37m"},
                     {"COLOR_LIGHT_GREEN","\\e[1;32m"},
                     {"COLOR_LIGHT_PURPLE","\\e[1;35m"},
                     {"COLOR_LIGHT_RED","\\e[1;31m"},
                     {"COLOR_NC","\\e[0m"},
                     {"COLOR_PURPLE","\\e[0;35m"},
                     {"COLOR_RED","\\e[0;31m"},
                     {"COLOR_WHITE","\\e[1;37m"},
                     {"COLOR_YELLOW","\\e[1;33m"},
                     {"COMMAND_MODE","unix2003"},
                     {"CXX","g++"},
                     {"DISPLAY","/tmp/launch-ohLkAD/org.x:0"},
                     {"DRV_CC_TEMPLATE",
                      "g++ -c $CFLAGS -g -Wall -fPIC  -I/Users/t4/Library/Erlang/Versions/R15B01-l64/lib/erlang/lib/erl_interface-3.7.7/include -I/Users/t4/Library/Erlang/Versions/R15B01-l64/lib/erlang/erts-5.9.1/include   $PORT_IN_FILES -o $PORT_OUT_FILE"},
                     {"DRV_CFLAGS",
                      "-g -Wall -fPIC  -I/Users/t4/Library/Erlang/Versions/R15B01-l64/lib/erlang/lib/erl_interface-3.7.7/include -I/Users/t4/Library/Erlang/Versions/R15B01-l64/lib/erlang/erts-5.9.1/include  "},
                     {"DRV_CXX_TEMPLATE",
                      "g++ -c $CXXFLAGS -g -Wall -fPIC  -I/Users/t4/Library/Erlang/Versions/R15B01-l64/lib/erlang/lib/erl_interface-3.7.7/include -I/Users/t4/Library/Erlang/Versions/R15B01-l64/lib/erlang/erts-5.9.1/include   $PORT_IN_FILES -o $PORT_OUT_FILE"},
                     {"DRV_LDFLAGS",
                      "-bundle -flat_namespace -undefined suppress  -L/Users/t4/Library/Erlang/Versions/R15B01-l64/lib/erlang/lib/erl_interface-3.7.7/lib -lerl_interface -lei"},
                     {"DRV_LINK_TEMPLATE",
                      "g++ $PORT_IN_FILES $LDFLAGS -bundle -flat_namespace -undefined suppress  -L/Users/t4/Library/Erlang/Versions/R15B01-l64/lib/erlang/lib/erl_interface-3.7.7/lib -lerl_interface -lei -o $PORT_OUT_FILE"},
                     {"DYLD_LIBRARY_PATH",
                      "/lib:/usr/lib:/usr/local/lib::/opt/local/lib:"},
                     {"EMU","beam"},
                     {"ERLANG_ARCH","64"},
                     {"ERLANG_TARGET",
                      "R15B01-i386-apple-darwin11.3.0-64-unix"},
                     {"ERL_CFLAGS",
                      " -I/Users/t4/Library/Erlang/Versions/R15B01-l64/lib/erlang/lib/erl_interface-3.7.7/include -I/Users/t4/Library/Erlang/Versions/R15B01-l64/lib/erlang/erts-5.9.1/include  "},
                     {"ERL_EI_LIBDIR",
                      "/Users/t4/Library/Erlang/Versions/R15B01-l64/lib/erlang/lib/erl_interface-3.7.7/lib"},
                     {"ERL_LDFLAGS",
                      " -L/Users/t4/Library/Erlang/Versions/R15B01-l64/lib/erlang/lib/erl_interface-3.7.7/lib -lerl_interface -lei"},
                     {"ERL_LIBS","/Users/t4/Library/Erlang/Site"},
                     {"ERL_ROOT","/Users/t4/Library/Erlang/Current"},
                     {"ERL_TOP","/Users/t4/Library/Erlang"},
                     {"EXE_CC_TEMPLATE",
                      "g++ -c $CFLAGS -g -Wall -fPIC  -I/Users/t4/Library/Erlang/Versions/R15B01-l64/lib/erlang/lib/erl_interface-3.7.7/include -I/Users/t4/Library/Erlang/Versions/R15B01-l64/lib/erlang/erts-5.9.1/include   $PORT_IN_FILES -o $PORT_OUT_FILE"},
                     {"EXE_CFLAGS",
                      "-g -Wall -fPIC  -I/Users/t4/Library/Erlang/Versions/R15B01-l64/lib/erlang/lib/erl_interface-3.7.7/include -I/Users/t4/Library/Erlang/Versions/R15B01-l64/lib/erlang/erts-5.9.1/include  "},
                     {"EXE_CXX_TEMPLATE",
                      "g++ -c $CXXFLAGS -g -Wall -fPIC  -I/Users/t4/Library/Erlang/Versions/R15B01-l64/lib/erlang/lib/erl_interface-3.7.7/include -I/Users/t4/Library/Erlang/Versions/R15B01-l64/lib/erlang/erts-5.9.1/include   $PORT_IN_FILES -o $PORT_OUT_FILE"},
                     {"EXE_LDFLAGS",
                      " -L/Users/t4/Library/Erlang/Versions/R15B01-l64/lib/erlang/lib/erl_interface-3.7.7/lib -lerl_interface -lei"},
                     {"EXE_LINK_TEMPLATE",
                      "g++ $PORT_IN_FILES $LDFLAGS  -L/Users/t4/Library/Erlang/Versions/R15B01-l64/lib/erlang/lib/erl_interface-3.7.7/lib -lerl_interface -lei -o $PORT_OUT_FILE"},
                     {"FEDORA_HOME","/usr/local/fedora"},
                     {"FLASH_HOME",
                      "/Applications/Flash Player.app/Contents/MacOS"},
                     {"GODI_TOP","/Users/t4/Library/OCaml"},
                     {"GREP_COLOR","1;32"},
                     {"GREP_OPTIONS","--color=auto"},
                     {"HAMCREST_PYPATH",
                      "/usr/local/src/python/hamcrest/hamcrest-read-only/hamcrest-python"},
                     {"HISTCONTROL","ignoredups"},
                     {"HOME","/Users/t4"},
                     {"JAVA_HOME",
                      "/System/Library/Frameworks/JavaVM.framework/Versions/CurrentJDK/Home"},
                     {"JDK_HOME",
                      "/System/Library/Frameworks/JavaVM.framework/Versions/CurrentJDK/Home"},
                     {"JRUBY_HOME","/usr/local/src/java/jruby/jruby1.0-svn/"},
                     {"JYTHON_HOME","/usr/local/jython"},
                     {"LANG","en_GB.UTF-8"},
                     {"LD_LIBRARY_PATH","/lib:/usr/lib:/usr/local/lib:"},
                     {"LIBPATH","/usr/local/xerces/lib:"},
                     {"LOGNAME","t4"},
                     {"M2_HOME","/usr/local/src/java/maven"},
                     {"MANPATH","/Users/t4/Library/Erlang/Current/man:"},
                     {"MAVEN_OPTS","-Xms768m -Xmx2046m -XX:MaxPermSize=1024m"},
                     {"ORACLE_HOME",[]},
                     {"PARSE_TRANS_ROOT",
                      "/Users/t4/Library/Erlang/Site/parse_trans"},
                     {"PATH",
                      "/Users/t4/Library/Erlang/Versions/R15B01-l64/lib/erlang/erts-5.9.1/bin:/Users/t4/Library/Erlang/Versions/R15B01-l64/lib/erlang/bin:/usr/local/pgsql/bin:/Library/PostgreSQL/8.4/bin::/usr/local/fedora/server/bin:/usr/local/fedora/client/bin:/usr/local/src/java/maven/bin:/usr/local/tomcat/bin:/bin:/System/Library/Frameworks/JavaVM.framework/Versions/CurrentJDK/Home:/usr/local/src/java/jruby/jruby1.0-svn//bin:/usr/local/jython:/usr/local/jython/bin:/Applications/Flash Player.app/Contents/MacOS:/usr/local/src/web/abbot/bin:/Users/t4/Library/Erlang/Current/bin:/Users/t4/Library/OCaml/bin:/Users/t4/.cabal/bin:/System/Library/Frameworks/Ruby.framework/Versions/Current/usr/bin:/Library/Frameworks/Python.framework/Versions/Current/bin:/Users/t4/.bash/utilities:/Users/t4/bin:/Users/t4/bin/cccs:/usr/bin:/bin:/usr/sbin:/sbin:/usr/local/bin:/usr/X11/bin:/usr/local/git/bin:/usr/local/MacGPG2/bin"},
                     {"PGDATA","/usr/local/pgsql/data"},
                     {"PGSQLHOME","/Library/PostgreSQL/8.4"},
                     {"POSTGRES_BIN","/usr/local/pgsql/bin"},
                     {"PROGNAME","erl"},
                     {"PWD","/usr/local/src/erlang/erlexec"},
                     {"PYTHONPATH",
                      "/usr/local/src/python/hamcrest/hamcrest-read-only/hamcrest-python:/usr/local/lib:/usr/local/src/python/matplotlib"},
                     {"PYTHON_BASE",
                      "/Library/Frameworks/Python.framework/Versions/Current"},
                     {"ROOTDIR",
                      "/Users/t4/Library/Erlang/Versions/R15B01-l64/lib/erlang"},
                     {"RUBY_HOME",
                      "/System/Library/Frameworks/Ruby.framework/Versions/Current/usr"},
                     {"SHELL","/bin/bash"},
                     {"SHLIB_PATH","/usr/local/xerces/lib:"},
                     {"SHLVL","1"},
                     {"SSH_AUTH_SOCK","/tmp/launch-Hg60op/Listeners"},
                     {"TERM","xterm-color"},
                     {"TERM_PROGRAM","Apple_Terminal"},
                     {"TERM_PROGRAM_VERSION","303.2"},
                     {"TERM_SESSION_ID",
                      "BF8E37CD-05C0-4E33-8FD9-0B954DFE4533"},
                     {"TMPDIR",
                      "/var/folders/lt/jg6yqg751pgfpmxn92l1sz1r0000gn/T/"},
                     {"TNS_ADMIN","/admin"},
                     {"USER","t4"},
                     {"XALANCROOT","/usr/local/src/native/xml-xalan2/c"},
                     {"XERCESCROOT","/usr/local/xerces"},
                     {"__CF_USER_TEXT_ENCODING","0x1F5:0:0"}]},
               return_on_error,
               {use_stdout,false}]
Compiling /usr/local/src/erlang/erlexec/c_src/exec.cpp
/usr/local/src/erlang/erlexec/c_src/exec.cpp: In function ‘void check_pending()’:
/usr/local/src/erlang/erlexec/c_src/exec.cpp:249: error: ‘sigtimedwait’ was not declared in this scope
/usr/local/src/erlang/erlexec/c_src/exec.cpp: In function ‘int main(int, char**)’:
/usr/local/src/erlang/erlexec/c_src/exec.cpp:349: error: ‘setresuid’ was not declared in this scope
/usr/local/src/erlang/erlexec/c_src/exec.cpp: In function ‘pid_t start_child(const char*, const char*, char* const*, int, int)’:
/usr/local/src/erlang/erlexec/c_src/exec.cpp:561: error: ‘setresuid’ was not declared in this scope
ERROR: compile failed while processing /usr/local/src/erlang/erlexec: rebar_abort

Exit code mismatch

Running erlexec with monitor as an option, I receive {'DOWN', #Ref<0.0.0.3303>, process, <0.207.0>, {exit_status, 256}}, however running the command in the shell returns the error code 1.

The command is started with:

exec:run(["/path/to/command", "--id", "1", "--control", ":3333"], [monitor, {stdout, print}, {stderr, print}, {kill_timeout, 1}])

The output and message received is:

Got stderr from 66903: <<"time=\"2015-07-16T16:18:53+02:00\" level=fatal msg=\"Could not create controller: dial tcp 127.0.0.1:23000: connection refused\" \n">>
{'DOWN', #Ref<0.0.0.3303>, process, <0.207.0>, {exit_status, 256}}

However, running the command in the shell shows the error code is actually 1:

$ /path/to/command --id 1 --control :3333
FATA[0000] Could not create controller: dial tcp :3333: connection refused
$ echo $?
1

Exit status shifted 8 bits?

This is surprising to me:

30> exec:run_link("sleep 1; exit 1", []).                   
{ok,<0.118.0>,20435}
** exception error: {exit_status,256}
31> exec:run_link("sleep 1; exit 2", []).
{ok,<0.121.0>,20437}
** exception error: {exit_status,512}

Why is the reported exit status shifted 8 bits?

c_src/*.d files present in hex package

Hi @saleyn!

Thanks for the great library. Building via hex dep on OS X is failing currently, because 2 files are present in the c_src dir in the hex package: ei++.d and exec.d. After deleting these files, the build works perfectly. Could you bump the version and publish to hex without those two files?

Thanks!

Feasability of adding args to set pty options?

I'm currently using this module to provide bash shell, and it works great.
The only problem that I've encountered so far is that the tty is sized at 80X24, which is moderately inconvenient for actually using the shell.

At the time when I'm actually exec'ing the subprocess, I know what terminal dimensions I would need, (as well as some other tty options, but those are gracefully handled by reset) so a mechanism to set tty options or at least dimensions would be very helpful.

Getting strange return from exec:run

I'm trying the following code:

exec:run(["/usr/bin/docker", "commit", TempName, FinalName], [sync, stdout, stderr])

And sometimes I get the usual response with exit codes and stdout/stderr strings, but sometimes I get this:

{error, [{reason,
            {'DOWN', #Ref<0.0.65.102600>, process, <0.14791.3>, normal}}]}

I checked and the command actually was executed just fine and exited properly, but for some reason the return is not the usual exit status.

I've enabled verbose/debug mode and enabled tracer for exec module – https://gist.github.com/dsabanin/80a8a1edb705377bfe34. Any ideas are greatly appreciated. Thanks!

Cannot disable pty echo: Inappropriate ioctl for device

First of all, thanks for the awesome library! You really covered a lot of use cases and I'm really enjoying how it works. I'm having a minor issue, but the overall experience is great. Thanks for the docs too!

I'm trying to enable PTY mode, but for some reason it doesn't work with the following error:

Cannot disable pty echo: Inappropriate ioctl for device

What could be the reason for this? Am I doing something wrong? Thanks!

Code excerpt:

OutputHandler = fun (Stream, OsPid, Data) -> ... end,
exec:run(SetupCmd, [sync, pty, {kill_timeout, ?SETUP_TIMEOUT},
                               {stdout, OutputHandler},
                               {stderr, OutputHandler}]).

Just in case it matters, Erlang VM (R17) is running in VirtualBox VM under Mac OS X. The command I executes starts a docker container.

exec-port gets stuck on Mac OS X

I've been trying to build a program with erlexec on mac os x, but noticed some fairly strange behaviours in my tests of the erlexec part. I've got some processes that run exec:run_link. If I run a few of these processes, everything works fine. But if I run a lot at once, most of them start timing out in a genserver call inside exec:run_link. From that point on, no exec:run_link call will work.

I used observer & some tracing to see what's going on when this happens: the exec process trans queue has a ton of queued transactions, and it stops receiving any handle_info calls from the exec-port application.

So, I attached a debugger to exec-port, and it seems like it's getting stuck in an infinite loop inside the check_pending function. It gets as far as this while loop, with errno set to EINTR. Which seems to be causing an infinite loop - as far as I can see sigtimedwait is stubbed on on mac, so nothing ever clears errno and the program loops forever.

Any ideas how to go about fixing this?

Not killing a process trapping SIGTERM

This bash script:

#!/bin/bash

rm -f /tmp/lpr

trap 'echo vampire_process_wont_die >> /tmp/lrp' SIGINT SIGTERM

while [ true ] ;
 do date >> /tmp/lrp
    sleep 1
done

Doesn't get killed by exec in a clean way.

exec:start(),
{ok, Pid, _} = exec:run("./lrp.sh", []),
timer:sleep(1000),
exec:stop(Pid).

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.