[Tarantool-patches] [PATCH v2 3/5] popen/fio: Merge popen engine into fio internal module
Cyrill Gorcunov
gorcunov at gmail.com
Tue Dec 10 12:48:53 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..37209f691 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_msecs: 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