moonbridge

changeset 205:5601a486e68a

Support asynchronous I/O with stdin/stdout/stderr of executed child processes
author jbe
date Sun Jun 21 20:10:21 2015 +0200 (2015-06-21)
parents 7d1cda1ed530
children 56efa825bfc7
files moonbridge_io.c reference.txt
line diff
     1.1 --- a/moonbridge_io.c	Sun Jun 21 02:49:23 2015 +0200
     1.2 +++ b/moonbridge_io.c	Sun Jun 21 20:10:21 2015 +0200
     1.3 @@ -18,6 +18,8 @@
     1.4  #include <time.h>
     1.5  #include <netdb.h>
     1.6  #include <arpa/inet.h>
     1.7 +#include <sys/types.h>
     1.8 +#include <sys/wait.h>
     1.9  
    1.10  #include <lua.h>
    1.11  #include <lauxlib.h>
    1.12 @@ -38,6 +40,8 @@
    1.13  #define MOONBR_IO_HANDLE_MT_REGKEY "moonbridge_io_handle"
    1.14  #define MOONBR_IO_HANDLE_PUBLIC_MT_REGKEY "moonbridge_io_handle_public"
    1.15  #define MOONBR_IO_LISTENER_MT_REGKEY "moonbridge_io_listener"
    1.16 +#define MOONBR_IO_CHILD_MT_REGKEY "moonbridge_io_child"
    1.17 +#define MOONBR_IO_CHILD_PT_REGKEY "moonbridge_io_child_pt"
    1.18  
    1.19  typedef struct {
    1.20    int fd;
    1.21 @@ -73,6 +77,13 @@
    1.22    int nonblocking;
    1.23  } moonbr_io_listener_t;
    1.24  
    1.25 +typedef struct {
    1.26 +  pid_t pid;
    1.27 +  int uninitialized_in;
    1.28 +  int uninitialized_out;
    1.29 +  int uninitialized_err;
    1.30 +} moonbr_io_child_t;
    1.31 +
    1.32  static int moonbr_io_yield(lua_State *L) {
    1.33    return lua_yield(L, lua_gettop(L));
    1.34  }
    1.35 @@ -857,6 +868,7 @@
    1.36  
    1.37  static int moonbr_io_handleindex(lua_State *L) {
    1.38    luaL_checkudata(L, 1, MOONBR_IO_HANDLE_MT_REGKEY);
    1.39 +  luaL_checkany(L, 2);
    1.40    lua_getuservalue(L, 1);
    1.41    lua_getfield(L, -1, "public");
    1.42    lua_pushvalue(L, 2);
    1.43 @@ -866,6 +878,8 @@
    1.44  
    1.45  static int moonbr_io_handlenewindex(lua_State *L) {
    1.46    luaL_checkudata(L, 1, MOONBR_IO_HANDLE_MT_REGKEY);
    1.47 +  luaL_checkany(L, 2);
    1.48 +  luaL_checkany(L, 3);
    1.49    lua_getuservalue(L, 1);
    1.50    lua_getfield(L, -1, "public");
    1.51    lua_pushvalue(L, 2);
    1.52 @@ -1217,6 +1231,265 @@
    1.53    return 0;
    1.54  }
    1.55  
    1.56 +static int moonbr_io_exec(lua_State *L) {
    1.57 +  char **argv;
    1.58 +  int i, argc;
    1.59 +  int sockin[2], sockout[2], sockerr[2];
    1.60 +  volatile int errorcond = 0;
    1.61 +  volatile char errmsgbuf[MOONBR_IO_MAXSTRERRORLEN];
    1.62 +  moonbr_io_child_t *child;
    1.63 +  argc = lua_gettop(L);
    1.64 +  argv = lua_newuserdata(L, (argc + 1) * sizeof(char *));
    1.65 +  for (i=0; i<argc; i++) argv[i] = (char *)luaL_checkstring(L, i+1);
    1.66 +  argv[argc] = NULL;
    1.67 +  if (socketpair(PF_LOCAL, SOCK_STREAM, 0, sockin)) {
    1.68 +    moonbr_io_errmsg();
    1.69 +    lua_pushnil(L);
    1.70 +    lua_pushfstring(L, "Could not create socket pair: %s", errmsg);
    1.71 +    return 2;
    1.72 +  }
    1.73 +  if (socketpair(PF_LOCAL, SOCK_STREAM, 0, sockout)) {
    1.74 +    moonbr_io_errmsg();
    1.75 +    close(sockin[0]);
    1.76 +    close(sockin[1]);
    1.77 +    lua_pushnil(L);
    1.78 +    lua_pushfstring(L, "Could not create socket pair: %s", errmsg);
    1.79 +    return 2;
    1.80 +  }
    1.81 +  if (socketpair(PF_LOCAL, SOCK_STREAM, 0, sockerr)) {
    1.82 +    moonbr_io_errmsg();
    1.83 +    close(sockin[0]);
    1.84 +    close(sockin[1]);
    1.85 +    close(sockout[0]);
    1.86 +    close(sockout[1]);
    1.87 +    lua_pushnil(L);
    1.88 +    lua_pushfstring(L, "Could not create socket pair: %s", errmsg);
    1.89 +    return 2;
    1.90 +  }
    1.91 +  child = lua_newuserdata(L, sizeof(moonbr_io_child_t));
    1.92 +  child->pid = vfork();
    1.93 +  if (child->pid == -1) {
    1.94 +    moonbr_io_errmsg();
    1.95 +    close(sockin[0]);
    1.96 +    close(sockin[1]);
    1.97 +    close(sockout[0]);
    1.98 +    close(sockout[1]);
    1.99 +    close(sockerr[0]);
   1.100 +    close(sockerr[1]);
   1.101 +    lua_pushnil(L);
   1.102 +    lua_pushfstring(L, "Could not fork: %s", errmsg);
   1.103 +    return 2;
   1.104 +  }
   1.105 +  if (!child->pid) {
   1.106 +    if (dup2(sockin[1], 0) == -1) {
   1.107 +      errorcond = 1;
   1.108 +      strerror_r(errno, (char *)errmsgbuf, MOONBR_IO_MAXSTRERRORLEN);
   1.109 +      _exit(0);
   1.110 +    }
   1.111 +    if (dup2(sockout[1], 1) == -1) {
   1.112 +      errorcond = 1;
   1.113 +      strerror_r(errno, (char *)errmsgbuf, MOONBR_IO_MAXSTRERRORLEN);
   1.114 +      _exit(0);
   1.115 +    }
   1.116 +    if (dup2(sockerr[1], 2) == -1) {
   1.117 +      errorcond = 1;
   1.118 +      strerror_r(errno, (char *)errmsgbuf, MOONBR_IO_MAXSTRERRORLEN);
   1.119 +      _exit(0);
   1.120 +    }
   1.121 +    closefrom(4);
   1.122 +    if (execvp(argv[0], argv)) {
   1.123 +      errorcond = 2;
   1.124 +      strerror_r(errno, (char *)errmsgbuf, MOONBR_IO_MAXSTRERRORLEN);
   1.125 +      _exit(0);
   1.126 +    }
   1.127 +  }
   1.128 +  close(sockin[1]);
   1.129 +  close(sockout[1]);
   1.130 +  close(sockerr[1]);
   1.131 +  if (errorcond) {
   1.132 +    int status;
   1.133 +    close(sockin[0]);
   1.134 +    close(sockout[0]);
   1.135 +    close(sockerr[0]);
   1.136 +    while (waitpid(child->pid, &status, 0) == -1) {
   1.137 +      if (errno != EINTR) {
   1.138 +        moonbr_io_errmsg();
   1.139 +        luaL_error(L, "Error in waitpid call after unsuccessful exec: %s", errmsg);
   1.140 +      }
   1.141 +    }
   1.142 +    lua_pushnil(L);
   1.143 +    if (errorcond == 2) lua_pushfstring(L, "Could not execute: %s", errmsgbuf);
   1.144 +    else lua_pushfstring(L, "Error in fork: %s", errmsgbuf);
   1.145 +    return 2;
   1.146 +  }
   1.147 +  if (fcntl(sockin[0], F_SETFD, FD_CLOEXEC) == -1) {
   1.148 +    moonbr_io_errmsg();
   1.149 +    close(sockin[0]);
   1.150 +    close(sockout[0]);
   1.151 +    close(sockerr[0]);
   1.152 +    luaL_error(L, "Error in fcntl call: %s", errmsg);
   1.153 +  }
   1.154 +  if (fcntl(sockout[0], F_SETFD, FD_CLOEXEC) == -1) {
   1.155 +    moonbr_io_errmsg();
   1.156 +    close(sockin[0]);
   1.157 +    close(sockout[0]);
   1.158 +    close(sockerr[0]);
   1.159 +    luaL_error(L, "Error in fcntl call: %s", errmsg);
   1.160 +  }
   1.161 +  if (fcntl(sockerr[0], F_SETFD, FD_CLOEXEC) == -1) {
   1.162 +    moonbr_io_errmsg();
   1.163 +    close(sockin[0]);
   1.164 +    close(sockout[0]);
   1.165 +    close(sockerr[0]);
   1.166 +    luaL_error(L, "Error in fcntl call: %s", errmsg);
   1.167 +  }
   1.168 +  /* close sockets during garbage collection in case a Lua error is raised */
   1.169 +  child->uninitialized_in = sockin[0];
   1.170 +  child->uninitialized_out = sockout[0];
   1.171 +  child->uninitialized_err = sockerr[0];
   1.172 +  lua_newtable(L);
   1.173 +  lua_setuservalue(L, -2);
   1.174 +  luaL_getmetatable(L, MOONBR_IO_CHILD_MT_REGKEY);
   1.175 +  lua_setmetatable(L, -2);
   1.176 +  moonbr_io_pushhandle(L, sockin[0]);
   1.177 +  lua_setfield(L, -2, "stdin");
   1.178 +  child->uninitialized_in = -1;
   1.179 +  moonbr_io_pushhandle(L, sockout[0]);
   1.180 +  lua_setfield(L, -2, "stdout");
   1.181 +  child->uninitialized_out = -1;
   1.182 +  moonbr_io_pushhandle(L, sockerr[0]);
   1.183 +  lua_setfield(L, -2, "stderr");
   1.184 +  child->uninitialized_err = -1;
   1.185 +  return 1;
   1.186 +}
   1.187 +
   1.188 +static int moonbr_io_childindex(lua_State *L) {
   1.189 +  luaL_checkudata(L, 1, MOONBR_IO_CHILD_MT_REGKEY);
   1.190 +  luaL_checkany(L, 2);
   1.191 +  lua_getuservalue(L, 1);
   1.192 +  lua_pushvalue(L, 2);
   1.193 +  lua_gettable(L, -2);
   1.194 +  if (lua_isnil(L, -1)) {
   1.195 +    luaL_getmetatable(L, MOONBR_IO_CHILD_PT_REGKEY);
   1.196 +    lua_pushvalue(L, 2);
   1.197 +    lua_gettable(L, -2);
   1.198 +  }
   1.199 +  return 1;
   1.200 +}
   1.201 +
   1.202 +static int moonbr_io_childnewindex(lua_State *L) {
   1.203 +  luaL_checkudata(L, 1, MOONBR_IO_CHILD_MT_REGKEY);
   1.204 +  luaL_checkany(L, 2);
   1.205 +  luaL_checkany(L, 3);
   1.206 +  lua_getuservalue(L, 1);
   1.207 +  lua_pushvalue(L, 2);
   1.208 +  lua_pushvalue(L, 3);
   1.209 +  lua_settable(L, -3);
   1.210 +  return 0;
   1.211 +}
   1.212 +
   1.213 +static int moonbr_io_childgc(lua_State *L) {
   1.214 +  moonbr_io_child_t *child;
   1.215 +  child = luaL_checkudata(L, 1, MOONBR_IO_CHILD_MT_REGKEY);
   1.216 +  if (child->pid) {
   1.217 +    int status;
   1.218 +    if (kill(child->pid, SIGKILL)) {
   1.219 +      moonbr_io_errmsg();
   1.220 +      luaL_error(L, "Error in kill call during garbage collection: %s", errmsg);
   1.221 +    }
   1.222 +    while (waitpid(child->pid, &status, 0) == -1) {
   1.223 +      if (errno != EINTR) {
   1.224 +        moonbr_io_errmsg();
   1.225 +        luaL_error(L, "Error in waitpid call during garbage collection: %s", errmsg);
   1.226 +      }
   1.227 +    }
   1.228 +  }
   1.229 +  if (child->uninitialized_in != -1) close(child->uninitialized_in);
   1.230 +  if (child->uninitialized_out != -1) close(child->uninitialized_out);
   1.231 +  if (child->uninitialized_err != -1) close(child->uninitialized_err);
   1.232 +  return 0;
   1.233 +}
   1.234 +
   1.235 +static int moonbr_io_kill(lua_State *L) {
   1.236 +  moonbr_io_child_t *child;
   1.237 +  int sig;
   1.238 +  child = luaL_checkudata(L, 1, MOONBR_IO_CHILD_MT_REGKEY);
   1.239 +  sig = luaL_optinteger(L, 2, SIGTERM);
   1.240 +  if (!child->pid) luaL_error(L, "Attempt to kill an already collected child process");
   1.241 +  if (kill(child->pid, sig)) {
   1.242 +    moonbr_io_errmsg();
   1.243 +    luaL_error(L, "Error in kill call: %s", errmsg);
   1.244 +  }
   1.245 +  lua_settop(L, 1);
   1.246 +  return 1;
   1.247 +}
   1.248 +
   1.249 +static int moonbr_io_wait_impl(lua_State *L, int nonblocking) {
   1.250 +  moonbr_io_child_t *child;
   1.251 +  pid_t waitedpid;
   1.252 +  int status;
   1.253 +  child = luaL_checkudata(L, 1, MOONBR_IO_CHILD_MT_REGKEY);
   1.254 +  if (!child->pid) luaL_error(L, "Attempt to wait for an already collected child process");
   1.255 +  while ((waitedpid = waitpid(child->pid, &status, nonblocking ? WNOHANG : 0)) == -1) {
   1.256 +    if (errno != EINTR) {
   1.257 +      moonbr_io_errmsg();
   1.258 +      luaL_error(L, "Error in waitpid call: %s", errmsg);
   1.259 +    }
   1.260 +  }
   1.261 +  if (!waitedpid) {
   1.262 +    lua_pushnil(L);
   1.263 +  } else {
   1.264 +    child->pid = 0;
   1.265 +    if (WIFEXITED(status)) {
   1.266 +      lua_pushinteger(L, WEXITSTATUS(status));
   1.267 +    } else if (WIFSIGNALED(status)) {
   1.268 +      lua_pushinteger(L, -WTERMSIG(status));
   1.269 +    } else {
   1.270 +      luaL_error(L, "Unexpected status value returned by waitpid call");
   1.271 +    }
   1.272 +  }
   1.273 +  return 1;
   1.274 +}
   1.275 +
   1.276 +static int moonbr_io_wait(lua_State *L) {
   1.277 +  return moonbr_io_wait_impl(L, 0);
   1.278 +}
   1.279 +
   1.280 +static int moonbr_io_wait_nb(lua_State *L) {
   1.281 +  return moonbr_io_wait_impl(L, 1);
   1.282 +}
   1.283 +
   1.284 +#if LUA_VERSION_NUM >= 503
   1.285 +static int moonbr_io_wait_cont(lua_State *L, int status, lua_KContext ctx) {
   1.286 +#else
   1.287 +static int moonbr_io_wait_cont(lua_State *L) {
   1.288 +#endif
   1.289 +#if !(LUA_VERSION_NUM >= 503)
   1.290 +  int ctx = 0;
   1.291 +  lua_getctx(L, &ctx);
   1.292 +#endif
   1.293 +  while (1) {
   1.294 +    lua_pushcfunction(L, moonbr_io_wait_nb);
   1.295 +    lua_pushvalue(L, 1);
   1.296 +    lua_call(L, 1, 1);
   1.297 +    if (!lua_isnil(L, -1)) break;
   1.298 +    lua_pushvalue(L, 2);
   1.299 +    lua_callk(L, 0, 0, ctx, moonbr_io_wait_cont);
   1.300 +  }
   1.301 +  return 1;
   1.302 +}
   1.303 +
   1.304 +static int moonbr_io_wait_call(lua_State *L) {
   1.305 +  lua_settop(L, 2);
   1.306 +#if LUA_VERSION_NUM >= 503
   1.307 +  return moonbr_io_wait_cont(L, 0, 0);
   1.308 +#else
   1.309 +  return moonbr_io_wait_cont(L);
   1.310 +#endif
   1.311 +}
   1.312 +
   1.313 +moonbr_io_yield_wrapper(moonbr_io_wait_yield, moonbr_io_wait_call);
   1.314 +
   1.315  static int moonbr_io_poll(lua_State *L) {
   1.316    moonbr_io_handle_t *handle;
   1.317    moonbr_io_listener_t *listener;
   1.318 @@ -1369,6 +1642,22 @@
   1.319    {NULL, NULL}
   1.320  };
   1.321  
   1.322 +static const struct luaL_Reg moonbr_io_child_methods[] = {
   1.323 +  {"kill", moonbr_io_kill},
   1.324 +  {"wait", moonbr_io_wait},
   1.325 +  {"wait_nb", moonbr_io_wait_nb},
   1.326 +  {"wait_call", moonbr_io_wait_call},
   1.327 +  {"wait_yield", moonbr_io_wait_yield},
   1.328 +  {NULL, NULL}
   1.329 +};
   1.330 +
   1.331 +static const struct luaL_Reg moonbr_io_child_metamethods[] = {
   1.332 +  {"__index", moonbr_io_childindex},
   1.333 +  {"__newindex", moonbr_io_childnewindex},
   1.334 +  {"__gc", moonbr_io_childgc},
   1.335 +  {NULL, NULL}
   1.336 +};
   1.337 +
   1.338  static const struct luaL_Reg moonbr_io_module_funcs[] = {
   1.339    {"localconnect", moonbr_io_localconnect},
   1.340    {"localconnect_nb", moonbr_io_localconnect_nb},
   1.341 @@ -1376,6 +1665,7 @@
   1.342    {"tcpconnect_nb", moonbr_io_tcpconnect_nb},
   1.343    {"locallisten", moonbr_io_locallisten},
   1.344    {"tcplisten", moonbr_io_tcplisten},
   1.345 +  {"exec", moonbr_io_exec},
   1.346    {"poll", moonbr_io_poll},
   1.347    {"timeref", moonbr_io_timeref},
   1.348    {NULL, NULL}
   1.349 @@ -1412,6 +1702,24 @@
   1.350    lua_setfield(L, -3, "listener_mt");
   1.351    lua_setfield(L, LUA_REGISTRYINDEX, MOONBR_IO_LISTENER_MT_REGKEY);
   1.352  
   1.353 +  lua_newtable(L);  // child methods
   1.354 +  luaL_setfuncs(L, moonbr_io_child_methods, 0);
   1.355 +  lua_pushvalue(L, -1);
   1.356 +  lua_setfield(L, -3, "child_pt");
   1.357 +  lua_setfield(L, LUA_REGISTRYINDEX, MOONBR_IO_CHILD_PT_REGKEY);
   1.358 +  lua_newtable(L);  // child metatable
   1.359 +  luaL_setfuncs(L, moonbr_io_child_metamethods, 0);
   1.360 +  lua_pushvalue(L, -1);
   1.361 +  lua_setfield(L, -3, "child_mt");
   1.362 +  lua_setfield(L, LUA_REGISTRYINDEX, MOONBR_IO_CHILD_MT_REGKEY);
   1.363 +
   1.364 +  moonbr_io_pushhandle(L, 0);
   1.365 +  lua_setfield(L, -2, "stdin");
   1.366 +  moonbr_io_pushhandle(L, 1);
   1.367 +  lua_setfield(L, -2, "stdout");
   1.368 +  moonbr_io_pushhandle(L, 2);
   1.369 +  lua_setfield(L, -2, "stderr");
   1.370 +  
   1.371    luaL_setfuncs(L, moonbr_io_module_funcs, 0);
   1.372    return 1;
   1.373  
     2.1 --- a/reference.txt	Sun Jun 21 02:49:23 2015 +0200
     2.2 +++ b/reference.txt	Sun Jun 21 20:10:21 2015 +0200
     2.3 @@ -292,6 +292,33 @@
     2.4  listed below.
     2.5  
     2.6  
     2.7 +### moonbridge_io.exec(command, arg1, arg2, ...)
     2.8 +
     2.9 +Executes the given command and returns a handle with three sockets named
    2.10 +"stdin", "stdout", and "stderr" as well as the following methods:
    2.11 +
    2.12 +- :kill(signal)
    2.13 +- :wait()
    2.14 +- :wait_nb()
    2.15 +- :wait_call(waitfunc) 
    2.16 +- :wait_yield()
    2.17 +
    2.18 +Use :kill(signal) to terminate the process with the given signal (defaults to
    2.19 +SIGTERM).
    2.20 +
    2.21 +The :wait() method will wait for the process to terminate and return its exit
    2.22 +code. If the process was terminated by a signal, a negative integer is returned
    2.23 +which corresponds to the respective positive signal number.
    2.24 +
    2.25 +The method :wait_nb() is the same as :wait(), except that it does not block but
    2.26 +returns nil if the child process has not terminated yet.
    2.27 +
    2.28 +The method :wait_call() is the same as :wait() but calls waitfunc() (in an
    2.29 +infinite loop) as long as the process is still running.
    2.30 +
    2.31 +The method :wait_yield() is an alias for :wait_call(coroutine.yield).
    2.32 +
    2.33 +
    2.34  ### moonbridge_io.localconnect(path)
    2.35  
    2.36  Tries to connect to a local socket (also known as Unix Domain Socket). Returns

Impressum / About Us