[Tarantool-patches] [PATCH v6 3/4] popen/lua: Add popen module

Maxim Melentiev m.melentiev at corp.mail.ru
Fri Dec 20 18:41:53 MSK 2019


0) Should we change the module name to `subprocess`?

1) Let's change doc's overview to
```
This module allows to start child processes and gives asynchronous 
access to their stdin, stdout, stderr.
```

2) Errors should be returned with `nil, err`. Successful results - 
`val1, val2, val3`.
Please use `err` in all code examples when apliable to make people copy 
the code in the right way:
```
process, err = subprocess.popen(...)
str, err = process:read()
```

3) I suggest using `subprocess.` for module-level functions and 
`process:` for process-instance methods in docs to avoid ambiguity.

4) Let's rename `.posix` -> `.popen` (because it actually conforms with 
this syscall) and `.popen` -> `.spawn`.

5) Let's move values out of `flags` to the outer options for all 
methods: `.spawn({argv = {...}, stdin = false, shell = false, ...})`.

6) `env` option should be map-table but not an array (`{WORKDIR = 
'some/path'}`).

7) Should not `start_new_session` be disabled by default? By default 
child processes should exit if parent exits.

8) `chdir` option to cwd before running a process.

9) Standard fds should not be closed by default to make it possible to 
read/write to spawned process. With closed fds `.popen({argv = {'echo', 
'test'}})` will fail because it will not be able to write to closed fd. 
Moreover it should be possible to set child fds to parent ones and this 
should be the default. Let's consider following options (constants are 
cdata values):
   - `true/subprocess.PIPE` - create pipe between parent and child for 
particular fd;
   - `false` - close fd;
   - `subprocess.DEVNULL` - forward to `/dev/null`;
   - `io/fio object` - use fd from this object;
   - `nil` (default) - use corresponding fd from parent process.


10) As of std* can be available in the child process in some cases and 
not available in others, there will be corner cases for `:read/:write`. 
I suggest to keep this straightforward and keep this checks in user 
code: create `.stdin, .stdout, .stderr` fields in process only when 
relevant pipe is created. There is no need for`stder=true|false` 
argument anymore.


11) Let's keep `write2` and `read2` private (or remove them if they no 
longer required).

12) Let's make `:read` api consistent with existing apis. Although this 
isn't easy while `fio:read` does not have `timeout` option and 
`socket:read` has complicated `delimiter` option and does not have 
`buffer` option. I think `:read({
   timeout=sec[0 - no wait],
   limit=at_most_bytes_to_read_without_yield[nil - no limit]
})` should be enough. I don't know if `buffer` from `fio:read` is 
useful, anyway it can be added later.

13) Clarify when `:read` yields. It'll be great if with `timeout = 0` it 
would not yield.

14) Clarify when `:write` yields, how/when it retries writes 
(SIGPIPE/epol/interval/etc.).

15) What if it has written just a part of payload before timeout? I 
think it should always return number of bytes has been written and in 
case of timeout error should also be provided along with the result.

16) Signal constants need to be provided in lua api. This is because 
some of signals have different values in different OS (ex., `SIGSTOP`, 
it's used in cartridge tests). Then let's keep only single `kill(signal 
= 15)` method.

17) I think current `popen.c.` values can be skipped. It's better to 
return original flags table in `:info().flags` o even skip this field as 
well. And string value from `:state()` because there is no static 
checker for constants in lua. This also will be similar to `fiber:status()`.

18) Let's not provide separate `:state` method because `:info` has the 
same data.


