Lua changes: LUA_ROOT is "/usr" instead of "/usr/local". LUA_PATH and LUA_CPATH do not have "./?.lua" and "./?.so" in them. Added os.spawn() for spawning and interacting with asynchronous processes. diff -r 8a23edc91533 src/luaconf.h --- a/src/luaconf.h Mon Jan 12 18:57:53 2015 -0500 +++ b/src/luaconf.h Mon Jan 12 23:50:08 2015 -0500 @@ -166,34 +166,32 @@ #define LUA_PATH_DEFAULT \ LUA_LDIR"?.lua;" LUA_LDIR"?\\init.lua;" \ LUA_CDIR"?.lua;" LUA_CDIR"?\\init.lua;" \ - LUA_SHRDIR"?.lua;" LUA_SHRDIR"?\\init.lua;" \ - ".\\?.lua;" ".\\?\\init.lua" + LUA_SHRDIR"?.lua;" LUA_SHRDIR"?\\init.lua;" #endif #if !defined(LUA_CPATH_DEFAULT) #define LUA_CPATH_DEFAULT \ LUA_CDIR"?.dll;" \ LUA_CDIR"..\\lib\\lua\\" LUA_VDIR "\\?.dll;" \ - LUA_CDIR"loadall.dll;" ".\\?.dll" + LUA_CDIR"loadall.dll;" #endif #else /* }{ */ -#define LUA_ROOT "/usr/local/" +#define LUA_ROOT "/usr/" #define LUA_LDIR LUA_ROOT "share/lua/" LUA_VDIR "/" #define LUA_CDIR LUA_ROOT "lib/lua/" LUA_VDIR "/" #if !defined(LUA_PATH_DEFAULT) #define LUA_PATH_DEFAULT \ LUA_LDIR"?.lua;" LUA_LDIR"?/init.lua;" \ - LUA_CDIR"?.lua;" LUA_CDIR"?/init.lua;" \ - "./?.lua;" "./?/init.lua" + LUA_CDIR"?.lua;" LUA_CDIR"?/init.lua;" #endif #if !defined(LUA_CPATH_DEFAULT) #define LUA_CPATH_DEFAULT \ - LUA_CDIR"?.so;" LUA_CDIR"loadall.so;" "./?.so" + LUA_CDIR"?.so;" LUA_CDIR"loadall.so;" #endif #endif /* } */ --- a/src/loslib.c 2022-02-27 11:51:08.935094072 -0500 +++ b/src/loslib.c 2022-02-26 18:43:06.214717620 -0500 @@ -4,6 +4,15 @@ ** See Copyright Notice in lua.h */ +// Defines for Textadept's process spawning extension. +#if __linux__ +#define _XOPEN_SOURCE 1 // for kill from signal.h +#define _XOPEN_SOURCE_EXTENDED 1 // for kill from signal.h +#if !GTK +#define _GNU_SOURCE 1 // for execvpe from unistd.h +#endif +#endif + #define loslib_c #define LUA_LIB @@ -403,6 +412,11 @@ return 0; } +// Forward declarations and exports for Textadept's process spawning extension. +static int os_spawn(lua_State *L); +int os_spawn_pushfds(lua_State *L); +int os_spawn_readfds(lua_State *L); + static const luaL_Reg syslib[] = { {"clock", os_clock}, @@ -414,6 +428,7 @@ {"remove", os_remove}, {"rename", os_rename}, {"setlocale", os_setlocale}, + {"spawn", os_spawn}, {"time", os_time}, {"tmpname", os_tmpname}, {NULL, NULL} @@ -425,6 +440,640 @@ LUAMOD_API int luaopen_os (lua_State *L) { luaL_newlib(L, syslib); +#if (!GTK && !_WIN32 || __APPLE__) + // Need to keep track of running processes for monitoring fds and pids. + lua_newtable(L), lua_setfield(L, LUA_REGISTRYINDEX, "spawn_procs"); +#endif return 1; } + +// Process spawning extension for Textadept using GLib or POSIX. +// Copyright 2012-2022 Mitchell. See LICENSE. + +#include +//#include +//#include +#include +#include +#if GTK +#include +#endif +#if !_WIN32 +#if (!GTK || __APPLE__) +#include +#include +#endif +#include +#include +#else +#include +#include +#endif + +#if _WIN32 +#define kill(pid, _) TerminateProcess(pid, 1) +#define g_io_channel_unix_new g_io_channel_win32_new_fd +#define close CloseHandle +#define FD(handle) _open_osfhandle((intptr_t)handle, _O_RDONLY) +#if !GTK +// The following macro is only for quieting compiler warnings. Spawning in Win32 +// console is not supported. +#define read(fd, buf, len) read((int)fd, buf, len) +#endif +#endif + +typedef struct { + lua_State *L; + int ref; +#if !_WIN32 + int pid, fstdin, fstdout, fstderr; +#else + HANDLE pid, fstdin, fstdout, fstderr; +#endif +#if (GTK && !__APPLE__) + GIOChannel *cstdout, *cstderr; +#endif + int stdout_cb, stderr_cb, exit_cb, exit_status; +} PStream; + +/** p:status() Lua function. */ +static int proc_status(lua_State *L) { + PStream *p = luaL_checkudata(L, 1, "ta_spawn"); + return (lua_pushstring(L, p->pid ? "running" : "terminated"), 1); +} + +/** Process exit cleanup function. */ +static void exited(PStream *p, int status) { + lua_State *L = p->L; + if (p->exit_cb != LUA_REFNIL) { + // Call exit callback function with exit status. + lua_rawgeti(L, LUA_REGISTRYINDEX, p->exit_cb); + lua_pushinteger(L, status); + if (lua_pcall(L, 1, 0, 0) != LUA_OK) + fprintf(stderr, "Lua: %s\n", lua_tostring(L, -1)), lua_pop(L, 1); + } +#if GTK && !__APPLE__ + g_source_remove_by_user_data(p); // disconnect cstdout watch + g_source_remove_by_user_data(p); // disconnect cstderr watch + g_source_remove_by_user_data(p); // disconnect child watch + g_spawn_close_pid(p->pid); +#elif (!GTK || __APPLE__) + // Stop tracking and monitoring this proc. + lua_getfield(L, LUA_REGISTRYINDEX, "spawn_procs"); + for (lua_pushnil(L); lua_next(L, -2); lua_pop(L, 1)) { + PStream *monitored_p = lua_touserdata(L, -2); + if (monitored_p->pid == p->pid) { + lua_pushnil(L), lua_replace(L, -2), lua_settable(L, -3); // t[proc] = nil + break; + } + } + lua_pop(L, 1); // spawn_procs +#endif + close(p->fstdin), close(p->fstdout), close(p->fstderr); + luaL_unref(L, LUA_REGISTRYINDEX, p->stdout_cb); + luaL_unref(L, LUA_REGISTRYINDEX, p->stderr_cb); + luaL_unref(L, LUA_REGISTRYINDEX, p->exit_cb); + luaL_unref(L, LUA_REGISTRYINDEX, p->ref); // allow proc to be collected + p->pid = 0, p->exit_status = status; +} + +/** p:wait() Lua function. */ +static int proc_wait(lua_State *L) { + PStream *p = luaL_checkudata(L, 1, "ta_spawn"); + if (!p->pid) return (lua_pushinteger(L, p->exit_status), 1); +#if !_WIN32 + int status; + waitpid(p->pid, &status, 0); + status = WIFEXITED(status) ? WEXITSTATUS(status) : 1; +#else + DWORD status; + WaitForSingleObject(p->pid, INFINITE); + GetExitCodeProcess(p->pid, &status); +#endif + exited(p, status); + return (lua_pushinteger(L, status), 1); +} + +/** p:read() Lua function. */ +static int proc_read(lua_State *L) { + PStream *p = luaL_checkudata(L, 1, "ta_spawn"); + luaL_argcheck(L, p->pid, 1, "process terminated"); + char *c = (char *)luaL_optstring(L, 2, "l"); + if (*c == '*') c++; // skip optional '*' (for compatibility) + luaL_argcheck( + L, *c == 'l' || *c == 'L' || *c == 'a' || lua_isnumber(L, 2), 2, + "invalid option"); +#if (GTK && !__APPLE__) + char *buf; + size_t len; + GError *error = NULL; + GIOStatus status = G_IO_STATUS_NORMAL; + if (!g_io_channel_get_buffered(p->cstdout)) + g_io_channel_set_buffered(p->cstdout, true); // needed for functions below + if (!lua_isnumber(L, 2)) { + if (*c == 'l' || *c == 'L') { + GString *s = g_string_new(NULL); + status = g_io_channel_read_line_string(p->cstdout, s, NULL, &error); + len = s->len, buf = g_string_free(s, false); + } else if (*c == 'a') { + status = g_io_channel_read_to_end(p->cstdout, &buf, &len, &error); + if (status == G_IO_STATUS_EOF) status = G_IO_STATUS_NORMAL; + } + } else { + size_t bytes = (size_t)lua_tointeger(L, 2); + buf = malloc(bytes); + status = g_io_channel_read_chars(p->cstdout, buf, bytes, &len, &error); + } + if ((g_io_channel_get_buffer_condition(p->cstdout) & G_IO_IN) == 0) + g_io_channel_set_buffered(p->cstdout, false); // needed for stdout callback + if (*c == 'l' && buf[len - 1] == '\n') len--; + if (*c == 'l' && buf[len - 1] == '\r') len--; + lua_pushlstring(L, buf, len); + free(buf); + if (status != G_IO_STATUS_NORMAL) { + lua_pushnil(L); + if (status == G_IO_STATUS_EOF) return 1; + lua_pushinteger(L, error->code); + lua_pushstring(L, error->message); + return 3; + } else return 1; +#else + int len = 0; + if (!lua_isnumber(L, 2)) { + luaL_Buffer buf; + luaL_buffinit(L, &buf); + int n; + char ch; + while ((n = read(p->fstdout, &ch, 1)) > 0) { + if ((ch != '\r' && ch != '\n') || *c == 'L' || *c == 'a') + luaL_addchar(&buf, ch), len++; + if (ch == '\n' && *c != 'a') break; + } + if (n < 0 && len == 0) len = n; + luaL_pushresult(&buf); + if (n == 0 && len == 0 && *c != 'a') lua_pushnil(L); // EOF + } else { + size_t bytes = (size_t)lua_tointeger(L, 2); + char *buf = malloc(bytes); + if ((len = read(p->fstdout, buf, bytes)) > 0) + lua_pushlstring(L, buf, len); + else if (len == 0) + lua_pushnil(L); // EOF + free(buf); + } + if (len < 0) { + lua_pushnil(L); + lua_pushinteger(L, errno); + lua_pushstring(L, strerror(errno)); + return 3; + } else return 1; +#endif +} + +/** p:write() Lua function. */ +static int proc_write(lua_State *L) { + PStream *p = luaL_checkudata(L, 1, "ta_spawn"); + luaL_argcheck(L, p->pid, 1, "process terminated"); + for (int i = 2; i <= lua_gettop(L); i++) { + size_t len; + const char *s = luaL_checklstring(L, i, &len); +#if !_WIN32 + len = write(p->fstdin, s, len); // assign result to fix compiler warning +#else + DWORD len_written; + WriteFile(p->fstdin, s, len, &len_written, NULL); +#endif + } + return 0; +} + +/** p:close() Lua function. */ +static int proc_close(lua_State *L) { + PStream *p = luaL_checkudata(L, 1, "ta_spawn"); + luaL_argcheck(L, p->pid, 1, "process terminated"); + return (close(p->fstdin), 0); +} + +/** p:kill() Lua function. */ +static int proc_kill(lua_State *L) { + PStream *p = luaL_checkudata(L, 1, "ta_spawn"); + if (p->pid) kill(p->pid, luaL_optinteger(L, 2, SIGKILL)); + return 0; +} + +/** tostring(p) Lua function. */ +static int proc_tostring(lua_State *L) { + PStream *p = luaL_checkudata(L, 1, "ta_spawn"); + if (p->pid) + lua_pushfstring(L, "process (pid=%d)", p->pid); + else + lua_pushstring(L, "process (terminated)"); + return 1; +} + +#if (GTK && !__APPLE__) +/** __gc Lua metamethod. */ +static int proc_gc(lua_State *L) { + PStream *p = luaL_checkudata(L, 1, "ta_spawn"); + if (p->pid) { + // lua_close() was called, forcing GC. Disconnect listeners since GTK is + // still running and may try to invoke callbacks. + g_source_remove_by_user_data(p); // disconnect cstdout watch + g_source_remove_by_user_data(p); // disconnect cstderr watch + g_source_remove_by_user_data(p); // disconnect child watch + g_spawn_close_pid(p->pid); + } + return 0; +} + +/** Signal that channel output is available for reading. */ +static int ch_read(GIOChannel *source, GIOCondition cond, void *data) { + PStream *p = data; + if (!p->pid || !(cond & G_IO_IN)) return false; + char buf[BUFSIZ]; + size_t len = 0; + do { + int status = g_io_channel_read_chars(source, buf, BUFSIZ, &len, NULL); + int r = (source == p->cstdout) ? p->stdout_cb : p->stderr_cb; + if (status == G_IO_STATUS_NORMAL && len > 0 && r > 0) { + lua_rawgeti(p->L, LUA_REGISTRYINDEX, r); + lua_pushlstring(p->L, buf, len); + if (lua_pcall(p->L, 1, 0, 0) != LUA_OK) + fprintf(stderr, "Lua: %s\n", lua_tostring(p->L, -1)), lua_pop(p->L, 1); + } + } while (len == BUFSIZ); + return p->pid && !(cond & G_IO_HUP); +} + +/** + * Creates a new channel that monitors a file descriptor for output. + * @param fd File descriptor returned by `g_spawn_async_with_pipes()` or + * `_open_osfhandle()`. + * @param p PStream to notify when output is available for reading. + * @param watch Whether or not to watch for output to send to a Lua callback. + */ +static GIOChannel *new_channel(int fd, PStream *p, bool watch) { + GIOChannel *channel = g_io_channel_unix_new(fd); + g_io_channel_set_encoding(channel, NULL, NULL); + g_io_channel_set_buffered(channel, false); + if (watch) { + g_io_add_watch(channel, G_IO_IN | G_IO_HUP, ch_read, p); + g_io_channel_unref(channel); + } + return channel; +} + +/** Signal that the child process finished. */ +static void proc_exited(GPid pid, int status, void *data) { + exited(data, status); +} +#elif !_WIN32 +/** + * Pushes onto the stack an fd_set of all spawned processes for use with + * `select()` and `os_spawn_readfds()` and returns the `nfds` to pass to + * `select()`. + */ +int os_spawn_pushfds(lua_State *L) { + int nfds = 1; + fd_set *fds = lua_newuserdata(L, sizeof(fd_set)); + FD_ZERO(fds); + lua_getfield(L, LUA_REGISTRYINDEX, "spawn_procs"); + for (lua_pushnil(L); lua_next(L, -2); lua_pop(L, 1)) { + PStream *p = lua_touserdata(L, -2); + FD_SET(p->fstdout, fds); + FD_SET(p->fstderr, fds); + if (p->fstdout >= nfds) nfds = p->fstdout + 1; + if (p->fstderr >= nfds) nfds = p->fstderr + 1; + } + lua_pop(L, 1); // spawn_procs + return nfds; +} + +/** Signal that a fd has output to read. */ +static void fd_read(int fd, PStream *p) { + char buf[BUFSIZ]; + ssize_t len; + do { + len = read(fd, buf, BUFSIZ); + int r = (fd == p->fstdout) ? p->stdout_cb : p->stderr_cb; + if (len > 0 && r > 0) { + lua_rawgeti(p->L, LUA_REGISTRYINDEX, r); + lua_pushlstring(p->L, buf, len); + if (lua_pcall(p->L, 1, 0, 0) != LUA_OK) + fprintf(stderr, "Lua: %s\n", lua_tostring(p->L, -1)), lua_pop(p->L, 1); + } + } while (len == BUFSIZ); +} + +/** + * Reads any output from the fds in the fd_set at the top of the stack and + * returns the number of fds read from. + * Also signals any registered child processes that have finished and cleans up + * after them. + */ +int os_spawn_readfds(lua_State *L) { + int n = 0; + fd_set *fds = lua_touserdata(L, -1); + lua_getfield(L, LUA_REGISTRYINDEX, "spawn_procs"); + for (lua_pushnil(L); lua_next(L, -2); lua_pop(L, 1)) { + PStream *p = lua_touserdata(L, -2); + // Read output if any is available. + if (FD_ISSET(p->fstdout, fds)) fd_read(p->fstdout, p), n++; + if (FD_ISSET(p->fstderr, fds)) fd_read(p->fstderr, p), n++; + // Check process status. + int status; + if (waitpid(p->pid, &status, WNOHANG) > 0) { + fd_read(p->fstdout, p), fd_read(p->fstderr, p); // read anything left + exited(p, status); + } + } + lua_pop(L, 1); // spawn_procs + return n; +} + +#if (GTK && __APPLE__) +static int monitoring_fds = 0; +/** + * Monitors spawned fds when GTK is idle. + * This is necessary because at the moment, using GLib on macOS to spawn and + * monitor file descriptors mostly blocks when attempting to poll those fds. + * Note that this idle event is considered a pending event, so the construct + * `while (gtk_events_pending()) gtk_main_iteration();` will cycle for as long + * as the monitor is active. To help get around this, this function sets a + * "spawn_procs_polled" boolean in the registry after poll. An application can + * set this boolean to `false` prior to calling `gtk_main_iteration()`. If there + * are still pending events and this boolean is `true`, then there are no + * non-idle pending events left. + */ +static int monitor_fds(void *L) { + struct timeval timeout = {0, 1e5}; // 0.1s + int nfds = os_spawn_pushfds(L); + if (select(nfds, lua_touserdata(L, -1), NULL, NULL, &timeout) > 0) + os_spawn_readfds(L); + lua_pop(L, 1); // fds + if (nfds == 1) monitoring_fds = 0; + lua_pushboolean(L, 1); + lua_setfield(L, LUA_REGISTRYINDEX, "spawn_procs_polled"); + return nfds > 1; +} +#endif +#endif + +/** spawn() Lua function. */ +static int os_spawn(lua_State *L) { + int narg = 1; + // Determine process parameters (argv, cwd, envp). +#if !_WIN32 + // Construct argv from first string param. +#if (GTK && !__APPLE__) + char **argv = NULL; + GError *error = NULL; + if (!g_shell_parse_argv(luaL_checkstring(L, narg++), NULL, &argv, &error)) { + lua_pushfstring(L, "invalid argv: %s", error->message); + luaL_argerror(L, 1, lua_tostring(L, -1)); + } +#else + lua_newtable(L); + const char *param = luaL_checkstring(L, narg++), *c = param; + while (*c) { + while (*c == ' ') c++; + param = c; + if (*c == '"') { + param = ++c; + while (*c && (*c != '"' || *(c - 1) == '\\')) c++; + } else while (*c && *c != ' ') c++; + lua_pushlstring(L, param, c - param); + lua_rawseti(L, -2, lua_rawlen(L, -2) + 1); + if (*c == '"') c++; + } + int argc = lua_rawlen(L, -1); + char **argv = calloc(argc + 1, sizeof(char *)); + for (int i = 0; i < argc; i++) { + lua_rawgeti(L, -1, i + 1); + argv[i] = strcpy(malloc(lua_rawlen(L, -1) + 1), lua_tostring(L, -1)); + lua_pop(L, 1); // param + } + lua_pop(L, 1); // argv +#endif + // Determine cwd from optional second string param. + const char *cwd = lua_isstring(L, narg) ? lua_tostring(L, narg++) : NULL; + // Construct environment from optional third table param. + int envn = 0; + char **envp = NULL; + if (lua_istable(L, narg)) { + for (lua_pushnil(L); lua_next(L, narg); lua_pop(L, 1)) envn++; + envp = calloc(envn + 1, sizeof(char *)); + int i = 0; + for (lua_pushnil(L); lua_next(L, narg); lua_pop(L, 1)) { + if (!lua_isstring(L, -2) || !lua_isstring(L, -1)) continue; + if (lua_type(L, -2) == LUA_TSTRING) { + lua_pushvalue(L, -2), lua_pushliteral(L, "="), lua_pushvalue(L, -3), + lua_concat(L, 3), lua_replace(L, -2); // construct "KEY=VALUE" + } + envp[i++] = strcpy(malloc(lua_rawlen(L, -1) + 1), lua_tostring(L, -1)); + } + narg++; + } +#else + // Construct argv from first string param. + lua_pushstring(L, getenv("COMSPEC")), lua_pushstring(L, " /c "), + lua_pushvalue(L, 1), lua_concat(L, 3), lua_replace(L, 1); + wchar_t argv[2048] = {L'\0'}; + MultiByteToWideChar( + GetACP(), 0, lua_tostring(L, narg++), -1, (LPWSTR)&argv, sizeof(argv)); + // Determine cwd from optional second string param. + wchar_t cwd[MAX_PATH] = {L'\0'}; + if (lua_isstring(L, narg)) + MultiByteToWideChar( + GetACP(), 0, lua_tostring(L, narg++), -1, (LPWSTR)&cwd, MAX_PATH); + // Construct environment from optional third table param. + char *envp = NULL; + if (lua_istable(L, narg)) { + luaL_Buffer buf; + luaL_buffinit(L, &buf); + for (lua_pushnil(L); lua_next(L, narg); lua_pop(L, 1)) { + if (!lua_isstring(L, -2) || !lua_isstring(L, -1)) continue; + if (lua_type(L, -2) == LUA_TSTRING) { + lua_pushvalue(L, -2), lua_pushliteral(L, "="), lua_pushvalue(L, -3), + lua_concat(L, 3), lua_replace(L, -2); // construct "KEY=VALUE" + } + luaL_addstring(&buf, lua_tostring(L, -1)), luaL_addchar(&buf, '\0'); + } + luaL_addchar(&buf, '\0'); + luaL_pushresult(&buf); + envp = malloc(lua_rawlen(L, -1) * sizeof(char)); + memcpy(envp, lua_tostring(L, -1), lua_rawlen(L, -1)); + lua_pop(L, 1); // buf + narg++; + } +#endif + lua_settop(L, 6); // ensure 6 values so userdata to be pushed is 7th + + // Create process object to be returned and link callback functions from + // optional fourth, fifth, and sixth function params. + PStream *p = lua_newuserdata(L, sizeof(PStream)); + p->L = L, p->ref = 0; + for (int i = narg; i < narg + 3; i++) + luaL_argcheck( + L, lua_isfunction(L, i) || lua_isnoneornil(L, i), i, + "function or nil expected"); + p->stdout_cb = (lua_pushvalue(L, narg++), luaL_ref(L, LUA_REGISTRYINDEX)); + p->stderr_cb = (lua_pushvalue(L, narg++), luaL_ref(L, LUA_REGISTRYINDEX)); + p->exit_cb = (lua_pushvalue(L, narg++), luaL_ref(L, LUA_REGISTRYINDEX)); + if (luaL_newmetatable(L, "ta_spawn")) { + lua_pushcfunction(L, proc_status), lua_setfield(L, -2, "status"); + lua_pushcfunction(L, proc_wait), lua_setfield(L, -2, "wait"); + lua_pushcfunction(L, proc_read), lua_setfield(L, -2, "read"); + lua_pushcfunction(L, proc_write), lua_setfield(L, -2, "write"); + lua_pushcfunction(L, proc_close), lua_setfield(L, -2, "close"); + lua_pushcfunction(L, proc_kill), lua_setfield(L, -2, "kill"); + lua_pushcfunction(L, proc_tostring), lua_setfield(L, -2, "__tostring"); +#if (GTK && !__APPLE__) + lua_pushcfunction(L, proc_gc), lua_setfield(L, -2, "__gc"); +#endif + lua_pushvalue(L, -1), lua_setfield(L, -2, "__index"); + } + lua_setmetatable(L, -2); + + // Spawn the process, connecting to stdin, stdout, stderr, and exit. +#if !_WIN32 +#if (GTK && !__APPLE__) + GSpawnFlags flags = G_SPAWN_DO_NOT_REAP_CHILD | G_SPAWN_SEARCH_PATH; + if (g_spawn_async_with_pipes( + cwd, argv, envp, flags, NULL, NULL, &p->pid, &p->fstdin, &p->fstdout, + &p->fstderr, &error)) { + p->cstdout = new_channel(p->fstdout, p, p->stdout_cb > 0); + p->cstderr = new_channel(p->fstderr, p, p->stderr_cb > 0); + g_child_watch_add(p->pid, proc_exited, p); + lua_pushnil(L); // no error + } else { + lua_pushnil(L); + lua_pushfstring(L, "%s: %s", lua_tostring(L, 1), error->message); + } + + g_strfreev(argv), g_strfreev(envp); +#else + // Adapted from Chris Emerson and GLib. + // Attempt to create pipes for stdin, stdout, and stderr and fork process. + int pstdin[2] = {-1, -1}, pstdout[2] = {-1, -1}, pstderr[2] = {-1, -1}, pid; + if (pipe(pstdin) == 0 && pipe(pstdout) == 0 && pipe(pstderr) == 0 && + (pid = fork()) >= 0) { + if (pid > 0) { + // Parent process: register child for monitoring its fds and pid. + close(pstdin[0]), close(pstdout[1]), close(pstderr[1]); + p->pid = pid; + p->fstdin = pstdin[1], p->fstdout = pstdout[0], p->fstderr = pstderr[0]; + lua_getfield(L, LUA_REGISTRYINDEX, "spawn_procs"); + // spawn_procs is of the form: t[proc] = true + lua_pushvalue(L, -2), lua_pushboolean(L, 1), lua_settable(L, -3); + lua_pop(L, 1); // spawn_procs + lua_pushnil(L); // no error +#if (GTK && __APPLE__) + // On GTK-OSX, manually monitoring spawned fds prevents the fd polling + // aborts caused by GLib. + if (!monitoring_fds) g_idle_add(monitor_fds, L), monitoring_fds = 1; +#endif + } else if (pid == 0) { + // Child process: redirect stdin, stdout, and stderr, chdir, and exec. + close(pstdin[1]), close(pstdout[0]), close(pstderr[0]); + close(0), close(1), close(2); + dup2(pstdin[0], 0), dup2(pstdout[1], 1), dup2(pstderr[1], 2); + close(pstdin[0]), close(pstdout[1]), close(pstderr[1]); + if (cwd && chdir(cwd) < 0) { + fprintf( + stderr, "Failed to change directory '%s' (%s)", cwd, strerror(errno)); + exit(EXIT_FAILURE); + } + extern char **environ; +#if __linux__ + if (!envp) envp = environ; + execvpe(argv[0], argv, envp); // does not return on success +#else + if (envp) environ = envp; + execvp(argv[0], argv); // does not return on success +#endif + fprintf( + stderr, "Failed to execute child process \"%s\" (%s)", argv[0], + strerror(errno)); + exit(EXIT_FAILURE); + } + } else { + if (pstdin[0] >= 0) close(pstdin[0]), close(pstdin[1]); + if (pstdout[0] >= 0) close(pstdout[0]), close(pstdout[1]); + if (pstderr[0] >= 0) close(pstderr[0]), close(pstderr[1]); + lua_pushnil(L); + lua_pushfstring(L, "%s: %s", lua_tostring(L, 1), strerror(errno)); + } + for (int i = 0; i < argc; i++) free(argv[i]); + free(argv); + if (envp) { + for (int i = 0; i < envn; i++) free(envp[i]); + free(envp); + } +#endif +#else +#if GTK + // Adapted from SciTE. + SECURITY_DESCRIPTOR sd; + InitializeSecurityDescriptor(&sd, SECURITY_DESCRIPTOR_REVISION); + SetSecurityDescriptorDacl(&sd, true, NULL, false); + SECURITY_ATTRIBUTES sa = {sizeof(SECURITY_ATTRIBUTES), 0, 0}; + sa.nLength = sizeof(SECURITY_ATTRIBUTES); + sa.lpSecurityDescriptor = &sd; + sa.bInheritHandle = true; + + // Redirect stdin. + HANDLE stdin_read = NULL, proc_stdin = NULL; + CreatePipe(&stdin_read, &proc_stdin, &sa, 0); + SetHandleInformation(proc_stdin, HANDLE_FLAG_INHERIT, 0); + // Redirect stdout. + HANDLE proc_stdout = NULL, stdout_write = NULL; + CreatePipe(&proc_stdout, &stdout_write, &sa, 0); + SetHandleInformation(proc_stdout, HANDLE_FLAG_INHERIT, 0); + // Redirect stderr. + HANDLE proc_stderr = NULL, stderr_write = NULL; + CreatePipe(&proc_stderr, &stderr_write, &sa, 0); + SetHandleInformation(proc_stderr, HANDLE_FLAG_INHERIT, 0); + + // Spawn with pipes and no window. + // TODO: CREATE_UNICODE_ENVIRONMENT? + STARTUPINFOW startup_info = { + sizeof(STARTUPINFOW), NULL, NULL, NULL, 0, 0, 0, 0, 0, 0, 0, + STARTF_USESHOWWINDOW | STARTF_USESTDHANDLES, SW_HIDE, 0, 0, stdin_read, + stdout_write, stderr_write + }; + PROCESS_INFORMATION proc_info = {0, 0, 0, 0}; + if (CreateProcessW( + NULL, argv, NULL, NULL, true, CREATE_NEW_PROCESS_GROUP, envp, + *cwd ? cwd : NULL, &startup_info, &proc_info)) { + p->pid = proc_info.hProcess; + p->fstdin = proc_stdin, p->fstdout = proc_stdout, p->fstderr = proc_stderr; + p->cstdout = new_channel(FD(proc_stdout), p, p->stdout_cb > 0); + p->cstderr = new_channel(FD(proc_stderr), p, p->stderr_cb > 0); + g_child_watch_add(p->pid, proc_exited, p); + // Close unneeded handles. + CloseHandle(proc_info.hThread); + CloseHandle(stdin_read); + CloseHandle(stdout_write), CloseHandle(stderr_write); + lua_pushnil(L); // no error + } else { + char *message = NULL; + FormatMessageA( + FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | + FORMAT_MESSAGE_IGNORE_INSERTS, NULL, GetLastError(), + MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), (LPSTR)&message, 0, NULL); + lua_pushnil(L); + lua_pushfstring(L, "%s: %s", lua_tostring(L, 1), message); + LocalFree(message); + } + if (envp) free(envp); +#else + luaL_error(L, "not implemented in this environment"); +#endif +#endif + if (lua_isuserdata(L, -2)) + p->ref = (lua_pushvalue(L, -2), luaL_ref(L, LUA_REGISTRYINDEX)); + + return 2; +}