[Tarantool-patches] [PATCH v3 3/5] popen/fio: Merge popen engine into fio internal module

Cyrill Gorcunov gorcunov at gmail.com
Wed Dec 11 12:28:31 MSK 2019


In the patch we wrap popen calls with lua C interface
which will allow us to use popen inside lua code.

Part-of #4031

Signed-off-by: Cyrill Gorcunov <gorcunov at gmail.com>
---
 src/lib/core/coio_file.c | 115 ++++++++++++++
 src/lib/core/coio_file.h |   8 +
 src/lua/fio.c            | 331 +++++++++++++++++++++++++++++++++++++++
 3 files changed, 454 insertions(+)

diff --git a/src/lib/core/coio_file.c b/src/lib/core/coio_file.c
index 62388344e..70bdd572a 100644
--- a/src/lib/core/coio_file.c
+++ b/src/lib/core/coio_file.c
@@ -30,6 +30,7 @@
  */
 #include "coio_file.h"
 #include "coio_task.h"
+#include "popen.h"
 #include "fiber.h"
 #include "say.h"
 #include "fio.h"
@@ -103,6 +104,15 @@ struct coio_file_task {
 			const char *source;
 			const char *dest;
 		} copyfile;
+
+		struct {
+			struct popen_handle *handle;
+			struct popen_opts *opts;
+			unsigned int flags;
+			int timeout_msecs;
+			size_t count;
+			void *buf;
+		} popen;
 	};
 };
 
@@ -639,3 +649,108 @@ coio_utime(const char *pathname, double atime, double mtime)
 	eio_req *req = eio_utime(pathname, atime, mtime, 0, coio_complete, &eio);
 	return coio_wait_done(req, &eio);
 }
+
+static void
+coio_do_popen_read(eio_req *req)
+{
+	struct coio_file_task *eio = req->data;
+	req->result = popen_read_timeout(eio->popen.handle,
+					 eio->popen.buf,
+					 eio->popen.count,
+					 eio->popen.flags,
+					 eio->popen.timeout_msecs);
+}
+
+ssize_t
+coio_popen_read(void *handle, void *buf, size_t count,
+		unsigned int flags, int timeout_msecs)
+{
+	INIT_COEIO_FILE(eio);
+	ssize_t res = -1;
+
+	eio.popen.timeout_msecs = timeout_msecs;
+	eio.popen.handle = handle;
+	eio.popen.flags	= flags;
+	eio.popen.count	= count;
+	eio.popen.buf = buf;
+
+	while (res < 0) {
+		eio_req *req = eio_custom(coio_do_popen_read,
+					  EIO_PRI_DEFAULT,
+					  coio_complete, &eio);
+		res = coio_wait_done(req, &eio);
+		if (res < 0) {
+			if (!popen_should_retry_io(errno))
+				break;
+			eio.done = false;
+		}
+	}
+
+	return res;
+}
+
+static void
+coio_do_popen_write(eio_req *req)
+{
+	struct coio_file_task *eio = req->data;
+	req->result = popen_write(eio->popen.handle,
+				  eio->popen.buf,
+				  eio->popen.count,
+				  eio->popen.flags);
+}
+
+ssize_t
+coio_popen_write(void *handle, void *buf, size_t count,
+		 unsigned int flags)
+{
+	ssize_t left = count, pos = 0;
+	INIT_COEIO_FILE(eio);
+
+	eio.popen.handle = handle;
+	eio.popen.flags	= flags;
+
+	while (left > 0) {
+		eio.popen.count	= left;
+		eio.popen.buf = buf + pos;
+
+		eio_req *req = eio_custom(coio_do_popen_write,
+					  EIO_PRI_DEFAULT,
+					  coio_complete, &eio);
+		ssize_t res = coio_wait_done(req, &eio);
+		if (res < 0) {
+			if (!popen_should_retry_io(errno)) {
+				pos = -1;
+				break;
+			}
+			eio.done = false;
+			continue;
+		}
+		left -= res;
+		pos += res;
+	}
+
+	return pos;
+}
+
+static void
+do_coio_popen_new(eio_req *req)
+{
+	struct coio_file_task *eio = req->data;
+
+	eio->popen.handle = popen_new(eio->popen.opts);
+	req->result = eio->popen.handle ? 0 : -1;
+}
+
+void *
+coio_popen_new(struct popen_opts *opts)
+{
+	INIT_COEIO_FILE(eio);
+	eio.popen.opts = opts;
+
+	eio_req *req = eio_custom(do_coio_popen_new,
+				  EIO_PRI_DEFAULT,
+				  coio_complete, &eio);
+	coio_wait_done(req, &eio);
+
+	return eio.popen.handle;
+}
diff --git a/src/lib/core/coio_file.h b/src/lib/core/coio_file.h
index ac7b1aacf..593769db5 100644
--- a/src/lib/core/coio_file.h
+++ b/src/lib/core/coio_file.h
@@ -85,6 +85,14 @@ int	coio_tempdir(char *path, size_t path_len);
 int	coio_readdir(const char *path, char **buf);
 int	coio_copyfile(const char *source, const char *dest);
 int	coio_utime(const char *pathname, double atime, double mtime);