On 17/12/2019 15:54, Cyrill Gorcunov wrote:
> @TarantoolBot document
> Title: popen module
>
> Overview
> ========
>
> Tarantool supports execution of external programs similarly
> to well known Python's `subprocess` or Ruby's `Open3`. Note
> though the `popen` module does not match one to one to the
> helpers these languages provide and provides only basic
> functions. The popen object creation is implemented via
> `vfork()` system call which means the caller thread is
> blocked until execution of a child process begins.
>
> Functions
> =========
>
> The `popen` module provides two functions to create that named
> popen object `popen.posix` which is the similar to libc `posix`
> syscall and `popen.popen` to create popen object with more
> specific options.
>
> `handler, err = popen.posix(command, mode)`
> -------------------------------------------
>
> Creates a new child process and executes a command as the "sh -c command".
>
> Parameters
> ```
> command     is the command to run
>
> mode        'r' - to grab child's stdout for read
>              'w' - to write into child's stdin stream
>              'rw' - combined mode to read and write
> ```
>
> If `mode` has `r` then `stderr` output stream is always opened to be
> able to redirect `stderr` to `stdout` by well known `2>&1` statement.
> If `mode` is not specified at all then non of the std stram will be
> opened inside a child process.
>
> On success returns popen object handler setting `err` to nil,
> otherwise error reported.
>
> __Example 1__
> ```
> ph = require('popen').posix("date", "r")
> ph:read()
> ph:close()
> ```
>
> Executes "sh -c date" then reads the output and closes the popen object.
>
> `handler, err = popen.popen(opts)`
> ----------------------------------
>
> Creates a new child process and execute a program inside. The `opts` is
> options table with the following keys
>
> ```
> argv        an array containing the binary to execute together
>              with command line arguments; this is the only mandatory
>              key in the options table
>
> env         an array of environment variables, if not set then they
>              are inherited; if env=nil then environment will be empty
>
> flags       a dictionary to describe communication pipes and other
>              parameters of a process to run
>
>              stdin=true
>                  to write into STDIN_FILENO of a child process
>              stdin=false
>                  to close STDIN_FILENO inside a child process, default
>              stdin="devnull"
>                  a child will get /dev/null as STDIN_FILENO
>
>              stdout=true
>                  to read STDOUT_FILENO of a child process
>              stdout=false
>                  to close STDOUT_FILENO inside a child process, default
>              stdout="devnull"
>                  a child will get /dev/null as STDOUT_FILENO
>
>              stderr=true
>                  to read STDERR_FILENO of a child process
>              stderr=false
>                  to close STDERR_FILENO inside a child process, default
>              stderr="devnull"
>                  a child will get /dev/null as STDERR_FILENO
>
>              shell=true
>                  runs a child process via "sh -c", default
>              shell=false
>                  invokes a child process executable directly
>
>              close_fds=true
>                  close all inherited fds from a parent, default
>
>              restore_signals=true
>                  all signals modified by a caller reset
>                  to default handler, default
>
>              start_new_session=true
>                  start executable inside a new session, default
> ```
>
> On success returns popen object handler setting `err` to nil,
> otherwise error reported.
>
> __Example 2__
> ```
> ph, err = require('popen').popen({argv = {"/usr/bin/echo", "-n", "hello"},
>                                   flags = {stdout=true, shell=false}})
> ```
>
> `str, err = popen:read([stderr=true|false,timeout])`
> ----------------------------------------------------
>
> Reads data from `stdout` or `stderr` streams with `timeout`. By default
> it reads from `stdout` stream, to read from `stderr` stream set
> `stderr=true`.
>
> On success returns string `str` read and `err=nil`, otherwise `err ~= nil`.
>
> __Example 3__
> ```
> ph = require('popen').popen({argv = {"/usr/bin/echo", "-n", "hello"},
>                              flags = {stdout=true, shell=false}})
> ph:read()
> ---
> - hello
> ```
>
> `err = popen:write(str[,timeout])`
> ----------------------------------
>
> Writes string `str` to `stdin` stream of a child process. Since the internal
> buffer on the reader side is limited in size and depends on OS settings the `write`
> may hang forever if buffer is full. For this wake `timeout` (float number) parameter
> allows to specify number of seconds which once elapsed interrupt `write` attempts
> and exit from this function. When not provided the `write` blocks until data is
> written or error happened.
>
> On success returns `err = nil`, otherwise `err ~= nil`.
>
> __Example 4__
> ```
> script = "prompt=''; read -n 5 prompt && echo -n $prompt;"
> ph = require('popen').posix(script, "rw")
> ph:write("12345")
> ph:read()
> ---
> - '12345'
> ```
>
> `err = popen:write2(opts)`
> --------------------------
>
> Writes data from a buffer to a stream of a child process. `opts` is
> a table of options with the following keys
>
> ```
> buf     buffer with raw data to write
> size    number of bytes to write
> flags   dictionary to specify the input stream
>          stdin=true
>              to write into stdin stream
> timeout write timeout in seconds, optional
> ```
>
> Basically the `popen:write` is a wrapper over `popen:write2` call
> with `opts` similar to `{timeout = -1, {flags = {stdin = true}}`.
>
> On success `err = nil`, otherwise `err ~= nil`.
>
> The reason for passing `{flags = {stdin = true}}` while we have only
> one `stdin` is that we reserve the interface for future where we
> might extend it without breaking backward compatibility.
>
> `bytes, err = popen:read2(opts)`
> --------------------------------
>
> Reads data from a stream of a child process to a buffer. `opts` is
> a table of options with the following keys
>
> ```
> buf     destination buffer
> size    number of bytes to read
> flags   dictionary to specify the input stream
>          stdout=true
>              to read from stdout stream
>          stderr=true
>              to read from stderr stream
> timeout read timeout in seconds, optional
> ```
>
> Basically the `popen:read` is a wrapper over `popen:read2` call
> with `opts` similar to `{timeout = -1, {flags = {stdout = true}}`.
>
> On success returns number of `bytes` read and `err = nil`,
> otherwise `err ~= nil`.
>
> __Example 5__
> ```
> buffer = require('buffer')
> popen = require('popen')
> ffi = require('ffi')
>
> script = "echo -n 1 2 3 4 5 1>&2"
> buf = buffer.ibuf()
>
> ph = popen.posix(script, "rw")
> ph:wait()
> size = 128
> dst = buf:reserve(size)
> res, err = ph:read2({buf = dst, size = size, nil, flags = {stderr = true}})
> res = buf:alloc(res)
> ffi.string(buf.rpos, buf:size())
> ---
> - 1 2 3 4 5
> ...
> buf:recycle()
> ph:close()
> ```
>
> `res, err = popen:terminate()`
> -----------------------------
>
> Terminates a child process in a soft way sending `SIGTERM` signal.
> On success returns `res = true, err = nil`, `err ~= nil` otherwise.
>
> `res, err = popen:kill()`
> -------------------------
>
> Terminates a child process in a soft way sending `SIGKILL` signal.
> On success returns `res = true, err = nil`, `err ~= nil` otherwise.
>
> `res, err = popen:send_signal(signo)`
> -------------------------------------
>
> Sends signal with number `signo` to a child process.
> On success returns `res = true, err = nil`, `err ~= nil`
> otherwise.
>
> `info, err = popen:info()`
> --------------------------
>
> Returns information about a child process in form of table
> with the following keys
>
> ```
> pid         PID of a child process, if already terminated
>              the it set to -1
>
> flags       flags been used to create the popen object; this
>              is bitmap of popen.c.flag constants
>
> stdout      file descriptor number associated with stdout
>              stream on a parent side, -1 if not set
>
> stderr      file descriptor number associated with stderr
>              stream on a parent side, -1 if not set
>
> stdint      file descriptor number associated with stdin
>              stream on a parent side, -1 if not set
>
> state       state of a child proces: "alive" if process
>              is running, "exited" if finished execution,
>              "signaled" is terminated by a signal
>
> exit_code   if process is in "signaled" state then this
>              number represent the signal number process
>              been terminated with, if process is in "exited"
>              state then it represent the exit code
> ```
>
> __Example 6__
> ```
> ph = require('popen').posix("echo -n 1 2 3 4 5 1>&2", "r")
> ph:info()
> ---
> - stdout: 12
>    command: sh -c echo -n 1 2 3 4 5 1>&2
>    stderr: 23
>    flags: 61510
>    state: exited
>    stdin: -1
>    exit_code: 0
>    pid: -1
> ```
>
> `err, state, exit_code = popen:state()`
> --------------------------------------
>
> Returns the current state of a chile process.
>
> On success returns `err = nil`, `state` is one of the
> constants `popen.c.state.ALIVE=1` when process is running,
> `popen.c.state.EXITED=2` when it exited by self or
> `popen.c.state.SIGNALED=3` when terminated via signal.
> In case if the child process is terminated by a signal
> the `exit_code` contains the signal number, otherwise
> it is an error code the child set by self.
>
> __Example 7__
> ```
> ph = require('popen').posix("echo -n 1 2 3 4 5 1>&2", "r")
> ph:state()
> ---
> - null
> - 2
> - 0
> ```
>
> `err, state, exit_code = popen:wait()`
> --------------------------------------
>
> Waits until a child process get exited or terminated.
> Returns the same data as `popen:state()`. Basically
> the `popen:wait()` is simply polling for the child state
> with `popen:wait()` in a cycle.
>
> `err = popen:close`
> -------------------
>
> Closes popen object releasing all resources occupied.
>
> If a child process is running then it get killed first. Once
> popen object is closed it no longed usable and any attempt
> to call function over will cause an error.
>
> On success `err = nil`.
>
> Part-of #4031
>
> Signed-off-by: Cyrill Gorcunov <gorcunov at gmail.com>
> ---
>   src/CMakeLists.txt |   2 +
>   src/lua/init.c     |   4 +
>   src/lua/popen.c    | 483 ++++++++++++++++++++++++++++++++++++++++++
>   src/lua/popen.h    |  44 ++++
>   src/lua/popen.lua  | 516 +++++++++++++++++++++++++++++++++++++++++++++
>   5 files changed, 1049 insertions(+)
>   create mode 100644 src/lua/popen.c
>   create mode 100644 src/lua/popen.h
>   create mode 100644 src/lua/popen.lua
>
> diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
> index e12de6005..e68030c5e 100644
> --- a/src/CMakeLists.txt
> +++ b/src/CMakeLists.txt
> @@ -38,6 +38,7 @@ lua_source(lua_sources lua/help.lua)
>   lua_source(lua_sources lua/help_en_US.lua)
>   lua_source(lua_sources lua/tap.lua)
>   lua_source(lua_sources lua/fio.lua)
> +lua_source(lua_sources lua/popen.lua)
>   lua_source(lua_sources lua/csv.lua)
>   lua_source(lua_sources lua/strict.lua)
>   lua_source(lua_sources lua/clock.lua)
> @@ -114,6 +115,7 @@ set (server_sources
>        lua/socket.c
>        lua/pickle.c
>        lua/fio.c
> +     lua/popen.c
>        lua/httpc.c
>        lua/utf8.c
>        lua/info.c
> diff --git a/src/lua/init.c b/src/lua/init.c
> index 097dd8495..09f34d614 100644
> --- a/src/lua/init.c
> +++ b/src/lua/init.c
> @@ -56,6 +56,7 @@
>   #include "lua/msgpack.h"
>   #include "lua/pickle.h"
>   #include "lua/fio.h"
> +#include "lua/popen.h"
>   #include "lua/httpc.h"
>   #include "lua/utf8.h"
>   #include "lua/swim.h"
> @@ -99,6 +100,7 @@ extern char strict_lua[],
>   	help_en_US_lua[],
>   	tap_lua[],
>   	fio_lua[],
> +	popen_lua[],
>   	error_lua[],
>   	argparse_lua[],
>   	iconv_lua[],
> @@ -141,6 +143,7 @@ static const char *lua_modules[] = {
>   	"log", log_lua,
>   	"uri", uri_lua,
>   	"fio", fio_lua,
> +	"popen", popen_lua,
>   	"error", error_lua,
>   	"csv", csv_lua,
>   	"clock", clock_lua,
> @@ -455,6 +458,7 @@ tarantool_lua_init(const char *tarantool_bin, int argc, char **argv)
>   	tarantool_lua_errno_init(L);
>   	tarantool_lua_error_init(L);
>   	tarantool_lua_fio_init(L);
> +	tarantool_lua_popen_init(L);
>   	tarantool_lua_socket_init(L);
>   	tarantool_lua_pickle_init(L);
>   	tarantool_lua_digest_init(L);
> diff --git a/src/lua/popen.c b/src/lua/popen.c
> new file mode 100644
> index 000000000..219d9ce6d
> --- /dev/null
> +++ b/src/lua/popen.c
> @@ -0,0 +1,483 @@
> +/*
> + * Copyright 2010-2019, Tarantool AUTHORS, please see AUTHORS file.
> + *
> + * 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.
> + *
> + * THIS SOFTWARE IS PROVIDED BY <COPYRIGHT HOLDER> ``AS IS'' AND
> + * ANY EXPRESS 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
> + * <COPYRIGHT HOLDER> OR CONTRIBUTORS 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.
> + */
> +
> +#include <sys/types.h>
> +
> +#include <lua.h>
> +#include <lauxlib.h>
> +#include <lualib.h>
> +
> +#include "diag.h"
> +#include "core/popen.h"
> +
> +#include "lua/utils.h"
> +#include "lua/popen.h"
> +
> +static inline int
> +lbox_pushsyserror(struct lua_State *L)
> +{
> +	diag_set(SystemError, "popen: %s", strerror(errno));
> +	return luaT_push_nil_and_error(L);
> +}
> +
> +static inline int
> +lbox_push_error(struct lua_State *L)
> +{
> +	diag_set(SystemError, "popen: %s", strerror(errno));
> +	struct error *e = diag_last_error(diag_get());
> +	assert(e != NULL);
> +	luaT_pusherror(L, e);
> +	return 1;
> +}
> +
> +static inline int
> +lbox_pushbool(struct lua_State *L, bool res)
> +{
> +	lua_pushboolean(L, res);
> +	if (!res) {
> +		lbox_push_error(L);
> +		return 2;
> +	}
> +	return 1;
> +}
> +
> +/**
> + * lbox_fio_popen_new - creates a new popen handle and runs a command inside
> + * @command:	a command to run
> + * @flags:	popen_flag_bits
> + *
> + * Returns pair @handle = data, @err = nil on success,
> + * @handle = nil, err ~= nil on error.
> + */
> +static int
> +lbox_popen_new(struct lua_State *L)
> +{
> +	struct popen_handle *handle;
> +	struct popen_opts opts = { };
> +	size_t i, argv_size;
> +	ssize_t nr_env;
> +
> +	if (lua_gettop(L) < 1 || !lua_istable(L, 1))
> +		luaL_error(L, "Usage: fio.run({opts}])");
> +
> +	lua_pushstring(L, "argv");
> +	lua_gettable(L, -2);
> +	if (!lua_istable(L, -1))
> +		luaL_error(L, "fio.run: {argv=...} is not a table");
> +	lua_pop(L, 1);
> +
> +	lua_pushstring(L, "flags");
> +	lua_gettable(L, -2);
> +	if (!lua_isnumber(L, -1))
> +		luaL_error(L, "fio.run: {flags=...} is not a number");
> +	opts.flags = lua_tonumber(L, -1);
> +	lua_pop(L, 1);
> +
> +	lua_pushstring(L, "argc");
> +	lua_gettable(L, -2);
> +	if (!lua_isnumber(L, -1))
> +		luaL_error(L, "fio.run: {argc=...} is not a number");
> +	opts.nr_argv = lua_tonumber(L, -1);
> +	lua_pop(L, 1);
> +
> +	if (opts.nr_argv < 1)
> +		luaL_error(L, "fio.run: {argc} is too small");
> +
> +	/*
> +	 * argv array should contain NULL element at the
> +	 * end and probably "sh", "-c" at the start, so
> +	 * reserve enough space for any flags.
> +	 */
> +	opts.nr_argv += 3;
> +	argv_size = sizeof(char *) * opts.nr_argv;
> +	opts.argv = malloc(argv_size);
> +	if (!opts.argv)
> +		luaL_error(L, "fio.run: can't allocate argv");
> +
> +	lua_pushstring(L, "argv");
> +	lua_gettable(L, -2);
> +	lua_pushnil(L);
> +	for (i = 2; lua_next(L, -2) != 0; i++) {
> +		assert(i < opts.nr_argv);
> +		opts.argv[i] = (char *)lua_tostring(L, -1);
> +		lua_pop(L, 1);
> +	}
> +	lua_pop(L, 1);
> +
> +	opts.argv[0] = NULL;
> +	opts.argv[1] = NULL;
> +	opts.argv[opts.nr_argv - 1] = NULL;
> +
> +	/*
> +	 * Environment can be filled, empty
> +	 * to inherit or contain one NULL to
> +	 * be zapped.
> +	 */
> +	lua_pushstring(L, "envc");
> +	lua_gettable(L, -2);
> +	if (!lua_isnumber(L, -1)) {
> +		free(opts.argv);
> +		luaL_error(L, "fio.run: {envc=...} is not a number");
> +	}
> +	nr_env = lua_tonumber(L, -1);
> +	lua_pop(L, 1);
> +
> +	if (nr_env >= 0) {
> +		/* Should be NULL terminating */
> +		opts.env = malloc((nr_env + 1) * sizeof(char *));
> +		if (!opts.env) {
> +			free(opts.argv);
> +			luaL_error(L, "fio.run: can't allocate env");
> +		}
> +
> +		lua_pushstring(L, "env");
> +		lua_gettable(L, -2);
> +		if (!lua_istable(L, -1)) {
> +			free(opts.argv);
> +			free(opts.env);
> +			luaL_error(L, "fio.run: {env=...} is not a table");
> +		}
> +		lua_pushnil(L);
> +		for (i = 0; lua_next(L, -2) != 0; i++) {
> +			assert((ssize_t)i <= nr_env);
> +			opts.env[i] = (char *)lua_tostring(L, -1);
> +			lua_pop(L, 1);
> +		}
> +		lua_pop(L, 1);
> +
> +		opts.env[nr_env] = NULL;
> +	} else {
> +		/*
> +		 * Just zap it to nil, the popen will
> +		 * process inheriting by self.
> +		 */
> +		opts.env = NULL;
> +	}
> +
> +	handle = popen_new(&opts);
> +
> +	free(opts.argv);
> +	free(opts.env);
> +
> +	if (!handle)
> +		return lbox_pushsyserror(L);
> +
> +	lua_pushlightuserdata(L, handle);
> +	return 1;
> +}
> +
> +/**
> + * lbox_fio_popen_kill - kill popen's child process
> + * @handle:	a handle carries child process to kill
> + *
> + * Returns true if process is killed and false
> + * otherwise. Note the process is simply signaled
> + * and it doesn't mean it is killed immediately,
> + * Poll lbox_fio_pstatus if need to find out when
> + * exactly the child is reaped out.
> + */
> +static int
> +lbox_popen_kill(struct lua_State *L)
> +{
> +	struct popen_handle *p = lua_touserdata(L, 1);
> +	return lbox_pushbool(L, popen_send_signal(p, SIGKILL) == 0);
> +}
> +
> +/**
> + * lbox_fio_popen_term - terminate popen's child process
> + * @handle:	a handle carries child process to terminate
> + *
> + * Returns true if process is terminated and false
> + * otherwise.
> + */
> +static int
> +lbox_popen_term(struct lua_State *L)
> +{
> +	struct popen_handle *p = lua_touserdata(L, 1);
> +	return lbox_pushbool(L, popen_send_signal(p, SIGTERM) == 0);
> +}
> +
> +/**
> + * lbox_fio_popen_signal - send signal to a child process
> + * @handle:	a handle carries child process to terminate
> + * @signo:	signal number to send
> + *
> + * Returns true if signal is sent.
> + */
> +static int
> +lbox_popen_signal(struct lua_State *L)
> +{
> +	struct popen_handle *p = lua_touserdata(L, 1);
> +	int signo = lua_tonumber(L, 2);
> +	return lbox_pushbool(L, popen_send_signal(p, signo) == 0);
> +}
> +
> +/**
> + * lbox_popen_state - fetch popen child process status
> + * @handle:	a handle to fetch status from
> + *
> + * Returns @err = nil, @reason = POPEN_STATE_x,
> + * @exit_code = 'number' on success, @err ~= nil on error.
> + */
> +static int
> +lbox_popen_state(struct lua_State *L)
> +{
> +	struct popen_handle *p = lua_touserdata(L, 1);
> +	int state, exit_code, ret;
> +
> +	ret = popen_state(p, &state, &exit_code);
> +	if (ret < 0)
> +		return lbox_push_error(L);
> +
> +	lua_pushnil(L);
> +	lua_pushinteger(L, state);
> +	lua_pushinteger(L, exit_code);
> +	return 3;
> +}
> +
> +/**
> + * lbox_popen_read - read data from a child peer
> + * @handle:	handle of a child process
> + * @buf:	destination buffer
> + * @count:	number of bytes to read
> + * @flags:	which peer to read (stdout,stderr)
> + * @timeout:	timeout in seconds; ignored if negative
> + *
> + * Returns @size = 'read bytes', @err = nil on success,
> + * @size = nil, @err ~= nil on error.
> + */
> +static int
> +lbox_popen_read(struct lua_State *L)
> +{
> +	struct popen_handle *handle = lua_touserdata(L, 1);
> +	uint32_t ctypeid;
> +	void *buf =  *(char **)luaL_checkcdata(L, 2, &ctypeid);
> +	size_t count = lua_tonumber(L, 3);
> +	unsigned int flags = lua_tonumber(L, 4);
> +	ev_tstamp timeout = lua_tonumber(L, 5);
> +	ssize_t ret;
> +
> +	ret = popen_read_timeout(handle, buf, count,
> +				 flags, timeout);
> +	if (ret < 0)
> +		return lbox_pushsyserror(L);
> +
> +	lua_pushinteger(L, ret);
> +	return 1;
> +}
> +
> +/**
> + * lbox_popen_write - write data to a child peer
> + * @handle:	a handle of a child process
> + * @buf:	source buffer
> + * @count:	number of bytes to write
> + * @flags:	which peer to write (stdin)
> + * @timeout:	timeout in seconds; ignored if negative
> + *
> + * Returns @err = nil on succes, @err ~= nil on error.
> + */
> +static int
> +lbox_popen_write(struct lua_State *L)
> +{
> +	struct popen_handle *handle = lua_touserdata(L, 1);
> +	void *buf = (void *)lua_tostring(L, 2);
> +	uint32_t ctypeid = 0;
> +	if (buf == NULL)
> +		buf =  *(char **)luaL_checkcdata(L, 2, &ctypeid);
> +	size_t count = lua_tonumber(L, 3);
> +	unsigned int flags = lua_tonumber(L, 4);
> +	ev_tstamp timeout = lua_tonumber(L, 5);
> +	ssize_t ret;
> +
> +	ret = popen_write_timeout(handle, buf, count, flags, timeout);
> +	if (ret < 0)
> +		return lbox_pushsyserror(L);
> +	return lbox_pushbool(L, ret == 0);
> +}
> +
> +/**
> + * lbox_popen_info - return information about popen handle
> + * @handle:	a handle of a child process
> + *
> + * Returns a @table ~= nil, @err = nil on success,
> + * @table = nil, @err ~= nil on error.
> + */
> +static int
> +lbox_popen_info(struct lua_State *L)
> +{
> +	struct popen_handle *handle = lua_touserdata(L, 1);
> +	int state, exit_code, ret;
> +	struct popen_stat st = { };
> +
> +	if (popen_stat(handle, &st))
> +		return lbox_pushsyserror(L);
> +
> +	ret = popen_state(handle, &state, &exit_code);
> +	if (ret < 0)
> +		return lbox_pushsyserror(L);
> +
> +	assert(state < POPEN_STATE_MAX);
> +
> +	lua_newtable(L);
> +
> +	lua_pushliteral(L, "pid");
> +	lua_pushinteger(L, st.pid);
> +	lua_settable(L, -3);
> +
> +	lua_pushliteral(L, "command");
> +	lua_pushstring(L, popen_command(handle));
> +	lua_settable(L, -3);
> +
> +	lua_pushliteral(L, "flags");
> +	lua_pushinteger(L, st.flags);
> +	lua_settable(L, -3);
> +
> +	lua_pushliteral(L, "state");
> +	lua_pushstring(L, popen_state_str(state));
> +	lua_settable(L, -3);
> +
> +	lua_pushliteral(L, "exit_code");
> +	lua_pushinteger(L, exit_code);
> +	lua_settable(L, -3);
> +
> +	lua_pushliteral(L, "stdin");
> +	lua_pushinteger(L, st.fds[STDIN_FILENO]);
> +	lua_settable(L, -3);
> +
> +	lua_pushliteral(L, "stdout");
> +	lua_pushinteger(L, st.fds[STDOUT_FILENO]);
> +	lua_settable(L, -3);
> +
> +	lua_pushliteral(L, "stderr");
> +	lua_pushinteger(L, st.fds[STDERR_FILENO]);
> +	lua_settable(L, -3);
> +
> +	return 1;
> +}
> +
> +/**
> + * lbox_popen_delete - close a popen handle
> + * @handle:	a handle to close
> + *
> + * If there is a running child it get killed first.
> + *
> + * Returns true if a handle is closeed, false otherwise.
> + */
> +static int
> +lbox_popen_delete(struct lua_State *L)
> +{
> +	void *handle = lua_touserdata(L, 1);
> +	return lbox_pushbool(L, popen_delete(handle) == 0);
> +}
> +
> +/**
> + * tarantool_lua_popen_init - Create popen methods
> + */
> +void
> +tarantool_lua_popen_init(struct lua_State *L)
> +{
> +	static const struct luaL_Reg popen_methods[] = {
> +		{ },
> +	};
> +
> +	/* public methods */
> +	luaL_register_module(L, "popen", popen_methods);
> +
> +	static const struct luaL_Reg builtin_methods[] = {
> +		{ "new",		lbox_popen_new,		},
> +		{ "delete",		lbox_popen_delete,	},
> +		{ "kill",		lbox_popen_kill,	},
> +		{ "term",		lbox_popen_term,	},
> +		{ "signal",		lbox_popen_signal,	},
> +		{ "state",		lbox_popen_state,	},
> +		{ "read",		lbox_popen_read,	},
> +		{ "write",		lbox_popen_write,	},
> +		{ "info",		lbox_popen_info,	},
> +		{ },
> +	};
> +
> +	/* builtin methods */
> +	lua_pushliteral(L, "builtin");
> +	lua_newtable(L);
> +
> +	luaL_register(L, NULL, builtin_methods);
> +	lua_settable(L, -3);
> +
> +	/*
> +	 * Popen constants.
> +	 */
> +#define lua_gen_const(_n, _v)		\
> +	lua_pushliteral(L, _n);		\
> +	lua_pushinteger(L, _v);		\
> +	lua_settable(L, -3)
> +
> +	lua_pushliteral(L, "c");
> +	lua_newtable(L);
> +
> +	/*
> +	 * Flag masks.
> +	 */
> +	lua_pushliteral(L, "flag");
> +	lua_newtable(L);
> +
> +	lua_gen_const("NONE",			POPEN_FLAG_NONE);
> +
> +	lua_gen_const("STDIN",			POPEN_FLAG_FD_STDIN);
> +	lua_gen_const("STDOUT",			POPEN_FLAG_FD_STDOUT);
> +	lua_gen_const("STDERR",			POPEN_FLAG_FD_STDERR);
> +
> +	lua_gen_const("STDIN_DEVNULL",		POPEN_FLAG_FD_STDIN_DEVNULL);
> +	lua_gen_const("STDOUT_DEVNULL",		POPEN_FLAG_FD_STDOUT_DEVNULL);
> +	lua_gen_const("STDERR_DEVNULL",		POPEN_FLAG_FD_STDERR_DEVNULL);
> +
> +	lua_gen_const("STDIN_CLOSE",		POPEN_FLAG_FD_STDIN_CLOSE);
> +	lua_gen_const("STDOUT_CLOSE",		POPEN_FLAG_FD_STDOUT_CLOSE);
> +	lua_gen_const("STDERR_CLOSE",		POPEN_FLAG_FD_STDERR_CLOSE);
> +
> +	lua_gen_const("SHELL",			POPEN_FLAG_SHELL);
> +	lua_gen_const("SETSID",			POPEN_FLAGS_SETSID);
> +	lua_gen_const("CLOSE_FDS",		POPEN_FLAG_CLOSE_FDS);
> +	lua_gen_const("RESTORE_SIGNALS",	POPEN_FLAGS_RESTORE_SIGNALS);
> +	lua_settable(L, -3);
> +
> +	lua_pushliteral(L, "state");
> +	lua_newtable(L);
> +
> +	lua_gen_const("ALIVE",			POPEN_STATE_ALIVE);
> +	lua_gen_const("EXITED",			POPEN_STATE_EXITED);
> +	lua_gen_const("SIGNALED",		POPEN_STATE_SIGNALED);
> +	lua_settable(L, -3);
> +
> +#undef lua_gen_const
> +
> +	lua_settable(L, -3);
> +	lua_pop(L, 1);
> +}
> diff --git a/src/lua/popen.h b/src/lua/popen.h
> new file mode 100644
> index 000000000..4e3f137c5
> --- /dev/null
> +++ b/src/lua/popen.h
> @@ -0,0 +1,44 @@
> +#ifndef INCLUDES_TARANTOOL_LUA_POPEN_H
> +#define INCLUDES_TARANTOOL_LUA_POPEN_H
> +/*
> + * Copyright 2010-2019, Tarantool AUTHORS, please see AUTHORS file.
> + *
> + * 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.
> + *
> + * THIS SOFTWARE IS PROVIDED BY <COPYRIGHT HOLDER> ``AS IS'' AND
> + * ANY EXPRESS 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
> + * <COPYRIGHT HOLDER> OR CONTRIBUTORS 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.
> + */
> +#if defined(__cplusplus)
> +extern "C" {
> +#endif /* defined(__cplusplus) */
> +
> +struct lua_State;
> +void tarantool_lua_popen_init(struct lua_State *L);
> +
> +#if defined(__cplusplus)
> +} /* extern "C" */
> +#endif /* defined(__cplusplus) */
> +
> +#endif /* INCLUDES_TARANTOOL_LUA_POPEN_H */
> diff --git a/src/lua/popen.lua b/src/lua/popen.lua
> new file mode 100644
> index 000000000..31af26cc6
> --- /dev/null
> +++ b/src/lua/popen.lua
> @@ -0,0 +1,516 @@
> +-- popen.lua (builtin file)
> +--
> +-- vim: ts=4 sw=4 et
> +
> +local buffer = require('buffer')
> +local popen = require('popen')
> +local fiber = require('fiber')
> +local ffi = require('ffi')
> +local bit = require('bit')
> +
> +local const_char_ptr_t = ffi.typeof('const char *')
> +
> +local builtin = popen.builtin
> +popen.builtin = nil
> +
> +local popen_methods = { }
> +
> +local function default_flags()
> +    local flags = popen.c.flag.NONE
> +
> +    -- default flags: close everything and use shell
> +    flags = bit.bor(flags, popen.c.flag.STDIN_CLOSE)
> +    flags = bit.bor(flags, popen.c.flag.STDOUT_CLOSE)
> +    flags = bit.bor(flags, popen.c.flag.STDERR_CLOSE)
> +    flags = bit.bor(flags, popen.c.flag.SHELL)
> +    flags = bit.bor(flags, popen.c.flag.SETSID)
> +    flags = bit.bor(flags, popen.c.flag.CLOSE_FDS)
> +    flags = bit.bor(flags, popen.c.flag.RESTORE_SIGNALS)
> +
> +    return flags
> +end
> +
> +--
> +-- A map for popen option keys into tfn ('true|false|nil') values
> +-- where bits are just set without additional manipulations.
> +--
> +-- For example stdin=true means to open stdin end for write,
> +-- stdin=false to close the end, finally stdin[=nil] means to
> +-- provide /dev/null into a child peer.
> +local flags_map_tfn = {
> +    stdin = {
> +        popen.c.flag.STDIN,
> +        popen.c.flag.STDIN_CLOSE,
> +        popen.c.flag.STDIN_DEVNULL,
> +    },
> +    stdout = {
> +        popen.c.flag.STDOUT,
> +        popen.c.flag.STDOUT_CLOSE,
> +        popen.c.flag.STDOUT_DEVNULL,
> +    },
> +    stderr = {
> +        popen.c.flag.STDERR,
> +        popen.c.flag.STDERR_CLOSE,
> +        popen.c.flag.STDERR_DEVNULL,
> +    },
> +}
> +
> +--
> +-- A map for popen option keys into tf ('true|false') values
> +-- where bits are set on 'true' and clear on 'false'.
> +local flags_map_tf = {
> +    shell = {
> +        popen.c.flag.SHELL,
> +    },
> +    close_fds = {
> +        popen.c.flag.CLOSE_FDS
> +    },
> +    restore_signals = {
> +        popen.c.flag.RESTORE_SIGNALS
> +    },
> +    start_new_session = {
> +        popen.c.flag.SETSID
> +    },
> +}
> +
> +--
> +-- Parses flags options from flags_map_tfn and
> +-- flags_map_tf tables.
> +local function parse_flags(epfx, flags, opts)
> +    if opts == nil then
> +        return flags
> +    end
> +    for k,v in pairs(opts) do
> +        if flags_map_tfn[k] == nil then
> +            if flags_map_tf[k] == nil then
> +                error(string.format("%s: Unknown key %s", epfx, k))
> +            end
> +            if v == true then
> +                flags = bit.bor(flags, flags_map_tf[k][1])
> +            elseif v == false then
> +                flags = bit.band(flags, bit.bnot(flags_map_tf[k][1]))
> +            else
> +                error(string.format("%s: Unknown value %s", epfx, v))
> +            end
> +        else
> +            if v == true then
> +                flags = bit.band(flags, bit.bnot(flags_map_tfn[k][2]))
> +                flags = bit.band(flags, bit.bnot(flags_map_tfn[k][3]))
> +                flags = bit.bor(flags, flags_map_tfn[k][1])
> +            elseif v == false then
> +                flags = bit.band(flags, bit.bnot(flags_map_tfn[k][1]))
> +                flags = bit.band(flags, bit.bnot(flags_map_tfn[k][3]))
> +                flags = bit.bor(flags, flags_map_tfn[k][2])
> +            elseif v == "devnull" then
> +                flags = bit.band(flags, bit.bnot(flags_map_tfn[k][1]))
> +                flags = bit.band(flags, bit.bnot(flags_map_tfn[k][2]))
> +                flags = bit.bor(flags, flags_map_tfn[k][3])
> +            else
> +                error(string.format("%s: Unknown value %s", epfx, v))
> +            end
> +        end
> +    end
> +    return flags
> +end
> +
> +--
> +-- Parse "mode" string to flags
> +local function parse_mode(epfx, flags, mode)
> +    if mode == nil then
> +        return flags
> +    end
> +    for i = 1, #mode do
> +        local c = mode:sub(i, i)
> +        if c == 'r' then
> +            flags = bit.band(flags, bit.bnot(popen.c.flag.STDOUT_CLOSE))
> +            flags = bit.bor(flags, popen.c.flag.STDOUT)
> +            flags = bit.band(flags, bit.bnot(popen.c.flag.STDERR_CLOSE))
> +            flags = bit.bor(flags, popen.c.flag.STDERR)
> +        elseif c == 'w' then
> +            flags = bit.band(flags, bit.bnot(popen.c.flag.STDIN_CLOSE))
> +            flags = bit.bor(flags, popen.c.flag.STDIN)
> +        else
> +            error(string.format("%s: Unknown mode %s", epfx, c))
> +        end
> +    end
> +    return flags
> +end
> +
> +--
> +-- Close a popen object and release all resources.
> +-- In case if there is a running child process
> +-- it will be killed.
> +--
> +-- Returns @ret = true if popen object is closed, and
> +-- @ret = false, @err ~= nil on error.
> +popen_methods.close = function(self)
> +    local ret, err = builtin.delete(self.cdata)
> +    if err ~= nil then
> +        return false, err
> +    end
> +    self.cdata = nil
> +    return true
> +end
> +
> +--
> +-- Kill a child process
> +--
> +-- Returns @ret = true on success,
> +-- @ret = false, @err ~= nil on error.
> +popen_methods.kill = function(self)
> +    return builtin.kill(self.cdata)
> +end
> +
> +--
> +-- Terminate a child process
> +--
> +-- Returns @ret = true on success,
> +-- @ret = false, @err ~= nil on error.
> +popen_methods.terminate = function(self)
> +    return builtin.term(self.cdata)
> +end
> +
> +--
> +-- Send signal with number @signo to a child process
> +--
> +-- Returns @ret = true on success,
> +-- @ret = false, @err ~= nil on error.
> +popen_methods.send_signal = function(self, signo)
> +    return builtin.signal(self.cdata, signo)
> +end
> +
> +--
> +-- Fetch a child process state
> +--
> +-- Returns @err = nil, @state = popen.c.state, @exit_code = num,
> +-- otherwise @err ~= nil.
> +popen_methods.state = function(self)
> +    return builtin.state(self.cdata)
> +end
> +
> +--
> +-- Wait until a child process get exited.
> +--
> +-- Returns the same as popen_methods.status.
> +popen_methods.wait = function(self)
> +    local err, state, code
> +    while true do
> +        err, state, code = builtin.state(self.cdata)
> +        if err or state ~= popen.c.state.ALIVE then
> +            break
> +        end
> +        fiber.sleep(self.wait_secs)
> +    end
> +    return err, state, code
> +end
> +
> +--
> +-- popen:read2 - read a stream of a child process
> +-- @opts:       options table
> +--
> +-- The options should have the following keys
> +--
> +-- @buf:        const_char_ptr_t buffer
> +-- @size:       size of the buffer
> +-- @flags:      stdout=true or stderr=true
> +-- @timeout:    read timeout in seconds, < 0 to ignore
> +--
> +-- Returns @res = bytes, err = nil in case if read processed
> +-- without errors, @res = nil, @err ~= nil otherwise.
> +popen_methods.read2 = function(self, opts)
> +    local flags = parse_flags("popen:read2",
> +                              popen.c.flag.NONE,
> +                              opts['flags'])
> +    local timeout = -1
> +
> +    if opts['buf'] == nil then
> +        error("popen:read2 {'buf'} key is missed")
> +    elseif opts['size'] == nil then
> +        error("popen:read2 {'size'} key is missed")
> +    elseif opts['timeout'] ~= nil then
> +        timeout = tonumber(opts['timeout'])
> +    end
> +
> +    return builtin.read(self.cdata, opts['buf'],
> +                        tonumber(opts['size']),
> +                        flags, timeout)
> +end
> +
> +--
> +-- popen:write2 - write to a child's streem
> +-- @opts:       options table
> +--
> +-- The options should have the following keys
> +--
> +-- @buf:        const_char_ptr_t buffer
> +-- @size:       size of the buffer
> +-- @flags:      stdin=true
> +-- @timeout:    write timeout in seconds, < 0 to ignore
> +--
> +-- Returns @err = nil on success, @err ~= nil otherwise.
> +popen_methods.write2 = function(self, opts)
> +    local flags = parse_flags("popen:write2",
> +                              popen.c.flag.NONE,
> +                              opts['flags'])
> +    local timeout = -1
> +
> +    if opts['buf'] == nil then
> +        error("popen:write2 {'buf'} key is missed")
> +    elseif opts['size'] == nil then
> +        error("popen:write2 {'size'} key is missed")
> +    elseif opts['timeout'] ~= nil then
> +        timeout = tonumber(opts['timeout'])
> +    end
> +
> +    return builtin.write(self.cdata, opts['buf'],
> +                         tonumber(opts['size']),
> +                         flags, timeout)
> +end
> +
> +--
> +-- popen:read - read string from a stream
> +-- @stderr:     set to true to read from stderr, optional
> +-- @timeout:    timeout in seconds, optional
> +--
> +-- Returns a result string, or res = nil, @err ~= nil on error.
> +popen_methods.read = function(self, stderr, timeout)
> +    local ibuf = buffer.ibuf()
> +    local buf = ibuf:reserve(self.read_size)
> +    local flags
> +
> +    if stderr ~= nil then
> +        flags = { stderr = true }
> +    else
> +        flags = { stdout = true }
> +    end
> +
> +    if timeout == nil then
> +        timeout = -1
> +    end
> +
> +    local res, err = self:read2({
> +        buf     = buf,
> +        size    = self.read_size,
> +        flags   = flags,
> +        timeout = timeout,
> +    })
> +
> +    if err ~= nil then
> +        ibuf:recycle()
> +        return nil, err
> +    end
> +
> +    ibuf:alloc(res)
> +    res = ffi.string(ibuf.rpos, ibuf:size())
> +    ibuf:recycle()
> +
> +    return res
> +end
> +
> +--
> +-- popen:write - write string @str to stdin stream
> +-- @str:        string to write
> +-- @timeout:    timeout in seconds, optional
> +--
> +-- Returns @err = nil on success, @err ~= nil on error.
> +popen_methods.write = function(self, str, timeout)
> +    if timeout == nil then
> +        timeout = -1
> +    end
> +    return self:write2({
> +        buf     = str,
> +        size    = #str,
> +        flags   = { stdin = true },
> +        timeout = timeout,
> +    })
> +end
> +
> +--
> +-- popen:info -- obtain information about a popen object
> +--
> +-- Returns @info table, err == nil on success,
> +-- @info = nil, @err ~= nil otherwise.
> +--
> +-- The @info table contains the following keys:
> +--
> +-- @pid:        pid of a child process
> +-- @flags:      flags associated (popen.c.flag bitmap)
> +-- @stdout:     parent peer for stdout, -1 if closed
> +-- @stderr:     parent peer for stderr, -1 if closed
> +-- @stdin:      parent peer for stdin, -1 if closed
> +-- @state:      alive | exited | signaled
> +-- @exit_code:  exit code of a child process if been
> +--              exited or killed by a signal
> +--
> +-- The child should never be in "unknown" state, reserved
> +-- for unexpected errors.
> +--
> +popen_methods.info = function(self)
> +    return builtin.info(self.cdata)
> +end
> +
> +--
> +-- Create a new popen object from options
> +local function popen_reify(opts)
> +    local cdata, err = builtin.new(opts)
> +    if err ~= nil then
> +        return nil, err
> +    end
> +
> +    local handle = {
> +        -- a handle itself for future use
> +        cdata           = cdata,
> +
> +        -- sleeping period for the @wait method
> +        wait_secs       = 0.3,
> +
> +        -- size of a read buffer to allocate
> +        -- in case of implicit read, this number
> +        -- is taken from luatest repo to fit the
> +        -- value people are familiar with
> +        read_size       = 4096,
> +    }
> +
> +    setmetatable(handle, {
> +        __index     = popen_methods,
> +    })
> +
> +    return handle
> +end
> +
> +--
> +-- popen.posix - create a new child process and execute a command inside
> +-- @command:    a command to run
> +-- @mode:       r - to grab child's stdout for read
> +--              (stderr kept opened as well for 2>&1 redirection)
> +--              w - to obtain stdin for write
> +--
> +-- Note: Since there are two options only the following parameters
> +-- are implied (see default_flags): all fds except stdin/out/err
> +-- are closed, signals are restored to default handlers, the command
> +-- is executed in a new session.
> +--
> +-- Examples:
> +--
> +--  ph = require('popen').posix("date", "r")
> +--      runs date as "sh -c date" to read the output,
> +--      closing all file descriptors except stdout/err
> +--      inside a child process
> +--
> +popen.posix = function(command, mode)
> +    local flags = default_flags()
> +
> +    if type(command) ~= 'string' then
> +        error("Usage: popen.posix(command[, rw])")
> +    end
> +
> +    -- Mode gives simplified flags
> +    flags = parse_mode("popen.posix", flags, mode)
> +
> +    local opts = {
> +        argv    = { command },
> +        argc    = 1,
> +        flags   = flags,
> +        envc    = -1,
> +    }
> +
> +    return popen_reify(opts)
> +end
> +
> +-- popen.popen - execute a child program in a new process
> +-- @opt:    options table
> +--
> +-- @opts carries of the following options:
> +--
> +--  @argv:  an array of a program to run with
> +--          command line options, mandatory
> +--
> +--  @env:   an array of environment variables to
> +--          be used inside a process, if not
> +--          set then the current environment is
> +--          inherited, if set to an empty array
> +--          then the environment will be dropped
> +--
> +--  @flags: a dictionary to describe communication pipes
> +--          and other parameters of a process to run
> +--
> +--      stdin=true      to write into STDIN_FILENO of a child process
> +--      stdin=false     to close STDIN_FILENO inside a child process [*]
> +--      stdin="devnull" a child will get /dev/null as STDIN_FILENO
> +--
> +--      stdout=true     to read STDOUT_FILENO of a child process
> +--      stdout=false    to close STDOUT_FILENO inside a child process [*]
> +--      stdout="devnull" a child will get /dev/null as STDOUT_FILENO
> +--
> +--      stderr=true     to read STDERR_FILENO from a child process
> +--      stderr=false    to close STDERR_FILENO inside a child process [*]
> +--      stderr="devnull" a child will get /dev/null as STDERR_FILENO
> +--
> +--      shell=true      runs a child process via "sh -c" [*]
> +--      shell=false     invokes a child process executable directly
> +--
> +--      close_fds=true  close all inherited fds from a parent [*]
> +--
> +--      restore_signals=true
> +--                      all signals modified by a caller reset
> +--                      to default handler [*]
> +--
> +--      start_new_session=true
> +--                      start executable inside a new session [*]
> +--
> +-- [*] are default values
> +--
> +-- Examples:
> +--
> +--  ph = require('popen').popen({argv = {"date"}, flags = {stdout=true}})
> +--  ph:read()
> +--  ph:close()
> +--
> +--      Execute 'date' command inside a shell, read the result
> +--      and close the popen object
> +--
> +--  ph = require('popen').popen({argv = {"/usr/bin/echo", "-n", "hello"},
> +--                               flags = {stdout=true, shell=false}})
> +--  ph:read()
> +--  ph:close()
> +--
> +--      Execute /usr/bin/echo with arguments '-n','hello' directly
> +--      without using a shell, read the result from stdout and close
> +--      the popen object
> +--
> +popen.popen = function(opts)
> +    local flags = default_flags()
> +
> +    if opts == nil or type(opts) ~= 'table' then
> +        error("Usage: popen({argv={}[, envp={}, flags={}]")
> +    end
> +
> +    -- Test for required arguments
> +    if opts["argv"] == nil then
> +        error("popen: argv key is missing")
> +    end
> +
> +    -- Process flags and save updated mask
> +    -- to pass into engine (in case of missing
> +    -- flags we just use defaults).
> +    if opts["flags"] ~= nil then
> +        flags = parse_flags("popen", flags, opts["flags"])
> +    end
> +    opts["flags"] = flags
> +
> +    -- We will need a number of args for speed sake
> +    opts["argc"] = #opts["argv"]
> +
> +    -- Same applies to the environment (note though
> +    -- that env={} is pretty valid and env=nil
> +    -- as well.
> +    if opts["env"] ~= nil then
> +        opts["envc"] = #opts["env"]
> +    else
> +        opts["envc"] = -1
> +    end
> +
> +    return popen_reify(opts)
> +end
> +
> +return popen


More information about the Tarantool-patches mailing list