# HG changeset patch # User jbe # Date 1434910221 -7200 # Node ID 5601a486e68a831652af163b503dbd67ca6c07e6 # Parent 7d1cda1ed530de86cec4a3b795a35da1b01ae0cd Support asynchronous I/O with stdin/stdout/stderr of executed child processes diff -r 7d1cda1ed530 -r 5601a486e68a moonbridge_io.c --- a/moonbridge_io.c Sun Jun 21 02:49:23 2015 +0200 +++ b/moonbridge_io.c Sun Jun 21 20:10:21 2015 +0200 @@ -18,6 +18,8 @@ #include #include #include +#include +#include #include #include @@ -38,6 +40,8 @@ #define MOONBR_IO_HANDLE_MT_REGKEY "moonbridge_io_handle" #define MOONBR_IO_HANDLE_PUBLIC_MT_REGKEY "moonbridge_io_handle_public" #define MOONBR_IO_LISTENER_MT_REGKEY "moonbridge_io_listener" +#define MOONBR_IO_CHILD_MT_REGKEY "moonbridge_io_child" +#define MOONBR_IO_CHILD_PT_REGKEY "moonbridge_io_child_pt" typedef struct { int fd; @@ -73,6 +77,13 @@ int nonblocking; } moonbr_io_listener_t; +typedef struct { + pid_t pid; + int uninitialized_in; + int uninitialized_out; + int uninitialized_err; +} moonbr_io_child_t; + static int moonbr_io_yield(lua_State *L) { return lua_yield(L, lua_gettop(L)); } @@ -857,6 +868,7 @@ static int moonbr_io_handleindex(lua_State *L) { luaL_checkudata(L, 1, MOONBR_IO_HANDLE_MT_REGKEY); + luaL_checkany(L, 2); lua_getuservalue(L, 1); lua_getfield(L, -1, "public"); lua_pushvalue(L, 2); @@ -866,6 +878,8 @@ static int moonbr_io_handlenewindex(lua_State *L) { luaL_checkudata(L, 1, MOONBR_IO_HANDLE_MT_REGKEY); + luaL_checkany(L, 2); + luaL_checkany(L, 3); lua_getuservalue(L, 1); lua_getfield(L, -1, "public"); lua_pushvalue(L, 2); @@ -1217,6 +1231,265 @@ return 0; } +static int moonbr_io_exec(lua_State *L) { + char **argv; + int i, argc; + int sockin[2], sockout[2], sockerr[2]; + volatile int errorcond = 0; + volatile char errmsgbuf[MOONBR_IO_MAXSTRERRORLEN]; + moonbr_io_child_t *child; + argc = lua_gettop(L); + argv = lua_newuserdata(L, (argc + 1) * sizeof(char *)); + for (i=0; ipid = vfork(); + if (child->pid == -1) { + moonbr_io_errmsg(); + close(sockin[0]); + close(sockin[1]); + close(sockout[0]); + close(sockout[1]); + close(sockerr[0]); + close(sockerr[1]); + lua_pushnil(L); + lua_pushfstring(L, "Could not fork: %s", errmsg); + return 2; + } + if (!child->pid) { + if (dup2(sockin[1], 0) == -1) { + errorcond = 1; + strerror_r(errno, (char *)errmsgbuf, MOONBR_IO_MAXSTRERRORLEN); + _exit(0); + } + if (dup2(sockout[1], 1) == -1) { + errorcond = 1; + strerror_r(errno, (char *)errmsgbuf, MOONBR_IO_MAXSTRERRORLEN); + _exit(0); + } + if (dup2(sockerr[1], 2) == -1) { + errorcond = 1; + strerror_r(errno, (char *)errmsgbuf, MOONBR_IO_MAXSTRERRORLEN); + _exit(0); + } + closefrom(4); + if (execvp(argv[0], argv)) { + errorcond = 2; + strerror_r(errno, (char *)errmsgbuf, MOONBR_IO_MAXSTRERRORLEN); + _exit(0); + } + } + close(sockin[1]); + close(sockout[1]); + close(sockerr[1]); + if (errorcond) { + int status; + close(sockin[0]); + close(sockout[0]); + close(sockerr[0]); + while (waitpid(child->pid, &status, 0) == -1) { + if (errno != EINTR) { + moonbr_io_errmsg(); + luaL_error(L, "Error in waitpid call after unsuccessful exec: %s", errmsg); + } + } + lua_pushnil(L); + if (errorcond == 2) lua_pushfstring(L, "Could not execute: %s", errmsgbuf); + else lua_pushfstring(L, "Error in fork: %s", errmsgbuf); + return 2; + } + if (fcntl(sockin[0], F_SETFD, FD_CLOEXEC) == -1) { + moonbr_io_errmsg(); + close(sockin[0]); + close(sockout[0]); + close(sockerr[0]); + luaL_error(L, "Error in fcntl call: %s", errmsg); + } + if (fcntl(sockout[0], F_SETFD, FD_CLOEXEC) == -1) { + moonbr_io_errmsg(); + close(sockin[0]); + close(sockout[0]); + close(sockerr[0]); + luaL_error(L, "Error in fcntl call: %s", errmsg); + } + if (fcntl(sockerr[0], F_SETFD, FD_CLOEXEC) == -1) { + moonbr_io_errmsg(); + close(sockin[0]); + close(sockout[0]); + close(sockerr[0]); + luaL_error(L, "Error in fcntl call: %s", errmsg); + } + /* close sockets during garbage collection in case a Lua error is raised */ + child->uninitialized_in = sockin[0]; + child->uninitialized_out = sockout[0]; + child->uninitialized_err = sockerr[0]; + lua_newtable(L); + lua_setuservalue(L, -2); + luaL_getmetatable(L, MOONBR_IO_CHILD_MT_REGKEY); + lua_setmetatable(L, -2); + moonbr_io_pushhandle(L, sockin[0]); + lua_setfield(L, -2, "stdin"); + child->uninitialized_in = -1; + moonbr_io_pushhandle(L, sockout[0]); + lua_setfield(L, -2, "stdout"); + child->uninitialized_out = -1; + moonbr_io_pushhandle(L, sockerr[0]); + lua_setfield(L, -2, "stderr"); + child->uninitialized_err = -1; + return 1; +} + +static int moonbr_io_childindex(lua_State *L) { + luaL_checkudata(L, 1, MOONBR_IO_CHILD_MT_REGKEY); + luaL_checkany(L, 2); + lua_getuservalue(L, 1); + lua_pushvalue(L, 2); + lua_gettable(L, -2); + if (lua_isnil(L, -1)) { + luaL_getmetatable(L, MOONBR_IO_CHILD_PT_REGKEY); + lua_pushvalue(L, 2); + lua_gettable(L, -2); + } + return 1; +} + +static int moonbr_io_childnewindex(lua_State *L) { + luaL_checkudata(L, 1, MOONBR_IO_CHILD_MT_REGKEY); + luaL_checkany(L, 2); + luaL_checkany(L, 3); + lua_getuservalue(L, 1); + lua_pushvalue(L, 2); + lua_pushvalue(L, 3); + lua_settable(L, -3); + return 0; +} + +static int moonbr_io_childgc(lua_State *L) { + moonbr_io_child_t *child; + child = luaL_checkudata(L, 1, MOONBR_IO_CHILD_MT_REGKEY); + if (child->pid) { + int status; + if (kill(child->pid, SIGKILL)) { + moonbr_io_errmsg(); + luaL_error(L, "Error in kill call during garbage collection: %s", errmsg); + } + while (waitpid(child->pid, &status, 0) == -1) { + if (errno != EINTR) { + moonbr_io_errmsg(); + luaL_error(L, "Error in waitpid call during garbage collection: %s", errmsg); + } + } + } + if (child->uninitialized_in != -1) close(child->uninitialized_in); + if (child->uninitialized_out != -1) close(child->uninitialized_out); + if (child->uninitialized_err != -1) close(child->uninitialized_err); + return 0; +} + +static int moonbr_io_kill(lua_State *L) { + moonbr_io_child_t *child; + int sig; + child = luaL_checkudata(L, 1, MOONBR_IO_CHILD_MT_REGKEY); + sig = luaL_optinteger(L, 2, SIGTERM); + if (!child->pid) luaL_error(L, "Attempt to kill an already collected child process"); + if (kill(child->pid, sig)) { + moonbr_io_errmsg(); + luaL_error(L, "Error in kill call: %s", errmsg); + } + lua_settop(L, 1); + return 1; +} + +static int moonbr_io_wait_impl(lua_State *L, int nonblocking) { + moonbr_io_child_t *child; + pid_t waitedpid; + int status; + child = luaL_checkudata(L, 1, MOONBR_IO_CHILD_MT_REGKEY); + if (!child->pid) luaL_error(L, "Attempt to wait for an already collected child process"); + while ((waitedpid = waitpid(child->pid, &status, nonblocking ? WNOHANG : 0)) == -1) { + if (errno != EINTR) { + moonbr_io_errmsg(); + luaL_error(L, "Error in waitpid call: %s", errmsg); + } + } + if (!waitedpid) { + lua_pushnil(L); + } else { + child->pid = 0; + if (WIFEXITED(status)) { + lua_pushinteger(L, WEXITSTATUS(status)); + } else if (WIFSIGNALED(status)) { + lua_pushinteger(L, -WTERMSIG(status)); + } else { + luaL_error(L, "Unexpected status value returned by waitpid call"); + } + } + return 1; +} + +static int moonbr_io_wait(lua_State *L) { + return moonbr_io_wait_impl(L, 0); +} + +static int moonbr_io_wait_nb(lua_State *L) { + return moonbr_io_wait_impl(L, 1); +} + +#if LUA_VERSION_NUM >= 503 +static int moonbr_io_wait_cont(lua_State *L, int status, lua_KContext ctx) { +#else +static int moonbr_io_wait_cont(lua_State *L) { +#endif +#if !(LUA_VERSION_NUM >= 503) + int ctx = 0; + lua_getctx(L, &ctx); +#endif + while (1) { + lua_pushcfunction(L, moonbr_io_wait_nb); + lua_pushvalue(L, 1); + lua_call(L, 1, 1); + if (!lua_isnil(L, -1)) break; + lua_pushvalue(L, 2); + lua_callk(L, 0, 0, ctx, moonbr_io_wait_cont); + } + return 1; +} + +static int moonbr_io_wait_call(lua_State *L) { + lua_settop(L, 2); +#if LUA_VERSION_NUM >= 503 + return moonbr_io_wait_cont(L, 0, 0); +#else + return moonbr_io_wait_cont(L); +#endif +} + +moonbr_io_yield_wrapper(moonbr_io_wait_yield, moonbr_io_wait_call); + static int moonbr_io_poll(lua_State *L) { moonbr_io_handle_t *handle; moonbr_io_listener_t *listener; @@ -1369,6 +1642,22 @@ {NULL, NULL} }; +static const struct luaL_Reg moonbr_io_child_methods[] = { + {"kill", moonbr_io_kill}, + {"wait", moonbr_io_wait}, + {"wait_nb", moonbr_io_wait_nb}, + {"wait_call", moonbr_io_wait_call}, + {"wait_yield", moonbr_io_wait_yield}, + {NULL, NULL} +}; + +static const struct luaL_Reg moonbr_io_child_metamethods[] = { + {"__index", moonbr_io_childindex}, + {"__newindex", moonbr_io_childnewindex}, + {"__gc", moonbr_io_childgc}, + {NULL, NULL} +}; + static const struct luaL_Reg moonbr_io_module_funcs[] = { {"localconnect", moonbr_io_localconnect}, {"localconnect_nb", moonbr_io_localconnect_nb}, @@ -1376,6 +1665,7 @@ {"tcpconnect_nb", moonbr_io_tcpconnect_nb}, {"locallisten", moonbr_io_locallisten}, {"tcplisten", moonbr_io_tcplisten}, + {"exec", moonbr_io_exec}, {"poll", moonbr_io_poll}, {"timeref", moonbr_io_timeref}, {NULL, NULL} @@ -1412,6 +1702,24 @@ lua_setfield(L, -3, "listener_mt"); lua_setfield(L, LUA_REGISTRYINDEX, MOONBR_IO_LISTENER_MT_REGKEY); + lua_newtable(L); // child methods + luaL_setfuncs(L, moonbr_io_child_methods, 0); + lua_pushvalue(L, -1); + lua_setfield(L, -3, "child_pt"); + lua_setfield(L, LUA_REGISTRYINDEX, MOONBR_IO_CHILD_PT_REGKEY); + lua_newtable(L); // child metatable + luaL_setfuncs(L, moonbr_io_child_metamethods, 0); + lua_pushvalue(L, -1); + lua_setfield(L, -3, "child_mt"); + lua_setfield(L, LUA_REGISTRYINDEX, MOONBR_IO_CHILD_MT_REGKEY); + + moonbr_io_pushhandle(L, 0); + lua_setfield(L, -2, "stdin"); + moonbr_io_pushhandle(L, 1); + lua_setfield(L, -2, "stdout"); + moonbr_io_pushhandle(L, 2); + lua_setfield(L, -2, "stderr"); + luaL_setfuncs(L, moonbr_io_module_funcs, 0); return 1; diff -r 7d1cda1ed530 -r 5601a486e68a reference.txt --- a/reference.txt Sun Jun 21 02:49:23 2015 +0200 +++ b/reference.txt Sun Jun 21 20:10:21 2015 +0200 @@ -292,6 +292,33 @@ listed below. +### moonbridge_io.exec(command, arg1, arg2, ...) + +Executes the given command and returns a handle with three sockets named +"stdin", "stdout", and "stderr" as well as the following methods: + +- :kill(signal) +- :wait() +- :wait_nb() +- :wait_call(waitfunc) +- :wait_yield() + +Use :kill(signal) to terminate the process with the given signal (defaults to +SIGTERM). + +The :wait() method will wait for the process to terminate and return its exit +code. If the process was terminated by a signal, a negative integer is returned +which corresponds to the respective positive signal number. + +The method :wait_nb() is the same as :wait(), except that it does not block but +returns nil if the child process has not terminated yet. + +The method :wait_call() is the same as :wait() but calls waitfunc() (in an +infinite loop) as long as the process is still running. + +The method :wait_yield() is an alias for :wait_call(coroutine.yield). + + ### moonbridge_io.localconnect(path) Tries to connect to a local socket (also known as Unix Domain Socket). Returns