summaryrefslogtreecommitdiff
path: root/deps
diff options
context:
space:
mode:
authorMagnus Feuer <mfeuer@jaguarlandrover.com>2015-10-01 16:32:37 -0700
committerMagnus Feuer <mfeuer@jaguarlandrover.com>2015-10-01 16:32:37 -0700
commit2811f64c8bd2c3179debbaf687c8db67c2839409 (patch)
tree625ac1836cab4e66aae5c71eb900dd6706ed7e85 /deps
parent1c619870a9b2165b7c1d9733156219852af524fa (diff)
parentbe63c09e8032db6ed820ee92292174449092c40f (diff)
downloadrvi_core-2811f64c8bd2c3179debbaf687c8db67c2839409.tar.gz
Merge pull request #54 from magnusfeuer/master
Now builds under tizen
Diffstat (limited to 'deps')
-rw-r--r--deps/base64url/LICENSE.txt18
-rw-r--r--deps/base64url/Makefile29
-rw-r--r--deps/base64url/README.md58
-rw-r--r--deps/base64url/rebar.config11
-rw-r--r--deps/base64url/src/base64url.app.src14
-rw-r--r--deps/base64url/src/base64url.erl98
-rw-r--r--deps/exec/.travis.yml4
-rw-r--r--deps/exec/AUTHORS6
-rw-r--r--deps/exec/LICENSE30
-rw-r--r--deps/exec/Makefile67
-rw-r--r--deps/exec/README50
-rw-r--r--deps/exec/TODO2
-rw-r--r--deps/exec/c_src/ei++.cpp354
-rw-r--r--deps/exec/c_src/ei++.hpp585
-rw-r--r--deps/exec/c_src/exec.cpp2111
-rw-r--r--deps/exec/include/exec.hrl8
-rwxr-xr-xdeps/exec/priv/x86_64-pc-linux-gnu/exec-portbin0 -> 450870 bytes
-rw-r--r--deps/exec/rebar.config6
-rw-r--r--deps/exec/rebar.config.script44
-rw-r--r--deps/exec/src/edoc.css144
-rw-r--r--deps/exec/src/exec.app.src16
-rw-r--r--deps/exec/src/exec.erl1130
-rw-r--r--deps/exec/src/exec_app.erl78
-rw-r--r--deps/exec/src/overview.edoc305
24 files changed, 5168 insertions, 0 deletions
diff --git a/deps/base64url/LICENSE.txt b/deps/base64url/LICENSE.txt
new file mode 100644
index 0000000..c3f0a46
--- /dev/null
+++ b/deps/base64url/LICENSE.txt
@@ -0,0 +1,18 @@
+Copyright (c) 2013 Vladimir Dronnikov <dronnikov@gmail.com>
+
+Permission is hereby granted, free of charge, to any person obtaining a copy of
+this software and associated documentation files (the "Software"), to deal in
+the Software without restriction, including without limitation the rights to
+use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
+the Software, and to permit persons to whom the Software is furnished to do so,
+subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
+FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
+COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
+IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
diff --git a/deps/base64url/Makefile b/deps/base64url/Makefile
new file mode 100644
index 0000000..7c582b2
--- /dev/null
+++ b/deps/base64url/Makefile
@@ -0,0 +1,29 @@
+all: deps compile check test
+
+deps:
+ rebar get-deps
+
+compile:
+ rebar compile
+
+run: compile
+ sh start.sh
+
+clean:
+ rebar clean
+ rm -fr ebin .ct test/*.beam
+
+check:
+ rebar eunit skip_deps=true
+
+test: deps compile check
+ ##rebar ct
+ #mkdir -p .ct
+ #ct_run -dir test -logdir .ct -pa ebin
+
+dist: deps compile
+ echo TODO
+
+.PHONY: all deps compile check test run clean dist
+.SILENT:
+
diff --git a/deps/base64url/README.md b/deps/base64url/README.md
new file mode 100644
index 0000000..165a5dd
--- /dev/null
+++ b/deps/base64url/README.md
@@ -0,0 +1,58 @@
+Base64Url
+==============
+
+[![Hex.pm](https://img.shields.io/hexpm/v/base64url.svg)](https://hex.pm/packages/base64url)
+
+Standalone [URL safe](http://tools.ietf.org/html/rfc4648) base64-compatible codec.
+
+Usage
+--------------
+
+URL-Safe base64 encoding:
+```erlang
+base64url:encode(<<255,127,254,252>>).
+<<"_3_-_A">>
+base64url:decode(<<"_3_-_A">>).
+<<255,127,254,252>>
+```
+
+Vanilla base64 encoding:
+```erlang
+base64:encode(<<255,127,254,252>>).
+<<"/3/+/A==">>
+```
+
+Some systems in the wild use base64 URL encoding, but keep the padding for MIME compatibility (base64 Content-Transfer-Encoding). To interact with such systems, use:
+```erlang
+base64url:encode_mime(<<255,127,254,252>>).
+<<"_3_-_A==">>
+base64url:decode(<<"_3_-_A==">>).
+<<255,127,254,252>>
+```
+
+Thanks
+--------------
+
+To authors of [this](https://github.com/basho/riak_control/blob/master/src/base64url.erl) and [this](https://github.com/mochi/mochiweb/blob/master/src/mochiweb_base64url.erl).
+
+[License](base64url/blob/master/LICENSE.txt)
+-------
+
+Copyright (c) 2013 Vladimir Dronnikov <dronnikov@gmail.com>
+
+Permission is hereby granted, free of charge, to any person obtaining a copy of
+this software and associated documentation files (the "Software"), to deal in
+the Software without restriction, including without limitation the rights to
+use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
+the Software, and to permit persons to whom the Software is furnished to do so,
+subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
+FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
+COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
+IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
diff --git a/deps/base64url/rebar.config b/deps/base64url/rebar.config
new file mode 100644
index 0000000..b3028a1
--- /dev/null
+++ b/deps/base64url/rebar.config
@@ -0,0 +1,11 @@
+{lib_dirs, ["deps"]}.
+
+{erl_opts, [
+ debug_info,
+ warn_format,
+ warn_export_vars,
+ warn_obsolete_guard,
+ warn_bif_clash
+]}.
+
+{cover_enabled, true}.
diff --git a/deps/base64url/src/base64url.app.src b/deps/base64url/src/base64url.app.src
new file mode 100644
index 0000000..b59259c
--- /dev/null
+++ b/deps/base64url/src/base64url.app.src
@@ -0,0 +1,14 @@
+{application, base64url, [
+ {description, "URL safe base64-compatible codec"},
+ {vsn, "0.0.1"},
+ {id, "repo"},
+ {registered, []},
+ {applications, [
+ kernel,
+ stdlib
+ ]},
+ {env, []},
+ {contributors, ["Vladimir Dronnikov"]},
+ {licenses, ["MIT"]}
+%% {links, [{"Github", "https://github.com/dvv/base64url"}]}
+]}.
diff --git a/deps/base64url/src/base64url.erl b/deps/base64url/src/base64url.erl
new file mode 100644
index 0000000..fa38269
--- /dev/null
+++ b/deps/base64url/src/base64url.erl
@@ -0,0 +1,98 @@
+%%
+%% @doc URL safe base64-compatible codec.
+%%
+%% Based heavily on the code extracted from:
+%% https://github.com/basho/riak_control/blob/master/src/base64url.erl and
+%% https://github.com/mochi/mochiweb/blob/master/src/mochiweb_base64url.erl.
+%%
+
+-module(base64url).
+-author('Vladimir Dronnikov <dronnikov@gmail.com>').
+
+-export([
+ decode/1,
+ encode/1,
+ encode_mime/1
+ ]).
+
+-spec encode(
+ binary() | iolist()
+ ) -> binary().
+
+encode(Bin) when is_binary(Bin) ->
+ << << (urlencode_digit(D)) >> || <<D>> <= base64:encode(Bin), D =/= $= >>;
+encode(L) when is_list(L) ->
+ encode(iolist_to_binary(L)).
+
+-spec encode_mime(
+ binary() | iolist()
+ ) -> binary().
+encode_mime(Bin) when is_binary(Bin) ->
+ << << (urlencode_digit(D)) >> || <<D>> <= base64:encode(Bin) >>;
+encode_mime(L) when is_list(L) ->
+ encode_mime(iolist_to_binary(L)).
+
+-spec decode(
+ binary() | iolist()
+ ) -> binary().
+
+decode(Bin) when is_binary(Bin) ->
+ Bin2 = case byte_size(Bin) rem 4 of
+ % 1 -> << Bin/binary, "===" >>;
+ 2 -> << Bin/binary, "==" >>;
+ 3 -> << Bin/binary, "=" >>;
+ _ -> Bin
+ end,
+ base64:decode(<< << (urldecode_digit(D)) >> || <<D>> <= Bin2 >>);
+decode(L) when is_list(L) ->
+ decode(iolist_to_binary(L)).
+
+urlencode_digit($/) -> $_;
+urlencode_digit($+) -> $-;
+urlencode_digit(D) -> D.
+
+urldecode_digit($_) -> $/;
+urldecode_digit($-) -> $+;
+urldecode_digit(D) -> D.
+
+-ifdef(TEST).
+-include_lib("eunit/include/eunit.hrl").
+
+aim_test() ->
+ % vanilla base64 produce URL unsafe output
+ ?assertNotEqual(
+ binary:match(base64:encode([255,127,254,252]), [<<"=">>, <<"/">>, <<"+">>]),
+ nomatch),
+ % this codec produce URL safe output
+ ?assertEqual(
+ binary:match(encode([255,127,254,252]), [<<"=">>, <<"/">>, <<"+">>]),
+ nomatch),
+ % the mime codec produces URL unsafe output, but only because of padding
+ ?assertEqual(
+ binary:match(encode_mime([255,127,254,252]), [<<"/">>, <<"+">>]),
+ nomatch),
+ ?assertNotEqual(
+ binary:match(encode_mime([255,127,254,252]), [<<"=">>]),
+ nomatch).
+
+codec_test() ->
+ % codec is lossless with or without padding
+ ?assertEqual(decode(encode(<<"foo">>)), <<"foo">>),
+ ?assertEqual(decode(encode(<<"foo1">>)), <<"foo1">>),
+ ?assertEqual(decode(encode(<<"foo12">>)), <<"foo12">>),
+ ?assertEqual(decode(encode(<<"foo123">>)), <<"foo123">>),
+ ?assertEqual(decode(encode_mime(<<"foo">>)), <<"foo">>),
+ ?assertEqual(decode(encode_mime(<<"foo1">>)), <<"foo1">>),
+ ?assertEqual(decode(encode_mime(<<"foo12">>)), <<"foo12">>),
+ ?assertEqual(decode(encode_mime(<<"foo123">>)), <<"foo123">>).
+
+iolist_test() ->
+ % codec supports iolists
+ ?assertEqual(decode(encode("foo")), <<"foo">>),
+ ?assertEqual(decode(encode(["fo", "o1"])), <<"foo1">>),
+ ?assertEqual(decode(encode([255,127,254,252])), <<255,127,254,252>>),
+ ?assertEqual(decode(encode_mime("foo")), <<"foo">>),
+ ?assertEqual(decode(encode_mime(["fo", "o1"])), <<"foo1">>),
+ ?assertEqual(decode(encode_mime([255,127,254,252])), <<255,127,254,252>>).
+
+-endif.
diff --git a/deps/exec/.travis.yml b/deps/exec/.travis.yml
new file mode 100644
index 0000000..73c5f9c
--- /dev/null
+++ b/deps/exec/.travis.yml
@@ -0,0 +1,4 @@
+language: erlang
+otp_release:
+ - R16B02
+ - R16B01
diff --git a/deps/exec/AUTHORS b/deps/exec/AUTHORS
new file mode 100644
index 0000000..97f1bdf
--- /dev/null
+++ b/deps/exec/AUTHORS
@@ -0,0 +1,6 @@
+Authors:
+ Serge Aleynikov <saleyn@gmail.com>
+
+Contributors:
+ Dmitry Kargapolov <dmitriy.kargapolov@gmail.com>
+ - Autotools support (deprecated)
diff --git a/deps/exec/LICENSE b/deps/exec/LICENSE
new file mode 100644
index 0000000..67bf740
--- /dev/null
+++ b/deps/exec/LICENSE
@@ -0,0 +1,30 @@
+BSD LICENSE
+===========
+
+Copyright (C) 2003 Serge Aleynikov <saleyn@gmail.com>
+
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+ 1. Redistributions of source code must retain the above copyright notice,
+ this list of conditions and the following disclaimer.
+
+ 2. Redistributions in binary form must reproduce the above copyright
+ notice, this list of conditions and the following disclaimer in
+ the documentation and/or other materials provided with the distribution.
+
+ 3. The names of the authors may not be used to endorse or promote products
+ derived from this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES,
+INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
+FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL JCRAFT,
+INC. OR ANY CONTRIBUTORS TO THIS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT,
+INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,
+OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/deps/exec/Makefile b/deps/exec/Makefile
new file mode 100644
index 0000000..9c13ad9
--- /dev/null
+++ b/deps/exec/Makefile
@@ -0,0 +1,67 @@
+# See LICENSE for licensing information.
+
+VSN = $(shell git describe --always --tags --abbrev=0 | sed 's/^v//')
+PROJECT = $(notdir $(PWD))
+TARBALL = $(PROJECT)-$(VSN)
+
+DIALYZER = dialyzer
+REBAR = rebar
+
+.PHONY : all clean test docs doc clean-docs github-docs dialyzer
+
+all:
+ @$(REBAR) compile
+
+clean:
+ @$(REBAR) clean
+ @rm -fr ebin doc
+
+docs: doc ebin clean-docs
+ @$(REBAR) doc skip_deps=true
+
+doc ebin:
+ mkdir -p $@
+
+test:
+ @$(REBAR) eunit
+
+clean-docs:
+ rm -f doc/*.{css,html,png} doc/edoc-info
+
+github-docs:
+ @if git branch | grep -q gh-pages ; then \
+ git checkout gh-pages; \
+ else \
+ git checkout -b gh-pages; \
+ fi
+ git checkout master -- src include
+ git checkout master -- Makefile rebar.*
+ make docs
+ mv doc/*.* .
+ make clean
+ rm -fr src c_src include Makefile erl_crash.dump priv rebar.* README*
+ @FILES=`git st -uall --porcelain | sed -n '/^?? [A-Za-z0-9]/{s/?? //p}'`; \
+ for f in $$FILES ; do \
+ echo "Adding $$f"; git add $$f; \
+ done
+ @sh -c "ret=0; set +e; \
+ if git commit -a --amend -m 'Documentation updated'; \
+ then git push origin +gh-pages; echo 'Pushed gh-pages to origin'; \
+ else ret=1; git reset --hard; \
+ fi; \
+ set -e; git checkout master && echo 'Switched to master'; exit $$ret"
+
+tar:
+ @rm -f $(TARBALL).tgz; \
+ cd ..; \
+ tar zcf $(TARBALL).tgz --exclude="core*" --exclude="erl_crash.dump" \
+ --exclude="*.tgz" --exclude="*.swp" --exclude="c_src" \
+ --exclude="Makefile" --exclude="rebar.*" --exclude="*.mk" \
+ --exclude="*.o" --exclude=".git*" $(PROJECT) && \
+ mv $(TARBALL).tgz $(PROJECT)/ && echo "Created $(TARBALL).tgz"
+
+dialyzer: build.plt
+ $(DIALYZER) -nn --plt $< ebin
+
+build.plt:
+ $(DIALYZER) -q --build_plt --apps erts kernel stdlib --output_plt $@
diff --git a/deps/exec/README b/deps/exec/README
new file mode 100644
index 0000000..17be7a7
--- /dev/null
+++ b/deps/exec/README
@@ -0,0 +1,50 @@
+erlexec
+=======
+
+ Execute and control OS processes from Erlang/OTP.
+
+ This project implements a C++ port program and Erlang application
+ that gives light-weight Erlang processes fine-grain control over
+ execution of OS processes.
+
+ It makes possible for an Erlang process to start, stop an OS process,
+ send POSIX signals, know process IDs of the started OS process, set up
+ a monitor and/or link to it, run interactive commands with psudo
+ terminals. This application provides 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.
+
+ See [http://saleyn.github.com/erlexec] for more information.
+
+SUPPORTED OS's
+==============
+ Linux, Solaris, MacOS X
+
+BUILDING
+========
+ Make sure you have rebar (http://github.com/basho/rebar) 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
+ make sure that libcap-dev[el] library is installed.
+
+ OS-specific libcap-dev installation instructions:
+
+ Fedora, CentOS: "yum install libcap-devel"
+ Ubuntu: "apt-get install libcap-dev"
+
+ $ git clone git@github.com:saleyn/erlexec.git
+ $ make
+
+DEPLOYING
+=========
+ Run "make tar". This produces a tarball which you can deploy to your
+ destination environment and untar the content.
+
+LICENSE
+=======
+ The program is distributed under BSD license.
+ Copyright (c) 2003 Serge Aleynikov <saleyn at gmail dot com>
diff --git a/deps/exec/TODO b/deps/exec/TODO
new file mode 100644
index 0000000..1e3954c
--- /dev/null
+++ b/deps/exec/TODO
@@ -0,0 +1,2 @@
+1. Rewrite non-portable sigwaitinfo and sigpending implementation to using
+ a dedicated pipe and streaming signals through it to the main select() loop
diff --git a/deps/exec/c_src/ei++.cpp b/deps/exec/c_src/ei++.cpp
new file mode 100644
index 0000000..35517b3
--- /dev/null
+++ b/deps/exec/c_src/ei++.cpp
@@ -0,0 +1,354 @@
+#include <unistd.h>
+#include <fcntl.h>
+#include <sstream>
+#include <iomanip>
+#include "ei++.hpp"
+
+using namespace ei;
+
+//-----------------------------------------------------------------------------
+bool ei::dump ( const char* header, std::ostream& os, const ei_x_buff& buf, bool condition )
+{
+ if ( !condition ) os << (header ? header : "") << buf;
+ return condition;
+}
+
+//-----------------------------------------------------------------------------
+std::ostream& ei::dump (std::ostream& os, const unsigned char* buf, int n, bool eol)
+{
+ std::stringstream s;
+ for(int i=0; i < n; i++)
+ s << (i == 0 ? "<<" : ",") << (int) (buf[i]);
+ s << (n == 0 ? "<<>>" : ">>");
+ if (eol) s << std::endl;
+ return os << s.str();
+}
+
+//-----------------------------------------------------------------------------
+std::ostream& ei::operator<< (std::ostream& os, const ei_x_buff& buf)
+{
+ return dump(os, (const unsigned char*)buf.buff, buf.index);
+}
+
+//-----------------------------------------------------------------------------
+int ei::stringIndex(const char** cmds, const std::string& cmd, int firstIdx, int size)
+{
+ for (int i=firstIdx; cmds != NULL && i < size; i++, cmds++)
+ if (cmd == *cmds)
+ return i;
+ return firstIdx-1;
+}
+
+//-----------------------------------------------------------------------------
+std::ostream& ei::Serializer::dump (std::ostream& os, bool outWriteBuffer)
+{
+ if (outWriteBuffer) {
+ size_t len = m_wbuf.read_header();
+ if (!len) len = m_wIdx;
+ os << "--Erl-<-C--[" << std::setw(len < 10000 ? 4 : 9) << len << "]: ";
+ return ::dump(os, (const unsigned char*)&m_wbuf, len, false) << "\r\n";
+ } else {
+ size_t len = m_rbuf.read_header();
+ os << "--Erl->-C--[" << std::setw(len < 10000 ? 4 : 9) << len << "]: ";
+ return ::dump(os, (const unsigned char*)&m_rbuf, len, false) << "\r\n";
+ }
+}
+
+//-----------------------------------------------------------------------------
+int ei::Serializer::print (std::ostream& os, const std::string& header)
+{
+ char* s = NULL;
+ int idx = 0;
+ if (ei_s_print_term(&s, &m_rbuf, &idx) < 0)
+ return -1;
+ if (!header.empty())
+ os << header << s << std::endl;
+ else
+ os << s << std::endl;
+
+ if (s)
+ free(s);
+
+ return 0;
+}
+
+//-----------------------------------------------------------------------------
+TimeVal ei::operator- (const TimeVal& t1, const TimeVal& t2) {
+ TimeVal t = t1; t -= t2;
+ return t;
+}
+
+//-----------------------------------------------------------------------------
+TimeVal ei::operator+ (const TimeVal& t1, const TimeVal& t2) {
+ TimeVal t = t1; t += t2;
+ return t;
+}
+
+//-----------------------------------------------------------------------------
+TimeVal::TimeVal(TimeType tp, int _s, int _us)
+{
+ switch (tp) {
+ case NOW:
+ gettimeofday(&m_tv, NULL);
+ break;
+ case RELATIVE:
+ new (this) TimeVal();
+ }
+ if (_s != 0 || _us != 0) add(_s, _us);
+}
+
+//-----------------------------------------------------------------------------
+int Serializer::set_handles(int in, int out, bool non_blocking)
+{
+ m_fin = in;
+ m_fout = out;
+ if (non_blocking) {
+ return fcntl(m_fin, F_SETFL, fcntl(m_fin, F_GETFL) | O_NONBLOCK)
+ || fcntl(m_fout, F_SETFL, fcntl(m_fout, F_GETFL) | O_NONBLOCK);
+ } else
+ return 0;
+}
+
+//-----------------------------------------------------------------------------
+int Serializer::read()
+{
+ if (m_readPacketSz == 0) {
+ int size = m_rbuf.headerSize();
+ if (read_exact(m_fin, &m_rbuf.c_str()[-size], size, m_readOffset) < size)
+ return -1;
+
+ m_readPacketSz = m_rbuf.read_header();
+ m_readOffset = 0;
+
+ if (m_debug)
+ std::cerr << "Serializer::read() - message size: " << m_readPacketSz << '\r' << std::endl;
+
+ if (!m_rbuf.resize(m_readPacketSz))
+ return -2;
+ }
+
+ int total = m_readPacketSz - m_readOffset;
+ if (read_exact(m_fin, &m_rbuf, m_readPacketSz, m_readOffset) < total)
+ return -3;
+
+ m_rIdx = 0;
+
+ if (m_debug)
+ dump(std::cerr, false);
+
+ int len = m_readPacketSz;
+ m_readOffset = m_readPacketSz = 0;
+
+ /* Ensure that we are receiving the binary term by reading and
+ * stripping the version byte */
+ int version;
+ if (ei_decode_version(&m_rbuf, &m_rIdx, &version))
+ return -4;
+
+ return len;
+}
+
+//-----------------------------------------------------------------------------
+int Serializer::write()
+{
+ if (m_writePacketSz == 0) {
+ m_wbuf.write_header(static_cast<size_t>(m_wIdx));
+ if (m_debug)
+ dump(std::cerr, true);
+
+ m_writePacketSz = m_wIdx+m_wbuf.headerSize();
+ m_writeOffset = 0;
+ }
+
+ int total = m_writePacketSz - m_writeOffset;
+ if (write_exact(m_fout, m_wbuf.header(), m_writePacketSz, m_writeOffset) < total)
+ return -1;
+
+ int len = m_writePacketSz;
+ m_writeOffset = m_writePacketSz = 0;
+
+ return len;
+}
+
+//-----------------------------------------------------------------------------
+int Serializer::read_exact(int fd, char *buf, size_t len, size_t& got)
+{
+ int i;
+
+ while (got < len) {
+ int size = len-got;
+ while ((i = ::read(fd, (void*)(buf+got), size)) < size && errno == EINTR)
+ if (i > 0)
+ got += i;
+
+ if (i <= 0)
+ return i;
+ got += i;
+ }
+
+ return len;
+}
+
+//-----------------------------------------------------------------------------
+int Serializer::write_exact(int fd, const char *buf, size_t len, size_t& wrote)
+{
+ int i;
+
+ while (wrote < len) {
+ int size = len-wrote;
+ while ((i = ::write(fd, buf+wrote, size)) < size && errno == EINTR)
+ if (i > 0)
+ wrote += i;
+
+ if (i <= 0)
+ return i;
+ wrote += i;
+ }
+
+ return wrote;
+}
+
+
+#define get8(s) ((s) += 1, ((unsigned char *)(s))[-1] & 0xff)
+#define put8(s,n) do { (s)[0] = (char)((n) & 0xff); (s) += 1; } while (0)
+
+#define put64be(s,n) do { \
+ (s)[0] = ((n) >> 56) & 0xff; \
+ (s)[1] = ((n) >> 48) & 0xff; \
+ (s)[2] = ((n) >> 40) & 0xff; \
+ (s)[3] = ((n) >> 32) & 0xff; \
+ (s)[4] = ((n) >> 24) & 0xff; \
+ (s)[5] = ((n) >> 16) & 0xff; \
+ (s)[6] = ((n) >> 8) & 0xff; \
+ (s)[7] = (n) & 0xff; \
+ (s) += 8; \
+ } while (0)
+
+#define get64be(s) \
+ ((s) += 8, \
+ (((unsigned long long)((unsigned char *)(s))[-8] << 56) | \
+ ((unsigned long long)((unsigned char *)(s))[-7] << 48) | \
+ ((unsigned long long)((unsigned char *)(s))[-6] << 40) | \
+ ((unsigned long long)((unsigned char *)(s))[-5] << 32) | \
+ ((unsigned long long)((unsigned char *)(s))[-4] << 24) | \
+ ((unsigned long long)((unsigned char *)(s))[-3] << 16) | \
+ ((unsigned long long)((unsigned char *)(s))[-2] << 8) | \
+ (unsigned long long)((unsigned char *)(s))[-1]))
+
+int Serializer::ei_decode_double(const char *buf, int *index, double *p)
+{
+ const char *s = buf + *index;
+ const char *s0 = s;
+ double f;
+
+ switch (get8(s)) {
+ case ERL_FLOAT_EXT:
+ if (sscanf(s, "%lf", &f) != 1) return -1;
+ s += 31;
+ break;
+ case NEW_FLOAT_EXT: {
+ // IEEE 754 decoder
+ const unsigned int bits = 64;
+ const unsigned int expbits = 11;
+ const unsigned int significantbits = bits - expbits - 1; // -1 for sign bit
+ unsigned long long i = get64be(s);
+ long long shift;
+ unsigned bias;
+
+ if (!p)
+ break;
+ else if (i == 0)
+ f = 0.0;
+ else {
+ // get the significant
+ f = (i & ((1LL << significantbits)-1)); // mask
+ f /= (1LL << significantbits); // convert back to float
+ f += 1.0f; // add the one back on
+
+ // get the exponent
+ bias = (1 << (expbits-1)) - 1;
+ shift = ((i >> significantbits) & ((1LL << expbits)-1)) - bias;
+ while (shift > 0) { f *= 2.0; shift--; }
+ while (shift < 0) { f /= 2.0; shift++; }
+
+ // signness
+ f *= (i >> (bits-1)) & 1 ? -1.0: 1.0;
+ }
+ break;
+ }
+ default:
+ return -1;
+ }
+
+ if (p) *p = f;
+ *index += s-s0;
+ return 0;
+}
+
+int Serializer::ei_encode_double(char *buf, int *index, double p)
+{
+ char *s = buf + *index;
+ char *s0 = s;
+
+ if (!buf)
+ s = s+9;
+ else { /* use IEEE 754 format */
+ const unsigned int bits = 64;
+ const unsigned int expbits = 11;
+ const unsigned int significantbits = bits - expbits - 1; // -1 for sign bit
+ long long sign, exp, significant;
+ long double norm;
+ int shift;
+
+ put8(s, NEW_FLOAT_EXT);
+ memset(s, 0, 8);
+
+ if (p == 0.0)
+ s += 8;
+ else {
+ // check sign and begin normalization
+ if (p < 0) { sign = 1; norm = -p; }
+ else { sign = 0; norm = p; }
+
+ // get the normalized form of p and track the exponent
+ shift = 0;
+ while(norm >= 2.0) { norm /= 2.0; shift++; }
+ while(norm < 1.0) { norm *= 2.0; shift--; }
+ norm = norm - 1.0;
+
+ // calculate the binary form (non-float) of the significant data
+ significant = (long long) ( norm * ((1LL << significantbits) + 0.5f) );
+
+ // get the biased exponent
+ exp = shift + ((1 << (expbits-1)) - 1); // shift + bias
+
+ // get the final answer
+ exp = (sign << (bits-1)) | (exp << (bits-expbits-1)) | significant;
+ put64be(s, exp);
+ }
+ }
+
+ *index += s-s0;
+ return 0;
+}
+
+int x_fix_buff(ei_x_buff* x, int szneeded)
+{
+ int sz = szneeded + 100;
+ if (sz > x->buffsz) {
+ sz += 100; /* to avoid reallocating each and every time */
+ x->buffsz = sz;
+ x->buff = (char*)realloc(x->buff, sz);
+ }
+ return x->buff != NULL;
+}
+
+int Serializer::ei_x_encode_double(ei_x_buff* x, double dbl)
+{
+ int i = x->index;
+ ei_encode_double(NULL, &i, dbl);
+ if (!x_fix_buff(x, i))
+ return -1;
+ return ei_encode_double(x->buff, &x->index, dbl);
+}
+
diff --git a/deps/exec/c_src/ei++.hpp b/deps/exec/c_src/ei++.hpp
new file mode 100644
index 0000000..888015d
--- /dev/null
+++ b/deps/exec/c_src/ei++.hpp
@@ -0,0 +1,585 @@
+/*
+ ei++.h
+
+ Author: Serge Aleynikov
+ Created: 2003/07/10
+
+ Description:
+ ============
+ C++ wrapper around C ei library distributed with Erlang.
+
+ LICENSE:
+ ========
+ Copyright (C) 2003 Serge Aleynikov <saleyn@gmail.com>
+
+ All rights reserved.
+
+ Redistribution and use in source and binary forms, with or without
+ modification, are permitted provided that the following conditions are met:
+
+ 1. Redistributions of source code must retain the above copyright notice,
+ this list of conditions and the following disclaimer.
+
+ 2. Redistributions in binary form must reproduce the above copyright
+ notice, this list of conditions and the following disclaimer in
+ the documentation and/or other materials provided with the distribution.
+
+ 3. The names of the authors may not be used to endorse or promote products
+ derived from this software without specific prior written permission.
+
+ THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES,
+ INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
+ FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL JCRAFT,
+ INC. OR ANY CONTRIBUTORS TO THIS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT,
+ INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,
+ OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+*/
+
+#ifndef _EMARSHAL_H_
+#define _EMARSHAL_H_
+
+#include <ei.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <unistd.h>
+#include <string.h>
+#include <string>
+#include <algorithm>
+#include <iostream>
+#include <sys/time.h>
+#include <sys/resource.h>
+#include <limits.h>
+#include <assert.h>
+
+#define NEW_FLOAT_EXT 'F'
+
+namespace ei {
+ typedef unsigned char byte;
+
+ /// Looks up a <cmd> in the <cmds> array.
+ /// @param <cmds> an array that either ends with a NULL element or has <size> number of elements.
+ /// @param <cmd> string to find.
+ /// @param <firstIdx> the mapping of the first element in the array. If != 0, then the return
+ /// value will be based on this starting index.
+ /// @param <size> optional size of the <cmds> array.
+ /// @return an offset <cmd> in the cmds array starting with <firstIdx> value. On failure
+ /// returns <firstIdx>-1.
+ int stringIndex(const char** cmds, const std::string& cmd, int firstIdx = 0, int size = INT_MAX);
+
+ /// Class for stack-based (and on-demand heap based) memory allocation
+ /// of string buffers. It's very efficient for strings not exceeding <N>
+ /// bytes as it doesn't allocate heap memory.
+ template < int N, class Allocator = std::allocator<char> >
+ class StringBuffer
+ {
+ char m_buff[N];
+ char* m_buffer;
+ size_t m_size;
+ int m_minAlloc;
+ int m_headerSize;
+ size_t m_maxMsgSize;
+ Allocator m_alloc; // allocator to use
+
+ char* base() { return m_buffer + m_headerSize; }
+ const char* base() const { return m_buffer + m_headerSize; }
+ char* write( int pos, const char* fmt, va_list vargs ) {
+ char s[512];
+ vsnprintf(s, sizeof(s), fmt, vargs);
+ return copy( s, pos );
+ }
+
+ public:
+ enum { DEF_QUANTUM = 512 };
+
+ StringBuffer(int _headerSz = 0, int _quantumSz = DEF_QUANTUM)
+ : m_buffer(m_buff), m_size(N), m_minAlloc(_quantumSz)
+ { m_buff[0] = '\0'; packetHeaderSize(_headerSz); }
+
+ StringBuffer( const char (&s)[N] )
+ : m_buffer(m_buff), m_size(N), m_minAlloc(DEF_QUANTUM), m_headerSize(0), m_maxMsgSize(0)
+ { copy(s); }
+
+ StringBuffer( const std::string& s)
+ : m_buffer(m_buff), m_size(N), m_minAlloc(DEF_QUANTUM), m_headerSize(0), m_maxMsgSize(0)
+ { copy(s.c_str()); }
+
+ ~StringBuffer() { reset(); }
+
+ /// Buffer allocation quantum
+ int quantum() const { return m_minAlloc; }
+ void quantum(int n) { m_minAlloc = n; }
+ /// Defines a prefix space in the buffer used for encoding packet size.
+ int packetHeaderSize() { return m_headerSize; }
+ void packetHeaderSize(size_t sz) {
+ assert(sz == 0 || sz == 1 || sz == 2 || sz == 4);
+ m_headerSize = sz;
+ m_maxMsgSize = (1u << (8*m_headerSize)) - 1;
+ }
+ /// Does the buffer have memory allocated on heap?
+ bool allocated() const { return m_buffer != m_buff; }
+ size_t capacity() const { return m_size - m_headerSize; }
+ size_t length() const { return strlen(base()); }
+ void clear() { m_buffer[m_headerSize] = '\0'; }
+ /// Free heap allocated memory and shrink buffer to original statically allocated size.
+ void reset() { if (allocated()) m_alloc.deallocate(m_buffer, m_size); m_buffer = m_buff; clear(); }
+ /// Pointer to a mutable char string of size <capacity()>.
+ const char* c_str() const { return base(); }
+ char* c_str() { return base(); }
+ char* append( const char* s ) { return copy( s, length() ); }
+ char* append( const std::string& s ) { return copy( s.c_str(), length() ); }
+ char* append( const char* fmt, ... ) {
+ va_list vargs;
+ va_start (vargs, fmt);
+ char* ret = write(length(), fmt, vargs);
+ va_end (vargs);
+ return ret;
+ }
+
+ char* operator[] ( int i ) { assert( i < (m_size-m_headerSize) ); return base()[i]; }
+ char* operator() () { return base(); }
+ char* operator& () { return base(); }
+ const char* operator& () const { return base(); }
+ bool operator== ( const char* rhs ) const { return strncmp(base(), rhs, m_size) == 0; }
+ bool operator== ( const std::string& rhs ) const { return operator== ( rhs.c_str() ); }
+ bool operator!= ( const std::string& rhs ) const { return !operator== ( rhs.c_str() ); }
+ bool operator!= ( const char* rhs ) const { return !operator== ( rhs ); }
+
+ size_t headerSize() const { return m_headerSize; }
+ const char* header() { return m_buffer; }
+
+ size_t read_header() {
+ size_t sz = (byte)m_buffer[m_headerSize-1];
+ for(int i=m_headerSize-2; i >= 0; i--)
+ sz |= (byte)m_buffer[i] << (8*(m_headerSize-i-1));
+ return sz;
+ }
+
+ int write_header(size_t sz) {
+ if (sz > m_maxMsgSize)
+ return -1;
+ byte b[4] = { (byte)((sz >> 24) & 0xff), (byte)((sz >> 16) & 0xff),
+ (byte)((sz >> 8) & 0xff), (byte)(sz & 0xff) };
+ memcpy(m_buffer, b + 4 - m_headerSize, m_headerSize);
+ return 0;
+ }
+
+ char* write( const char* fmt, ... ) {
+ va_list vargs;
+ va_start (vargs, fmt);
+ char* ret = write(0, fmt, vargs);
+ va_end (vargs);
+ return ret;
+ }
+
+ char* copy( const char* s, size_t pos=0 )
+ {
+ if ( resize( strlen(s) + pos + 1, pos != 0 ) == NULL )
+ return NULL;
+ assert( pos < m_size );
+ strcpy( base() + pos, s );
+ return base();
+ }
+ char* copy( const std::string& s, size_t pos=0 )
+ {
+ if ( resize( length(s) + pos + 1, pos != 0 ) == NULL )
+ return NULL;
+ assert( pos < m_size );
+ strcpy( base() + pos, s.c_str() );
+ return base();
+ }
+
+ char* copy( const char* s, size_t pos, size_t len)
+ {
+ assert( pos > 0 && len > 0 && (pos+len) < m_size );
+ if ( resize( len + pos + 1, pos != 0 ) == NULL )
+ return NULL;
+ memcpy( base() + pos, s, len );
+ return base();
+ }
+
+ char* resize( size_t size, bool reallocate = false )
+ {
+ char* old = m_buffer;
+ const size_t old_sz = m_size;
+ const size_t new_sz = size + m_headerSize;
+
+ if ( new_sz <= m_size ) {
+ return m_buffer;
+ } else
+ m_size = std::max((const size_t)m_size + m_headerSize + m_minAlloc, new_sz);
+
+ if ( (m_buffer = m_alloc.allocate(m_size)) == NULL ) {
+ m_buffer = old;
+ m_size = old_sz;
+ return (char*) NULL;
+ }
+ //fprintf(stderr, "Allocated: x1 = %p, x2=%p (m_size=%d)\r\n", m_buffer, m_buff, m_size);
+ if ( reallocate && old != m_buffer )
+ memcpy(m_buffer, old, old_sz);
+ if ( old != m_buff ) {
+ m_alloc.deallocate(old, old_sz);
+ }
+ return base();
+ }
+
+ };
+
+ template<int N> std::ostream& operator<< ( std::ostream& os, StringBuffer<N>& buf ) {
+ return os << buf.c_str();
+ }
+
+ template<int N> StringBuffer<N>& operator<< ( StringBuffer<N>& buf, const std::string& s ) {
+ size_t n = buf.length();
+ buf.resize( n + s.size() + 1 );
+ strcpy( buf.c_str() + n, s.c_str() );
+ return buf;
+ }
+
+ /// A helper class for dealing with 'struct timeval' structure. This class adds ability
+ /// to perform arithmetic with the structure leaving the same footprint.
+ class TimeVal
+ {
+ struct timeval m_tv;
+
+ void normalize() {
+ if (m_tv.tv_usec >= 1000000)
+ do { ++m_tv.tv_sec; m_tv.tv_usec -= 1000000; } while (m_tv.tv_usec >= 1000000);
+ else if (m_tv.tv_usec <= -1000000)
+ do { --m_tv.tv_sec; m_tv.tv_usec += 1000000; } while (m_tv.tv_usec <= -1000000);
+
+ if (m_tv.tv_sec >= 1 && m_tv.tv_usec < 0) { --m_tv.tv_sec; m_tv.tv_usec += 1000000; }
+ else if (m_tv.tv_sec < 0 && m_tv.tv_usec > 0) { ++m_tv.tv_sec; m_tv.tv_usec -= 1000000; }
+ }
+
+ public:
+ enum TimeType { NOW, RELATIVE };
+
+ TimeVal() { m_tv.tv_sec=0; m_tv.tv_usec=0; }
+ TimeVal(int _s, int _us) { m_tv.tv_sec=_s; m_tv.tv_usec=_us; normalize(); }
+ TimeVal(const TimeVal& tv, int _s=0, int _us=0) { set(tv, _s, _us); }
+ TimeVal(const struct timeval& tv) { m_tv.tv_sec=tv.tv_sec; m_tv.tv_usec=tv.tv_usec; normalize(); }
+ TimeVal(TimeType tp, int _s=0, int _us=0);
+
+ struct timeval& timeval() { return m_tv; }
+ const struct timeval& timeval() const { return m_tv; }
+ int32_t sec() const { return m_tv.tv_sec; }
+ int32_t usec() const { return m_tv.tv_usec; }
+ int64_t microsec() const { return (int64_t)m_tv.tv_sec*1000000ull + (int64_t)m_tv.tv_usec; }
+ void sec (int32_t _sec) { m_tv.tv_sec = _sec; }
+ void usec(int32_t _usec) { m_tv.tv_usec = _usec; normalize(); }
+ void microsec(int32_t _m) { m_tv.tv_sec = _m / 1000000ull; m_tv.tv_usec = _m % 1000000ull; }
+
+ void set(const TimeVal& tv, int _s=0, int _us=0) {
+ m_tv.tv_sec = tv.sec() + _s; m_tv.tv_usec = tv.usec() + _us; normalize();
+ }
+
+ double diff(const TimeVal& t) const {
+ TimeVal tv(this->timeval());
+ tv -= t;
+ return (double)tv.sec() + (double)tv.usec() / 1000000.0;
+ }
+
+ void clear() { m_tv.tv_sec = 0; m_tv.tv_usec = 0; }
+ bool zero() { return sec() == 0 && usec() == 0; }
+ void add(int _sec, int _us) { m_tv.tv_sec += _sec; m_tv.tv_usec += _us; if (_sec || _us) normalize(); }
+ TimeVal& now(int addS=0, int addUS=0) { gettimeofday(&m_tv, NULL); add(addS, addUS); return *this; }
+
+ void operator-= (const TimeVal& tv) {
+ m_tv.tv_sec -= tv.sec(); m_tv.tv_usec -= tv.usec(); normalize();
+ }
+ void operator+= (const TimeVal& tv) {
+ m_tv.tv_sec += tv.sec(); m_tv.tv_usec += tv.usec(); normalize();
+ }
+ void operator+= (int32_t _sec) { m_tv.tv_sec += _sec; }
+ void operator+= (int64_t _microsec) {
+ m_tv.tv_sec += (_microsec / 1000000ll);
+ m_tv.tv_usec += (_microsec % 1000000ll);
+ normalize();
+ }
+ TimeVal& operator= (const TimeVal& t) { m_tv.tv_sec = t.sec(); m_tv.tv_usec = t.usec(); return *this; }
+ struct timeval* operator& () { return &m_tv; }
+ bool operator== (const TimeVal& tv) const { return sec() == tv.sec() && usec() == tv.usec(); }
+ bool operator!= (const TimeVal& tv) const { return !operator== (tv); }
+ bool operator< (const TimeVal& tv) const {
+ return sec() < tv.sec() || (sec() == tv.sec() && usec() < tv.usec());
+ }
+ bool operator<= (const TimeVal& tv) const {
+ return sec() <= tv.sec() && usec() <= tv.usec();
+ }
+ };
+
+ TimeVal operator- (const TimeVal& t1, const TimeVal& t2);
+ TimeVal operator+ (const TimeVal& t1, const TimeVal& t2);
+
+ struct atom_t: public std::string {
+ typedef std::string BaseT;
+ atom_t() : BaseT() {}
+ atom_t(const char* s) : BaseT(s) {}
+ atom_t(const atom_t& a) : BaseT(reinterpret_cast<const BaseT&>(a)) {}
+ atom_t(const std::string& s): BaseT(s) {}
+ };
+
+ enum ErlTypeT {
+ etSmallInt = ERL_SMALL_INTEGER_EXT // 'a'
+ , etInt = ERL_INTEGER_EXT // 'b'
+ , etFloatOld = ERL_FLOAT_EXT // 'c'
+ , etFloat = NEW_FLOAT_EXT // 'F'
+ , etAtom = ERL_ATOM_EXT // 'd'
+ , etRefOld = ERL_REFERENCE_EXT // 'e'
+ , etRef = ERL_NEW_REFERENCE_EXT // 'r'
+ , etPort = ERL_PORT_EXT // 'f'
+ , etPid = ERL_PID_EXT // 'g'
+ , etTuple = ERL_SMALL_TUPLE_EXT // 'h'
+ , etTupleLarge = ERL_LARGE_TUPLE_EXT // 'i'
+ , etNil = ERL_NIL_EXT // 'j'
+ , etString = ERL_STRING_EXT // 'k'
+ , etList = ERL_LIST_EXT // 'l'
+ , etBinary = ERL_BINARY_EXT // 'm'
+ , etBignum = ERL_SMALL_BIG_EXT // 'n'
+ , etBignumLarge = ERL_LARGE_BIG_EXT // 'o'
+ , etFun = ERL_NEW_FUN_EXT // 'p'
+ , etFunOld = ERL_FUN_EXT // 'u'
+ , etNewCache = ERL_NEW_CACHE // 'N' /* c nodes don't know these two */
+ , etAtomCached = ERL_CACHED_ATOM // 'C'
+ };
+
+ /// Erlang term serializer/deserializer C++ wrapper around C ei library included in
+ /// Erlang distribution.
+ class Serializer
+ {
+ StringBuffer<1024> m_wbuf; // for writing output commands
+ StringBuffer<1024> m_rbuf; // for reading input commands
+ size_t m_readOffset, m_writeOffset;
+ size_t m_readPacketSz, m_writePacketSz;
+ int m_wIdx, m_rIdx;
+ int m_fin, m_fout;
+ bool m_debug;
+
+ void wcheck(int n) {
+ if (m_wbuf.resize(m_wIdx + n + 16, true) == NULL)
+ throw "out of memory";
+ }
+ static int ei_decode_double(const char *buf, int *m_wIdx, double *p);
+ static int ei_encode_double(char *buf, int *m_wIdx, double p);
+ static int ei_x_encode_double(ei_x_buff* x, double d);
+ static int read_exact (int fd, char *buf, size_t len, size_t& offset);
+ static int write_exact(int fd, const char *buf, size_t len, size_t& offset);
+ public:
+
+ Serializer(int _headerSz = 2)
+ : m_wbuf(_headerSz), m_rbuf(_headerSz)
+ , m_readOffset(0), m_writeOffset(0)
+ , m_readPacketSz(0), m_writePacketSz(0)
+ , m_wIdx(0), m_rIdx(0)
+ , m_fin(0), m_fout(1), m_debug(false)
+ , tuple(*this)
+ {
+ ei_encode_version(&m_wbuf, &m_wIdx);
+ }
+
+ void reset_rbuf(bool _saveVersion=true) {
+ m_rIdx = _saveVersion ? 1 : 0;
+ m_readPacketSz = m_readOffset = 0;
+ }
+ void reset_wbuf(bool _saveVersion=true) {
+ m_wIdx = _saveVersion ? 1 : 0;
+ m_writePacketSz = m_writeOffset = 0;
+ }
+ void reset(bool _saveVersion=true) {
+ reset_rbuf(_saveVersion);
+ reset_wbuf(_saveVersion);
+ }
+ void debug(bool _enable) { m_debug = _enable; }
+
+ // This is a helper class for encoding tuples using streaming operator.
+ // Example: encode {ok, 123, "test"}
+ //
+ // Serializer ser;
+ // ser.tuple << atom_t("ok") << 123 << "test";
+ //
+ class Tuple {
+ Serializer& m_parent;
+
+ class Temp {
+ Tuple& m_tuple;
+ mutable int m_idx; // offset to the tuple's size in m_parent.m_wbuf
+ mutable int m_size;
+ mutable bool m_last;
+ public:
+ template<typename T>
+ Temp(Tuple& t, const T& v)
+ : m_tuple(t), m_idx(m_tuple.m_parent.m_wIdx+1), m_size(1), m_last(true)
+ {
+ m_tuple.m_parent.encodeTupleSize(1);
+ m_tuple.m_parent.encode(v);
+ }
+
+ Temp(const Temp& o)
+ : m_tuple(o.m_tuple), m_idx(o.m_idx), m_size(o.m_size+1), m_last(o.m_last)
+ {
+ o.m_last = false;
+ }
+
+ ~Temp() {
+ if (m_last) {
+ // This is the end of the tuple being streamed to this class. Update tuple size.
+ if (m_size > 255)
+ throw "Use of operator<< only allowed for tuples with less than 256 items!";
+ else if (m_size > 1) {
+ char* sz = &m_tuple.m_parent.m_wbuf + m_idx;
+ *sz = m_size;
+ }
+ }
+ }
+ template<typename T>
+ Temp operator<< (const T& v) {
+ Temp t(*this);
+ m_tuple.m_parent.encode(v);
+ return t;
+ }
+ };
+
+ public:
+ Tuple(Serializer& s) : m_parent(s) {}
+
+ template<typename T>
+ Temp operator<< (const T& v) {
+ Temp t(*this, v);
+ return t;
+ }
+ };
+
+ /// Helper class for encoding/decoding tuples using streaming operator.
+ Tuple tuple;
+
+ void encode(const char* s) { wcheck(strlen(s)+1); ei_encode_string(&m_wbuf, &m_wIdx, s); }
+ void encode(char v) { wcheck(2); ei_encode_char(&m_wbuf, &m_wIdx, v); }
+ void encode(int i) { wcheck(sizeof(i)); ei_encode_long(&m_wbuf, &m_wIdx, i); }
+ void encode(unsigned int i) { wcheck(8); ei_encode_ulong(&m_wbuf, &m_wIdx, i); }
+ void encode(long l) { wcheck(sizeof(l)); ei_encode_long(&m_wbuf, &m_wIdx, l); }
+ void encode(unsigned long l) { wcheck(sizeof(l)); ei_encode_ulong(&m_wbuf, &m_wIdx, l); }
+ void encode(long long i) { int n=0; ei_encode_longlong (NULL,&n,i); wcheck(n); ei_encode_longlong(&m_wbuf,&m_wIdx,i); }
+ void encode(unsigned long long i) { int n=0; ei_encode_ulonglong(NULL,&n,i); wcheck(n); ei_encode_ulonglong(&m_wbuf,&m_wIdx,i); }
+ void encode(bool b) { wcheck(8); ei_encode_boolean(&m_wbuf, &m_wIdx, b); }
+ void encode(double v) { wcheck(9); ei_encode_double(&m_wbuf, &m_wIdx, v); }
+ void encode(const std::string& s) { wcheck(s.size()+1); ei_encode_string(&m_wbuf, &m_wIdx, s.c_str()); }
+ void encode(const atom_t& a) { wcheck(a.size()+1); ei_encode_atom(&m_wbuf, &m_wIdx, a.c_str()); }
+ void encode(const erlang_pid& p) { int n=0; ei_encode_pid(NULL, &n, &p); wcheck(n); ei_encode_pid(&m_wbuf, &m_wIdx, &p); }
+ void encode(const void* p, int sz) { wcheck(sz+4); ei_encode_binary(&m_wbuf, &m_wIdx, p, sz); }
+ void encodeTupleSize(int sz) { wcheck(5); ei_encode_tuple_header(&m_wbuf, &m_wIdx, sz); }
+ void encodeListSize(int sz) { wcheck(5); ei_encode_list_header(&m_wbuf, &m_wIdx, sz); }
+ void encodeListEnd() { wcheck(1); ei_encode_empty_list(&m_wbuf, &m_wIdx); }
+
+ int encodeListBegin() { wcheck(5); int n=m_wIdx; ei_encode_list_header(&m_wbuf, &m_wIdx, 1); return n; }
+ /// This function for encoding the list size after all elements are encoded.
+ /// @param sz is the number of elements in the list.
+ /// @param idx is the index position of the beginning of the list.
+ // E.g.
+ // Serializer se;
+ // int n = 0;
+ // int idx = se.encodeListBegin();
+ // se.encode(1); n++;
+ // se.encode("abc"); n++;
+ // se.encodeListEnd(n, idx);
+ void encodeListEnd(int sz,int idx) { ei_encode_list_header(&m_wbuf, &idx, sz); encodeListEnd(); }
+
+ ErlTypeT decodeType(int& size) { int t; return (ErlTypeT)(ei_get_type(&m_rbuf, &m_rIdx, &t, &size) < 0 ? -1 : t); }
+ int decodeInt(int& v) { long l, ret = decodeInt(l); v = l; return ret; }
+ int decodeInt(long& v) { return (ei_decode_long(&m_rbuf, &m_rIdx, &v) < 0) ? -1 : 0; }
+ int decodeUInt(unsigned int& v) { unsigned long l, ret = decodeUInt(l); v = l; return ret; }
+ int decodeUInt(unsigned long& v) { return (ei_decode_ulong(&m_rbuf, &m_rIdx, &v) < 0) ? -1 : 0; }
+ int decodeTupleSize() { int v; return (ei_decode_tuple_header(&m_rbuf,&m_rIdx,&v) < 0) ? -1 : v; }
+ int decodeListSize() { int v; return (ei_decode_list_header(&m_rbuf,&m_rIdx,&v) < 0) ? -1 : v; }
+ int decodeListEnd() { bool b = *(m_rbuf.c_str()+m_rIdx) == ERL_NIL_EXT; if (b) { m_rIdx++; return 0; } else return -1; }
+ int decodeAtom(std::string& a) { char s[MAXATOMLEN]; if (ei_decode_atom(&m_rbuf,&m_rIdx,s) < 0) return -1; a=s; return 0; }
+ int decodeBool(bool& a) {
+ std::string s;
+ if (decodeAtom(s) < 0) return -1;
+ else if (s == "true") { a = true; return 0; }
+ else if (s == "false") { a = false; return 0; }
+ else return -1;
+ }
+
+ int decodeString(std::string& a) {
+ StringBuffer<256> s;
+ if (decodeString(s) < 0)
+ return -1;
+ a = s.c_str();
+ return 0;
+ }
+ template <int N>
+ int decodeString(StringBuffer<N>& s) {
+ int size;
+ if (decodeType(size) != etString || !s.resize(size) || ei_decode_string(&m_rbuf, &m_rIdx, s.c_str()))
+ return -1;
+ return size;
+ }
+
+ int decodeBinary(std::string& data) {
+ int size;
+ if (decodeType(size) != etBinary) return -1;
+ data.resize(size);
+ long sz;
+ if (ei_decode_binary(&m_rbuf, &m_rIdx, (void*)data.c_str(), &sz) < 0) return -1;
+ return sz;
+ }
+
+ /// Print input buffer to stream
+ int print(std::ostream& os, const std::string& header = "");
+
+ /// Assumes the command is encoded as an atom. This function takes an
+ /// array of strings and matches the atom to it. The index of the matched
+ /// string in the <cmds> array is returned.
+ template <int M>
+ int decodeAtomIndex(const char* (&cmds)[M], std::string& cmd, int firstIdx = 0) {
+ if (decodeAtom(cmd) < 0) return -1;
+ return stringIndex(cmds, cmd, firstIdx, M);
+ }
+
+ /// Same as previous version but <cmds> array must have the last element being NULL
+ int decodeAtomIndex(const char** cmds, std::string& cmd, int firstIdx = 0) {
+ if (decodeAtom(cmd) < 0) return -1;
+ return stringIndex(cmds, cmd, firstIdx);
+ }
+
+ int set_handles(int in, int out, bool non_blocking = false);
+ void close_handles() { ::close(m_fin); ::close(m_fout); }
+
+ int read_handle() { return m_fin; }
+ int write_handle() { return m_fout; }
+
+ const char* read_buffer() const { return &m_rbuf; }
+ const char* write_buffer() const { return &m_wbuf; }
+ int* read_index() { return &m_rIdx; }
+ int* write_index() { return &m_wIdx; }
+ int read_idx() const { return m_rIdx; }
+ int write_idx() const { return m_wIdx; }
+
+ /// Read command from <m_fin> into the internal buffer
+ int read();
+ /// Write command from <m_fout> into the internal buffer
+ int write();
+
+ /// Copy the content of write buffer from another serializer
+ int wcopy( const Serializer& ser) { return m_wbuf.copy( ser.write_buffer(), 0, ser.write_idx()) != 0 ? 0 : -1; }
+ /// Copy the content of read buffer from another serializer
+ int rcopy( const Serializer& ser) { return m_rbuf.copy( ser.read_buffer(), 0, ser.read_idx() ) != 0 ? 0 : -1; }
+
+ /// dump read/write buffer's content to stream
+ std::ostream& dump(std::ostream& os, bool outWriteBuffer);
+ };
+
+ /// Dump content of internal buffer to stream.
+ std::ostream& dump(std::ostream& out, const unsigned char* a_buf = NULL, int n = 0, bool eol = true);
+ // Write ei_x_buff to stream
+ std::ostream& operator<< (std::ostream& os, const ei_x_buff& buf);
+ bool dump(const char* header, std::ostream& out, const ei_x_buff& buf, bool condition);
+
+} // namespace
+
+#endif
+
diff --git a/deps/exec/c_src/exec.cpp b/deps/exec/c_src/exec.cpp
new file mode 100644
index 0000000..c1281c1
--- /dev/null
+++ b/deps/exec/c_src/exec.cpp
@@ -0,0 +1,2111 @@
+/*
+ exec.cpp
+
+ Author: Serge Aleynikov
+ Created: 2003/07/10
+
+ Description:
+ ============
+
+ Erlang port program for spawning and controlling OS tasks.
+ It listens for commands sent from Erlang and executes them until
+ the pipe connecting it to Erlang VM is closed or the program
+ receives SIGINT or SIGTERM. At that point it kills all processes
+ it forked by issuing SIGTERM followed by SIGKILL in 6 seconds.
+
+ Marshalling protocol:
+ Erlang C++
+ | ---- {TransId::integer(), Instruction::tuple()} ---> |
+ | <----------- {TransId::integer(), Reply} ----------- |
+
+ Instruction = {manage, OsPid::integer(), Options} |
+ {run, Cmd::string(), Options} |
+ {list} |
+ {stop, OsPid::integer()} |
+ {kill, OsPid::integer(), Signal::integer()} |
+ {stdin, OsPid::integer(), Data::binary()}
+
+ Options = [Option]
+ Option = {cd, Dir::string()} |
+ {env, [string() | {string(), string()}]} |
+ {kill, Cmd::string()} |
+ {kill_timeout, Sec::integer()} |
+ kill_group |
+ {group, integer() | string()} |
+ {user, User::string()} |
+ {nice, Priority::integer()} |
+ stdin | {stdin, null | close | File::string()} |
+ stdout | {stdout, Device::string()} |
+ stderr | {stderr, Device::string()} |
+ pty | {success_exit_code, N::integer()}
+
+ Device = close | null | stderr | stdout | File::string() | {append, File::string()}
+
+ Reply = ok | // For kill/stop commands
+ {ok, OsPid} | // For run command
+ {ok, [OsPid]} | // For list command
+ {error, Reason} |
+ {exit_status, OsPid, Status} // OsPid terminated with Status
+
+ Reason = atom() | string()
+ OsPid = integer()
+ Status = integer()
+*/
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <errno.h>
+#include <signal.h>
+#include <unistd.h>
+#include <signal.h>
+#include <termios.h>
+#include <sys/ioctl.h>
+
+#ifdef HAVE_CAP
+#include <sys/prctl.h>
+#include <sys/capability.h>
+#endif
+
+#include <assert.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <sys/time.h>
+#include <sys/resource.h>
+#include <setjmp.h>
+#include <limits.h>
+#include <grp.h>
+#include <pwd.h>
+#include <fcntl.h>
+#include <map>
+#include <list>
+#include <deque>
+#include <set>
+#include <sstream>
+
+#include <ei.h>
+#include "ei++.hpp"
+
+#if defined(__CYGWIN__) || defined(__WIN32) || defined(__APPLE__) \
+ || (defined(__sun) && defined(__SVR4))
+# define NO_SIGTIMEDWAIT
+# define sigtimedwait(a, b, c) 0
+# define sigisemptyset(s) \
+ !(sigismember(s, SIGCHLD) || sigismember(s, SIGPIPE) || \
+ sigismember(s, SIGTERM) || sigismember(s, SIGINT) || \
+ sigismember(s, SIGHUP))
+#endif
+
+using namespace ei;
+
+//-------------------------------------------------------------------------
+// Defines
+//-------------------------------------------------------------------------
+
+#define BUF_SIZE 2048
+
+// In the event we have tried to kill something, wait this many
+// seconds and then *really* kill it with SIGKILL if needs be
+#define KILL_TIMEOUT_SEC 5
+
+// Max number of seconds to sleep in the select() call
+#define SLEEP_TIMEOUT_SEC 5
+
+// Number of seconds allowed for cleanup before exit
+#define FINALIZE_DEADLINE_SEC 10
+
+//-------------------------------------------------------------------------
+// Global variables
+//-------------------------------------------------------------------------
+
+extern char **environ; // process environment
+
+ei::Serializer eis(/* packet header size */ 2);
+
+sigjmp_buf jbuf;
+static int alarm_max_time = FINALIZE_DEADLINE_SEC + 2;
+static int debug = 0;
+static bool oktojump = false;
+static int terminated = 0; // indicates that we got a SIGINT / SIGTERM event
+static bool superuser = false;
+static bool pipe_valid = true;
+static int max_fds;
+static int dev_null;
+
+static const int DEF_MODE = S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH;
+static const char* CS_DEV_NULL = "/dev/null";
+
+//-------------------------------------------------------------------------
+// Types & variables
+//-------------------------------------------------------------------------
+
+struct CmdInfo;
+
+typedef unsigned char byte;
+typedef int exit_status_t;
+typedef pid_t kill_cmd_pid_t;
+typedef std::list<std::string> CmdArgsList;
+typedef std::pair<pid_t, exit_status_t> PidStatusT;
+typedef std::pair<pid_t, CmdInfo> PidInfoT;
+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 MapEnv::iterator MapEnvIterator;
+typedef std::map<pid_t, exit_status_t> ExitedChildrenT;
+
+MapChildrenT children; // Map containing all managed processes started by this port program.
+MapKillPidT transient_pids; // Map of pids of custom kill commands.
+ExitedChildrenT exited_children;// Set of processed SIGCHLD events
+
+#define SIGCHLD_MAX_SIZE 4096
+
+enum RedirectType {
+ REDIRECT_STDOUT = -1, // Redirect to stdout
+ REDIRECT_STDERR = -2, // Redirect to stderr
+ REDIRECT_NONE = -3, // No output redirection
+ REDIRECT_CLOSE = -4, // Close output file descriptor
+ REDIRECT_ERL = -5, // Redirect output back to Erlang
+ REDIRECT_FILE = -6, // Redirect output to file
+ REDIRECT_NULL = -7 // Redirect input/output to /dev/null
+};
+
+std::string fd_type(int tp) {
+ switch (tp) {
+ case REDIRECT_STDOUT: return "stdout";
+ case REDIRECT_STDERR: return "stderr";
+ case REDIRECT_NONE: return "none";
+ case REDIRECT_CLOSE: return "close";
+ case REDIRECT_ERL: return "erlang";
+ case REDIRECT_FILE: return "file";
+ case REDIRECT_NULL: return "null";
+ default: {
+ std::stringstream s;
+ if (tp == dev_null)
+ s << "null(fd:" << tp << ')';
+ else
+ s << "fd:" << tp;
+ return s.str();
+ }
+ }
+ return std::string(); // Keep the compiler happy
+}
+
+struct CmdOptions;
+
+//-------------------------------------------------------------------------
+// Local Functions
+//-------------------------------------------------------------------------
+
+int send_ok(int transId, pid_t pid = -1);
+int send_pid_status_term(const PidStatusT& stat);
+int send_error_str(int transId, bool asAtom, const char* fmt, ...);
+int send_pid_list(int transId, const MapChildrenT& children);
+int send_ospid_output(int pid, const char* type, const char* data, int len);
+
+pid_t start_child(CmdOptions& op, std::string& err);
+int kill_child(pid_t pid, int sig, int transId, bool notify=true);
+int check_children(const TimeVal& now, int& isTerminated, bool notify = true);
+bool process_pid_input(CmdInfo& ci);
+void process_pid_output(CmdInfo& ci, int maxsize = 4096);
+void stop_child(pid_t pid, int transId, const TimeVal& now);
+int stop_child(CmdInfo& ci, int transId, const TimeVal& now, bool notify = true);
+void erase_child(MapChildrenT::iterator& it);
+
+int process_command();
+void initialize(int userid, bool use_alt_fds);
+int finalize();
+int set_nonblock_flag(pid_t pid, int fd, bool value);
+int erl_exec_kill(pid_t pid, int signal);
+int open_file(const char* file, bool append, const char* stream,
+ ei::StringBuffer<128>& err, int mode = DEF_MODE);
+int open_pipe(int fds[2], const char* stream, ei::StringBuffer<128>& err);
+
+//-------------------------------------------------------------------------
+// Types
+//-------------------------------------------------------------------------
+
+struct CmdOptions {
+private:
+ ei::StringBuffer<256> m_tmp;
+ std::stringstream m_err;
+ bool m_shell;
+ bool m_pty;
+ std::string m_executable;
+ CmdArgsList m_cmd;
+ std::string m_cd;
+ std::string m_kill_cmd;
+ int m_kill_timeout;
+ bool m_kill_group;
+ MapEnv m_env;
+ const char** m_cenv;
+ long m_nice; // niceness level
+ int m_group; // used in setgid()
+ int m_user; // run as
+ int m_success_exit_code;
+ std::string m_std_stream[3];
+ bool m_std_stream_append[3];
+ int m_std_stream_fd[3];
+ int m_std_stream_mode[3];
+
+ void init_streams() {
+ for (int i=STDIN_FILENO; i <= STDERR_FILENO; i++) {
+ m_std_stream_append[i] = false;
+ m_std_stream_mode[i] = DEF_MODE;
+ m_std_stream_fd[i] = REDIRECT_NULL;
+ m_std_stream[i] = CS_DEV_NULL;
+ }
+ }
+
+public:
+ CmdOptions()
+ : m_tmp(0, 256), m_shell(true), m_pty(false)
+ , m_kill_timeout(KILL_TIMEOUT_SEC)
+ , m_kill_group(false)
+ , m_cenv(NULL), m_nice(INT_MAX)
+ , m_group(INT_MAX), m_user(INT_MAX)
+ , m_success_exit_code(0)
+ {
+ init_streams();
+ }
+ CmdOptions(const CmdArgsList& cmd, const char* cd = NULL, const char** env = NULL,
+ int user = INT_MAX, int nice = INT_MAX, int group = INT_MAX)
+ : m_shell(true), m_pty(false), m_cmd(cmd), m_cd(cd ? cd : "")
+ , m_kill_timeout(KILL_TIMEOUT_SEC)
+ , m_kill_group(false)
+ , m_cenv(NULL), m_nice(INT_MAX)
+ , m_group(group), m_user(user)
+ {
+ init_streams();
+ }
+ ~CmdOptions() {
+ if (m_cenv != (const char**)environ) delete [] m_cenv;
+ m_cenv = NULL;
+ }
+
+ const char* strerror() const { return m_err.str().c_str(); }
+ const std::string& executable() const { return m_executable; }
+ const CmdArgsList& cmd() const { return m_cmd; }
+ bool shell() const { return m_shell; }
+ bool pty() const { return m_pty; }
+ const char* cd() const { return m_cd.c_str(); }
+ char* const* env() const { return (char* const*)m_cenv; }
+ const char* kill_cmd() const { return m_kill_cmd.c_str(); }
+ int kill_timeout() const { return m_kill_timeout; }
+ bool kill_group() const { return m_kill_group; }
+ int group() const { return m_group; }
+ int user() const { return m_user; }
+ int success_exit_code() const { return m_success_exit_code; }
+ int nice() const { return m_nice; }
+ const char* stream_file(int i) const { return m_std_stream[i].c_str(); }
+ bool stream_append(int i) const { return m_std_stream_append[i]; }
+ int stream_mode(int i) const { return m_std_stream_mode[i]; }
+ int stream_fd(int i) const { return m_std_stream_fd[i]; }
+ int& stream_fd(int i) { return m_std_stream_fd[i]; }
+ std::string stream_fd_type(int i) const { return fd_type(stream_fd(i)); }
+
+ void executable(const std::string& s) { m_executable = s; }
+
+ void stream_file(int i, const std::string& file, bool append = false, int mode = DEF_MODE) {
+ m_std_stream_fd[i] = REDIRECT_FILE;
+ m_std_stream_append[i] = append;
+ m_std_stream_mode[i] = mode;
+ m_std_stream[i] = file;
+ }
+
+ void stream_null(int i) {
+ m_std_stream_fd[i] = REDIRECT_NULL;
+ m_std_stream_append[i] = false;
+ m_std_stream[i] = CS_DEV_NULL;
+ }
+
+ void stream_redirect(int i, RedirectType type) {
+ m_std_stream_fd[i] = type;
+ m_std_stream_append[i] = false;
+ m_std_stream[i].clear();
+ }
+
+ int ei_decode(ei::Serializer& ei, bool getCmd = false);
+ int init_cenv();
+};
+
+/// Contains run-time info of a child OS process.
+/// When a user provides a custom command to kill a process this
+/// structure will contain its run-time information.
+struct CmdInfo {
+ CmdArgsList cmd; // Executed command
+ pid_t cmd_pid; // Pid of the custom kill command
+ pid_t cmd_gid; // Command's group ID
+ std::string kill_cmd; // Kill command to use (default: use SIGTERM)
+ kill_cmd_pid_t kill_cmd_pid; // Pid of the command that <pid> is supposed to kill
+ ei::TimeVal deadline; // Time when the <cmd_pid> is supposed to be killed using SIGTERM.
+ bool sigterm; // <true> if sigterm was issued.
+ bool sigkill; // <true> if sigkill was issued.
+ int kill_timeout; // Pid shutdown interval in sec before it's killed with SIGKILL
+ bool kill_group; // Indicates if at exit the whole group needs to be killed
+ int success_code; // Exit code to use on success
+ bool managed; // <true> if this pid is started externally, but managed by erlexec
+ int stream_fd[3]; // Pipe fd getting process's stdin/stdout/stderr
+ int stdin_wr_pos; // Offset of the unwritten portion of the head item of stdin_queue
+ std::list<std::string> stdin_queue;
+
+ CmdInfo() {
+ new (this) CmdInfo(CmdArgsList(), "", 0, INT_MAX, 0);
+ }
+ CmdInfo(const CmdInfo& ci) {
+ new (this) CmdInfo(ci.cmd, ci.kill_cmd.c_str(), ci.cmd_pid, ci.cmd_gid,
+ ci.success_code, ci.managed,
+ ci.stream_fd[STDIN_FILENO], ci.stream_fd[STDOUT_FILENO],
+ ci.stream_fd[STDERR_FILENO], ci.kill_timeout, ci.kill_group);
+ }
+ CmdInfo(bool managed, const char* _kill_cmd, pid_t _cmd_pid, int _ok_code,
+ bool _kill_group = false) {
+ new (this) CmdInfo(cmd, _kill_cmd, _cmd_pid, getpgid(_cmd_pid), _ok_code, managed);
+ kill_group = _kill_group;
+ }
+ CmdInfo(const CmdArgsList& _cmd, const char* _kill_cmd, pid_t _cmd_pid, pid_t _cmd_gid,
+ int _success_code,
+ bool _managed = false,
+ int _stdin_fd = REDIRECT_NULL,
+ int _stdout_fd = REDIRECT_NONE,
+ int _stderr_fd = REDIRECT_NONE,
+ int _kill_timeout = KILL_TIMEOUT_SEC,
+ bool _kill_group = false)
+ : cmd(_cmd)
+ , cmd_pid(_cmd_pid)
+ , cmd_gid(_cmd_gid)
+ , kill_cmd(_kill_cmd), kill_cmd_pid(-1)
+ , sigterm(false), sigkill(false)
+ , kill_timeout(_kill_timeout)
+ , kill_group(_kill_group)
+ , success_code(_success_code)
+ , managed(_managed), stdin_wr_pos(0)
+ {
+ stream_fd[STDIN_FILENO] = _stdin_fd;
+ stream_fd[STDOUT_FILENO] = _stdout_fd;
+ stream_fd[STDERR_FILENO] = _stderr_fd;
+ }
+
+ void include_stream_fd(int i, int& maxfd, fd_set* readfds, fd_set* writefds) {
+ bool ok;
+ fd_set* fds;
+
+ if (i == STDIN_FILENO) {
+ ok = stream_fd[i] >= 0 && stdin_wr_pos > 0;
+ if (ok && debug > 2)
+ fprintf(stderr, "Pid %d adding stdin available notification (fd=%d, pos=%d)\r\n",
+ cmd_pid, stream_fd[i], stdin_wr_pos);
+ fds = writefds;
+ } else {
+ ok = stream_fd[i] >= 0;
+ if (ok && debug > 2)
+ fprintf(stderr, "Pid %d adding stdout checking (fd=%d)\r\n", cmd_pid, stream_fd[i]);
+ fds = readfds;
+ }
+
+ if (ok) {
+ FD_SET(stream_fd[i], fds);
+ if (stream_fd[i] > maxfd) maxfd = stream_fd[i];
+ }
+ }
+
+ void process_stream_data(int i, fd_set* readfds, fd_set* writefds) {
+ int fd = stream_fd[i];
+ fd_set* fds = i == STDIN_FILENO ? writefds : readfds;
+
+ if (fd < 0 || !FD_ISSET(fd, fds)) return;
+
+ if (i == STDIN_FILENO)
+ process_pid_input(*this);
+ else
+ process_pid_output(*this);
+ }
+};
+
+//-------------------------------------------------------------------------
+// Local Functions
+//-------------------------------------------------------------------------
+
+const char* stream_name(int i) {
+ switch (i) {
+ case STDIN_FILENO: return "stdin";
+ case STDOUT_FILENO: return "stdout";
+ case STDERR_FILENO: return "stderr";
+ default: return "<unknown>";
+ }
+}
+
+void gotsignal(int signal)
+{
+ if (signal == SIGTERM || signal == SIGINT || signal == SIGPIPE)
+ terminated = 1;
+ if (signal == SIGPIPE)
+ pipe_valid = false;
+ if (debug)
+ fprintf(stderr, "Got signal: %d (oktojump=%d)\r\n", signal, oktojump);
+ if (oktojump) siglongjmp(jbuf, 1);
+}
+
+void check_child(pid_t pid, int signal = -1)
+{
+ int status = 0;
+ pid_t ret;
+
+ if (pid == getpid()) // Safety check. Never kill itself
+ return;
+
+ if (exited_children.find(pid) != exited_children.end())
+ return;
+
+ while ((ret = waitpid(pid, &status, WNOHANG)) < 0 && errno == EINTR);
+
+ if (debug)
+ fprintf(stderr,
+ "* Process %d (ret=%d, status=%d, sig=%d, "
+ "oktojump=%d, exited_count=%ld%s%s)\r\n",
+ pid, ret, status, signal, oktojump, exited_children.size(),
+ ret > 0 && WIFEXITED(status) ? " [exited]":"",
+ ret > 0 && WIFSIGNALED(status) ? " [signaled]":"");
+
+ if (ret < 0 && errno == ECHILD) {
+ if (erl_exec_kill(pid, 0) == 0) // process likely forked and is alive
+ status = 0;
+ if (status != 0)
+ exited_children.insert(std::make_pair(pid <= 0 ? ret : pid, status));
+ } else if (pid <= 0 && ret > 0) {
+ exited_children.insert(std::make_pair(ret, status == 0 && signal == -1 ? 1 : status));
+ } else if (ret == pid || WIFEXITED(status) || WIFSIGNALED(status)) {
+ if (ret > 0)
+ exited_children.insert(std::make_pair(pid, status));
+ }
+
+ if (oktojump) siglongjmp(jbuf, 1);
+}
+
+void gotsigchild(int signal, siginfo_t* si, void* context)
+{
+ // If someone used kill() to send SIGCHLD ignore the event
+ if (si->si_code == SI_USER || signal != SIGCHLD)
+ return;
+
+ pid_t pid = si->si_pid;
+
+ if (debug)
+ fprintf(stderr, "Child process %d exited\r\n", pid);
+
+ check_child(pid, signal);
+}
+
+void add_exited_child(pid_t pid, exit_status_t status) {
+ std::pair<pid_t, exit_status_t> value = std::make_pair(pid, status);
+ // Note the following function doesn't insert anything if the element
+ // with given key was already present in the map
+ exited_children.insert(value);
+}
+
+void check_pending()
+{
+ #if !defined(NO_SIGTIMEDWAIT)
+ static const struct timespec timeout = {0, 0};
+ #endif
+
+ sigset_t set;
+ siginfo_t info;
+ int sig;
+ sigemptyset(&set);
+ if (sigpending(&set) == 0 && !sigisemptyset(&set)) {
+ if (debug > 1)
+ fprintf(stderr, "Detected pending signals\r\n");
+
+ while ((sig = sigtimedwait(&set, &info, &timeout)) > 0 || errno == EINTR)
+ switch (sig) {
+ case SIGCHLD: gotsigchild(sig, &info, NULL); break;
+ case SIGPIPE: pipe_valid = false; /* intentionally follow through */
+ case SIGTERM:
+ case SIGINT:
+ case SIGHUP: gotsignal(sig); break;
+ default: break;
+ }
+ }
+}
+
+int set_nice(pid_t pid,int nice, std::string& error)
+{
+ ei::StringBuffer<128> err;
+
+ if (nice != INT_MAX && setpriority(PRIO_PROCESS, pid, nice) < 0) {
+ err.write("Cannot set priority of pid %d to %d", pid, nice);
+ error = err.c_str();
+ if (debug)
+ fprintf(stderr, "%s\r\n", error.c_str());
+ return -1;
+ }
+ return 0;
+}
+
+void usage(char* progname) {
+ fprintf(stderr,
+ "Usage:\n"
+ " %s [-n] [-alarm N] [-debug [Level]] [-user User]\n"
+ "Options:\n"
+ " -n - Use marshaling file descriptors 3&4 instead of default 0&1.\n"
+ " -alarm N - Allow up to <N> seconds to live after receiving SIGTERM/SIGINT (default %d)\n"
+ " -debug [Level] - Turn on debug mode (default Level: 1)\n"
+ " -user User - If started by root, run as User\n"
+ "Description:\n"
+ " This is a port program intended to be started by an Erlang\n"
+ " virtual machine. It can start/kill/list OS processes\n"
+ " as requested by the virtual machine.\n",
+ progname, alarm_max_time);
+ exit(1);
+}
+
+//-------------------------------------------------------------------------
+// MAIN
+//-------------------------------------------------------------------------
+
+int main(int argc, char* argv[])
+{
+ fd_set readfds, writefds;
+ struct sigaction sact, sterm;
+ int userid = 0;
+ bool use_alt_fds = false;
+
+ sterm.sa_handler = gotsignal;
+ sigemptyset(&sterm.sa_mask);
+ sigaddset(&sterm.sa_mask, SIGCHLD);
+ sterm.sa_flags = 0;
+ sigaction(SIGINT, &sterm, NULL);
+ sigaction(SIGTERM, &sterm, NULL);
+ sigaction(SIGHUP, &sterm, NULL);
+ sigaction(SIGPIPE, &sterm, NULL);
+
+ sact.sa_handler = NULL;
+ sact.sa_sigaction = gotsigchild;
+ sigemptyset(&sact.sa_mask);
+ sact.sa_flags = SA_SIGINFO | SA_RESTART | SA_NOCLDSTOP; // | SA_NODEFER;
+ sigaction(SIGCHLD, &sact, NULL);
+
+ if (argc > 1) {
+ int res;
+ for(res = 1; res < argc; res++) {
+ if (strcmp(argv[res], "-h") == 0 || strcmp(argv[res], "--help") == 0) {
+ usage(argv[0]);
+ } else if (strcmp(argv[res], "-debug") == 0) {
+ debug = (res+1 < argc && argv[res+1][0] != '-') ? atoi(argv[++res]) : 1;
+ if (debug > 3)
+ eis.debug(true);
+ } else if (strcmp(argv[res], "-alarm") == 0 && res+1 < argc) {
+ if (argv[res+1][0] != '-')
+ alarm_max_time = atoi(argv[++res]);
+ else
+ usage(argv[0]);
+ } else if (strcmp(argv[res], "-n") == 0) {
+ use_alt_fds = true;
+ } else if (strcmp(argv[res], "-user") == 0 && res+1 < argc && argv[res+1][0] != '-') {
+ char* run_as_user = argv[++res];
+ struct passwd *pw = NULL;
+ if ((pw = getpwnam(run_as_user)) == NULL) {
+ fprintf(stderr, "User %s not found!\r\n", run_as_user);
+ exit(3);
+ }
+ userid = pw->pw_uid;
+ }
+ }
+ }
+
+ initialize(userid, use_alt_fds);
+
+ while (!terminated) {
+
+ sigsetjmp(jbuf, 1); oktojump = 0;
+
+ FD_ZERO (&writefds);
+ FD_ZERO (&readfds);
+
+ FD_SET (eis.read_handle(), &readfds);
+
+ int maxfd = eis.read_handle();
+
+ TimeVal now(TimeVal::NOW);
+
+ while (!terminated && !exited_children.empty()) {
+ if (check_children(now, terminated) < 0)
+ break;
+ }
+
+ double wakeup = SLEEP_TIMEOUT_SEC;
+
+ // Set up all stdout/stderr input streams that we need to monitor and redirect to Erlang
+ for(MapChildrenT::iterator it=children.begin(), end=children.end(); it != end; ++it)
+ for (int i=STDIN_FILENO; i <= STDERR_FILENO; i++) {
+ it->second.include_stream_fd(i, maxfd, &readfds, &writefds);
+ if (!it->second.deadline.zero())
+ wakeup = std::max(0.0, std::min(wakeup, it->second.deadline.diff(now)));
+ }
+
+ check_pending(); // Check for pending signals arrived while we were in the signal handler
+
+ if (terminated || wakeup < 0) break;
+
+ oktojump = 1;
+ ei::TimeVal timeout((int)wakeup, (wakeup - (int)wakeup) * 1000000);
+
+ if (debug > 2)
+ fprintf(stderr, "Selecting maxfd=%d (sleep={%ds,%dus})\r\n",
+ maxfd, timeout.sec(), timeout.usec());
+
+ int cnt = select (maxfd+1, &readfds, &writefds, (fd_set *) 0, &timeout.timeval());
+ int interrupted = (cnt < 0 && errno == EINTR);
+ oktojump = 0;
+
+ if (debug > 2)
+ fprintf(stderr, "Select got %d events (maxfd=%d)\r\n", cnt, maxfd);
+
+ if (interrupted || cnt == 0) {
+ now.now();
+ if (check_children(now, terminated) < 0) {
+ terminated = 11;
+ break;
+ }
+ } else if (cnt < 0) {
+ if (errno == EBADF) {
+ if (debug)
+ fprintf(stderr, "Error EBADF(9) in select: %s (terminated=%d)\r\n",
+ strerror(errno), terminated);
+ continue;
+ }
+ fprintf(stderr, "Error %d in select: %s\r\n", errno, strerror(errno));
+ terminated = 12;
+ break;
+ } else if ( FD_ISSET (eis.read_handle(), &readfds) ) {
+ /* Read from input stream a command sent by Erlang */
+ if (process_command() < 0) {
+ break;
+ }
+ } else {
+ // Check if any stdout/stderr streams have data
+ for(MapChildrenT::iterator it=children.begin(), end=children.end(); it != end; ++it)
+ for (int i=STDIN_FILENO; i <= STDERR_FILENO; i++)
+ it->second.process_stream_data(i, &readfds, &writefds);
+ }
+ }
+
+ sigsetjmp(jbuf, 1); oktojump = 0;
+
+ return finalize();
+
+}
+
+int process_command()
+{
+ int err, arity;
+ long transId;
+ std::string command;
+
+ // Note that if we were using non-blocking reads, we'd also need to check
+ // for errno EWOULDBLOCK.
+ if ((err = eis.read()) < 0) {
+ if (debug)
+ fprintf(stderr, "Broken Erlang command pipe (%d): %s\r\n",
+ errno, strerror(errno));
+ terminated = errno;
+ return -1;
+ }
+
+ /* Our marshalling spec is that we are expecting a tuple
+ * TransId, {Cmd::atom(), Arg1, Arg2, ...}} */
+ if (eis.decodeTupleSize() != 2 ||
+ (eis.decodeInt(transId)) < 0 ||
+ (arity = eis.decodeTupleSize()) < 1)
+ {
+ terminated = 12;
+ return -1;
+ }
+
+ enum CmdTypeT { MANAGE, RUN, STOP, KILL, LIST, SHUTDOWN, STDIN } cmd;
+ const char* cmds[] = { "manage","run","stop","kill","list","shutdown","stdin" };
+
+ /* Determine the command */
+ if ((int)(cmd = (CmdTypeT) eis.decodeAtomIndex(cmds, command)) < 0) {
+ if (send_error_str(transId, false, "Unknown command: %s", command.c_str()) < 0) {
+ terminated = 13;
+ return -1;
+ }
+ return 0;
+ }
+
+ switch (cmd) {
+ case SHUTDOWN: {
+ terminated = 0;
+ return -1;
+ }
+ case MANAGE: {
+ // {manage, Cmd::string(), Options::list()}
+ CmdOptions po;
+ long pid;
+ pid_t realpid;
+
+ if (arity != 3 || (eis.decodeInt(pid)) < 0 || po.ei_decode(eis) < 0) {
+ send_error_str(transId, true, "badarg");
+ return 0;
+ }
+ realpid = pid;
+
+ CmdInfo ci(true, po.kill_cmd(), realpid, po.success_exit_code(), po.kill_group());
+ ci.kill_timeout = po.kill_timeout();
+ children[realpid] = ci;
+
+ // Set nice priority for managed process if option is present
+ std::string error;
+ set_nice(realpid,po.nice(),error);
+
+ send_ok(transId, pid);
+ break;
+ }
+ case RUN: {
+ // {run, Cmd::string(), Options::list()}
+ CmdOptions po;
+
+ if (arity != 3 || po.ei_decode(eis, true) < 0) {
+ send_error_str(transId, false, po.strerror());
+ break;
+ }
+
+ pid_t pid;
+ std::string err;
+ if ((pid = start_child(po, err)) < 0)
+ send_error_str(transId, false, "Couldn't start pid: %s", err.c_str());
+ else {
+ CmdInfo ci(po.cmd(), po.kill_cmd(), pid,
+ getpgid(pid),
+ po.success_exit_code(), false,
+ po.stream_fd(STDIN_FILENO),
+ po.stream_fd(STDOUT_FILENO),
+ po.stream_fd(STDERR_FILENO),
+ po.kill_timeout(),
+ po.kill_group());
+ children[pid] = ci;
+ send_ok(transId, pid);
+ }
+ break;
+ }
+ case STOP: {
+ // {stop, OsPid::integer()}
+ long pid;
+ if (arity != 2 || eis.decodeInt(pid) < 0) {
+ send_error_str(transId, true, "badarg");
+ break;
+ }
+ stop_child(pid, transId, TimeVal(TimeVal::NOW));
+ break;
+ }
+ case KILL: {
+ // {kill, OsPid::integer(), Signal::integer()}
+ long pid, sig;
+ if (arity != 3 || eis.decodeInt(pid) < 0 || eis.decodeInt(sig) < 0 || pid == -1) {
+ send_error_str(transId, true, "badarg");
+ break;
+ } else if (pid < 0) {
+ send_error_str(transId, false, "Not allowed to send signal to all processes");
+ break;
+ } else if (superuser && children.find(pid) == children.end()) {
+ send_error_str(transId, false, "Cannot kill a pid not managed by this application");
+ break;
+ }
+ kill_child(pid, sig, transId);
+ break;
+ }
+ case LIST: {
+ // {list}
+ if (arity != 1) {
+ send_error_str(transId, true, "badarg");
+ break;
+ }
+ send_pid_list(transId, children);
+ break;
+ }
+ case STDIN: {
+ // {stdin, OsPid::integer(), Data::binary()}
+ long pid;
+ std::string data;
+ if (arity != 3 || eis.decodeInt(pid) < 0 || eis.decodeBinary(data) < 0) {
+ send_error_str(transId, true, "badarg");
+ break;
+ }
+
+ MapChildrenT::iterator it = children.find(pid);
+ if (it == children.end()) {
+ if (debug)
+ fprintf(stderr, "Stdin (%ld bytes) cannot be sent to non-existing pid %ld\r\n",
+ data.size(), pid);
+ break;
+ }
+ it->second.stdin_queue.push_front(data);
+ process_pid_input(it->second);
+ break;
+ }
+ }
+ return 0;
+}
+
+void initialize(int userid, bool use_alt_fds)
+{
+ // If we are root, switch to non-root user and set capabilities
+ // to be able to adjust niceness and run commands as other users.
+ if (getuid() == 0) {
+ superuser = true;
+ if (userid == 0) {
+ fprintf(stderr, "When running as root, \"-user User\" option must be provided!\r\n");
+ exit(4);
+ }
+
+ #ifdef HAVE_CAP
+ if (prctl(PR_SET_KEEPCAPS, 1) < 0) {
+ perror("Failed to call prctl to keep capabilities");
+ exit(5);
+ }
+ #endif
+
+ if (
+ #ifdef HAVE_SETRESUID
+ setresuid(-1, userid, geteuid()) // glibc, FreeBSD, OpenBSD, HP-UX
+ #elif HAVE_SETREUID
+ setreuid(-1, userid) // MacOSX, NetBSD, AIX, IRIX, Solaris>=2.5, OSF/1, Cygwin
+ #else
+ #error setresuid(3) not supported!
+ #endif
+ < 0) {
+ perror("Failed to set userid");
+ exit(6);
+ }
+
+ struct passwd* pw;
+ if (debug && (pw = getpwuid(geteuid())) != NULL)
+ fprintf(stderr, "exec: running as: %s (euid=%d)\r\n", pw->pw_name, geteuid());
+
+ if (geteuid() == 0) {
+ fprintf(stderr, "exec: failed to set effective userid to a non-root user %s (uid=%d)\r\n",
+ pw ? pw->pw_name : "", geteuid());
+ exit(7);
+ }
+
+ #ifdef HAVE_CAP
+ cap_t cur;
+ if ((cur = cap_from_text("cap_setuid=eip cap_kill=eip cap_sys_nice=eip")) == 0) {
+ fprintf(stderr, "exec: failed to convert cap_setuid & cap_sys_nice from text");
+ exit(8);
+ }
+ if (cap_set_proc(cur) < 0) {
+ fprintf(stderr, "exec: failed to set cap_setuid & cap_sys_nice");
+ exit(9);
+ }
+ cap_free(cur);
+
+ if (debug && (cur = cap_get_proc()) != NULL) {
+ fprintf(stderr, "exec: current capabilities: %s\r\n", cap_to_text(cur, NULL));
+ cap_free(cur);
+ }
+ #else
+ if (debug)
+ fprintf(stderr, "exec: capability feature is not implemented for this plaform!\r\n");
+ #endif
+
+ if (!getenv("SHELL") || strncmp(getenv("SHELL"), "", 1) == 0) {
+ fprintf(stderr, "exec: SHELL variable is not set!\r\n");
+ exit(10);
+ }
+
+ }
+
+ #if !defined(NO_SYSCONF)
+ max_fds = sysconf(_SC_OPEN_MAX);
+ #else
+ max_fds = OPEN_MAX;
+ #endif
+ if (max_fds < 1024) max_fds = 1024;
+
+ dev_null = open(CS_DEV_NULL, O_RDWR);
+
+ if (dev_null < 0) {
+ fprintf(stderr, "exec: cannot open %s: %s\r\n", CS_DEV_NULL, strerror(errno));
+ exit(10);
+ }
+
+ if (use_alt_fds) {
+ // TODO: when closing stdin/stdout we need to ensure that redirected
+ // streams in the forked children have FDs different from 0,1,2 or else
+ // wrong file handles get closed. Therefore for now just leave
+ // stdin/stdout open even when not needed
+
+ //eis.close_handles(); // Close stdin, stdout
+ eis.set_handles(3, 4);
+ }
+}
+
+int finalize()
+{
+ if (debug) fprintf(stderr, "Setting alarm to %d seconds\r\n", alarm_max_time);
+ alarm(alarm_max_time); // Die in <alarm_max_time> seconds if not done
+
+ int old_terminated = terminated;
+ terminated = 0;
+
+ kill(0, SIGTERM); // Kill all children in our process group
+
+ TimeVal now(TimeVal::NOW);
+ TimeVal deadline(now, FINALIZE_DEADLINE_SEC, 0);
+
+ while (children.size() > 0) {
+ sigsetjmp(jbuf, 1);
+
+ now.now();
+ if (children.size() > 0 || !exited_children.empty()) {
+ int term = 0;
+ check_children(now, term, pipe_valid);
+ }
+
+ for(MapChildrenT::iterator it=children.begin(), end=children.end(); it != end; ++it)
+ stop_child(it->second, 0, now, false);
+
+ for(MapKillPidT::iterator it=transient_pids.begin(), end=transient_pids.end(); it != end; ++it) {
+ erl_exec_kill(it->first, SIGKILL);
+ transient_pids.erase(it);
+ }
+
+ if (children.size() == 0)
+ break;
+
+ TimeVal timeout(TimeVal::NOW);
+ if (timeout < deadline) {
+ timeout = deadline - timeout;
+
+ oktojump = 1;
+ while (select(0, (fd_set *)0, (fd_set *)0, (fd_set *)0, &timeout) < 0 && errno == EINTR);
+ oktojump = 0;
+ }
+ }
+
+ if (debug)
+ fprintf(stderr, "Exiting (%d)\r\n", old_terminated);
+
+ return old_terminated;
+}
+
+static int getpty(int& fdmp, int& fdsp, ei::StringBuffer<128>& err) {
+ int fdm, fds;
+ int rc;
+
+ fdm = posix_openpt(O_RDWR);
+ if (fdm < 0) {
+ err.write("error %d on posix_openpt: %s\n", errno, strerror(errno));
+ return -1;
+ }
+
+ rc = grantpt(fdm);
+ if (rc != 0) {
+ close(fdm);
+ err.write("error %d on grantpt: %s\n", errno, strerror(errno));
+ return -1;
+ }
+
+ rc = unlockpt(fdm);
+ if (rc != 0) {
+ close(fdm);
+ err.write("error %d on unlockpt: %s\n", errno, strerror(errno));
+ return -1;
+ }
+
+ fds = open(ptsname(fdm), O_RDWR);
+
+ if (fds < 0) {
+ close(fdm);
+ err.write("error %d on open pty slave: %s\n", errno, strerror(errno));
+ return -1;
+ }
+
+ fdmp = fdm;
+ fdsp = fds;
+
+ if (debug)
+ fprintf(stderr, " Opened PTY pair (master=%d, slave=%d)\r\n",
+ fdm, fds);
+
+ return 0;
+}
+
+pid_t start_child(CmdOptions& op, std::string& error)
+{
+ enum { RD = 0, WR = 1 };
+
+ int stream_fd[][2] = {
+ // ChildReadFD ChildWriteFD
+ { REDIRECT_NULL, REDIRECT_NONE },
+ { REDIRECT_NONE, REDIRECT_NULL },
+ { REDIRECT_NONE, REDIRECT_NULL }
+ };
+
+ ei::StringBuffer<128> err;
+
+ // Optionally setup pseudoterminal
+ int fdm, fds;
+
+ if (op.pty()) {
+ if (getpty(fdm, fds, err) < 0) {
+ error = err.c_str();
+ return -1;
+ }
+ }
+
+ // Optionally setup stdin/stdout/stderr redirect
+ for (int i=STDIN_FILENO; i <= STDERR_FILENO; i++) {
+ int crw = i==0 ? RD : WR;
+ int cfd = op.stream_fd(i);
+ int* sfd = stream_fd[i];
+
+ // Optionally setup stdout redirect
+ switch (cfd) {
+ case REDIRECT_CLOSE:
+ sfd[RD] = cfd;
+ sfd[WR] = cfd;
+ if (debug)
+ fprintf(stderr, " Closing %s\r\n", stream_name(i));
+ break;
+ case REDIRECT_STDOUT:
+ case REDIRECT_STDERR:
+ sfd[crw] = cfd;
+ if (debug)
+ fprintf(stderr, " Redirecting [%s -> %s]\r\n", stream_name(i),
+ fd_type(cfd).c_str());
+ break;
+ case REDIRECT_ERL:
+ if (op.pty()) {
+ if (i == STDIN_FILENO) {
+ sfd[RD] = fds;
+ sfd[WR] = fdm;
+ } else {
+ sfd[WR] = fds;
+ sfd[RD] = fdm;
+ }
+ if (debug)
+ fprintf(stderr, " Redirecting [%s -> pipe:{r=%d,w=%d}] (PTY)\r\n",
+ stream_name(i), sfd[0], sfd[1]);
+ } else if (open_pipe(sfd, stream_name(i), err) < 0) {
+ error = err.c_str();
+ return -1;
+ }
+ break;
+ case REDIRECT_NULL:
+ sfd[crw] = dev_null;
+ if (debug)
+ fprintf(stderr, " Redirecting [%s -> null]\r\n",
+ stream_name(i));
+ break;
+ case REDIRECT_FILE: {
+ sfd[crw] = open_file(op.stream_file(i), op.stream_append(i),
+ stream_name(i), err, op.stream_mode(i));
+ if (sfd[crw] < 0) {
+ error = err.c_str();
+ return -1;
+ }
+ break;
+ }
+ }
+ }
+
+ if (debug) {
+ fprintf(stderr, "Starting child: '%s'\r\n"
+ " child = (stdin=%s, stdout=%s, stderr=%s)\r\n"
+ " parent = (stdin=%s, stdout=%s, stderr=%s)\r\n",
+ op.cmd().front().c_str(),
+ fd_type(stream_fd[STDIN_FILENO ][RD]).c_str(),
+ fd_type(stream_fd[STDOUT_FILENO][WR]).c_str(),
+ fd_type(stream_fd[STDERR_FILENO][WR]).c_str(),
+ fd_type(stream_fd[STDIN_FILENO ][WR]).c_str(),
+ fd_type(stream_fd[STDOUT_FILENO][RD]).c_str(),
+ fd_type(stream_fd[STDERR_FILENO][RD]).c_str()
+ );
+ if (!op.executable().empty())
+ fprintf(stderr, " Executable: %s\r\n", op.executable().c_str());
+ if (op.cmd().size() > 0) {
+ int i = 0;
+ if (op.shell()) {
+ const char* s = getenv("SHELL");
+ fprintf(stderr, " Args[%d]: %s\r\n", i++, s ? s : "(null)");
+ fprintf(stderr, " Args[%d]: -c\r\n", i++);
+ }
+ typedef CmdArgsList::const_iterator const_iter;
+ for(const_iter it = op.cmd().begin(), end = op.cmd().end(); it != end; ++it)
+ fprintf(stderr, " Args[%d]: %s\r\n", i++, it->c_str());
+ }
+ }
+
+ pid_t pid = fork();
+
+ if (pid < 0) {
+ error = strerror(errno);
+ return pid;
+ } else if (pid == 0) {
+ // I am the child
+
+ // Setup stdin/stdout/stderr redirect
+ for (int fd=STDIN_FILENO; fd <= STDERR_FILENO; fd++) {
+ int (&sfd)[2] = stream_fd[fd];
+ int crw = fd==STDIN_FILENO ? RD : WR;
+ int prw = fd==STDIN_FILENO ? WR : RD;
+
+ if (sfd[prw] >= 0)
+ close(sfd[prw]); // Close parent end of child pipes
+
+ if (sfd[crw] == REDIRECT_CLOSE)
+ close(fd);
+ else if (sfd[crw] >= 0) { // Child end of the parent pipe
+ dup2(sfd[crw], fd);
+ // Don't close sfd[crw] here, since if the same fd is used for redirecting
+ // stdout and stdin (e.g. /dev/null) if won't work correctly. Instead the loop
+ // following this one will close all extra fds.
+
+ //setlinebuf(stdout); // Set line buffering
+ }
+ }
+
+ // See if we need to redirect STDOUT <-> STDERR
+ if (stream_fd[STDOUT_FILENO][WR] == REDIRECT_STDERR)
+ dup2(STDERR_FILENO, STDOUT_FILENO);
+ if (stream_fd[STDERR_FILENO][WR] == REDIRECT_STDOUT)
+ dup2(STDOUT_FILENO, STDERR_FILENO);
+
+ for(int i=STDERR_FILENO+1; i < max_fds; i++)
+ close(i);
+
+ if (op.pty()) {
+ struct termios ios;
+ tcgetattr(STDIN_FILENO, &ios);
+ // Disable the ECHO mode
+ ios.c_lflag &= ~(ECHO | ECHONL | ECHOE | ECHOK);
+ // We don't check if it succeeded because if the STDIN is not a terminal
+ // it won't be able to disable the ECHO anyway.
+ tcsetattr(STDIN_FILENO, TCSANOW, &ios);
+
+ // Make the current process a new session leader
+ setsid();
+
+ // as a session leader, set the controlling terminal to be the
+ // slave side
+ ioctl(STDIN_FILENO, TIOCSCTTY, 1);
+ }
+
+ #if !defined(__CYGWIN__) && !defined(__WIN32)
+ 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());
+ exit(EXIT_FAILURE);
+ }
+ #endif
+
+ if (op.group() != INT_MAX && setpgid(0, op.group()) < 0) {
+ err.write("Cannot set effective group to %d", op.group());
+ perror(err.c_str());
+ exit(EXIT_FAILURE);
+ }
+
+ // Build the command arguments list
+ size_t sz = op.cmd().size() + 1 + (op.shell() ? 2 : 0);
+ const char** argv = new const char*[sz];
+ const char** p = argv;
+
+ if (op.shell()) {
+ *p++ = getenv("SHELL");
+ *p++ = "-c";
+ }
+
+ for (CmdArgsList::const_iterator
+ it = op.cmd().begin(), end = op.cmd().end(); it != end; ++it)
+ *p++ = it->c_str();
+
+ *p++ = (char*)NULL;
+
+ if (op.cd() != NULL && op.cd()[0] != '\0' && chdir(op.cd()) < 0) {
+ err.write("Cannot chdir to '%s'", op.cd());
+ perror(err.c_str());
+ exit(EXIT_FAILURE);
+ }
+
+ // Setup process environment
+ if (op.init_cenv() < 0) {
+ perror(err.c_str());
+ exit(EXIT_FAILURE);
+ }
+
+ const char* executable = op.executable().empty()
+ ? (const char*)argv[0] : op.executable().c_str();
+
+ // Execute the process
+ if (execve(executable, (char* const*)argv, op.env()) < 0) {
+ err.write("Pid %d: cannot execute '%s'", getpid(), executable);
+ perror(err.c_str());
+ exit(EXIT_FAILURE);
+ }
+ // On success execve never returns
+ exit(EXIT_FAILURE);
+ }
+
+ // I am the parent
+
+ if (debug > 1)
+ fprintf(stderr, "Spawned child pid %d\r\n", pid);
+
+ // Either the parent or the child could use setpgid() to change
+ // the process group ID of the child. However, because the scheduling
+ // of the parent and child is indeterminate after a fork(), we can’t
+ // rely on the parent changing the child’s process group ID before the
+ // child does an exec(); nor can we rely on the child changing its
+ // process group ID before the parent tries to send any job-control
+ // signals to it (dependence on either one of these behaviors would
+ // result in a race condition). Therefore, here the parent and the
+ // child process both call setpgid() to change the child’s process
+ // group ID to the same value immediately after a fork(), and the
+ // parent ignores any occurrence of the EACCES error on the setpgid() call.
+
+ if (op.group() != INT_MAX) {
+ pid_t gid = op.group() ? op.group() : pid;
+ if (setpgid(pid, gid) == -1 && errno != EACCES && debug)
+ fprintf(stderr, " Parent failed to set group of pid %d to %d: %s\r\n",
+ pid, gid, strerror(errno));
+ else if (debug)
+ fprintf(stderr, " Set group of pid %d to %d\r\n", pid, gid);
+ }
+
+ for (int i=STDIN_FILENO; i <= STDERR_FILENO; i++) {
+ int wr = i==STDIN_FILENO ? WR : RD;
+ int& cfd = op.stream_fd(i);
+ int* sfd = stream_fd[i];
+
+ int fd = sfd[i==0 ? RD : WR];
+ if (fd >= 0 && fd != dev_null) {
+ if (debug)
+ fprintf(stderr, " Parent closing pid %d pipe %s end (fd=%d)\r\n",
+ pid, i==STDIN_FILENO ? "reading" : "writing", fd);
+ close(fd); // Close stdin/reading or stdout(err)/writing end of the child pipe
+ }
+
+ if (sfd[wr] >= 0 && sfd[wr] != dev_null) {
+ cfd = sfd[wr];
+ // Make sure the writing end is non-blocking
+ set_nonblock_flag(pid, cfd, true);
+
+ if (debug)
+ fprintf(stderr, " Setup %s end of pid %d %s redirection (fd=%d%s)\r\n",
+ i==STDIN_FILENO ? "writing" : "reading", pid, stream_name(i), cfd,
+ (fcntl(cfd, F_GETFL, 0) & O_NONBLOCK) == O_NONBLOCK ? " [non-block]" : "");
+ }
+ }
+
+ set_nice(pid,op.nice(),error);
+
+ return pid;
+}
+
+int stop_child(CmdInfo& ci, int transId, const TimeVal& now, bool notify)
+{
+ bool use_kill = false;
+
+ if (ci.sigkill) // Kill signal already sent
+ return 0;
+ else if (ci.kill_cmd_pid > 0 || ci.sigterm) {
+ // There was already an attempt to kill it.
+ if (ci.sigterm && now.diff(ci.deadline) > 0) {
+ // More than KILL_TIMEOUT_SEC secs elapsed since the last kill attempt
+ erl_exec_kill(ci.kill_group ? -ci.cmd_gid : ci.cmd_pid, SIGKILL);
+ if (ci.kill_cmd_pid > 0)
+ erl_exec_kill(ci.kill_cmd_pid, SIGKILL);
+
+ ci.sigkill = true;
+ }
+ if (notify) send_ok(transId);
+ return 0;
+ } else if (!ci.kill_cmd.empty()) {
+ // This is the first attempt to kill this pid and kill command is provided.
+ CmdArgsList kill_cmd;
+ kill_cmd.push_front(ci.kill_cmd.c_str());
+ CmdOptions co(kill_cmd);
+ std::string err;
+ ci.kill_cmd_pid = start_child(co, err);
+ if (!err.empty() && debug)
+ fprintf(stderr, "Error executing kill command '%s': %s\r\r",
+ ci.kill_cmd.c_str(), err.c_str());
+
+ if (ci.kill_cmd_pid > 0) {
+ transient_pids[ci.kill_cmd_pid] = ci.cmd_pid;
+ ci.deadline.set(now, ci.kill_timeout);
+ if (notify) send_ok(transId);
+ return 0;
+ } else {
+ if (notify) send_error_str(transId, false, "bad kill command - using SIGTERM");
+ use_kill = true;
+ notify = false;
+ }
+ } else {
+ // This is the first attempt to kill this pid and no kill command is provided.
+ use_kill = true;
+ }
+
+ if (use_kill) {
+ // Use SIGTERM / SIGKILL to nuke the pid
+ pid_t pid = ci.kill_group ? -ci.cmd_gid : ci.cmd_pid;
+ const char* spid = ci.kill_group ? "gid" : "pid";
+ int n;
+ if (!ci.sigterm && (n = kill_child(pid, SIGTERM, transId, notify)) == 0) {
+ if (debug)
+ fprintf(stderr, "Sent SIGTERM to %s %d (timeout=%ds)\r\n",
+ spid, abs(pid), ci.kill_timeout);
+ ci.deadline.set(now, ci.kill_timeout);
+ } else if (!ci.sigkill && (n = kill_child(pid, SIGKILL, 0, false)) == 0) {
+ if (debug)
+ fprintf(stderr, "Sent SIGKILL to %s %d\r\n", spid, abs(pid));
+ ci.deadline.clear();
+ ci.sigkill = true;
+ } else {
+ n = 0; // FIXME
+ // Failed to send SIGTERM & SIGKILL to the process - give up
+ ci.deadline.clear();
+ ci.sigkill = true;
+ if (debug)
+ fprintf(stderr, "Failed to kill %s %d - leaving a zombie\r\n", spid, abs(pid));
+ MapChildrenT::iterator it = children.find(ci.cmd_pid);
+ if (it != children.end())
+ erase_child(it);
+ }
+ ci.sigterm = true;
+ return n;
+ }
+ return 0;
+}
+
+void stop_child(pid_t pid, int transId, const TimeVal& now)
+{
+ int n = 0;
+
+ MapChildrenT::iterator it = children.find(pid);
+ if (it == children.end()) {
+ send_error_str(transId, false, "pid not alive");
+ return;
+ } else if ((n = erl_exec_kill(pid, 0)) < 0) {
+ send_error_str(transId, false, "pid not alive (err: %d)", n);
+ return;
+ }
+ stop_child(it->second, transId, now);
+}
+
+int send_std_error(int err, bool notify, int transId)
+{
+ if (err == 0) {
+ if (notify) send_ok(transId);
+ return 0;
+ }
+
+ switch (errno) {
+ case EACCES:
+ if (notify) send_error_str(transId, true, "eacces");
+ break;
+ case EINVAL:
+ if (notify) send_error_str(transId, true, "einval");
+ break;
+ case ESRCH:
+ if (notify) send_error_str(transId, true, "esrch");
+ break;
+ case EPERM:
+ if (notify) send_error_str(transId, true, "eperm");
+ break;
+ default:
+ if (notify) send_error_str(transId, false, strerror(errno));
+ break;
+ }
+ return err;
+}
+
+int kill_child(pid_t pid, int signal, int transId, bool notify)
+{
+ // We can't use -pid here to kill the whole process group, because our process is
+ // the group leader.
+ int err = erl_exec_kill(pid, signal);
+ switch (err) {
+ case EINVAL:
+ if (notify) send_error_str(transId, false, "Invalid signal: %d", signal);
+ break;
+ default:
+ send_std_error(err, notify, transId);
+ break;
+ }
+ return err;
+}
+
+bool process_pid_input(CmdInfo& ci)
+{
+ int& fd = ci.stream_fd[STDIN_FILENO];
+
+ if (fd < 0) return true;
+
+ while (!ci.stdin_queue.empty()) {
+ std::string& s = ci.stdin_queue.back();
+
+ const void* p = s.c_str() + ci.stdin_wr_pos;
+ int n, len = s.size() - ci.stdin_wr_pos;
+
+ while ((n = write(fd, p, len)) < 0 && errno == EINTR);
+
+ if (debug) {
+ if (n < 0)
+ fprintf(stderr, "Error writing %d bytes to stdin (fd=%d) of pid %d: %s\r\n",
+ len, fd, ci.cmd_pid, strerror(errno));
+ else
+ fprintf(stderr, "Wrote %d/%d bytes to stdin (fd=%d) of pid %d\r\n",
+ n, len, fd, ci.cmd_pid);
+ }
+
+ if (n > 0 && n < len) {
+ ci.stdin_wr_pos += n;
+ return false;
+ } else if (n < 0 && errno == EAGAIN) {
+ break;
+ } else if (n <= 0) {
+ if (debug)
+ fprintf(stderr, "Eof writing pid %d's stdin, closing fd=%d: %s\r\n",
+ ci.cmd_pid, fd, strerror(errno));
+ ci.stdin_wr_pos = 0;
+ close(fd);
+ fd = REDIRECT_CLOSE;
+ ci.stdin_queue.clear();
+ return true;
+ }
+
+ ci.stdin_queue.pop_back();
+ ci.stdin_wr_pos = 0;
+ }
+
+ return true;
+}
+
+void process_pid_output(CmdInfo& ci, int maxsize)
+{
+ char buf[4096];
+ bool dead = false;
+
+ for (int i=STDOUT_FILENO; i <= STDERR_FILENO; i++) {
+ int& fd = ci.stream_fd[i];
+
+ if (fd >= 0) {
+ for(int got = 0, n = sizeof(buf); got < maxsize && n == sizeof(buf); got += n) {
+ while ((n = read(fd, buf, sizeof(buf))) < 0 && errno == EINTR);
+ if (debug > 1)
+ fprintf(stderr, "Read %d bytes from pid %d's %s (fd=%d): %s\r\n",
+ n, ci.cmd_pid, stream_name(i), fd, n > 0 ? "ok" : strerror(errno));
+ if (n > 0) {
+ send_ospid_output(ci.cmd_pid, stream_name(i), buf, n);
+ if (n < (int)sizeof(buf))
+ break;
+ } else if (n < 0 && errno == EAGAIN)
+ break;
+ else if (n <= 0) {
+ if (debug)
+ fprintf(stderr, "Eof reading pid %d's %s, closing fd=%d: %s\r\n",
+ ci.cmd_pid, stream_name(i), fd, strerror(errno));
+ close(fd);
+ fd = REDIRECT_CLOSE;
+ dead = true;
+ break;
+ }
+ }
+ }
+ }
+
+ if (dead)
+ check_child(ci.cmd_pid);
+}
+
+void erase_child(MapChildrenT::iterator& it)
+{
+ for (int i=STDIN_FILENO; i<=STDERR_FILENO; i++)
+ if (it->second.stream_fd[i] >= 0) {
+ if (debug)
+ fprintf(stderr, "Closing pid %d's %s\r\n", it->first, stream_name(i));
+ close(it->second.stream_fd[i]);
+ }
+
+ children.erase(it);
+}
+
+int check_children(const TimeVal& now, int& isTerminated, bool notify)
+{
+ if (debug > 2)
+ fprintf(stderr, "Checking %ld running children (exited count=%ld)\r\n",
+ children.size(), exited_children.size());
+
+ for (MapChildrenT::iterator it=children.begin(), end=children.end(); it != end; ++it) {
+ int status = ECHILD;
+ pid_t pid = it->first;
+ int n = erl_exec_kill(pid, 0);
+
+ if (n == 0) { // process is alive
+ /* If a deadline has been set, and we're over it, wack it. */
+ if (!it->second.deadline.zero() && it->second.deadline.diff(now) <= 0) {
+ stop_child(it->second, 0, now, false);
+ it->second.deadline.clear();
+ }
+
+ while ((n = waitpid(pid, &status, WNOHANG)) < 0 && errno == EINTR);
+
+ if (n > 0) {
+ if (WIFEXITED(status) || WIFSIGNALED(status)) {
+ add_exited_child(pid <= 0 ? n : pid, status);
+ } else if (WIFSTOPPED(status)) {
+ if (debug)
+ fprintf(stderr, "Pid %d %swas stopped by delivery of a signal %d\r\n",
+ pid, it->second.managed ? "(managed) " : "", WSTOPSIG(status));
+ } else if (WIFCONTINUED(status)) {
+ if (debug)
+ fprintf(stderr, "Pid %d %swas resumed by delivery of SIGCONT\r\n",
+ pid, it->second.managed ? "(managed) " : "");
+ }
+ }
+ } else if (n < 0 && errno == ESRCH) {
+ add_exited_child(pid, -1);
+ }
+ }
+
+ if (debug > 2)
+ fprintf(stderr, "Checking %ld exited children (notify=%d)\r\n",
+ exited_children.size(), notify);
+
+ // For each process info in the <exited_children> queue deliver it to the Erlang VM
+ // and remove it from the managed <children> map.
+ for (ExitedChildrenT::iterator it=exited_children.begin(); !isTerminated && it!=exited_children.end();)
+ {
+ MapChildrenT::iterator i = children.find(it->first);
+ MapKillPidT::iterator j;
+
+ if (i != children.end()) {
+ process_pid_output(i->second, INT_MAX);
+ // Override status code if termination was requested by Erlang
+ PidStatusT ps(it->first,
+ i->second.sigterm
+ ? 0 // Override status code if termination was requested by Erlang
+ : i->second.success_code && !it->second
+ ? i->second.success_code // Override success status code
+ : it->second);
+ // The process exited and it requires to kill all other processes in the group
+ if (i->second.kill_group && i->second.cmd_gid != INT_MAX && i->second.cmd_gid)
+ erl_exec_kill(-(i->second.cmd_gid), SIGTERM); // Kill all children in this group
+
+ if (notify && send_pid_status_term(ps) < 0) {
+ isTerminated = 1;
+ return -1;
+ }
+ erase_child(i);
+ } else if ((j = transient_pids.find(it->first)) != transient_pids.end()) {
+ // the pid is one of the custom 'kill' commands started by us.
+ transient_pids.erase(j);
+ }
+
+ exited_children.erase(it++);
+ }
+
+ return 0;
+}
+
+int send_pid_list(int transId, const MapChildrenT& children)
+{
+ // Reply: {TransId, [OsPid::integer()]}
+ eis.reset();
+ eis.encodeTupleSize(2);
+ eis.encode(transId);
+ eis.encodeListSize(children.size());
+ for(MapChildrenT::const_iterator it=children.begin(), end=children.end(); it != end; ++it)
+ eis.encode(it->first);
+ eis.encodeListEnd();
+ return eis.write();
+}
+
+int send_error_str(int transId, bool asAtom, const char* fmt, ...)
+{
+ char str[MAXATOMLEN];
+ va_list vargs;
+ va_start (vargs, fmt);
+ vsnprintf(str, sizeof(str), fmt, vargs);
+ va_end (vargs);
+
+ eis.reset();
+ eis.encodeTupleSize(2);
+ eis.encode(transId);
+ eis.encodeTupleSize(2);
+ eis.encode(atom_t("error"));
+ (asAtom) ? eis.encode(atom_t(str)) : eis.encode(str);
+ return eis.write();
+}
+
+int send_ok(int transId, pid_t pid)
+{
+ eis.reset();
+ eis.encodeTupleSize(2);
+ eis.encode(transId);
+ if (pid < 0)
+ eis.encode(atom_t("ok"));
+ else {
+ eis.encodeTupleSize(2);
+ eis.encode(atom_t("ok"));
+ eis.encode(pid);
+ }
+ return eis.write();
+}
+
+int send_pid_status_term(const PidStatusT& stat)
+{
+ eis.reset();
+ eis.encodeTupleSize(2);
+ eis.encode(0);
+ eis.encodeTupleSize(3);
+ eis.encode(atom_t("exit_status"));
+ eis.encode(stat.first);
+ eis.encode(stat.second);
+ return eis.write();
+}
+
+int send_ospid_output(int pid, const char* type, const char* data, int len)
+{
+ eis.reset();
+ eis.encodeTupleSize(2);
+ eis.encode(0);
+ eis.encodeTupleSize(3);
+ eis.encode(atom_t(type));
+ eis.encode(pid);
+ eis.encode(data, len);
+ return eis.write();
+}
+
+int open_file(const char* file, bool append, const char* stream,
+ ei::StringBuffer<128>& err, int mode)
+{
+ int flags = O_RDWR | O_CREAT | (append ? O_APPEND : O_TRUNC);
+ int fd = open(file, flags, mode);
+ if (fd < 0) {
+ err.write("Failed to redirect %s to file: %s", stream, strerror(errno));
+ return -1;
+ }
+ if (debug)
+ fprintf(stderr, " Redirecting [%s -> {file:%s, fd:%d}]\r\n",
+ stream, file, fd);
+
+ return fd;
+}
+
+int open_pipe(int fds[2], const char* stream, ei::StringBuffer<128>& err)
+{
+ if (pipe(fds) < 0) {
+ err.write("Failed to create a pipe for %s: %s", stream, strerror(errno));
+ return -1;
+ }
+ if (fds[1] > max_fds) {
+ close(fds[0]);
+ close(fds[1]);
+ err.write("Exceeded number of available file descriptors (fd=%d)", fds[1]);
+ return -1;
+ }
+ if (debug)
+ fprintf(stderr, " Redirecting [%s -> pipe:{r=%d,w=%d}]\r\n", stream, fds[0], fds[1]);
+
+ return 0;
+}
+
+/* This exists just to make sure that we don't inadvertently do a
+ * kill(-1, SIGKILL), which will cause all kinds of bad things to
+ * happen. */
+
+int erl_exec_kill(pid_t pid, int signal) {
+ if (pid == -1 || pid == 0) {
+ if (debug)
+ fprintf(stderr, "kill(%d, %d) attempt prohibited!\r\n", pid, signal);
+ return -1;
+ }
+
+ int r = kill(pid, signal);
+
+ if (debug && signal > 0)
+ fprintf(stderr, "Called kill(pid=%d, sig=%d) -> %d\r\n", pid, signal, r);
+
+ return r;
+}
+
+int set_nonblock_flag(pid_t pid, int fd, bool value)
+{
+ int oldflags = fcntl(fd, F_GETFL, 0);
+ if (oldflags < 0)
+ return oldflags;
+ if (value != 0)
+ oldflags |= O_NONBLOCK;
+ else
+ oldflags &= ~O_NONBLOCK;
+
+ int ret = fcntl(fd, F_SETFL, oldflags);
+ if (debug > 3) {
+ oldflags = fcntl(fd, F_GETFL, 0);
+ fprintf(stderr, " Set pid %d's fd=%d to non-blocking mode (flags=%x)\r\n",
+ pid, fd, oldflags);
+ }
+
+ return ret;
+}
+
+int CmdOptions::ei_decode(ei::Serializer& ei, bool getCmd)
+{
+ // {Cmd::string(), [Option]}
+ // Option = {env, Strings} | {cd, Dir} | {kill, Cmd}
+ int sz;
+ std::string op, val;
+
+ m_err.str("");
+ m_cmd.clear();
+ m_kill_cmd.clear();
+ m_env.clear();
+
+ m_nice = INT_MAX;
+
+ if (getCmd) {
+ std::string s;
+
+ if (eis.decodeString(s) != -1) {
+ m_cmd.push_front(s);
+ m_shell=true;
+ } else if ((sz = eis.decodeListSize()) > 0) {
+ for (int i=0; i < sz; i++) {
+ if (eis.decodeString(s) < 0) {
+ m_err << "badarg: invalid command argument #" << i;
+ return -1;
+ }
+ m_cmd.push_back(s);
+ }
+ eis.decodeListEnd();
+ m_shell = false;
+ } else {
+ m_err << "badarg: cmd string or non-empty list is expected";
+ return -1;
+ }
+ }
+
+ if ((sz = eis.decodeListSize()) < 0) {
+ m_err << "option list expected";
+ return -1;
+ }
+
+ // Note: The STDIN, STDOUT, STDERR enums must occupy positions 0, 1, 2!!!
+ enum OptionT {
+ STDIN, STDOUT, STDERR,
+ PTY, SUCCESS_EXIT_CODE, CD, ENV,
+ EXECUTABLE, KILL, KILL_TIMEOUT,
+ KILL_GROUP, NICE, USER, GROUP
+ } opt;
+ const char* opts[] = {
+ "stdin", "stdout", "stderr",
+ "pty", "success_exit_code", "cd", "env",
+ "executable", "kill", "kill_timeout",
+ "kill_group", "nice", "user", "group"
+ };
+
+ bool seen_opt[sizeof(opts) / sizeof(char*)] = {false};
+
+ for(int i=0; i < sz; i++) {
+ int arity, type = eis.decodeType(arity);
+
+ if (type == etAtom && (int)(opt = (OptionT)eis.decodeAtomIndex(opts, op)) >= 0)
+ arity = 1;
+ else if (type != etTuple || ((arity = eis.decodeTupleSize()) != 2 && arity != 3)) {
+ m_err << "badarg: option must be {Cmd, Opt} or {Cmd, Opt, Args} or atom "
+ "(got tp=" << (char)type << ", arity=" << arity << ')';
+ return -1;
+ } else if ((int)(opt = (OptionT)eis.decodeAtomIndex(opts, op)) < 0) {
+ m_err << "badarg: invalid cmd option tuple";
+ return -1;
+ }
+
+ if (seen_opt[opt]) {
+ m_err << "duplicate " << op << " option specified";
+ return -1;
+ }
+ seen_opt[opt] = true;
+
+ switch (opt) {
+ case EXECUTABLE:
+ if (eis.decodeString(m_executable) < 0) {
+ m_err << op << " - bad option value"; return -1;
+ }
+ break;
+
+ case CD:
+ // {cd, Dir::string()}
+ if (eis.decodeString(m_cd) < 0) {
+ m_err << op << " - bad option value"; return -1;
+ }
+ break;
+
+ case KILL:
+ // {kill, Cmd::string()}
+ if (eis.decodeString(m_kill_cmd) < 0) {
+ m_err << op << " - bad option value"; return -1;
+ }
+ break;
+
+ case GROUP: {
+ // {group, integer() | string()}
+ type = eis.decodeType(arity);
+ if (type == etString) {
+ if (eis.decodeString(val) < 0) {
+ m_err << op << " - bad group value"; return -1;
+ }
+ struct group g;
+ char buf[1024];
+ struct group* res;
+ if (getgrnam_r(val.c_str(), &g, buf, sizeof(buf), &res) < 0) {
+ m_err << op << " - invalid group name: " << val;
+ return -1;
+ }
+ m_group = g.gr_gid;
+ } else if (eis.decodeInt(m_group) < 0) {
+ m_err << op << " - bad group value type (expected int or string)";
+ return -1;
+ }
+ break;
+ }
+ case USER:
+ // {user, Dir::string()} | {kill, Cmd::string()}
+ if (eis.decodeString(val) < 0) {
+ m_err << op << " - bad option value"; return -1;
+ }
+ if (opt == CD) m_cd = val;
+ else if (opt == KILL) m_kill_cmd = val;
+ else if (opt == USER) {
+ struct passwd *pw = getpwnam(val.c_str());
+ if (pw == NULL) {
+ m_err << "Invalid user " << val << ": " << ::strerror(errno);
+ return -1;
+ }
+ m_user = pw->pw_uid;
+ }
+ break;
+
+ case KILL_TIMEOUT:
+ // {kill_timeout, Timeout::int()}
+ if (eis.decodeInt(m_kill_timeout) < 0) {
+ m_err << op << " - invalid value";
+ return -1;
+ }
+ break;
+
+ case KILL_GROUP:
+ m_kill_group = true;
+ break;
+
+ case NICE:
+ // {nice, Level::int()}
+ if (eis.decodeInt(m_nice) < 0 || m_nice < -20 || m_nice > 20) {
+ m_err << "nice option must be an integer between -20 and 20";
+ return -1;
+ }
+ break;
+
+ case ENV: {
+ // {env, [NameEqualsValue::string()]}
+ // passed in env variables are appended to the existing ones
+ // obtained from environ global var
+ int opt_env_sz = eis.decodeListSize();
+ if (opt_env_sz < 0) {
+ m_err << op << " - list expected";
+ return -1;
+ }
+
+ for (int i=0; i < opt_env_sz; i++) {
+ int sz, type = eis.decodeType(sz);
+ bool res = false;
+ std::string s, key;
+
+ if (type == ERL_STRING_EXT) {
+ res = !eis.decodeString(s);
+ if (res) {
+ size_t pos = s.find_first_of('=');
+ if (pos == std::string::npos)
+ res = false;
+ else
+ key = s.substr(0, pos);
+ }
+ } else if (type == ERL_SMALL_TUPLE_EXT && sz == 2) {
+ eis.decodeTupleSize();
+ std::string s2;
+ if (eis.decodeString(key) == 0 && eis.decodeString(s2) == 0) {
+ res = true;
+ s = key + "=" + s2;
+ }
+ }
+
+ if (!res) {
+ m_err << op << " - invalid argument #" << i;
+ return -1;
+ }
+ m_env[key] = s;
+ }
+ eis.decodeListEnd();
+ break;
+ }
+
+ case PTY:
+ m_pty = true;
+ break;
+
+ case SUCCESS_EXIT_CODE:
+ if (eis.decodeInt(m_success_exit_code) < 0 ||
+ m_success_exit_code < 0 ||
+ m_success_exit_code > 255)
+ {
+ m_err << "success exit code must be an integer between 0 and 255";
+ return -1;
+ }
+ break;
+
+ case STDIN:
+ case STDOUT:
+ case STDERR: {
+ int& fdr = stream_fd(opt);
+
+ if (arity == 1)
+ stream_redirect(opt, REDIRECT_ERL);
+ else if (arity == 2) {
+ int type = 0, sz;
+ std::string s, fop;
+ type = eis.decodeType(sz);
+
+ if (type == ERL_ATOM_EXT)
+ eis.decodeAtom(s);
+ else if (type == ERL_STRING_EXT)
+ eis.decodeString(s);
+ else {
+ m_err << op << " - atom or string value in tuple required";
+ return -1;
+ }
+
+ if (s == "null") {
+ stream_null(opt);
+ fdr = REDIRECT_NULL;
+ } else if (s == "close") {
+ stream_redirect(opt, REDIRECT_CLOSE);
+ } else if (s == "stderr" && opt == STDOUT)
+ stream_redirect(opt, REDIRECT_STDERR);
+ else if (s == "stdout" && opt == STDERR)
+ stream_redirect(opt, REDIRECT_STDOUT);
+ else if (!s.empty()) {
+ stream_file(opt, s);
+ }
+ } else if (arity == 3) {
+ int n, sz, mode = DEF_MODE;
+ bool append = false;
+ std::string s, a, fop;
+ if (eis.decodeString(s) < 0) {
+ m_err << "filename must be a string for option " << op;
+ return -1;
+ }
+ if ((n = eis.decodeListSize()) < 0) {
+ m_err << "option " << op << " requires a list of file options" << op;
+ return -1;
+ }
+ for(int i=0; i < n; i++) {
+ int tp = eis.decodeType(sz);
+ if (eis.decodeAtom(a) >= 0) {
+ if (a == "append")
+ append = true;
+ else {
+ m_err << "option " << op << ": unsupported file option '" << a << "'";
+ return -1;
+ }
+ }
+ else if (tp != etTuple || eis.decodeTupleSize() != 2 ||
+ eis.decodeAtom(a) < 0 || a != "mode" || eis.decodeInt(mode) < 0) {
+ m_err << "option " << op << ": unsupported file option '" << a << "'";
+ return -1;
+
+ }
+ }
+ eis.decodeListEnd();
+
+ stream_file(opt, s, append, mode);
+ }
+
+ if (opt == STDIN &&
+ !(fdr == REDIRECT_NONE || fdr == REDIRECT_ERL ||
+ fdr == REDIRECT_CLOSE || fdr == REDIRECT_NULL || fdr == REDIRECT_FILE)) {
+ m_err << "invalid " << op << " redirection option: '" << op << "'";
+ return -1;
+ }
+ break;
+ }
+ default:
+ m_err << "bad option: " << op; return -1;
+ }
+ }
+
+ eis.decodeListEnd();
+
+ for (int i=STDOUT_FILENO; i <= STDERR_FILENO; i++)
+ if (stream_fd(i) == (i == STDOUT_FILENO ? REDIRECT_STDOUT : REDIRECT_STDERR)) {
+ m_err << "self-reference of " << stream_fd_type(i);
+ return -1;
+ }
+
+ if (stream_fd(STDOUT_FILENO) == REDIRECT_STDERR &&
+ stream_fd(STDERR_FILENO) == REDIRECT_STDOUT)
+ {
+ m_err << "circular reference of stdout and stderr";
+ return -1;
+ }
+
+ //if (cmd_is_list && m_shell)
+ // m_shell = false;
+
+ if (debug > 1) {
+ fprintf(stderr, "Parsed cmd '%s' options\r\n (stdin=%s, stdout=%s, stderr=%s)\r\n",
+ m_cmd.front().c_str(),
+ stream_fd_type(0).c_str(), stream_fd_type(1).c_str(), stream_fd_type(2).c_str());
+ }
+
+ return 0;
+}
+
+int CmdOptions::init_cenv()
+{
+ if (m_env.empty()) {
+ m_cenv = (const char**)environ;
+ return 0;
+ }
+
+ // Copy environment of the caller process
+ for (char **env_ptr = environ; *env_ptr; env_ptr++) {
+ std::string s(*env_ptr), key(s.substr(0, s.find_first_of('=')));
+ MapEnvIterator it = m_env.find(key);
+ if (it == m_env.end())
+ m_env[key] = s;
+ }
+
+ if ((m_cenv = (const char**) new char* [m_env.size()+1]) == NULL) {
+ m_err << "Cannot allocate memory for " << m_env.size()+1 << " environment entries";
+ return -1;
+ }
+
+ int i = 0;
+ for (MapEnvIterator it = m_env.begin(), end = m_env.end(); it != end; ++it, ++i)
+ m_cenv[i] = it->second.c_str();
+ m_cenv[i] = NULL;
+
+ return 0;
+}
diff --git a/deps/exec/include/exec.hrl b/deps/exec/include/exec.hrl
new file mode 100644
index 0000000..0ee625f
--- /dev/null
+++ b/deps/exec/include/exec.hrl
@@ -0,0 +1,8 @@
+-define(SIGHUP, -1).
+-define(SIGINT, -2).
+-define(SIGKILL, -9).
+-define(SIGTERM, -15).
+-define(SIGUSR1, -10).
+-define(SIGUSR2, -12).
+
+-define(FMT(Fmt, Args), lists:flatten(io_lib:format(Fmt, Args))).
diff --git a/deps/exec/priv/x86_64-pc-linux-gnu/exec-port b/deps/exec/priv/x86_64-pc-linux-gnu/exec-port
new file mode 100755
index 0000000..555048c
--- /dev/null
+++ b/deps/exec/priv/x86_64-pc-linux-gnu/exec-port
Binary files differ
diff --git a/deps/exec/rebar.config b/deps/exec/rebar.config
new file mode 100644
index 0000000..5db9612
--- /dev/null
+++ b/deps/exec/rebar.config
@@ -0,0 +1,6 @@
+{erl_opts, [
+ warnings_as_errors,
+ warn_export_all
+]}.
+
+{pre_hooks, [{clean, "rm -fr ebin priv erl_crash.dump"}]}.
diff --git a/deps/exec/rebar.config.script b/deps/exec/rebar.config.script
new file mode 100644
index 0000000..4c7fc65
--- /dev/null
+++ b/deps/exec/rebar.config.script
@@ -0,0 +1,44 @@
+Arch = erlang:system_info(system_architecture),
+Vsn = string:strip(os:cmd("git describe --always --tags --abbrev=0 | sed 's/^v//'"), right, $\n),
+%% Check for Linux capability API (Install package: libcap-devel).
+{LinCXX, LinLD} =
+ case file:read_file_info("/usr/include/sys/capability.h") of
+ {ok, _} ->
+ io:put_chars("INFO: Detected support of linux capabilities.\n"),
+ {" -DHAVE_CAP", " -lcap"};
+ _ ->
+ {"", ""}
+ end,
+
+X64 = case Arch of
+ "x86_64" ++ _E -> " -m64";
+ _ -> ""
+ end,
+
+% Replace configuration options read from rebar.config with those dynamically set below
+lists:keymerge(1,
+ lists:keysort(1, [
+ {port_env, [{"solaris", "CXXFLAGS", "$CXXFLAGS -DHAVE_SETREUID -DHAVE_PTRACE" ++ X64},
+ {"solaris", "LDFLAGS", "$LDFLAGS -lrt" ++ X64},
+
+ {"darwin", "CXXFLAGS", "$CXXFLAGS -DHAVE_SETREUID -DHAVE_PTRACE" ++ X64},
+ {"darwin", "LDFLAGS", "$LDFLAGS" ++ X64},
+
+ {"linux", "CXXFLAGS", "$CXXFLAGS -DHAVE_SETRESUID -DHAVE_PTRACE" ++ LinCXX},
+ {"linux", "LDFLAGS", "$LDFLAGS" ++ LinLD},
+
+ {"CC", "g++"},
+ {"CXX", "g++"},
+ {"CXXFLAGS", "$CXXFLAGS -O0"}
+ ]},
+
+ {port_specs,[{filename:join(["priv", Arch, "exec-port"]), ["c_src/*.cpp"]}]},
+ {edoc_opts, [{overview, "src/overview.edoc"},
+ {title, "The exec application"},
+ {includes, ["include"]},
+ {def, {vsn, Vsn}},
+ {stylesheet_file, "src/edoc.css"},
+ {app_default, "http://www.erlang.org/doc/man"}]}
+ ]),
+ lists:keysort(1, CONFIG)).
+
diff --git a/deps/exec/src/edoc.css b/deps/exec/src/edoc.css
new file mode 100644
index 0000000..b98c948
--- /dev/null
+++ b/deps/exec/src/edoc.css
@@ -0,0 +1,144 @@
+/* Baseline rhythm */
+body {
+ font-size: 16px;
+ font-family: Helvetica, sans-serif;
+ margin: 8px;
+}
+
+p {
+ font-size: 1em; /* 16px */
+ line-height: 1.5em; /* 24px */
+ margin: 0 0 1.5em 0;
+}
+
+h1 {
+ font-size: 1.5em; /* 24px */
+ line-height: 1em; /* 24px */
+ margin-top: 1em;
+ margin-bottom: 0em;
+}
+
+h2 {
+ font-size: 1.375em; /* 22px */
+ line-height: 1.0909em; /* 24px */
+ margin-top: 1.0909em;
+ margin-bottom: 0em;
+}
+
+h3 {
+ font-size: 1.25em; /* 20px */
+ line-height: 1.2em; /* 24px */
+ margin-top: 1.2em;
+ margin-bottom: 0em;
+}
+
+h4 {
+ font-size: 1.125em; /* 18px */
+ line-height: 1.3333em; /* 24px */
+ margin-top: 1.3333em;
+ margin-bottom: 0em;
+}
+
+.class-for-16px {
+ font-size: 1em; /* 16px */
+ line-height: 1.5em; /* 24px */
+ margin-top: 1.5em;
+ margin-bottom: 0em;
+}
+
+.class-for-14px {
+ font-size: 0.875em; /* 14px */
+ line-height: 1.7143em; /* 24px */
+ margin-top: 1.7143em;
+ margin-bottom: 0em;
+}
+
+ul {
+ margin: 0 0 1.5em 0;
+ list-style-position: outside;
+}
+
+/* Customizations */
+body {
+ color: #333;
+}
+
+tt, code, pre { font-size: 0.95em }
+
+pre {
+ font-size: 0.875em; /* 14px */
+ margin: 0 1em 1.7143em;
+ padding: 0 1em;
+ background: #E0F0FF;
+}
+
+.navbar img, hr {
+ display: none;
+}
+
+.navbar {
+ background-color: #85C2FF;
+}
+
+table {
+ border-collapse: collapse;
+}
+
+h1 {
+ border-left: 0.5em solid #85C2FF;
+ padding-left: 0.5em;
+ background-color: #85C2FF;
+}
+
+h2.indextitle {
+ font-size: 1.25em; /* 20px */
+ line-height: 1.2em; /* 24px */
+ margin: -8px -8px 0.6em;
+ background-color: #85C2FF;
+ padding: 0.3em;
+}
+
+ul.index {
+ list-style: none;
+ margin-left: 0em;
+ padding-left: 0;
+}
+
+ul.index li {
+ display: inline;
+ padding-right: 0.75em
+}
+
+ul.definitions {
+ background-color: #E0F0FF;
+}
+
+dt {
+ font-weight: 600;
+}
+
+div.spec p {
+ margin-bottom: 0;
+ padding-left: 1.25em;
+ background-color: #E0F0FF;
+}
+
+h3.function {
+ border-left: 0.5em solid #85C2FF;
+ padding-left: 0.5em;
+ background-color: #BECFE0;
+}
+a, a:visited, a:hover, a:active { color: #06C }
+h2 a, h3 a { color: #333 }
+
+h3.typedecl {
+ background-color: #BECFE0;
+}
+
+i {
+ font-size: 0.875em; /* 14px */
+ line-height: 1.7143em; /* 24px */
+ margin-top: 1.7143em;
+ margin-bottom: 0em;
+ font-style: normal;
+}
diff --git a/deps/exec/src/exec.app.src b/deps/exec/src/exec.app.src
new file mode 100644
index 0000000..b6a178e
--- /dev/null
+++ b/deps/exec/src/exec.app.src
@@ -0,0 +1,16 @@
+{application, exec,
+ [
+ {description, "OS Process Manager"},
+ {vsn, git},
+ {id, "exec"},
+ {modules, []},
+ {registered, [ exec ] },
+ %% NOTE: do not list applications which are load-only!
+ {applications, [ kernel, stdlib ] },
+ %%
+ %% mod: Specify the module name to start the application, plus args
+ %%
+ {mod, {exec_app, []}},
+ {env, []}
+ ]
+}.
diff --git a/deps/exec/src/exec.erl b/deps/exec/src/exec.erl
new file mode 100644
index 0000000..6ac17e5
--- /dev/null
+++ b/deps/exec/src/exec.erl
@@ -0,0 +1,1130 @@
+%%%------------------------------------------------------------------------
+%%% File: $Id$
+%%%------------------------------------------------------------------------
+%%% @doc OS shell command runner.
+%%% It communicates with a separate C++ port process `exec-port'
+%%% spawned by this module, which is responsible
+%%% for starting, killing, listing, terminating, and notifying of
+%%% state changes.
+%%%
+%%% The port program serves as a middle-man between
+%%% the OS and the virtual machine to carry out OS-specific low-level
+%%% process control. The Erlang/C++ protocol is described in the
+%%% `exec.cpp' file. On platforms/environments which permit
+%%% setting the suid bit on the `exec-port' executable, it can
+%%% run external tasks by impersonating a different user. When
+%%% suid bit is on, the application requires `exec:start_link/2'
+%%% to be given the `{user, User}' option so that `exec-port'
+%%% will not run as root. Before changing the effective `User',
+%%% it sets the kernel capabilities so that it's able to start
+%%% processes as other users and adjust process priorities.
+%%%
+%%% At exit the port program makes its best effort to perform
+%%% clean shutdown of all child OS processes.
+%%% Every started OS process is linked to a spawned light-weight
+%%% Erlang process returned by the run/2, run_link/2 command.
+%%% The application ensures that termination of spawned OsPid
+%%% leads to termination of the associated Erlang Pid, and vice
+%%% versa.
+%%%
+%%% @author Serge Aleynikov <saleyn@gmail.com>
+%%% @version {@vsn}
+%%% @end
+%%%------------------------------------------------------------------------
+%%% Created: 2003-06-10 by Serge Aleynikov <saleyn@gmail.com>
+%%% $Header$
+%%%------------------------------------------------------------------------
+-module(exec).
+-author('saleyn@gmail.com').
+
+-behaviour(gen_server).
+
+%% External exports
+-export([
+ start/0, start/1, start_link/1, run/2, run_link/2, manage/2, send/2,
+ which_children/0, kill/2, setpgid/2, stop/1, stop_and_wait/2,
+ ospid/1, pid/1, status/1, signal/1
+]).
+
+%% Internal exports
+-export([default/0, default/1]).
+
+%% gen_server callbacks
+-export([init/1, handle_call/3, handle_cast/2, handle_info/2,
+ code_change/3, terminate/2]).
+
+-include("exec.hrl").
+
+-ifdef(TEST).
+-include_lib("eunit/include/eunit.hrl").
+-endif.
+
+-record(state, {
+ port,
+ last_trans = 0, % Last transaction number sent to port
+ trans = queue:new(), % Queue of outstanding transactions sent to port
+ limit_users = [], % Restricted list of users allowed to run commands
+ registry, % Pids to notify when an OsPid exits
+ debug = false
+}).
+
+-type exec_options() :: [exec_option()].
+-type exec_option() ::
+ debug
+ | {debug, integer()}
+ | verbose
+ | {args, [string(), ...]}
+ | {alarm, non_neg_integer()}
+ | {user, string()}
+ | {limit_users, [string(), ...]}
+ | {portexe, string()}
+ | {env, [{string(), string()}, ...]}.
+%% Options passed to the exec process at startup.
+%% <dl>
+%% <dt>debug</dt><dd>Same as {debug, 1}</dd>
+%% <dt>{debug, Level}</dt><dd>Enable port-programs debug trace at `Level'.</dd>
+%% <dt>verbose</dt><dd>Enable verbose prints of the Erlang process.</dd>
+%% <dt>{args, Args}</dt><dd>Append `Args' to the port command.</dd>
+%% <dt>{alarm, Secs}</dt>
+%% <dd>Give `Secs' deadline for the port program to clean up
+%% child pids before exiting</dd>
+%% <dt>{user, User}</dt>
+%% <dd>When the port program was compiled with capability (Linux)
+%% support enabled, and is owned by root with a a suid bit set,
+%% this option must be specified so that upon startup the port
+%% program is running under the effective user different from root.
+%% This is a security measure that will also prevent the port program
+%% to execute root commands.</dd>
+%% <dt>{limit_users, LimitUsers}</dt>
+%% <dd>Limit execution of external commands to these set of users.
+%% This option is only valid when the port program is owned
+%% by root.</dd>
+%% <dt>{portexe, Exe}</dt>
+%% <dd>Provide an alternative location of the port program.
+%% This option is useful when this application is stored
+%% on NFS and the port program needs to be copied locally
+%% so that root suid bit can be set.</dd>
+%% <dt>{env, Env}</dt>
+%% <dd>Extend environment of the port program by using `Env' specification.
+%% `Env' should be a list of tuples `{Name, Val}', where Name is the
+%% name of an environment variable, and Val is the value it is to have
+%% in the spawned port process.</dd>
+%% </dl>.
+
+-type cmd() :: string() | [string()].
+%% Command to be executed. If specified as a string, the specified command
+%% will be executed through the shell. The current shell is obtained
+%% from environtment variable `SHELL'. This can be useful if you
+%% are using Erlang primarily for the enhanced control flow it
+%% offers over most system shells and still want convenient
+%% access to other shell features such as shell pipes, filename
+%% wildcards, environment variable expansion, and expansion of
+%% `~' to a user's home directory. All command arguments must
+%% be properly escaped including whitespace and shell
+%% metacharacters.
+%%
+%% <ul>
+%% <b><u>Warning:</u></b> Executing shell commands that
+%% incorporate unsanitized input from an untrusted source makes
+%% a program vulnerable to
+%% [http://en.wikipedia.org/wiki/Shell_injection#Shell_injection shell injection],
+%% a serious security flaw which can result in arbitrary command
+%% execution. For this reason, the use of `shell' is strongly
+%% discouraged in cases where the command string is constructed
+%% from external input:
+%% </ul>
+%%
+%% ```
+%% 1> {ok, Filename} = io:read("Enter filename: ").
+%% Enter filename: "non_existent; rm -rf / #".
+%% {ok, "non_existent; rm -rf / #"}
+%% 2> exec(Filename, []) % Argh!!! This is not good!
+%% '''
+%%
+%% When command is given in the form of a list of strings,
+%% it is passed to `execve(3)' library call directly without
+%% involving the shell process, so the list of strings
+%% represents the program to be executed with arguments.
+%% In this case all shell-based features are disabled
+%% and there's no shell injection vulnerability.
+
+-type cmd_options() :: [cmd_option()].
+-type cmd_option() ::
+ monitor
+ | sync
+ | {executable, string()}
+ | {cd, WorkDir::string()}
+ | {env, [string() | {Name :: string(), Value :: string()}, ...]}
+ | {kill, KillCmd::string()}
+ | {kill_timeout, Sec::non_neg_integer()}
+ | kill_group
+ | {group, GID :: string() | integer()}
+ | {user, RunAsUser :: string()}
+ | {nice, Priority :: integer()}
+ | {success_exit_code, ExitCode :: integer() }
+ | stdin | {stdin, null | close | string()}
+ | stdout | stderr
+ | {stdout, stderr | output_dev_opt()}
+ | {stderr, stdout | output_dev_opt()}
+ | {stdout | stderr, string(), [output_file_opt()]}
+ | pty.
+%% Command options:
+%% <dl>
+%% <dt>monitor</dt><dd>Set up a monitor for the spawned process</dd>
+%% <dt>sync</dt><dd>Block the caller until the OS command exits</dd>
+%% <dt>{executable, Executable::string()}</dt>
+%% <dd>Specifies a replacement program to execute. It is very seldomly
+%% needed. When the port program executes a child process using
+%% `execve(3)' call, the call takes the following arguments:
+%% `(Executable, Args, Env)'. When `Cmd' argument passed to the
+%% `run/2' function is specified as the list of strings,
+%% the executable replaces the first paramter in the call, and
+%% the original args provided in the `Cmd' parameter are passed as
+%% as the second parameter. Most programs treat the program
+%% specified by args as the command name, which can then be different
+%% from the program actually executed. On Unix, the args name becomes
+%% the display name for the executable in utilities such as `ps'.
+%%
+%% If `Cmd' argument passed to the `run/2' function is given as a
+%% string, on Unix the `Executable' specifies a replacement shell
+%% for the default `/bin/sh'.</dd>
+%% <dt>{cd, WorkDir}</dt><dd>Working directory</dd>
+%% <dt>{env, Env}</dt>
+%% <dd>List of "VAR=VALUE" environment variables or
+%% list of {Var, Value} tuples. Both representations are
+%% used in other parts of Erlang/OTP
+%% (e.g. os:getenv/0, erlang:open_port/2)</dd>
+%% <dt>{kill, KillCmd}</dt>
+%% <dd>This command will be used for killing the process. After
+%% a 5-sec timeout if the process is still alive, it'll be
+%% killed with SIGTERM followed by SIGKILL. By default
+%% SIGTERM/SIGKILL combination is used for process
+%% termination.</dd>
+%% <dt>{kill_timeout, Sec::integer()}</dt>
+%% <dd>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</dd>
+%% <dt>kill_group</dt>
+%% <dd>At process exit kill the whole process group associated with this pid.
+%% The process group is obtained by the call to getpgid(3).</dd>
+%% <dt>{group, GID}</dt>
+%% <dd>Sets the effective group ID of the spawned process. The value 0
+%% means to create a new group ID equal to the OS pid of the process.</dd>
+%% <dt>{user, RunAsUser}</dt>
+%% <dd>When exec-port was compiled with capability (Linux) support
+%% enabled and has a suid bit set, it's capable of running
+%% commands with a different RunAsUser effective user. Passing
+%% "root" value of `RunAsUser' is prohibited.</dd>
+%% <dt>{success_exit_code, IntExitCode}</dt>
+%% <dd>On success use `IntExitCode' return value instead of default 0.</dd>
+%% <dt>{nice, Priority}</dt>
+%% <dd>Set process priority between -20 and 20. Note that
+%% negative values can be specified only when `exec-port'
+%% is started with a root suid bit set.</dd>
+%% <dt>stdin | {stdin, null | close | Filename}</dt>
+%% <dd>Enable communication with an OS process via its `stdin'. The
+%% input to the process is sent by `exec:send(OsPid, Data)'.
+%% When specified as a tuple, `null' means redirection from `/dev/null',
+%% `close' means to close `stdin' stream, and `Filename' means to
+%% take input from file.</dd>
+%% <dt>stdout</dt>
+%% <dd>Same as `{stdout, self()}'.</dd>
+%% <dt>stderr</dt>
+%% <dd>Same as `{stderr, self()}'.</dd>
+%% <dt>{stdout, output_device()}</dt>
+%% <dd>Redirect process's standard output stream</dd>
+%% <dt>{stderr, output_device()}</dt>
+%% <dd>Redirect process's standard error stream</dd>
+%% <dt>{stdout | stderr, Filename::string(), [output_dev_opt()]}</dt>
+%% <dd>Redirect process's stdout/stderr stream to file</dd>
+%% <dt>pty</dt>
+%% <dd>Use pseudo terminal for the process's stdin, stdout and stderr</dd>
+%% </dl>
+
+-type output_dev_opt() :: null | close | print | string() | pid()
+ | fun((stdout | stderr, integer(), binary()) -> none()).
+%% Output device option:
+%% <dl>
+%% <dt>null</dt><dd>Suppress output.</dd>
+%% <dt>close</dt><dd>Close file descriptor for writing.</dd>
+%% <dt>print</dt>
+%% <dd>A debugging convenience device that prints the output to the
+%% console shell</dd>
+%% <dt>Filename</dt><dd>Save output to file by overwriting it.</dd>
+%% <dt>pid()</dt><dd>Redirect output to this pid.</dd>
+%% <dt>fun((Stream, OsPid, Data) -> none())</dt>
+%% <dd>Execute this callback on receiving output data</dd>
+%% </dl>
+
+-type output_file_opt() :: append | {mode, Mode::integer()}.
+%% Defines file opening attributes:
+%% <dl>
+%% <dt>append</dt><dd>Open the file in `append' mode</dd>
+%% <dt>{mode, Mode}</dt>
+%% <dd>File creation access mode <b>specified in base 8</b> (e.g. 8#0644)</dd>
+%% </dl>
+
+-type ospid() :: integer().
+%% Representation of OS process ID.
+-type osgid() :: integer().
+%% Representation of OS group ID.
+
+%%-------------------------------------------------------------------------
+%% @doc Supervised start an external program manager.
+%% @end
+%%-------------------------------------------------------------------------
+-spec start_link(exec_options()) -> {ok, pid()} | {error, any()}.
+start_link(Options) when is_list(Options) ->
+ gen_server:start_link({local, ?MODULE}, ?MODULE, [Options], []).
+
+%%-------------------------------------------------------------------------
+%% @equiv start_link/1
+%% @doc Start of an external program manager without supervision.
+%% @end
+%%-------------------------------------------------------------------------
+-spec start() -> {ok, pid()} | {error, any()}.
+start() ->
+ start([]).
+
+-spec start(exec_options()) -> {ok, pid()} | {error, any()}.
+start(Options) when is_list(Options) ->
+ gen_server:start({local, ?MODULE}, ?MODULE, [Options], []).
+
+%%-------------------------------------------------------------------------
+%% @doc Run an external program. `OsPid' is the OS process identifier of
+%% the new process. If `sync' is specified in `Options' the return
+%% value is `{ok, Status}' where `Status' is OS process exit status.
+%% @end
+%%-------------------------------------------------------------------------
+-spec run(cmd(), cmd_options()) ->
+ {ok, pid(), ospid()} | {ok, [{stdout | stderr, [binary()]}]} | {error, any()}.
+run(Exe, Options) when is_list(Exe), is_list(Options) ->
+ do_run({run, Exe, Options}, Options).
+
+%%-------------------------------------------------------------------------
+%% @equiv run/2
+%% @doc Run an external program and link to the OsPid. If OsPid exits,
+%% the calling process will be killed or if it's trapping exits,
+%% it'll get {'EXIT', OsPid, Status} message. If the calling process
+%% dies the OsPid will be killed.
+%% @end
+%%-------------------------------------------------------------------------
+-spec run_link(cmd(), cmd_options()) ->
+ {ok, pid(), ospid()} | {ok, [{stdout | stderr, [binary()]}]} | {error, any()}.
+run_link(Exe, Options) when is_list(Exe), is_list(Options) ->
+ do_run({run, Exe, Options}, [link | Options]).
+
+%%-------------------------------------------------------------------------
+%% @doc Manage an existing external process. `OsPid' is the OS process
+%% identifier of the external OS process or an Erlang `Port' that
+%% would be managed by erlexec.
+%% @end
+%%-------------------------------------------------------------------------
+-spec manage(ospid() | port(), Options::cmd_options()) ->
+ {ok, pid(), ospid()} | {error, any()}.
+manage(Pid, Options) when is_integer(Pid) ->
+ do_run({manage, Pid, Options}, Options);
+manage(Port, Options) when is_port(Port) ->
+ {os_pid, OsPid} = erlang:port_info(Port, os_pid),
+ manage(OsPid, Options).
+
+%%-------------------------------------------------------------------------
+%% @doc Get a list of children managed by port program.
+%% @end
+%%-------------------------------------------------------------------------
+-spec which_children() -> [ospid(), ...].
+which_children() ->
+ gen_server:call(?MODULE, {port, {list}}).
+
+%%-------------------------------------------------------------------------
+%% @doc Send a `Signal' to a child `Pid', `OsPid' or an Erlang `Port'.
+%% @end
+%%-------------------------------------------------------------------------
+-spec kill(pid() | ospid(), integer()) -> ok | {error, any()}.
+kill(Pid, Signal) when is_pid(Pid); is_integer(Pid) ->
+ gen_server:call(?MODULE, {port, {kill, Pid, Signal}});
+kill(Port, Signal) when is_port(Port) ->
+ {os_pid, Pid} = erlang:port_info(Port, os_pid),
+ kill(Pid, Signal).
+
+%%-------------------------------------------------------------------------
+%% @doc Change group ID of a given `OsPid' to `Gid'.
+%% @end
+%%-------------------------------------------------------------------------
+-spec setpgid(ospid(), osgid()) -> ok | {error, any()}.
+setpgid(OsPid, Gid) when is_integer(OsPid), is_integer(Gid) ->
+ gen_server:call(?MODULE, {port, {setpgid, OsPid, Gid}}).
+
+%%-------------------------------------------------------------------------
+%% @doc Terminate a managed `Pid', `OsPid', or `Port' process. The OS process is
+%% terminated gracefully. If it was given a `{kill, Cmd}' option at
+%% startup, that command is executed and a timer is started. If
+%% the program doesn't exit, then the default termination is
+%% performed. Default termination implies sending a `SIGTERM' command
+%% followed by `SIGKILL' in 5 seconds, if the program doesn't get
+%% killed.
+%% @end
+%%-------------------------------------------------------------------------
+-spec stop(pid() | ospid() | port()) -> ok | {error, any()}.
+stop(Pid) when is_pid(Pid); is_integer(Pid) ->
+ gen_server:call(?MODULE, {port, {stop, Pid}}, 30000);
+stop(Port) when is_port(Port) ->
+ {os_pid, Pid} = erlang:port_info(Port, os_pid),
+ stop(Pid).
+
+%%-------------------------------------------------------------------------
+%% @doc Terminate a managed `Pid', `OsPid', or `Port' process, like
+%% `stop/1', and wait for it to exit.
+%% @end
+%%-------------------------------------------------------------------------
+
+-spec stop_and_wait(pid() | ospid() | port(), integer()) -> term() | {error, any()}.
+stop_and_wait(Port, Timeout) when is_port(Port) ->
+ {os_pid, OsPid} = erlang:port_info(Port, os_pid),
+ stop_and_wait(OsPid, Timeout);
+
+stop_and_wait(OsPid, Timeout) when is_integer(OsPid) ->
+ [{_, Pid}] = ets:lookup(exec_mon, OsPid),
+ stop_and_wait(Pid, Timeout);
+
+stop_and_wait(Pid, Timeout) when is_pid(Pid) ->
+ gen_server:call(?MODULE, {port, {stop, Pid}}, Timeout),
+ receive
+ {'DOWN', _Ref, process, Pid, ExitStatus} -> ExitStatus
+ after Timeout -> {error, timeout}
+ end;
+
+stop_and_wait(Port, Timeout) when is_port(Port) ->
+ {os_pid, Pid} = erlang:port_info(Port, os_pid),
+ stop_and_wait(Pid, Timeout).
+
+%%-------------------------------------------------------------------------
+%% @doc Get `OsPid' of the given Erlang `Pid'. The `Pid' must be created
+%% previously by running the run/2 or run_link/2 commands.
+%% @end
+%%-------------------------------------------------------------------------
+-spec ospid(pid()) -> ospid() | {error, Reason::any()}.
+ospid(Pid) when is_pid(Pid) ->
+ Ref = make_ref(),
+ Pid ! {{self(), Ref}, ospid},
+ receive
+ {Ref, Reply} -> Reply;
+ Other -> Other
+ after 5000 -> {error, timeout}
+ end.
+
+%%-------------------------------------------------------------------------
+%% @doc Get `Pid' of the given `OsPid'. The `OsPid' must be created
+%% previously by running the run/2 or run_link/2 commands.
+%% @end
+%%-------------------------------------------------------------------------
+-spec pid(OsPid::ospid()) -> pid() | undefined | {error, timeout}.
+pid(OsPid) when is_integer(OsPid) ->
+ gen_server:call(?MODULE, {pid, OsPid}).
+
+%%-------------------------------------------------------------------------
+%% @doc Send `Data' to stdin of the OS process identified by `OsPid'.
+%% @end
+%%-------------------------------------------------------------------------
+-spec send(OsPid :: ospid() | pid(), binary()) -> ok.
+send(OsPid, Data) when (is_integer(OsPid) orelse is_pid(OsPid)) andalso is_binary(Data) ->
+ gen_server:call(?MODULE, {port, {send, OsPid, Data}}).
+
+%%-------------------------------------------------------------------------
+%% @doc Decode the program's exit_status. If the program exited by signal
+%% the function returns `{signal, Signal, Core}' where the `Signal'
+%% is the signal number or atom, and `Core' indicates if the core file
+%% was generated.
+%% @end
+%%-------------------------------------------------------------------------
+-spec status(integer()) ->
+ {status, ExitStatus :: integer()} |
+ {signal, Singnal :: integer() | atom(), Core :: boolean()}.
+status(Status) when is_integer(Status) ->
+ TermSignal = Status band 16#7F,
+ IfSignaled = ((TermSignal + 1) bsr 1) > 0,
+ ExitStatus = (Status band 16#FF00) bsr 8,
+ case IfSignaled of
+ true ->
+ CoreDump = (Status band 16#80) =:= 16#80,
+ {signal, signal(TermSignal), CoreDump};
+ false ->
+ {status, ExitStatus}
+ end.
+
+%%-------------------------------------------------------------------------
+%% @doc Convert a signal number to atom
+%% @end
+%%-------------------------------------------------------------------------
+-spec signal(integer()) -> atom() | integer().
+signal( 1) -> sighup;
+signal( 2) -> sigint;
+signal( 3) -> sigquit;
+signal( 4) -> sigill;
+signal( 5) -> sigtrap;
+signal( 6) -> sigabrt;
+signal( 7) -> sigbus;
+signal( 8) -> sigfpe;
+signal( 9) -> sigkill;
+signal(11) -> sigsegv;
+signal(13) -> sigpipe;
+signal(14) -> sigalrm;
+signal(15) -> sigterm;
+signal(16) -> sigstkflt;
+signal(17) -> sigchld;
+signal(18) -> sigcont;
+signal(19) -> sigstop;
+signal(20) -> sigtstp;
+signal(21) -> sigttin;
+signal(22) -> sigttou;
+signal(23) -> sigurg;
+signal(24) -> sigxcpu;
+signal(25) -> sigxfsz;
+signal(26) -> sigvtalrm;
+signal(27) -> sigprof;
+signal(28) -> sigwinch;
+signal(29) -> sigio;
+signal(30) -> sigpwr;
+signal(31) -> sigsys;
+signal(34) -> sigrtmin;
+signal(64) -> sigrtmax;
+signal(Num) when is_integer(Num) -> Num.
+
+%%-------------------------------------------------------------------------
+%% @private
+%% @spec () -> Default::exec_options()
+%% @doc Provide default value of a given option.
+%% @end
+%%-------------------------------------------------------------------------
+default() ->
+ [{debug, 0}, % Debug mode of the port program.
+ {verbose, false}, % Verbose print of events on the Erlang side.
+ {args, ""}, % Extra arguments that can be passed to port program
+ {alarm, 12},
+ {user, ""}, % Run port program as this user
+ {limit_users, []}, % Restricted list of users allowed to run commands
+ {portexe, default(portexe)}].
+
+%% @private
+default(portexe) ->
+ % Get architecture (e.g. i386-linux)
+ Dir = filename:dirname(filename:dirname(code:which(?MODULE))),
+ filename:join([Dir, "priv", erlang:system_info(system_architecture), "exec-port"]);
+default(Option) ->
+ proplists:get_value(Option, default()).
+
+%%%----------------------------------------------------------------------
+%%% Callback functions from gen_server
+%%%----------------------------------------------------------------------
+
+%%-----------------------------------------------------------------------
+%% Func: init/1
+%% Returns: {ok, State} |
+%% {ok, State, Timeout} |
+%% ignore |
+%% {stop, Reason}
+%% @private
+%%-----------------------------------------------------------------------
+init([Options]) ->
+ process_flag(trap_exit, true),
+ Opts0 = proplists:normalize(Options,
+ [{expand, [{debug, {debug, 1}},
+ {verbose, {verbose, true}}]}]),
+ Opts1 = [T || T = {O,_} <- Opts0,
+ lists:member(O, [debug, verbose, args, alarm, user])],
+ Opts = proplists:normalize(Opts1, [{aliases, [{args, ''}]}]),
+ Args = lists:foldl(
+ fun({Opt, I}, Acc) when is_list(I), I =/= "" ->
+ [" -"++atom_to_list(Opt)++" "++I | Acc];
+ ({Opt, I}, Acc) when is_integer(I) ->
+ [" -"++atom_to_list(Opt)++" "++integer_to_list(I) | Acc];
+ (_, Acc) -> Acc
+ end, [], Opts),
+ Exe = proplists:get_value(portexe, Options, default(portexe)) ++ lists:flatten([" -n"|Args]),
+ Users = proplists:get_value(limit_users, Options, default(limit_users)),
+ Debug = proplists:get_value(verbose, Options, default(verbose)),
+ Env = case proplists:get_value(env, Options) of
+ undefined -> [];
+ Other -> [{env, Other}]
+ end,
+ try
+ debug(Debug, "exec: port program: ~s\n env: ~p\n", [Exe, Env]),
+ PortOpts = Env ++ [binary, exit_status, {packet, 2}, nouse_stdio, hide],
+ Port = erlang:open_port({spawn, Exe}, PortOpts),
+ Tab = ets:new(exec_mon, [protected,named_table]),
+ {ok, #state{port=Port, limit_users=Users, debug=Debug, registry=Tab}}
+ catch _:Reason ->
+ {stop, ?FMT("Error starting port '~s': ~200p", [Exe, Reason])}
+ end.
+
+%%----------------------------------------------------------------------
+%% Func: handle_call/3
+%% Returns: {reply, Reply, State} |
+%% {reply, Reply, State, Timeout} |
+%% {noreply, State} |
+%% {noreply, State, Timeout} |
+%% {stop, Reason, Reply, State} | (terminate/2 is called)
+%% {stop, Reason, State} (terminate/2 is called)
+%% @private
+%%----------------------------------------------------------------------
+handle_call({port, Instruction}, From, #state{last_trans=Last} = State) ->
+ try is_port_command(Instruction, element(1, From), State) of
+ {ok, Term} ->
+ erlang:port_command(State#state.port, term_to_binary({0, Term})),
+ {reply, ok, State};
+ {ok, Term, Link, PidOpts} ->
+ Next = next_trans(Last),
+ erlang:port_command(State#state.port, term_to_binary({Next, Term})),
+ {noreply, State#state{trans = queue:in({Next, From, Link, PidOpts}, State#state.trans)}}
+ catch _:{error, Why} ->
+ {reply, {error, Why}, State}
+ end;
+
+handle_call({pid, OsPid}, _From, State) ->
+ case ets:lookup(exec_mon, OsPid) of
+ [{_, Pid}] -> {reply, Pid, State};
+ _ -> {reply, undefined, State}
+ end;
+
+handle_call(Request, _From, _State) ->
+ {stop, {not_implemented, Request}}.
+
+%%----------------------------------------------------------------------
+%% Func: handle_cast/2
+%% Returns: {noreply, State} |
+%% {noreply, State, Timeout} |
+%% {stop, Reason, State} (terminate/2 is called)
+%% @private
+%%----------------------------------------------------------------------
+handle_cast(_Msg, State) ->
+ {noreply, State}.
+
+%%----------------------------------------------------------------------
+%% Func: handle_info/2
+%% Returns: {noreply, State} |
+%% {noreply, State, Timeout} |
+%% {stop, Reason, State} (terminate/2 is called)
+%% @private
+%%----------------------------------------------------------------------
+handle_info({Port, {data, Bin}}, #state{port=Port, debug=Debug} = State) ->
+ Msg = binary_to_term(Bin),
+ debug(Debug, "~w got msg from port: ~p\n", [?MODULE, Msg]),
+ case Msg of
+ {N, Reply} when N =/= 0 ->
+ case get_transaction(State#state.trans, N) of
+ {true, {Pid,_} = From, MonType, PidOpts, Q} ->
+ NewReply = maybe_add_monitor(Reply, Pid, MonType, PidOpts, Debug),
+ gen_server:reply(From, NewReply);
+ {false, Q} ->
+ ok
+ end,
+ {noreply, State#state{trans=Q}};
+ {0, {Stream, OsPid, Data}} when Stream =:= stdout; Stream =:= stderr ->
+ send_to_ospid_owner(OsPid, {Stream, Data}),
+ {noreply, State};
+ {0, {exit_status, OsPid, Status}} ->
+ debug(Debug, "Pid ~w exited with status: ~s{~w,~w}\n",
+ [OsPid, if (((Status band 16#7F)+1) bsr 1) > 0 -> "signaled "; true -> "" end,
+ (Status band 16#FF00 bsr 8), Status band 127]),
+ notify_ospid_owner(OsPid, Status),
+ {noreply, State};
+ {0, Ignore} ->
+ error_logger:warning_msg("~w [~w] unknown msg: ~p\n", [self(), ?MODULE, Ignore]),
+ {noreply, State}
+ end;
+
+handle_info({Port, {exit_status, 0}}, #state{port=Port} = State) ->
+ {stop, normal, State};
+handle_info({Port, {exit_status, Status}}, #state{port=Port} = State) ->
+ {stop, {exit_status, Status}, State};
+handle_info({'EXIT', Port, Reason}, #state{port=Port} = State) ->
+ {stop, Reason, State};
+handle_info({'EXIT', Pid, Reason}, State) ->
+ % OsPid's Pid owner died. Kill linked OsPid.
+ do_unlink_ospid(Pid, Reason, State),
+ {noreply, State};
+handle_info(_Info, State) ->
+ error_logger:info_msg("~w - unhandled message: ~p\n", [?MODULE, _Info]),
+ {noreply, State}.
+
+%%----------------------------------------------------------------------
+%% Func: code_change/3
+%% Purpose: Convert process state when code is changed
+%% Returns: {ok, NewState}
+%% @private
+%%----------------------------------------------------------------------
+code_change(_OldVsn, State, _Extra) ->
+ {ok, State}.
+
+%%----------------------------------------------------------------------
+%% Func: terminate/2
+%% Purpose: Shutdown the server
+%% Returns: any (ignored by gen_server)
+%% @private
+%%----------------------------------------------------------------------
+terminate(_Reason, State) ->
+ try
+ erlang:port_command(State#state.port, term_to_binary({0, {shutdown}})),
+ case wait_port_exit(State#state.port) of
+ 0 -> ok;
+ S -> error_logger:warning_msg("~w - exec process terminated (status: ~w)\n",
+ [self(), S])
+ end
+ catch _:_ ->
+ ok
+ end.
+
+wait_port_exit(Port) ->
+ receive
+ {Port,{exit_status,Status}} ->
+ Status;
+ _ ->
+ wait_port_exit(Port)
+ end.
+
+%%%---------------------------------------------------------------------
+%%% Internal functions
+%%%---------------------------------------------------------------------
+
+-spec do_run(Cmd::any(), Options::cmd_options()) ->
+ {ok, pid(), ospid()} | {ok, [{stdout | stderr, [binary()]}]} | {error, any()}.
+do_run(Cmd, Options) ->
+ Sync = proplists:get_value(sync, Options, false),
+ Mon = Sync =:= true orelse proplists:get_value(monitor, Options),
+ Link = case proplists:get_value(link, Options) of
+ true -> link;
+ _ -> nolink
+ end,
+ Cmd2 = {port, {Cmd, Link}},
+ case {Mon, gen_server:call(?MODULE, Cmd2, 30000)} of
+ {true, {ok, Pid, OsPid} = R} ->
+ Ref = monitor(process, Pid),
+ case Sync of
+ true -> wait_for_ospid_exit(OsPid, Ref, [], []);
+ _ -> R
+ end;
+ {_, R} ->
+ R
+ end.
+
+wait_for_ospid_exit(OsPid, Ref, OutAcc, ErrAcc) ->
+ receive
+ {stdout, OsPid, Data} ->
+ wait_for_ospid_exit(OsPid, Ref, [Data | OutAcc], ErrAcc);
+ {stderr, OsPid, Data} ->
+ wait_for_ospid_exit(OsPid, Ref, OutAcc, [Data | ErrAcc]);
+ {'DOWN', Ref, process, _, normal} ->
+ {ok, sync_res(OutAcc, ErrAcc)};
+ {'DOWN', Ref, process, _, noproc} ->
+ {ok, sync_res(OutAcc, ErrAcc)};
+ {'DOWN', Ref, process, _, {exit_status,_}=R} ->
+ {error, [R | sync_res(OutAcc, ErrAcc)]};
+ Other ->
+ {error, [{reason, Other} | sync_res(OutAcc, ErrAcc)]}
+ end.
+
+sync_res([], []) -> [];
+sync_res([], L) -> [{stderr, lists:reverse(L)}];
+sync_res(LO, LE) -> [{stdout, lists:reverse(LO)} | sync_res([], LE)].
+
+%% Add a link for Pid to OsPid if requested.
+maybe_add_monitor({ok, OsPid}, Pid, MonType, PidOpts, Debug) when is_integer(OsPid) ->
+ % This is a reply to a run/run_link command. The port program indicates
+ % of creating a new OsPid process.
+ % Spawn a light-weight process responsible for monitoring this OsPid
+ Self = self(),
+ LWP = spawn_link(fun() -> ospid_init(Pid, OsPid, MonType, Self, PidOpts, Debug) end),
+ ets:insert(exec_mon, [{OsPid, LWP}, {LWP, OsPid}]),
+ {ok, LWP, OsPid};
+maybe_add_monitor(Reply, _Pid, _MonType, _PidOpts, _Debug) ->
+ Reply.
+
+%%----------------------------------------------------------------------
+%% @spec (Pid, OsPid::integer(), LinkType, Parent, PidOpts::list(), Debug::boolean()) ->
+%% void()
+%% @doc Every OsPid is associated with an Erlang process started with
+%% this function. The `Parent' is the ?MODULE port manager that
+%% spawned this process and linked to it. `Pid' is the process
+%% that ran an OS command associated with OsPid. If that process
+%% requested a link (LinkType = 'link') we'll link to it.
+%% @end
+%% @private
+%%----------------------------------------------------------------------
+ospid_init(Pid, OsPid, LinkType, Parent, PidOpts, Debug) ->
+ process_flag(trap_exit, true),
+ StdOut = proplists:get_value(stdout, PidOpts),
+ StdErr = proplists:get_value(stderr, PidOpts),
+ case LinkType of
+ link -> link(Pid); % The caller pid that requested to run the OsPid command & link to it.
+ _ -> ok
+ end,
+ ospid_loop({Pid, OsPid, Parent, StdOut, StdErr, Debug}).
+
+ospid_loop({Pid, OsPid, Parent, StdOut, StdErr, Debug} = State) ->
+ receive
+ {{From, Ref}, ospid} ->
+ From ! {Ref, OsPid},
+ ospid_loop(State);
+ {stdout, Data} when is_binary(Data) ->
+ ospid_deliver_output(StdOut, {stdout, OsPid, Data}),
+ ospid_loop(State);
+ {stderr, Data} when is_binary(Data) ->
+ ospid_deliver_output(StdErr, {stderr, OsPid, Data}),
+ ospid_loop(State);
+ {'DOWN', OsPid, {exit_status, Status}} ->
+ debug(Debug, "~w ~w got down message (~w)\n", [self(), OsPid, status(Status)]),
+ % OS process died
+ case Status of
+ 0 -> exit(normal);
+ _ -> exit({exit_status, Status})
+ end;
+ {'EXIT', Pid, Reason} ->
+ % Pid died
+ debug(Debug, "~w ~w got exit from linked ~w: ~p\n", [self(), OsPid, Pid, Reason]),
+ exit({owner_died, Reason});
+ {'EXIT', Parent, Reason} ->
+ % Port program died
+ debug(Debug, "~w ~w got exit from parent ~w: ~p\n", [self(), OsPid, Parent, Reason]),
+ exit({port_closed, Reason});
+ Other ->
+ error_logger:warning_msg("~w - unknown msg: ~p\n", [self(), Other]),
+ ospid_loop(State)
+ end.
+
+ospid_deliver_output(DestPid, Msg) when is_pid(DestPid) ->
+ DestPid ! Msg;
+ospid_deliver_output(DestFun, {Stream, OsPid, Data}) when is_function(DestFun) ->
+ DestFun(Stream, OsPid, Data).
+
+notify_ospid_owner(OsPid, Status) ->
+ % See if there is a Pid owner of this OsPid. If so, sent the 'DOWN' message.
+ case ets:lookup(exec_mon, OsPid) of
+ [{_OsPid, Pid}] ->
+ unlink(Pid),
+ Pid ! {'DOWN', OsPid, {exit_status, Status}},
+ ets:delete(exec_mon, Pid),
+ ets:delete(exec_mon, OsPid);
+ [] ->
+ %error_logger:warning_msg("Owner ~w not found\n", [OsPid]),
+ ok
+ end.
+
+send_to_ospid_owner(OsPid, Msg) ->
+ case ets:lookup(exec_mon, OsPid) of
+ [{_, Pid}] -> Pid ! Msg;
+ _ -> ok
+ end.
+
+debug(false, _, _) ->
+ ok;
+debug(true, Fmt, Args) ->
+ io:format(Fmt, Args).
+
+%%----------------------------------------------------------------------
+%% @spec (Pid::pid(), Action, State::#state{}) ->
+%% {ok, LastTok::integer(), LeftLinks::integer()}
+%% @doc Pid died or requested to unlink - remove linked Pid records and
+%% optionally kill all OsPids linked to the Pid.
+%% @end
+%%----------------------------------------------------------------------
+do_unlink_ospid(Pid, _Reason, State) ->
+ case ets:lookup(exec_mon, Pid) of
+ [{_Pid, OsPid}] when is_integer(OsPid) ->
+ debug(State#state.debug, "Pid ~p died. Killing linked OsPid ~w\n", [Pid, OsPid]),
+ ets:delete(exec_mon, Pid),
+ ets:delete(exec_mon, OsPid),
+ erlang:port_command(State#state.port, term_to_binary({0, {stop, OsPid}}));
+ _ ->
+ ok
+ end.
+
+get_transaction(Q, I) ->
+ get_transaction(Q, I, Q).
+get_transaction(Q, I, OldQ) ->
+ case queue:out(Q) of
+ {{value, {I, From, LinkType, PidOpts}}, Q2} ->
+ {true, From, LinkType, PidOpts, Q2};
+ {empty, _} ->
+ {false, OldQ};
+ {_, Q2} ->
+ get_transaction(Q2, I, OldQ)
+ end.
+
+is_port_command({{run, Cmd, Options}, Link}, Pid, State) ->
+ {PortOpts, Other} = check_cmd_options(Options, Pid, State, [], []),
+ {ok, {run, Cmd, PortOpts}, Link, Other};
+is_port_command({list} = T, _Pid, _State) ->
+ {ok, T, undefined, []};
+is_port_command({stop, OsPid}=T, _Pid, _State) when is_integer(OsPid) ->
+ {ok, T, undefined, []};
+is_port_command({stop, Pid}, _Pid, _State) when is_pid(Pid) ->
+ case ets:lookup(exec_mon, Pid) of
+ [{_StoredPid, OsPid}] -> {ok, {stop, OsPid}, undefined, []};
+ [] -> throw({error, no_process})
+ end;
+is_port_command({{manage, OsPid, Options}, Link}, Pid, State) when is_integer(OsPid) ->
+ {PortOpts, _Other} = check_cmd_options(Options, Pid, State, [], []),
+ {ok, {manage, OsPid, PortOpts}, Link, []};
+is_port_command({send, Pid, Data}, _Pid, _State) when is_pid(Pid), is_binary(Data) ->
+ case ets:lookup(exec_mon, Pid) of
+ [{Pid, OsPid}] -> {ok, {stdin, OsPid, Data}};
+ [] -> throw({error, no_process})
+ end;
+is_port_command({send, OsPid, Data}, _Pid, _State) when is_integer(OsPid), is_binary(Data) ->
+ {ok, {stdin, OsPid, Data}};
+is_port_command({kill, OsPid, Sig}=T, _Pid, _State) when is_integer(OsPid),is_integer(Sig) ->
+ {ok, T, undefined, []};
+is_port_command({setpgid, OsPid, Gid}=T, _Pid, _State) when is_integer(OsPid),is_integer(Gid) ->
+ {ok, T, undefined, []};
+is_port_command({kill, Pid, Sig}, _Pid, _State) when is_pid(Pid),is_integer(Sig) ->
+ case ets:lookup(exec_mon, Pid) of
+ [{Pid, OsPid}] -> {ok, {kill, OsPid, Sig}, undefined, []};
+ [] -> throw({error, no_process})
+ end.
+
+check_cmd_options([monitor|T], Pid, State, PortOpts, OtherOpts) ->
+ check_cmd_options(T, Pid, State, PortOpts, OtherOpts);
+check_cmd_options([sync|T], Pid, State, PortOpts, OtherOpts) ->
+ check_cmd_options(T, Pid, State, PortOpts, OtherOpts);
+check_cmd_options([link|T], Pid, State, PortOpts, OtherOpts) ->
+ check_cmd_options(T, Pid, State, PortOpts, OtherOpts);
+check_cmd_options([{executable,V}=H|T], Pid, State, PortOpts, OtherOpts) when is_list(V) ->
+ check_cmd_options(T, Pid, State, [H|PortOpts], OtherOpts);
+check_cmd_options([{cd, Dir}=H|T], Pid, State, PortOpts, OtherOpts) when is_list(Dir) ->
+ check_cmd_options(T, Pid, State, [H|PortOpts], OtherOpts);
+check_cmd_options([{env, Env}=H|T], Pid, State, PortOpts, OtherOpts) when is_list(Env) ->
+ case lists:filter(fun(S) when is_list(S) -> false;
+ ({S1,S2}) when is_list(S1), is_list(S2) -> false;
+ (_) -> true
+ end, Env) of
+ [] -> check_cmd_options(T, Pid, State, [H|PortOpts], OtherOpts);
+ L -> throw({error, {invalid_env_value, L}})
+ end;
+check_cmd_options([{kill, Cmd}=H|T], Pid, State, PortOpts, OtherOpts) when is_list(Cmd) ->
+ check_cmd_options(T, Pid, State, [H|PortOpts], OtherOpts);
+check_cmd_options([{kill_timeout, I}=H|T], Pid, State, PortOpts, OtherOpts) when is_integer(I), I >= 0 ->
+ check_cmd_options(T, Pid, State, [H|PortOpts], OtherOpts);
+check_cmd_options([kill_group=H|T], Pid, State, PortOpts, OtherOpts) ->
+ check_cmd_options(T, Pid, State, [H|PortOpts], OtherOpts);
+check_cmd_options([{nice, I}=H|T], Pid, State, PortOpts, OtherOpts) when is_integer(I), I >= -20, I =< 20 ->
+ check_cmd_options(T, Pid, State, [H|PortOpts], OtherOpts);
+check_cmd_options([{success_exit_code, I}=H|T], Pid, State, PortOpts, OtherOpts)
+ when is_integer(I), I >= 0, I < 256 ->
+ check_cmd_options(T, Pid, State, [H|PortOpts], OtherOpts);
+check_cmd_options([H|T], Pid, State, PortOpts, OtherOpts) when H=:=stdin; H=:=stdout; H=:=stderr ->
+ check_cmd_options(T, Pid, State, [H|PortOpts], [{H, Pid}|OtherOpts]);
+check_cmd_options([H|T], Pid, State, PortOpts, OtherOpts) when H=:=pty ->
+ check_cmd_options(T, Pid, State, [H|PortOpts], [{H, Pid}|OtherOpts]);
+check_cmd_options([{stdin, I}=H|T], Pid, State, PortOpts, OtherOpts)
+ when I=:=null; I=:=close; is_list(I) ->
+ check_cmd_options(T, Pid, State, [H|PortOpts], OtherOpts);
+check_cmd_options([{Std, I, Opts}=H|T], Pid, State, PortOpts, OtherOpts)
+ when (Std=:=stdout orelse Std=:=stderr), is_list(Opts) ->
+ io_lib:printable_list(I) orelse
+ throw({error, ?FMT("Invalid ~w filename: ~200p", [Std, I])}),
+ lists:foreach(fun
+ (append) -> ok;
+ ({mode, Mode}) when is_integer(Mode) -> ok;
+ (Other) -> throw({error, ?FMT("Invalid ~w option: ~p", [Std, Other])})
+ end, Opts),
+ check_cmd_options(T, Pid, State, [H|PortOpts], OtherOpts);
+check_cmd_options([{Std, I}=H|T], Pid, State, PortOpts, OtherOpts)
+ when Std=:=stderr, I=/=Std; Std=:=stdout, I=/=Std ->
+ if
+ I=:=null; I=:=close; I=:=stderr; I=:=stdout; is_list(I) ->
+ check_cmd_options(T, Pid, State, [H|PortOpts], OtherOpts);
+ I=:=print ->
+ check_cmd_options(T, Pid, State, [Std | PortOpts], [{Std, fun print/3} | OtherOpts]);
+ is_pid(I) ->
+ check_cmd_options(T, Pid, State, [Std | PortOpts], [H|OtherOpts]);
+ is_function(I) ->
+ {arity, 3} =:= erlang:fun_info(I, arity)
+ orelse throw({error, ?FMT("Invalid ~w option ~p: expected Fun/3", [Std, I])}),
+ check_cmd_options(T, Pid, State, [Std | PortOpts], [H|OtherOpts]);
+ true ->
+ throw({error, ?FMT("Invalid ~w option ~p", [Std, I])})
+ end;
+check_cmd_options([{group, I}=H|T], Pid, State, PortOpts, OtherOpts) when is_integer(I), I >= 0; is_list(I) ->
+ check_cmd_options(T, Pid, State, [H|PortOpts], OtherOpts);
+check_cmd_options([{user, U}=H|T], Pid, State, PortOpts, OtherOpts) when is_list(U), U =/= "" ->
+ case lists:member(U, State#state.limit_users) of
+ true -> check_cmd_options(T, Pid, State, [H|PortOpts], OtherOpts);
+ false -> throw({error, ?FMT("User ~s is not allowed to run commands!", [U])})
+ end;
+check_cmd_options([Other|_], _Pid, _State, _PortOpts, _OtherOpts) ->
+ throw({error, {invalid_option, Other}});
+check_cmd_options([], _Pid, _State, PortOpts, OtherOpts) ->
+ {PortOpts, OtherOpts}.
+
+next_trans(I) when I =< 134217727 ->
+ I+1;
+next_trans(_) ->
+ 1.
+
+print(Stream, OsPid, Data) ->
+ io:format("Got ~w from ~w: ~p\n", [Stream, OsPid, Data]).
+
+%%%---------------------------------------------------------------------
+%%% Unit testing
+%%%---------------------------------------------------------------------
+
+-ifdef(EUNIT).
+
+-define(receiveMatch(A, Timeout),
+ (fun() ->
+ receive
+ _M -> ?assertMatch(A, _M)
+ after Timeout ->
+ ?assertMatch(A, timeout)
+ end
+ end)()).
+
+-define(tt(F), {timeout, 20, ?_test(F)}).
+
+temp_file() ->
+ Dir = case os:getenv("TEMP") of
+ false -> "/tmp";
+ Path -> Path
+ end,
+ {I1, I2, I3} = now(),
+ filename:join(Dir, io_lib:format("exec_temp_~w_~w_~w", [I1, I2, I3])).
+
+exec_test_() ->
+ {setup,
+ fun() -> {ok, Pid} = exec:start([{debug, 3}]), Pid end,
+ fun(Pid) -> exit(Pid, kill) end,
+ [
+ ?tt(test_monitor()),
+ ?tt(test_sync()),
+ ?tt(test_stdin()),
+ ?tt(test_std(stdout)),
+ ?tt(test_std(stderr)),
+ ?tt(test_cmd()),
+ ?tt(test_executable()),
+ ?tt(test_redirect()),
+ ?tt(test_env()),
+ ?tt(test_kill_timeout()),
+ ?tt(test_setpgid()),
+ ?tt(test_pty())
+ ]
+ }.
+
+test_monitor() ->
+ {ok, P, _} = exec:run("echo ok", [{stdout, null}, monitor]),
+ ?receiveMatch({'DOWN', _, process, P, normal}, 5000).
+
+test_sync() ->
+ ?assertMatch({ok, [{stdout, [<<"Test\n">>]}, {stderr, [<<"ERR\n">>]}]},
+ exec:run("echo Test; echo ERR 1>&2", [stdout, stderr, sync])).
+
+test_stdin() ->
+ {ok, P, I} = exec:run("read x; echo \"Got: $x\"", [stdin, stdout, monitor]),
+ ok = exec:send(I, <<"Test data\n">>),
+ ?receiveMatch({stdout,I,<<"Got: Test data\n">>}, 3000),
+ ?receiveMatch({'DOWN', _, process, P, normal}, 5000).
+
+test_std(Stream) ->
+ Suffix = case Stream of
+ stderr -> " 1>&2";
+ stdout -> ""
+ end,
+ {ok, _, I} = exec:run("for i in 1 2; do echo TEST$i; sleep 0.05; done" ++ Suffix, [Stream]),
+ ?receiveMatch({Stream,I,<<"TEST1\n">>}, 5000),
+ ?receiveMatch({Stream,I,<<"TEST2\n">>}, 5000),
+
+ Filename = temp_file(),
+ try
+ ?assertMatch({ok, []}, exec:run("echo Test"++Suffix, [{Stream, Filename}, sync])),
+ ?assertMatch({ok, <<"Test\n">>}, file:read_file(Filename)),
+
+ ?assertMatch({ok, []}, exec:run("echo Test"++Suffix, [{Stream, Filename}, sync])),
+ ?assertMatch({ok, <<"Test\n">>}, file:read_file(Filename)),
+
+ ?assertMatch({ok, []}, exec:run("echo Test2"++Suffix, [{Stream, Filename, [append]}, sync])),
+ ?assertMatch({ok, <<"Test\nTest2\n">>}, file:read_file(Filename))
+
+ after
+ ?assertEqual(ok, file:delete(Filename))
+ end.
+
+test_cmd() ->
+ % Cmd given as string
+ ?assertMatch(
+ {ok, [{stdout, [<<"ok\n">>]}]},
+ exec:run("/bin/echo ok", [sync, stdout])),
+ % Cmd given as list
+ ?assertMatch(
+ {ok, [{stdout, [<<"ok\n">>]}]},
+ exec:run(["/bin/bash", "-c", "echo ok"], [sync, stdout])),
+ ?assertMatch(
+ {ok, [{stdout, [<<"ok\n">>]}]},
+ exec:run(["/bin/echo", "ok"], [sync, stdout])).
+
+test_executable() ->
+ % Cmd given as string
+ ?assertMatch(
+ [<<"Pid ", _/binary>>, <<" cannot execute '00kuku00': No such file or directory\n">>],
+ begin
+ {error, [{exit_status,_}, {stderr, [E]}]} =
+ exec:run("ls", [sync, {executable, "00kuku00"}, stdout, stderr]),
+ binary:split(E, <<":">>)
+ end),
+
+ ?assertMatch(
+ {ok, [{stdout,[<<"ok\n">>]}]},
+ exec:run("echo ok", [sync, {executable, "/bin/sh"}, stdout, stderr])),
+
+ % Cmd given as list
+ ?assertMatch(
+ {ok, [{stdout,[<<"ok\n">>]}]},
+ exec:run(["/bin/bash", "-c", "/bin/echo ok"],
+ [sync, {executable, "/bin/sh"}, stdout, stderr])),
+ ?assertMatch(
+ {ok, [{stdout,[<<"XYZ\n">>]}]},
+ exec:run(["/bin/echoXXXX abc", "XYZ"],
+ [sync, {executable, "/bin/echo"}, stdout, stderr])).
+
+test_redirect() ->
+ ?assertMatch({ok,[{stderr,[<<"TEST1\n">>]}]},
+ exec:run("echo TEST1", [stderr, {stdout, stderr}, sync])),
+ ?assertMatch({ok,[{stdout,[<<"TEST2\n">>]}]},
+ exec:run("echo TEST2 1>&2", [stdout, {stderr, stdout}, sync])),
+ ok.
+
+test_env() ->
+ ?assertMatch({ok, [{stdout, [<<"X\n">>]}]},
+ exec:run("echo $XXX", [stdout, {env, [{"XXX", "X"}]}, sync])).
+
+test_kill_timeout() ->
+ {ok, P, I} = exec:run("trap '' SIGTERM; sleep 30", [{kill_timeout, 1}, monitor]),
+ exec:stop(I),
+ ?receiveMatch({'DOWN', _, process, P, normal}, 5000).
+
+test_setpgid() ->
+ % Cmd given as string
+ {ok, P0, P} = exec:run("sleep 1", [{group, 0}, kill_group, monitor]),
+ {ok, P1, _} = exec:run("sleep 15", [{group, P}, monitor]),
+ {ok, P2, _} = exec:run("sleep 15", [{group, P}, monitor]),
+ ?receiveMatch({'DOWN',_,process, P0, normal}, 5000),
+ ?receiveMatch({'DOWN',_,process, P1, {exit_status, 15}}, 5000),
+ ?receiveMatch({'DOWN',_,process, P2, {exit_status, 15}}, 5000).
+
+test_pty() ->
+ ?assertMatch({error,[{exit_status,256},{stdout,[<<"not a tty\n">>]}]},
+ exec:run("tty", [stdin, stdout, sync])),
+ ?assertMatch({ok,[{stdout,[<<"/dev/pts/", _/binary>>]}]},
+ exec:run("tty", [stdin, stdout, pty, sync])),
+ {ok, P, I} = exec:run("/bin/bash --norc -i", [stdin, stdout, pty, monitor]),
+ exec:send(I, <<"echo ok\n">>),
+ receive
+ {stdout, I, <<"echo ok\r\n">>} ->
+ ?receiveMatch({stdout, I, <<"ok\r\n">>}, 1000);
+ {stdout, I, <<"ok\r\n">>} ->
+ ok
+ after 1000 ->
+ ?assertMatch({stdout, I, <<"ok\r\n">>}, timeout)
+ end,
+ exec:send(I, <<"exit\n">>),
+ ?receiveMatch({'DOWN', _, process, P, normal}, 1000).
+
+-endif.
diff --git a/deps/exec/src/exec_app.erl b/deps/exec/src/exec_app.erl
new file mode 100644
index 0000000..94077ba
--- /dev/null
+++ b/deps/exec/src/exec_app.erl
@@ -0,0 +1,78 @@
+%%%------------------------------------------------------------------------
+%%% File: $Id$
+%%%------------------------------------------------------------------------
+%%% @doc This module implements application and supervisor behaviors
+%%% of the `exec' application.
+%%% @author Serge Aleynikov <saleyn@gmail.com>
+%%% @version $Revision: 1.1 $
+%%% @end
+%%%----------------------------------------------------------------------
+%%% Created: 2003-06-25 by Serge Aleynikov <saleyn@gmail.com>
+%%% $URL$
+%%%------------------------------------------------------------------------
+-module(exec_app).
+-author('saleyn@gmail.com').
+-id ("$Id$").
+
+-behaviour(application).
+-behaviour(supervisor).
+
+%% application and supervisor callbacks
+-export([start/2, stop/1, init/1]).
+
+%%%----------------------------------------------------------------------
+%%% API
+%%%----------------------------------------------------------------------
+
+%%----------------------------------------------------------------------
+%% This is the entry module for your application. It contains the
+%% start function and some other stuff. You identify this module
+%% using the 'mod' attribute in the .app file.
+%%
+%% The start function is called by the application controller.
+%% It normally returns {ok,Pid}, i.e. the same as gen_server and
+%% supervisor. Here, we simply call the start function in our supervisor.
+%% One can also return {ok, Pid, State}, where State is reused in stop(State).
+%%
+%% Type can be 'normal' or {takeover,FromNode}. If the 'start_phases'
+%% attribute is present in the .app file, Type can also be {failover,FromNode}.
+%% This is an odd compatibility thing.
+%% @private
+%%----------------------------------------------------------------------
+start(_Type, _Args) ->
+ supervisor:start_link({local, ?MODULE}, ?MODULE, []).
+
+%%----------------------------------------------------------------------
+%% stop(State) is called when the application has been terminated, and
+%% all processes are gone. The return value is ignored.
+%% @private
+%%----------------------------------------------------------------------
+stop(_S) ->
+ ok.
+
+%%%---------------------------------------------------------------------
+%%% Supervisor behaviour callbacks
+%%%---------------------------------------------------------------------
+
+%% @private
+init([]) ->
+ Options =
+ lists:foldl(
+ fun(I, Acc) -> add_option(I, Acc) end,
+ [], [I || {I, _} <- exec:default()]),
+ {ok, {
+ {one_for_one, 3, 30}, % Allow MaxR restarts within MaxT seconds
+ [{ exec, % Id = internal id
+ {exec, start_link, [Options]}, % StartFun = {M, F, A}
+ permanent, % Restart = permanent | transient | temporary
+ 10000, % Shutdown - wait 10 seconds, to give child processes time to be killed off.
+ worker, % Type = worker | supervisor
+ [exec] % Modules = [Module] | dynamic
+ }]
+ }}.
+
+add_option(Option, Acc) ->
+ case application:get_env(exec, Option) of
+ {ok, Value} -> [{Option, Value} | Acc];
+ undefined -> Acc
+ end.
diff --git a/deps/exec/src/overview.edoc b/deps/exec/src/overview.edoc
new file mode 100644
index 0000000..79a32ad
--- /dev/null
+++ b/deps/exec/src/overview.edoc
@@ -0,0 +1,305 @@
+
+ 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