From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: from mail-lj1-f169.google.com (mail-lj1-f169.google.com [209.85.208.169]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by dev.tarantool.org (Postfix) with ESMTPS id 14D3E4696C1 for ; Tue, 10 Dec 2019 12:49:38 +0300 (MSK) Received: by mail-lj1-f169.google.com with SMTP id a13so19105313ljm.10 for ; Tue, 10 Dec 2019 01:49:38 -0800 (PST) From: Cyrill Gorcunov Date: Tue, 10 Dec 2019 12:48:53 +0300 Message-Id: <20191210094855.24953-4-gorcunov@gmail.com> In-Reply-To: <20191210094855.24953-1-gorcunov@gmail.com> References: <20191210094855.24953-1-gorcunov@gmail.com> MIME-Version: 1.0 Content-Transfer-Encoding: 8bit Subject: [Tarantool-patches] [PATCH v2 3/5] popen/fio: Merge popen engine into fio internal module List-Id: Tarantool development patches List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , To: tml 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 --- 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 #include #include "coio_task.h" +#include "popen.h" #include #include @@ -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