+
+struct popen_opts;
+void	*coio_popen_new(struct popen_opts *opts);
+ssize_t coio_popen_read(void *handle, void *buf, size_t count,
+			unsigned int flags, int timeout_msecs);
+ssize_t coio_popen_write(void *handle, void *buf, size_t count,
+			 unsigned int flags);
+
 #if defined(__cplusplus)
 } /* extern "C" */
 #endif /* defined(__cplusplus) */
diff --git a/src/lua/fio.c b/src/lua/fio.c
index 33ccd5d71..de213f813 100644
--- a/src/lua/fio.c
+++ b/src/lua/fio.c
@@ -39,6 +39,7 @@
 #include <time.h>
 #include <errno.h>
 #include "coio_task.h"
+#include "popen.h"
 
 #include <lua.h>
 #include <lauxlib.h>
@@ -683,6 +684,328 @@ lbox_fio_utime(struct lua_State *L)
 	return lbox_fio_pushbool(L, coio_utime(pathname, atime, mtime) == 0);
 }
 
+/**
+ * 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_fio_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 = coio_popen_new(&opts);
+
+	free(opts.argv);
+	free(opts.env);
+
+	if (!handle)
+		return lbox_fio_pushsyserror(L);
+
+	lua_pushlightuserdata(L, handle);
+	return 1;
+}
+
+/**
+ * lbox_fio_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_fio_popen_delete(struct lua_State *L)
+{
+	void *handle = lua_touserdata(L, 1);
+	return lbox_fio_pushbool(L, popen_delete(handle) == 0);
+}
+
+/**
+ * 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_fio_popen_kill(struct lua_State *L)
+{
+	struct popen_handle *p = lua_touserdata(L, 1);
+	return lbox_fio_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_fio_popen_term(struct lua_State *L)
+{
+	struct popen_handle *p = lua_touserdata(L, 1);
+	return lbox_fio_pushbool(L, popen_send_signal(p, SIGTERM) == 0);
+}
+
+/**
+ * lbox_fio_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_fio_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_fio_push_error(L);
+
+	lua_pushnil(L);
+	lua_pushinteger(L, state);
+	lua_pushinteger(L, exit_code);
+	return 3;
+}
+
+/**
+ * lbox_fio_popen_read - read data from a child peer
+ * @handle:		a handle of a child process
+ * @buf:		destination buffer
+ * @count:		number of bytes to read
+ * @timeout:		read timeout, in microseconds, -1 to not use
+ * @flags:		which peer to read (stdout,stderr)
+ *
+ * Returns @size = 'read bytes', @err = nil on success,
+ * @size = nil, @err = nil if timeout expired, and
+ * @size = nil, @err ~= nil on error.
+ */
+static int
+lbox_fio_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);
+	int timeout_msecs = lua_tonumber(L, 4);
+	unsigned int flags = lua_tonumber(L, 5);
+	ssize_t ret;
+
+	ret = coio_popen_read(handle, buf, count,
+			      flags, timeout_msecs);
+	if (ret < 0) {
+		if (errno == EAGAIN) {
+			lua_pushnil(L);
+			lua_pushnil(L);
+			return 2;
+		} else
+			return lbox_fio_pushsyserror(L);
+	}
+
+	lua_pushinteger(L, ret);
+	return 1;
+}
+
+/**
+ * lbox_fio_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)
+ *
+ * Returns @size = 'bytes wrote', @err = nil on succes,
+ * @size = nil, @err ~= nil on error.
+ */
+static int
+lbox_fio_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);
+	ssize_t ret;
+
+	ret = popen_write(handle, buf, count, flags);
+	if (ret < 0)
+		return lbox_fio_pushsyserror(L);
+
+	lua_pushinteger(L, ret);
+	return 1;
+}
+
+/**
+ * lbox_fio_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_fio_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_fio_pushsyserror(L);
+
+	ret = popen_state(handle, &state, &exit_code);
+	if (ret < 0)
+		return lbox_fio_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[POPEN_FLAG_FD_STDIN_BIT]);
+	lua_settable(L, -3);
+
+	lua_pushliteral(L, "stdout");
+	lua_pushinteger(L, st.fds[POPEN_FLAG_FD_STDOUT_BIT]);
+	lua_settable(L, -3);
+
+	lua_pushliteral(L, "stderr");
+	lua_pushinteger(L, st.fds[POPEN_FLAG_FD_STDERR_BIT]);
+	lua_settable(L, -3);
+
+	return 1;
+}
+
 void
 tarantool_lua_fio_init(struct lua_State *L)
 {
@@ -726,6 +1049,14 @@ tarantool_lua_fio_init(struct lua_State *L)
 		{ "fstat",		lbox_fio_fstat			},
 		{ "copyfile",		lbox_fio_copyfile,		},
 		{ "utime",		lbox_fio_utime			},
+		{ "popen_new",		lbox_fio_popen_new		},
+		{ "popen_delete",	lbox_fio_popen_delete		},
+		{ "popen_kill",		lbox_fio_popen_kill		},
+		{ "popen_term",		lbox_fio_popen_term		},
+		{ "popen_state",	lbox_fio_popen_state		},
+		{ "popen_read",		lbox_fio_popen_read		},
+		{ "popen_write",	lbox_fio_popen_write		},
+		{ "popen_info",		lbox_fio_popen_info		},
 		{ NULL,			NULL				}
 	};
 	luaL_register(L, NULL, internal_methods);
-- 
2.20.1



More information about the Tarantool-patches mailing list