├── LICENSE ├── Makefile ├── README.md ├── rockspec ├── subprocess-0.1-1.rockspec ├── subprocess-0.1-2.rockspec ├── subprocess-scm-1.rockspec └── subprocess-scm-2.rockspec ├── subprocess.lua ├── subprocess.tl ├── subprocess ├── exceptions.lua ├── exceptions.tl ├── inheritable.c ├── inheritable.h ├── posix.lua ├── posix.tl ├── posix │ ├── close_fds.c │ ├── close_fds.h │ ├── compat-5.3.c │ ├── compat-5.3.h │ └── core.c ├── types.lua ├── types.tl ├── windows.lua └── windows.tl ├── test └── subprocess_spec.lua └── tlc.lua /LICENSE: -------------------------------------------------------------------------------- 1 | 2 | This program is free software, and its licensed in its entirety under 3 | permissive, non-copyleft, and GPL-compatible licenses. 4 | 5 | All new code in this repository is released under the MIT license: 6 | 7 | -------------------------------------------------------------------------------- 8 | Copyright 2015 Hisham Muhammad. 9 | 10 | Permission is hereby granted, free of charge, to any person obtaining a copy 11 | of this software and associated documentation files (the "Software"), to deal 12 | in the Software without restriction, including without limitation the rights 13 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 14 | copies of the Software, and to permit persons to whom the Software is 15 | furnished to do so, subject to the following conditions: 16 | 17 | The above copyright notice and this permission notice shall be included in all 18 | copies or substantial portions of the Software. 19 | 20 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 21 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 22 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 23 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 24 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 25 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 26 | SOFTWARE. 27 | -------------------------------------------------------------------------------- 28 | 29 | Includes code adapted from the source code of Python 3.4.3, 30 | (Lib/subprocess.py, Lib/test/test_subprocess.py, Modules/_posixsubprocess.c) 31 | which follows the following license: 32 | 33 | -------------------------------------------------------------------------------- 34 | PSF LICENSE AGREEMENT FOR PYTHON 3.4.3 35 | 36 | 1. This LICENSE AGREEMENT is between the Python Software Foundation (“PSF”), 37 | and the Individual or Organization (“Licensee”) accessing and otherwise using 38 | Python 3.4.3 software in source or binary form and its associated 39 | documentation. 40 | 41 | 2. Subject to the terms and conditions of this License Agreement, PSF hereby 42 | grants Licensee a nonexclusive, royalty-free, world-wide license to reproduce, 43 | analyze, test, perform and/or display publicly, prepare derivative works, 44 | distribute, and otherwise use Python 3.4.3 alone or in any derivative version, 45 | provided, however, that PSF’s License Agreement and PSF’s notice of copyright, 46 | i.e., “Copyright © 2001-2015 Python Software Foundation; All Rights Reserved” 47 | are retained in Python 3.4.3 alone or in any derivative version prepared by 48 | Licensee. 49 | 50 | 3. In the event Licensee prepares a derivative work that is based on or 51 | incorporates Python 3.4.3 or any part thereof, and wants to make the 52 | derivative work available to others as provided herein, then Licensee hereby 53 | agrees to include in any such work a brief summary of the changes made to 54 | Python 3.4.3. 55 | 56 | 4. PSF is making Python 3.4.3 available to Licensee on an “AS IS” basis. PSF 57 | MAKES NO REPRESENTATIONS OR WARRANTIES, EXPRESS OR IMPLIED. BY WAY OF EXAMPLE, 58 | BUT NOT LIMITATION, PSF MAKES NO AND DISCLAIMS ANY REPRESENTATION OR WARRANTY 59 | OF MERCHANTABILITY OR FITNESS FOR ANY PARTICULAR PURPOSE OR THAT THE USE OF 60 | PYTHON 3.4.3 WILL NOT INFRINGE ANY THIRD PARTY RIGHTS. 61 | 62 | 5. PSF SHALL NOT BE LIABLE TO LICENSEE OR ANY OTHER USERS OF PYTHON 3.4.3 FOR 63 | ANY INCIDENTAL, SPECIAL, OR CONSEQUENTIAL DAMAGES OR LOSS AS A RESULT OF 64 | MODIFYING, DISTRIBUTING, OR OTHERWISE USING PYTHON 3.4.3, OR ANY DERIVATIVE 65 | THEREOF, EVEN IF ADVISED OF THE POSSIBILITY THEREOF. 66 | 67 | 6. This License Agreement will automatically terminate upon a material breach 68 | of its terms and conditions. 69 | 70 | 7. Nothing in this License Agreement shall be deemed to create any 71 | relationship of agency, partnership, or joint venture between PSF and 72 | Licensee. This License Agreement does not grant permission to use PSF 73 | trademarks or trade name in a trademark sense to endorse or promote products 74 | or services of Licensee, or any third party. 75 | 76 | 8. By copying, installing or otherwise using Python 3.4.3, Licensee agrees to 77 | be bound by the terms and conditions of this License Agreement. 78 | -------------------------------------------------------------------------------- 79 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | 2 | .PHONY: test all luac clean rock install tlc lua 3 | 4 | LUAS= ./subprocess/types.lua ./subprocess/exceptions.lua ./subprocess/posix.lua ./subprocess/windows.lua ./subprocess.lua 5 | LUACS=./subprocess/types.luac ./subprocess/exceptions.luac ./subprocess/posix.luac ./subprocess/windows.luac ./subprocess.luac 6 | 7 | PREFIX=/usr/local 8 | INST_LIBDIR=$(PREFIX)/lib/lua/5.3/ 9 | INST_LUADIR=$(PREFIX)/share/lua/5.3/ 10 | 11 | SYSCALL_INCDIR=/usr/include 12 | LIBFLAG=-shared 13 | LUA_INCDIR=/usr/local/include 14 | 15 | EXISTS=test -f 16 | ECHO=echo -n 17 | 18 | TLCFLAGS= 19 | 20 | POSIX_CORE=subprocess/posix/core.so 21 | POSIX_CORE_SOURCES=subprocess/posix/compat-5.3.c subprocess/posix/close_fds.c subprocess/inheritable.c subprocess/posix/core.c 22 | POSIX_CORE_HEADERS=subprocess/posix/compat-5.3.h subprocess/posix/close_fds.h subprocess/inheritable.h 23 | 24 | all: $(POSIX_CORE) $(LUACS) test 25 | 26 | lua: $(LUAS) 27 | luac: $(LUACS) 28 | 29 | test: $(LUACS) 30 | LUA_PATH="$$PWD/?.luac;$$LUA_PATH" lua test/subprocess_spec.lua 31 | 32 | busted: $(LUAS) 33 | LUA_PATH="$$PWD/?.lua;$$LUA_PATH" busted test 34 | 35 | $(POSIX_CORE): $(POSIX_CORE_SOURCES) $(POSIX_CORE_HEADERS) 36 | $(CC) $(CFLAGS) $(LIBFLAG) -o $@ -I$(LUA_INCDIR) $(POSIX_CORE_SOURCES) `$(EXISTS) "$(SYSCALL_INCDIR)/sys/syscall.h" && $(ECHO) "-DHAVE_SYS_SYSCALL_H"` 37 | 38 | $(LUAS): %.lua: %.tl 39 | tlc $(TLCFLAGS) -o $@ $^ || true 40 | 41 | $(LUACS): %.luac: %.tl 42 | mkdir -p .out/`dirname $^` 43 | tlc $(TLCFLAGS) -o .out/$^ $^ && { cd .out && luac -o ../$@ $^ && rm $^ ;} || { cd .out && luac -o ../$@ $^ && rm $^ && cd .. && touch $^ ;} 44 | 45 | rock: $(LUAS) $(POSIX_CORE) 46 | 47 | install: 48 | mkdir -p $(INST_LIBDIR)/`dirname $(POSIX_CORE)` 49 | cp -a $(POSIX_CORE) $(INST_LIBDIR)/$(POSIX_CORE) 50 | mkdir -p $(INST_LUADIR)/subprocess 51 | for i in $(LUAS); do cp -a $$i $(INST_LUADIR)/$$i; done 52 | 53 | tlc: 54 | cd /Users/hisham/projects/github/typedlua && luarocks make --local 55 | 56 | clean: 57 | rm -f $(LUAS) $(LUACS) $(POSIX_CORE) 58 | rm -rf .out 59 | 60 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | subprocess 3 | ========== 4 | 5 | A port of the Python subprocess module to (Typed) Lua. 6 | 7 | Current status 8 | -------------- 9 | 10 | Very unfinished. 11 | 12 | * POSIX: core `subprocess.Popen()` object creation works, and you can get 13 | three file descriptors `p.stdin`, `p.stderr`, `p.stdout` from it, 14 | as well as `subprocess.call()`. `p:communicate()` (and all other 15 | methods that depend on it) not implemented yet. 16 | * Windows: not implemented yet, but it's a matter of porting the 17 | Python version. 18 | 19 | -------------------------------------------------------------------------------- /rockspec/subprocess-0.1-1.rockspec: -------------------------------------------------------------------------------- 1 | package = "subprocess" 2 | version = "0.1-1" 3 | source = { 4 | url = "git://github.com/hishamhm/subprocess", 5 | tag = "0.1", 6 | } 7 | description = { 8 | summary = "A port of the Python subprocess module to Lua", 9 | detailed = [[ 10 | Provides a high-level Popen object, which contains file objects 11 | stdin, stderr and stdout for two-way communication with the 12 | subprocess and also operations such as wait(), kill() and 13 | timeout. 14 | ]], 15 | homepage = "http://github.com/hishamhm/subprocess", 16 | license = "MIT/X11 + PSF License Agreement for Python 3.4.3" 17 | } 18 | dependencies = { 19 | -- "typedlua" -- build time only 20 | } 21 | external_dependencies = { 22 | SYS_SYSCALL = { 23 | header = "sys/syscall.h" 24 | } 25 | } 26 | build = { 27 | type = "make", 28 | build_target = "rock", 29 | variables = { 30 | CFLAGS="$(CFLAGS)", 31 | LIBFLAG="$(LIBFLAG)", 32 | PREFIX="$(PREFIX)", 33 | LUA_INCDIR="$(LUA_INCDIR)", 34 | INST_LIBDIR="$(LIBDIR)", 35 | INST_LUADIR="$(LUADIR)", 36 | SYSCALL_INCDIR="$(SYS_SYSCALL_INCDIR)", 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /rockspec/subprocess-0.1-2.rockspec: -------------------------------------------------------------------------------- 1 | package = "subprocess" 2 | version = "0.1-2" 3 | source = { 4 | url = "git://github.com/hishamhm/subprocess", 5 | tag = "0.1", 6 | } 7 | description = { 8 | summary = "A port of the Python subprocess module to Lua", 9 | detailed = [[ 10 | Provides a high-level Popen object, which contains file objects 11 | stdin, stderr and stdout for two-way communication with the 12 | subprocess and also operations such as wait(), kill() and 13 | timeout. 14 | ]], 15 | homepage = "http://github.com/hishamhm/subprocess", 16 | license = "MIT/X11 + PSF License Agreement for Python 3.4.3" 17 | } 18 | supported_platforms = { 19 | "unix" -- at this point. "windows" also planned. 20 | } 21 | dependencies = { 22 | -- "typedlua" -- build time only 23 | platforms = { 24 | unix = { 25 | "luaposix >= 33.3.1" 26 | } 27 | } 28 | } 29 | external_dependencies = { 30 | platforms = { 31 | unix = { 32 | SYS_SYSCALL = { 33 | header = "sys/syscall.h" 34 | } 35 | } 36 | } 37 | } 38 | build = { 39 | type = "make", 40 | build_target = "rock", 41 | variables = { 42 | CFLAGS="$(CFLAGS)", 43 | LIBFLAG="$(LIBFLAG)", 44 | PREFIX="$(PREFIX)", 45 | LUA_INCDIR="$(LUA_INCDIR)", 46 | INST_LIBDIR="$(LIBDIR)", 47 | INST_LUADIR="$(LUADIR)", 48 | }, 49 | platforms = { 50 | unix = { 51 | variables = { 52 | SYSCALL_INCDIR="$(SYS_SYSCALL_INCDIR)", 53 | } 54 | } 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /rockspec/subprocess-scm-1.rockspec: -------------------------------------------------------------------------------- 1 | package = "subprocess" 2 | version = "scm-1" 3 | source = { 4 | url = "git://github.com/hishamhm/subprocess", 5 | } 6 | description = { 7 | summary = "A port of the Python subprocess module to Lua", 8 | detailed = [[ 9 | Provides a high-level Popen object, which contains file objects 10 | stdin, stderr and stdout for two-way communication with the 11 | subprocess and also operations such as wait(), kill() and 12 | timeout. 13 | ]], 14 | homepage = "http://github.com/hishamhm/subprocess", 15 | license = "MIT/X11 + PSF License Agreement for Python 3.4.3" 16 | } 17 | dependencies = { 18 | -- "typedlua" -- build time only 19 | } 20 | external_dependencies = { 21 | SYS_SYSCALL = { 22 | header = "sys/syscall.h" 23 | } 24 | } 25 | build = { 26 | type = "make", 27 | build_target = "rock", 28 | variables = { 29 | CFLAGS="$(CFLAGS)", 30 | LIBFLAG="$(LIBFLAG)", 31 | PREFIX="$(PREFIX)", 32 | LUA_INCDIR="$(LUA_INCDIR)", 33 | INST_LIBDIR="$(LIBDIR)", 34 | INST_LUADIR="$(LUADIR)", 35 | SYSCALL_INCDIR="$(SYS_SYSCALL_INCDIR)", 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /rockspec/subprocess-scm-2.rockspec: -------------------------------------------------------------------------------- 1 | package = "subprocess" 2 | version = "scm-2" 3 | source = { 4 | url = "git://github.com/hishamhm/subprocess", 5 | } 6 | description = { 7 | summary = "A port of the Python subprocess module to Lua", 8 | detailed = [[ 9 | Provides a high-level Popen object, which contains file objects 10 | stdin, stderr and stdout for two-way communication with the 11 | subprocess and also operations such as wait(), kill() and 12 | timeout. 13 | ]], 14 | homepage = "http://github.com/hishamhm/subprocess", 15 | license = "MIT/X11 + PSF License Agreement for Python 3.4.3" 16 | } 17 | supported_platforms = { 18 | "unix" -- at this point. "windows" also planned. 19 | } 20 | dependencies = { 21 | -- "typedlua" -- build time only 22 | platforms = { 23 | unix = { 24 | "luaposix >= 33.3.1" 25 | } 26 | } 27 | } 28 | external_dependencies = { 29 | platforms = { 30 | unix = { 31 | SYS_SYSCALL = { 32 | header = "sys/syscall.h" 33 | } 34 | } 35 | } 36 | } 37 | build = { 38 | type = "make", 39 | build_target = "rock", 40 | variables = { 41 | CFLAGS="$(CFLAGS)", 42 | LIBFLAG="$(LIBFLAG)", 43 | PREFIX="$(PREFIX)", 44 | LUA_INCDIR="$(LUA_INCDIR)", 45 | INST_LIBDIR="$(LIBDIR)", 46 | INST_LUADIR="$(LUADIR)", 47 | }, 48 | platforms = { 49 | unix = { 50 | variables = { 51 | SYSCALL_INCDIR="$(SYS_SYSCALL_INCDIR)", 52 | } 53 | } 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /subprocess.lua: -------------------------------------------------------------------------------- 1 | 2 | local subprocess = {} 3 | 4 | 5 | local mswindows = (package.cpath:lower()):match("%.dll") 6 | 7 | local types = require("subprocess.types") 8 | local exceptions = require("subprocess.exceptions") 9 | 10 | local plat 11 | if mswindows then 12 | plat = require("subprocess.windows") 13 | else 14 | plat = require("subprocess.posix") 15 | end 16 | 17 | local MAXFD = plat.MAXFD 18 | 19 | local PIPE = types.PIPE 20 | local STDOUT = types.PIPE 21 | local DEVNULL = types.PIPE 22 | 23 | subprocess.PIPE = types.PIPE 24 | subprocess.STDOUT = types.PIPE 25 | subprocess.DEVNULL = types.PIPE 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | local active = {} 34 | 35 | local function cleanup () 36 | local mark = {} 37 | for i, inst in ipairs(active) do 38 | local res = inst:poll(math.maxinteger) 39 | if res then 40 | table.insert(mark,i) 41 | end 42 | end 43 | for i = #(mark), 1 do 44 | table.remove(active,mark[i]) 45 | end 46 | end 47 | 48 | local Popen_metatable = {__gc = function (self) 49 | 50 | if not (self.child_created) then 51 | 52 | return 53 | end 54 | 55 | self:poll(math.maxinteger) 56 | if not (self.returncode) then 57 | 58 | table.insert(active,self) 59 | end 60 | end} 61 | 62 | 63 | local function exit (self) 64 | if self.stdin then self.stdin:close() end 65 | if self.stdout then self.stdout:close() end 66 | if self.stderr then self.stderr:close() end 67 | 68 | self:wait() 69 | end 70 | 71 | communicate = function (self, input, timeout) 72 | if self.communication_started and input then 73 | error("Cannot send input after starting communication") 74 | end 75 | 76 | local stdout, stderr 77 | 78 | 79 | 80 | 81 | 82 | local nils = (self.stdin and 1 or 0) + (self.stdout and 1 or 0) + (self.stderr and 1 or 0) 83 | 84 | 85 | if not (timeout) and not (self.communication_started) and 2 <= nils then 86 | stdout = nil 87 | stderr = nil 88 | local self_stdin, self_stdout, self_stderr = self.stdin, self.stdout, self.stderr 89 | if self_stdin then 90 | if input then 91 | local ok, err = pcall(self_stdin.write,self_stdin,input) 92 | if not (ok) then return nil, nil, err end 93 | end 94 | self_stdin:close() 95 | elseif self_stdout then 96 | stdout = self_stdout:read("*a") 97 | self_stdout:close() 98 | elseif self_stderr then 99 | stderr = self_stderr:read("*a") 100 | self_stderr:close() 101 | end 102 | self:wait() 103 | else 104 | local endtime = timeout and plat.time() + timeout or nil 105 | local ok 106 | ok, stdout, stderr = pcall(plat.communicate,input,endtime,timeout) 107 | self.communication_started = true 108 | self:wait(endtime and self.remaining_time(endtime) or nil,endtime) 109 | end 110 | return stdout, stderr 111 | end 112 | 113 | local function remaining_time (endtime) 114 | return (endtime - plat.time()) 115 | end 116 | 117 | local function check_timeout (self, endtime, orig_timeout) 118 | if not (endtime) then 119 | return nil 120 | end 121 | if endtime < plat.time() then 122 | return nil, exceptions.TimeoutExpired(self.args,orig_timeout) 123 | end 124 | end 125 | 126 | local function open_and_set_buf (fobj, fd, mode, bufsize) 127 | local bufmode = "full" 128 | if not (fd == -(1)) then 129 | local err 130 | fobj, err = plat.open(fd,mode) 131 | if bufsize then 132 | bufmode = 0 < bufsize and "full" or "no" 133 | fobj:setvbuf(bufmode,bufsize) 134 | end 135 | end 136 | return fobj, bufmode 137 | end 138 | 139 | subprocess.Popen = function (args, kwargs, with_fn) 140 | if not (kwargs) then kwargs = {} end 141 | local pass_fds = kwargs.pass_fds or {} 142 | local close_fds = plat.check_close_fds(kwargs.close_fds,pass_fds,kwargs.stdin,kwargs.stdout,kwargs.stderr) 143 | local creationflags = plat.check_creationflags(kwargs.creationflags or 0) 144 | local shell = (not (kwargs.shell == nil)) or false 145 | local start_new_session = kwargs.start_new_session and true or false 146 | 147 | local self = {args = args, input = nil, input_offset = 0, communication_started = false, closed_child_pipe_fds = false, child_created = false, fileobj2output = {}, stdin_buf = "full", stdout_buf = "full", stderr_buf = "full", exit = exit, get_devnull = plat.get_devnull, communicate = communicate, poll = plat.poll, remaining_time = remaining_time, check_timeout = check_timeout, wait = plat.wait, kill = plat.kill, terminate = plat.terminate} 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | setmetatable(self,Popen_metatable) 171 | 172 | cleanup() 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | local p2cread, p2cwrite, c2pread, c2pwrite, errread, errwrite = plat.get_handles(self,kwargs.stdin,kwargs.stdout,kwargs.stderr) 193 | 194 | 195 | 196 | 197 | 198 | 199 | 200 | 201 | p2cwrite, c2pread, errread = plat.wrap_handles(p2cwrite,c2pread,errread) 202 | 203 | self.stdin, self.stdin_buf = open_and_set_buf(self.stdin,p2cwrite,"wb",kwargs.bufsize) 204 | self.stdout, self.stdout_buf = open_and_set_buf(self.stdout,c2pread,"rb",kwargs.bufsize) 205 | self.stderr, self.stderr_buf = open_and_set_buf(self.stderr,errread,"rb",kwargs.bufsize) 206 | 207 | local ok, err, errcode = plat.execute_child(self,args,kwargs.executable,close_fds,pass_fds,kwargs.cwd,kwargs.env,kwargs.startupinfo,creationflags,shell,p2cread,p2cwrite,c2pread,c2pwrite,errread,errwrite,start_new_session) 208 | 209 | 210 | 211 | 212 | 213 | 214 | 215 | 216 | if not (ok) then 217 | if self.stdin then self.stdin:close() end 218 | if self.stdout then self.stdout:close() end 219 | if self.stderr then self.stderr:close() end 220 | if not (self.closed_child_pipe_fds) then 221 | if kwargs.stdin == PIPE then plat.close(p2cread) end 222 | if kwargs.stdout == PIPE then plat.close(c2pwrite) end 223 | if kwargs.stderr == PIPE then plat.close(errwrite) end 224 | end 225 | return nil, err, errcode 226 | end 227 | if with_fn then 228 | local ret = table.pack(with_fn(self)) 229 | self:exit() 230 | return table.unpack(ret,1,ret.n) 231 | end 232 | 233 | return self 234 | end 235 | 236 | subprocess.call = function (args, kwargs) 237 | return subprocess.Popen(args,kwargs,function (p) 238 | local exit, err = p:wait(kwargs and kwargs.timeout) 239 | if err then 240 | p:kill() 241 | p:wait() 242 | return nil, err 243 | end 244 | return exit 245 | end) 246 | end 247 | 248 | subprocess.check_call = function (args, kwargs) 249 | local exit, err = subprocess.call(args,kwargs) 250 | if not (exit == 0) then 251 | error("Error calling process: " .. tostring(exit) .. " " .. tostring(err)) 252 | end 253 | return 0 254 | end 255 | 256 | 257 | 258 | 259 | 260 | 261 | 262 | 263 | 264 | 265 | 266 | 267 | 268 | 269 | 270 | 271 | 272 | 273 | 274 | 275 | 276 | 277 | 278 | 279 | 280 | 281 | 282 | 283 | 284 | 285 | 286 | 287 | 288 | 289 | 290 | 291 | 292 | 293 | 294 | 295 | 296 | 297 | 298 | 299 | 300 | 301 | 302 | 303 | 304 | 305 | 306 | 307 | 308 | 309 | 310 | return subprocess 311 | 312 | 313 | -------------------------------------------------------------------------------- /subprocess.tl: -------------------------------------------------------------------------------- 1 | 2 | local subprocess = {} 3 | 4 | -- TL? shouldn't need the extra parentheses 5 | local mswindows = (package.cpath:lower()):match("%.dll") 6 | 7 | local types = require("subprocess.types") 8 | local exceptions = require("subprocess.exceptions") 9 | 10 | local plat 11 | if mswindows then 12 | plat = require("subprocess.windows") 13 | else 14 | plat = require("subprocess.posix") 15 | end 16 | 17 | local MAXFD = plat.MAXFD 18 | 19 | local PIPE = types.PIPE 20 | local STDOUT = types.PIPE 21 | local DEVNULL = types.PIPE 22 | 23 | subprocess.PIPE = types.PIPE 24 | subprocess.STDOUT = types.PIPE 25 | subprocess.DEVNULL = types.PIPE 26 | 27 | --[[ 28 | This lists holds Popen instances for which the underlying process had not 29 | exited at the time its __gc method got called: those processes are wait()ed 30 | for synchronously from cleanup() when a new Popen object is created, to avoid 31 | zombie processes. 32 | ]] 33 | local active: {Popen} = {} 34 | 35 | local function cleanup() 36 | local mark: {number:integer} = {} 37 | for i, inst in ipairs(active) do 38 | local res = inst:poll(math.maxinteger) 39 | if res then 40 | table.insert(mark, i) 41 | end 42 | end 43 | for i = #mark, 1 do 44 | table.remove(active, mark[i]) 45 | end 46 | end 47 | 48 | local Popen_metatable = { 49 | __gc = function(self:Popen) 50 | if not self.child_created then 51 | -- We didn't get to successfully create a child process. 52 | return 53 | end 54 | -- In case the child hasn't been waited on, check if it's done. 55 | self:poll(math.maxinteger) 56 | if not self.returncode then 57 | -- Child is still running, keep us alive until we can wait on it. 58 | table.insert(active, self) 59 | end 60 | end 61 | } 62 | 63 | local function exit(self: Popen) 64 | if self.stdin then self.stdin:close() end 65 | if self.stdout then self.stdout:close() end 66 | if self.stderr then self.stderr:close() end 67 | -- Wait for the process to terminate, to avoid zombies. 68 | self:wait() 69 | end 70 | 71 | function communicate(self: Popen, input: string?, timeout: number?): (string?, string?)|(nil, nil, string) 72 | if self.communication_started and input then 73 | error("Cannot send input after starting communication") 74 | end 75 | 76 | local stdout, stderr 77 | --[[ 78 | Optimization: If we are not worried about timeouts, we haven't 79 | started communicating, and we have one or zero pipes, using select() 80 | or threads is unnecessary. 81 | ]] 82 | local nils = (self.stdin and 1 or 0) 83 | + (self.stdout and 1 or 0) 84 | + (self.stderr and 1 or 0) 85 | if not timeout and not self.communication_started and nils >= 2 then 86 | stdout = nil 87 | stderr = nil 88 | local self_stdin, self_stdout, self_stderr = self.stdin, self.stdout, self.stderr -- TL? can't infer on self... 89 | if self_stdin then 90 | if input then 91 | local ok, err = pcall(self_stdin.write, self_stdin, input) 92 | if not ok then return nil, nil, err end 93 | end 94 | self_stdin:close() 95 | elseif self_stdout then 96 | stdout = self_stdout:read("*a") 97 | self_stdout:close() 98 | elseif self_stderr then 99 | stderr = self_stderr:read("*a") 100 | self_stderr:close() 101 | end 102 | self:wait() 103 | else 104 | local endtime = timeout and plat.time() + timeout or nil 105 | local ok 106 | ok, stdout, stderr = pcall(plat.communicate, input, endtime, timeout) 107 | self.communication_started = true 108 | self:wait(endtime and self.remaining_time(endtime) or nil, endtime) 109 | end 110 | return stdout, stderr 111 | end 112 | 113 | local function remaining_time(endtime: number): number 114 | return (endtime - plat.time()) 115 | end 116 | 117 | local function check_timeout(self: Popen, endtime: number?, orig_timeout: number?) 118 | if not endtime then 119 | return nil 120 | end 121 | if plat.time() > endtime then 122 | return nil, exceptions.TimeoutExpired(self.args, orig_timeout) 123 | end 124 | end 125 | 126 | local function open_and_set_buf(fobj, fd, mode, bufsize) 127 | local bufmode = "full" 128 | if fd ~= -1 then 129 | local err 130 | fobj, err = plat.open(fd, mode) 131 | if bufsize then 132 | bufmode = bufsize > 0 and "full" or "no" 133 | fobj:setvbuf(bufmode, bufsize) 134 | end 135 | end 136 | return fobj, bufmode 137 | end 138 | 139 | function subprocess.Popen(args:string|{string}, kwargs:PopenArgs, with_fn: nil|(Popen) -> ()): Popen 140 | if not kwargs then kwargs = {} end 141 | local pass_fds = kwargs.pass_fds or {} 142 | local close_fds = plat.check_close_fds(kwargs.close_fds, pass_fds, kwargs.stdin, kwargs.stdout, kwargs.stderr) 143 | local creationflags = plat.check_creationflags(kwargs.creationflags or 0) 144 | local shell = (kwargs.shell ~= nil) or false 145 | local start_new_session = kwargs.start_new_session and true or false 146 | 147 | local self: Popen = { 148 | args = args, 149 | -- waitpid_lock = threading.Lock(), -- XXX not yet implemented 150 | input = nil, 151 | input_offset = 0, 152 | communication_started = false, 153 | closed_child_pipe_fds = false, 154 | child_created = false, 155 | fileobj2output = {}, 156 | stdin_buf = "full", 157 | stdout_buf = "full", 158 | stderr_buf = "full", 159 | 160 | exit = exit, 161 | get_devnull = plat.get_devnull, 162 | communicate = communicate, 163 | poll = plat.poll, 164 | remaining_time = remaining_time, 165 | check_timeout = check_timeout, 166 | wait = plat.wait, 167 | kill = plat.kill, 168 | terminate = plat.terminate, 169 | } 170 | setmetatable(self, Popen_metatable) 171 | 172 | cleanup() 173 | 174 | --[[ 175 | Input and output objects. The general principle is like 176 | this: 177 | 178 | Parent Child 179 | ------ ----- 180 | p2cwrite ---stdin---> p2cread 181 | c2pread <--stdout--- c2pwrite 182 | errread <--stderr--- errwrite 183 | 184 | On POSIX, the child objects are file descriptors. On 185 | Windows, these are Windows file handles. The parent objects 186 | are file descriptors on both platforms. The parent objects 187 | are -1 when not using PIPEs. The child objects are -1 188 | when not redirecting. 189 | ]] 190 | 191 | local 192 | p2cread, p2cwrite, 193 | c2pread, c2pwrite, 194 | errread, errwrite = plat.get_handles(self, kwargs.stdin, kwargs.stdout, kwargs.stderr) 195 | 196 | --[[ 197 | We wrap OS handles *before* launching the child, otherwise a 198 | quickly terminating child could make our fds unwrappable 199 | ]] 200 | 201 | p2cwrite, c2pread, errread = plat.wrap_handles(p2cwrite, c2pread, errread) 202 | 203 | self.stdin, self.stdin_buf = open_and_set_buf(self.stdin, p2cwrite, "wb", kwargs.bufsize) 204 | self.stdout, self.stdout_buf = open_and_set_buf(self.stdout, c2pread, "rb", kwargs.bufsize) 205 | self.stderr, self.stderr_buf = open_and_set_buf(self.stderr, errread, "rb", kwargs.bufsize) 206 | 207 | local ok, err, errcode = plat.execute_child(self, 208 | args, kwargs.executable, close_fds, 209 | pass_fds, kwargs.cwd, kwargs.env, 210 | kwargs.startupinfo, creationflags, shell, 211 | p2cread, p2cwrite, 212 | c2pread, c2pwrite, 213 | errread, errwrite, 214 | start_new_session) 215 | 216 | if not ok then 217 | if self.stdin then self.stdin:close() end 218 | if self.stdout then self.stdout:close() end 219 | if self.stderr then self.stderr:close() end 220 | if not self.closed_child_pipe_fds then 221 | if kwargs.stdin == PIPE then plat.close(p2cread) end 222 | if kwargs.stdout == PIPE then plat.close(c2pwrite) end 223 | if kwargs.stderr == PIPE then plat.close(errwrite) end 224 | end 225 | return nil, err, errcode 226 | end 227 | if with_fn then 228 | local ret = table.pack( with_fn(self) ) 229 | self:exit() 230 | return table.unpack(ret, 1, ret.n) 231 | end 232 | 233 | return self 234 | end 235 | 236 | function subprocess.call(args:string|{string}, kwargs:PopenArgs) 237 | return subprocess.Popen(args, kwargs, function(p) 238 | local exit, err = p:wait(kwargs and kwargs.timeout) 239 | if err then 240 | p:kill() 241 | p:wait() 242 | return nil, err 243 | end 244 | return exit 245 | end) 246 | end 247 | 248 | function subprocess.check_call(args:string|{string}, kwargs:PopenArgs) 249 | local exit, err = subprocess.call(args, kwargs) 250 | if exit ~= 0 then 251 | error("Error calling process: "..tostring(exit).." "..tostring(err)) 252 | end 253 | return 0 254 | end 255 | 256 | --[[ 257 | function subprocess.check_output(args:string|{string}, kwargs:PopenArgs) 258 | if args.stdout then 259 | error("stdout argument not allowed, it will be overridden.") 260 | end 261 | local inputdata: string? = nil 262 | if args.input then 263 | if args.stdin then 264 | error("stdin and input arguments may not both be used.") 265 | end 266 | inputdata = args.input 267 | args.input = nil 268 | args.stdin = PIPE 269 | end 270 | 271 | args.stdout = PIPE 272 | return Popen(args, kwargs, function(process) 273 | local output, err = process:communicate(inputdata, args.timeout) 274 | if err == "TimeoutExpired" then 275 | process:kill() 276 | output, err = process:communicate() 277 | return nil, exceptions.TimeoutExpired(args, timeout, output) 278 | elseif err then 279 | process:kill() 280 | process:wait() 281 | return nil, err 282 | end 283 | local retcode = process:poll() 284 | if retcode > 0 then 285 | return nil, exceptions.CalledProcessError(retcode, process.args, output) 286 | end 287 | return output 288 | end) 289 | end 290 | 291 | function subprocess.getstatusoutput(cmd: string) 292 | local status = 0 293 | local data, err = subprocess.check_output(cmd, {shell = true, stderr = STDOUT}) 294 | if err and err.type == "CalledProcessError" then 295 | data = err.output 296 | status = err.returncode 297 | end 298 | if data:sub(-1) == "\n" then 299 | data = data:sub(1,-2) 300 | end 301 | return status, data 302 | end 303 | 304 | function subprocess.getoutput(cmd: string) 305 | local _, data = subprocess.getstatusoutput(cmd) 306 | return data 307 | end 308 | ]] 309 | 310 | return subprocess 311 | -------------------------------------------------------------------------------- /subprocess/exceptions.lua: -------------------------------------------------------------------------------- 1 | 2 | local exceptions = {} 3 | 4 | local types = require("subprocess.types") 5 | 6 | exceptions.TimeoutExpired = function (cmd, timeout, output) 7 | return {type = "TimeoutExpired", timeout = timeout, cmd = cmd, output = output} 8 | end 9 | 10 | 11 | 12 | 13 | 14 | 15 | exceptions.CalledProcessError = function (returncode, cmd, output) 16 | return {type = "CalledProcessError", returncode = returncode, cmd = cmd, output = output} 17 | end 18 | 19 | 20 | 21 | 22 | 23 | 24 | return exceptions 25 | 26 | 27 | -------------------------------------------------------------------------------- /subprocess/exceptions.tl: -------------------------------------------------------------------------------- 1 | 2 | local exceptions = {} 3 | 4 | local types = require("subprocess.types") 5 | 6 | function exceptions.TimeoutExpired(cmd: string|{string}, timeout: number?, output: string?): TimeoutExpired 7 | return { 8 | type = "TimeoutExpired", 9 | timeout = timeout, 10 | cmd = cmd, 11 | output = output, 12 | } 13 | end 14 | 15 | function exceptions.CalledProcessError(returncode: integer, cmd: string|{string}, output: string?): CalledProcessError 16 | return { 17 | type = "CalledProcessError", 18 | returncode = returncode, 19 | cmd = cmd, 20 | output = output, 21 | } 22 | end 23 | 24 | return exceptions -------------------------------------------------------------------------------- /subprocess/inheritable.c: -------------------------------------------------------------------------------- 1 | 2 | #ifdef MS_WINDOWS 3 | #else 4 | #include 5 | #include 6 | #include 7 | #endif 8 | 9 | int get_inheritable(int fd) { 10 | #ifdef MS_WINDOWS 11 | HANDLE handle; 12 | DWORD flags = 0; 13 | 14 | handle = (HANDLE)_get_osfhandle(fd); 15 | if (handle == INVALID_HANDLE_VALUE) { 16 | return -1; 17 | } 18 | 19 | if (!GetHandleInformation(handle, &flags)) { 20 | return -1; 21 | } 22 | 23 | return (flags & HANDLE_FLAG_INHERIT); 24 | #else 25 | int flags = 0; 26 | 27 | flags = fcntl(fd, F_GETFD, 0); 28 | if (flags == -1) { 29 | return -1; 30 | } 31 | return !(flags & FD_CLOEXEC); 32 | #endif 33 | } 34 | 35 | int set_inheritable(int fd, int inheritable) { 36 | #ifdef MS_WINDOWS 37 | HANDLE handle; 38 | DWORD flags; 39 | #else 40 | int flags; 41 | int res; 42 | #endif 43 | 44 | #ifdef MS_WINDOWS 45 | handle = (HANDLE)_get_osfhandle(fd); 46 | if (handle == INVALID_HANDLE_VALUE) { 47 | return -1; 48 | } 49 | if (inheritable) 50 | flags = HANDLE_FLAG_INHERIT; 51 | else 52 | flags = 0; 53 | if (!SetHandleInformation(handle, HANDLE_FLAG_INHERIT, flags)) { 54 | return -1; 55 | } 56 | return 0; 57 | #else 58 | flags = fcntl(fd, F_GETFD); 59 | if (flags < 0) { 60 | return -1; 61 | } 62 | if (inheritable) 63 | flags &= ~FD_CLOEXEC; 64 | else 65 | flags |= FD_CLOEXEC; 66 | res = fcntl(fd, F_SETFD, flags); 67 | if (res < 0) { 68 | return -1; 69 | } 70 | return 0; 71 | #endif 72 | } 73 | -------------------------------------------------------------------------------- /subprocess/inheritable.h: -------------------------------------------------------------------------------- 1 | 2 | #ifndef SUBPROCESS_INHERITABLE_H 3 | #define SUBPROCESS_INHERITABLE_H 4 | 5 | int get_inheritable(int fd); 6 | int set_inheritable(int fd, int inheritable); 7 | 8 | #endif 9 | 10 | -------------------------------------------------------------------------------- /subprocess/posix.lua: -------------------------------------------------------------------------------- 1 | 2 | 3 | local subprocess_posix = {} 4 | 5 | local types = require("subprocess.types") 6 | local exceptions = require("subprocess.exceptions") 7 | 8 | local errno = require("posix.errno") 9 | local fcntl = require("posix.fcntl") 10 | local libgen = require("posix.libgen") 11 | local posix_sys_time = require("posix.sys.time") 12 | local posix_time = require("posix.time") 13 | local signal = require("posix.signal") 14 | local stdio = require("posix.stdio") 15 | local unistd = require("posix.unistd") 16 | local wait = require("posix.sys.wait") 17 | 18 | local core = require("subprocess.posix.core") 19 | 20 | local PIPE_BUF = unistd._PC_PIPE_BUF 21 | 22 | local PIPE = types.PIPE 23 | local STDOUT = types.STDOUT 24 | local DEVNULL = types.DEVNULL 25 | 26 | subprocess_posix.MAXFD = unistd._SC_OPEN_MAX or 256 27 | 28 | subprocess_posix.time = function () 29 | local tv = posix_sys_time.gettimeofday() 30 | return tv.tv_sec + (tv.tv_usec / 1000000) 31 | end 32 | 33 | subprocess_posix.check_close_fds = function (close_fds, pass_fds, stdin, stdout, stderr) 34 | if close_fds == nil then 35 | return true 36 | end 37 | 38 | if pass_fds then 39 | if 0 < #(pass_fds) then 40 | return true 41 | end 42 | end 43 | return close_fds 44 | end 45 | 46 | subprocess_posix.check_creationflags = function (creationflags) 47 | if not (creationflags == 0) then 48 | error("creationflags is only supported on Windows platforms") 49 | end 50 | return 0 51 | end 52 | 53 | subprocess_posix.wrap_handles = function (p2cwrite, c2pread, errread) 54 | return p2cwrite, c2pread, errread 55 | end 56 | 57 | local function get_devnull (self) 58 | if not (self.devnull) then 59 | self.devnull = fcntl.open("/dev/null",fcntl.O_RDWR) 60 | end 61 | return self.devnull 62 | end 63 | 64 | subprocess_posix.get_handles = function (self, stdin, stdout, stderr) 65 | local p2cread, p2cwrite = -(1), -(1) 66 | local c2pread, c2pwrite = -(1), -(1) 67 | local errread, errwrite = -(1), -(1) 68 | local errno 69 | 70 | if stdin == PIPE then 71 | local r, w, e = unistd.pipe() 72 | if not (r) then 73 | error(w,e) 74 | end 75 | p2cread, p2cwrite, errno = r, w, e 76 | elseif stdin == DEVNULL then 77 | p2cread = self:get_devnull() 78 | elseif math.type(stdin) == "integer" then 79 | p2cread = stdin 80 | elseif stdin then 81 | 82 | p2cread = stdio.fileno(stdin) 83 | end 84 | 85 | if stdout == PIPE then 86 | local r, w, e = unistd.pipe() 87 | if not (r) then 88 | error(w,e) 89 | end 90 | c2pread, c2pwrite, errno = r, w, e 91 | elseif stdout == DEVNULL then 92 | c2pwrite = self:get_devnull() 93 | elseif math.type(stdout) == "integer" then 94 | c2pwrite = stdout 95 | elseif stdout then 96 | 97 | c2pwrite = stdio.fileno(stdout) 98 | end 99 | 100 | if stderr == PIPE then 101 | local r, w, e = unistd.pipe() 102 | if not (r) then 103 | error(w,e) 104 | end 105 | errread, errwrite, errno = r, w, e 106 | elseif stderr == STDOUT then 107 | errwrite = c2pwrite 108 | elseif stderr == DEVNULL then 109 | errwrite = self:get_devnull() 110 | elseif math.type(stderr) == "integer" then 111 | errwrite = stderr 112 | elseif stderr then 113 | 114 | errwrite = stdio.fileno(stderr) 115 | end 116 | 117 | return p2cread, p2cwrite, c2pread, c2pwrite, errread, errwrite 118 | end 119 | 120 | 121 | 122 | subprocess_posix.communicate = function (...) end 123 | 124 | 125 | 126 | local function make_set (array) 127 | local set = {} 128 | for _, elem in ipairs(array) do 129 | set[elem] = true 130 | end 131 | return set 132 | end 133 | 134 | local function sorted (set) 135 | local array = {} 136 | for k, _ in pairs(set) do 137 | table.insert(array,k) 138 | end 139 | table.sort(array) 140 | return array 141 | end 142 | 143 | 144 | 145 | 146 | 147 | local function eintr_retry_call (fn, ...) 148 | while true do 149 | local res, err, errcode = fn(...) 150 | if not (res == nil) or not (errcode == errno.EINTR) then 151 | return res, err, errcode 152 | end 153 | end 154 | end 155 | 156 | subprocess_posix.execute_child = function (self, cmd, executable_p, close_fds, pass_fds, cwd, env, startupinfo, creationflags, shell, p2cread, p2cwrite, c2pread, c2pwrite, errread, errwrite, start_new_session) 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | local args = {} 166 | 167 | if type(cmd) == "string" then 168 | args[1] = cmd 169 | else 170 | args = cmd 171 | end 172 | 173 | if shell then 174 | table.insert(args,1,"/bin/sh") 175 | table.insert(args,2,"-c") 176 | if executable_p then 177 | args[1] = executable_p 178 | end 179 | end 180 | self.args = args 181 | 182 | local executable = executable_p or args[1] 183 | 184 | 185 | 186 | 187 | 188 | local errpipe_read, errpipe_write = unistd.pipe() 189 | if not (errpipe_read) then 190 | error("could not open error pipe") 191 | end 192 | local low_fds_to_close = {} 193 | while errpipe_write < 3 do 194 | table.insert(low_fds_to_close,errpipe_write) 195 | errpipe_write = unistd.dup(errpipe_write) 196 | end 197 | for _, low_fd in ipairs(low_fds_to_close) do 198 | unistd.close(low_fd) 199 | end 200 | 201 | local errpipe_data = "" 202 | pcall(function () 203 | pcall(function () 204 | 205 | 206 | 207 | 208 | 209 | 210 | local env_list 211 | if env and 0 < #(env) then 212 | env_list = {} 213 | for k, v in pairs(env) do 214 | table.insert(env_list,tostring(k) .. "=" .. tostring(v)) 215 | end 216 | end 217 | 218 | local executable_list = {} 219 | local dname = libgen.dirname(executable) 220 | if dname == "." and not (executable:sub(1,2) == "./") then 221 | 222 | local PATH = os.getenv("PATH") or "" 223 | for dir in PATH:gmatch("([^:]+):?") do 224 | table.insert(executable_list,dir .. "/" .. executable) 225 | end 226 | else 227 | executable_list[1] = executable 228 | end 229 | 230 | local fds_to_keep = make_set(pass_fds) 231 | fds_to_keep[errpipe_write] = true 232 | 233 | self.pid = core.fork_exec(args,executable_list,close_fds,sorted(fds_to_keep),cwd,env_list,p2cread,p2cwrite,c2pread,c2pwrite,errread,errwrite,errpipe_read,errpipe_write,start_new_session) 234 | 235 | 236 | 237 | 238 | 239 | 240 | self.child_created = true 241 | end) 242 | 243 | unistd.close(errpipe_write) 244 | 245 | if not (p2cread == -(1)) and not (p2cwrite == -(1)) and not (p2cread == self.devnull) then 246 | unistd.close(p2cread) 247 | end 248 | if not (c2pwrite == -(1)) and not (c2pread == -(1)) and not (c2pwrite == self.devnull) then 249 | unistd.close(c2pwrite) 250 | end 251 | if not (errwrite == -(1)) and not (errread == -(1)) and not (errwrite == self.devnull) then 252 | unistd.close(errwrite) 253 | end 254 | local dn = self.devnull 255 | if dn then 256 | unistd.close(dn) 257 | self.devnull = nil 258 | end 259 | 260 | self.closed_child_pipe_fds = true 261 | 262 | 263 | 264 | while true do 265 | local part = eintr_retry_call(unistd.read,errpipe_read,50000) 266 | if not (part) or part == "" then break end 267 | errpipe_data = errpipe_data .. part 268 | if 50000 <= #(errpipe_data) then 269 | break 270 | end 271 | end 272 | end) 273 | 274 | unistd.close(errpipe_read) 275 | if 0 < #(errpipe_data) then 276 | local pid, str, errcode = eintr_retry_call(wait.wait,self.pid) 277 | if not (pid) and not (errcode == errno.ECHILD) then 278 | error(str) 279 | end 280 | 281 | local exception_name, hex_errno, err_msg = errpipe_data:match("([^:]+):([^:]+):([^:]+)") 282 | 283 | if not (exception_name) then 284 | hex_errno = "00" 285 | err_msg = "Bad exception data from child: " .. errpipe_data 286 | end 287 | return nil, err_msg, tonumber(hex_errno,16) 288 | end 289 | return self 290 | end 291 | 292 | local function handle_exitstatus (self, res, sts) 293 | if res == "exited" then 294 | self.returncode = sts 295 | else 296 | self.returncode = -(sts) 297 | end 298 | end 299 | 300 | 301 | 302 | 303 | 304 | 305 | 306 | 307 | local my_wait = wait.wait 308 | local my_WNOHANG = wait.WNOHANG 309 | local my_ECHILD = errno.ECHILD 310 | subprocess_posix.poll = function (self, deadstate) 311 | if not (self.returncode) then 312 | 313 | 314 | local pid, res, sts = my_wait(self.pid,my_WNOHANG) 315 | if pid then 316 | if pid == self.pid then 317 | handle_exitstatus(self,res,sts) 318 | end 319 | else 320 | if deadstate then 321 | self.returncode = deadstate 322 | elseif sts == my_ECHILD then 323 | 324 | 325 | 326 | 327 | 328 | 329 | 330 | self.returncode = 0 331 | end 332 | end 333 | end 334 | 335 | return self.returncode 336 | end 337 | 338 | local function try_wait (self, wait_flags) 339 | return eintr_retry_call(wait.wait,self.pid,wait_flags) 340 | end 341 | 342 | subprocess_posix.wait = function (self, timeout, endtime) 343 | if self.returncode then 344 | return self.returncode 345 | end 346 | 347 | if endtime or timeout then 348 | if not (endtime) then 349 | endtime = subprocess_posix.time() + timeout 350 | elseif not (timeout) then 351 | timeout = self.remaining_time(endtime) 352 | end 353 | end 354 | if endtime then 355 | 356 | 357 | local delay = 0.0005 358 | while true do 359 | 360 | if self.returncode then 361 | break 362 | end 363 | local pid, res, sts = try_wait(self,wait.WNOHANG) 364 | assert(pid == self.pid or pid == 0) 365 | if pid == self.pid then 366 | handle_exitstatus(self,res,sts) 367 | break 368 | end 369 | 370 | local remaining = self.remaining_time(endtime) 371 | if remaining <= 0 then 372 | return nil, exceptions.TimeoutExpired(self.cmd,timeout) 373 | end 374 | delay = math.min(delay * 2,remaining,0.05) 375 | posix_time.nanosleep({tv_sec = math.floor(delay), tv_nsec = (delay - math.floor(delay)) * 1000000000}) 376 | end 377 | else 378 | while not (self.returncode) do 379 | 380 | local pid, res, sts = try_wait(self,0) 381 | 382 | 383 | 384 | if pid == self.pid then 385 | handle_exitstatus(self,res,sts) 386 | end 387 | end 388 | end 389 | 390 | return self.returncode 391 | end 392 | 393 | 394 | 395 | 396 | 397 | 398 | 399 | 400 | 401 | 402 | 403 | 404 | 405 | 406 | 407 | 408 | 409 | 410 | 411 | 412 | 413 | 414 | 415 | 416 | 417 | 418 | 419 | 420 | 421 | 422 | 423 | 424 | 425 | 426 | 427 | 428 | 429 | subprocess_posix.kill = function (self) 430 | signal.kill(self.pid,signal.SIGKILL) 431 | end 432 | 433 | subprocess_posix.terminate = function (self) 434 | signal.kill(self.pid,signal.SIGTERM) 435 | end 436 | 437 | subprocess_posix.open = function (fd, mode) 438 | return stdio.fdopen(fd,mode) 439 | end 440 | 441 | subprocess_posix.close = function (fd) 442 | return unistd.close(fd) 443 | end 444 | 445 | return subprocess_posix 446 | 447 | 448 | -------------------------------------------------------------------------------- /subprocess/posix.tl: -------------------------------------------------------------------------------- 1 | 2 | -- POSIX implementation of the subprocess module. 3 | local subprocess_posix = {} 4 | 5 | local types = require("subprocess.types") 6 | local exceptions = require("subprocess.exceptions") 7 | 8 | local errno = require("posix.errno") 9 | local fcntl = require("posix.fcntl") 10 | local libgen = require("posix.libgen") 11 | local posix_sys_time = require("posix.sys.time") 12 | local posix_time = require("posix.time") 13 | local signal = require("posix.signal") 14 | local stdio = require("posix.stdio") 15 | local unistd = require("posix.unistd") 16 | local wait = require("posix.sys.wait") 17 | 18 | local core = require("subprocess.posix.core") 19 | 20 | local PIPE_BUF = unistd._PC_PIPE_BUF 21 | 22 | local PIPE = types.PIPE 23 | local STDOUT = types.STDOUT 24 | local DEVNULL = types.DEVNULL 25 | 26 | subprocess_posix.MAXFD = unistd._SC_OPEN_MAX or 256 27 | 28 | function subprocess_posix.time() 29 | local tv = posix_sys_time.gettimeofday() 30 | return tv.tv_sec + (tv.tv_usec / 1000000) 31 | end 32 | 33 | function subprocess_posix.check_close_fds(close_fds: boolean, pass_fds:{any}?, stdin: integer?, stdout: integer?, stderr: integer?) 34 | if close_fds == nil then 35 | return true 36 | end 37 | -- TL? does not do inference in `and` expressions 38 | if pass_fds then 39 | if #pass_fds > 0 then 40 | return true 41 | end 42 | end 43 | return close_fds 44 | end 45 | 46 | function subprocess_posix.check_creationflags(creationflags: integer) 47 | if creationflags ~= 0 then 48 | error("creationflags is only supported on Windows platforms") 49 | end 50 | return 0 51 | end 52 | 53 | function subprocess_posix.wrap_handles(p2cwrite, c2pread, errread) 54 | return p2cwrite, c2pread, errread 55 | end 56 | 57 | local function get_devnull(self: Popen) 58 | if not self.devnull then 59 | self.devnull = fcntl.open("/dev/null", fcntl.O_RDWR) 60 | end 61 | return self.devnull 62 | end 63 | 64 | function subprocess_posix.get_handles(self: Popen, stdin: file|integer|nil, stdout: file|integer|nil, stderr: file|integer|nil): (integer,integer,integer,integer,integer,integer) 65 | local p2cread, p2cwrite = -1, -1 66 | local c2pread, c2pwrite = -1, -1 67 | local errread, errwrite = -1, -1 68 | local errno 69 | 70 | if stdin == PIPE then 71 | local r, w, e = unistd.pipe() 72 | if not r then 73 | error(w, e) 74 | end 75 | p2cread, p2cwrite, errno = r, w, e 76 | elseif stdin == DEVNULL then 77 | p2cread = self:get_devnull() 78 | elseif type(stdin) == "integer" then 79 | p2cread = stdin 80 | elseif stdin then 81 | -- Assuming file-like object 82 | p2cread = stdio.fileno(stdin) 83 | end 84 | 85 | if stdout == PIPE then 86 | local r, w, e = unistd.pipe() 87 | if not r then 88 | error(w, e) 89 | end 90 | c2pread, c2pwrite, errno = r, w, e 91 | elseif stdout == DEVNULL then 92 | c2pwrite = self:get_devnull() 93 | elseif type(stdout) == "integer" then 94 | c2pwrite = stdout 95 | elseif stdout then 96 | -- Assuming file-like object 97 | c2pwrite = stdio.fileno(stdout) 98 | end 99 | 100 | if stderr == PIPE then 101 | local r, w, e = unistd.pipe() 102 | if not r then 103 | error(w, e) 104 | end 105 | errread, errwrite, errno = r, w, e 106 | elseif stderr == STDOUT then 107 | errwrite = c2pwrite 108 | elseif stderr == DEVNULL then 109 | errwrite = self:get_devnull() 110 | elseif type(stderr) == "integer" then 111 | errwrite = stderr 112 | elseif stderr then 113 | -- Assuming file-like object 114 | errwrite = stdio.fileno(stderr) 115 | end 116 | 117 | return p2cread, p2cwrite, 118 | c2pread, c2pwrite, 119 | errread, errwrite 120 | end 121 | 122 | function subprocess_posix.communicate(...) 123 | -- TODO 124 | end 125 | 126 | local function make_set(array:{any}): {any:boolean} 127 | local set: {any:boolean} = {} 128 | for _, elem in ipairs(array) do 129 | set[elem] = true 130 | end 131 | return set 132 | end 133 | 134 | local function sorted(set: {any:boolean}) 135 | local array: {any} = {} 136 | for k, _ in pairs(set) do 137 | table.insert(array, k) 138 | end 139 | table.sort(array) 140 | return array 141 | end 142 | 143 | --[[ 144 | Call functions that have the (nil, err, errno) protocol 145 | for errors, auto-retrying in case of EINTR. 146 | ]] 147 | local function eintr_retry_call(fn, ...) 148 | while true do 149 | local res, err, errcode = fn(...) 150 | if res ~= nil or errcode ~= errno.EINTR then 151 | return res, err, errcode 152 | end 153 | end 154 | end 155 | 156 | function subprocess_posix.execute_child( 157 | self:Popen, cmd: string|{string}, executable_p: string?, close_fds: boolean, 158 | pass_fds: {any}, cwd, env, 159 | startupinfo, creationflags, shell: boolean, 160 | p2cread: integer, p2cwrite: integer, 161 | c2pread: integer, c2pwrite: integer, 162 | errread: integer, errwrite: integer, 163 | start_new_session: boolean) 164 | 165 | local args: {string} = {} 166 | 167 | if type(cmd) == "string" then 168 | args[1] = cmd 169 | else 170 | args = cmd 171 | end 172 | 173 | if shell then 174 | table.insert(args, 1, "/bin/sh") 175 | table.insert(args, 2, "-c") 176 | if executable_p then 177 | args[1] = executable_p 178 | end 179 | end 180 | self.args = args 181 | 182 | local executable: string = executable_p or args[1] 183 | 184 | --[[ 185 | For transferring possible exec failure from child to parent. 186 | Data format: "exception name:hex errno:description" 187 | ]] 188 | local errpipe_read, errpipe_write = unistd.pipe() 189 | if not errpipe_read then 190 | error("could not open error pipe") 191 | end 192 | local low_fds_to_close: {integer} = {} 193 | while errpipe_write < 3 do 194 | table.insert(low_fds_to_close, errpipe_write) 195 | errpipe_write = unistd.dup(errpipe_write) 196 | end 197 | for _, low_fd in ipairs(low_fds_to_close) do 198 | unistd.close(low_fd) 199 | end 200 | 201 | local errpipe_data = "" 202 | pcall(function() 203 | pcall(function() 204 | --[[ 205 | We must avoid complex work that could involve 206 | malloc or free in the child process to avoid 207 | potential deadlocks, thus we do all this here. 208 | and pass it to fork_exec() 209 | ]] 210 | local env_list: {string}? 211 | if env and #env > 0 then 212 | env_list = {} 213 | for k, v in pairs(env) do 214 | table.insert(env_list, tostring(k).."="..tostring(v)) 215 | end 216 | end 217 | 218 | local executable_list: {string} = {} 219 | local dname = libgen.dirname(executable) 220 | if dname == "." and executable:sub(1,2) ~= "./" then 221 | -- This matches the behavior of execvpe() 222 | local PATH = os.getenv("PATH") or "" 223 | for dir in PATH:gmatch("([^:]+):?") do 224 | table.insert(executable_list, dir.."/"..executable) 225 | end 226 | else 227 | executable_list[1] = executable 228 | end 229 | 230 | local fds_to_keep = make_set(pass_fds) 231 | fds_to_keep[errpipe_write] = true 232 | 233 | self.pid = core.fork_exec(args, executable_list, 234 | close_fds, sorted(fds_to_keep), 235 | cwd, env_list, 236 | p2cread, p2cwrite, c2pread, c2pwrite, 237 | errread, errwrite, 238 | errpipe_read, errpipe_write, 239 | start_new_session) 240 | self.child_created = true 241 | end) 242 | -- be sure the FD is closed no matter what 243 | unistd.close(errpipe_write) 244 | 245 | if p2cread ~= -1 and p2cwrite ~= -1 and p2cread ~= self.devnull then 246 | unistd.close(p2cread) 247 | end 248 | if c2pwrite ~= -1 and c2pread ~= -1 and c2pwrite ~= self.devnull then 249 | unistd.close(c2pwrite) 250 | end 251 | if errwrite ~= -1 and errread ~= -1 and errwrite ~= self.devnull then 252 | unistd.close(errwrite) 253 | end 254 | local dn = self.devnull -- TL? 255 | if dn then 256 | unistd.close(dn) 257 | self.devnull = nil 258 | end 259 | -- Prevent a double close of these fds on error. 260 | self.closed_child_pipe_fds = true 261 | 262 | -- Wait for exec to fail or succeed; possibly raising an 263 | -- exception (limited in size) 264 | while true do 265 | local part = eintr_retry_call(unistd.read, errpipe_read, 50000) 266 | if not part or part == "" then break end 267 | errpipe_data = errpipe_data .. part 268 | if #errpipe_data >= 50000 then 269 | break 270 | end 271 | end 272 | end) 273 | -- be sure the FD is closed no matter what 274 | unistd.close(errpipe_read) 275 | if #errpipe_data > 0 then 276 | local pid, str, errcode = eintr_retry_call(wait.wait, self.pid) 277 | if not pid and errcode ~= errno.ECHILD then 278 | error(str) 279 | end 280 | 281 | local exception_name, hex_errno, err_msg = 282 | errpipe_data:match("([^:]+):([^:]+):([^:]+)") 283 | if not exception_name then 284 | hex_errno = "00" 285 | err_msg = "Bad exception data from child: "..errpipe_data 286 | end 287 | return nil, err_msg, tonumber(hex_errno, 16) 288 | end 289 | return self 290 | end 291 | 292 | local function handle_exitstatus(self: Popen, res: string, sts: integer) 293 | if res == "exited" then 294 | self.returncode = sts 295 | else 296 | self.returncode = -sts 297 | end 298 | end 299 | 300 | --[[ 301 | Check if child process has terminated. Returns returncode 302 | attribute. 303 | 304 | This method is called by __gc, so it cannot reference anything 305 | outside of the local scope (nor can any methods it calls). 306 | ]] 307 | local my_wait = wait.wait 308 | local my_WNOHANG = wait.WNOHANG 309 | local my_ECHILD = errno.ECHILD 310 | function subprocess_posix.poll(self: Popen, deadstate: integer?) 311 | if not self.returncode then 312 | -- self.waidpid_lock:acquire() 313 | -- if self.returncode then return self.returncode end 314 | local pid, res, sts = my_wait(self.pid, my_WNOHANG) 315 | if pid then 316 | if pid == self.pid then 317 | handle_exitstatus(self, res, sts) 318 | end 319 | else 320 | if deadstate then 321 | self.returncode = deadstate 322 | elseif sts == my_ECHILD then 323 | --[[ 324 | This happens if SIGCLD is set to be ignored or 325 | waiting for child processes has otherwise been 326 | disabled for our process. This child is dead, we 327 | can't get the status. 328 | http://bugs.python.org/issue15756 329 | ]] 330 | self.returncode = 0 331 | end 332 | end 333 | -- self.waitpid_lock:release() 334 | end 335 | return self.returncode 336 | end 337 | 338 | local function try_wait(self: Popen, wait_flags: integer?) 339 | return eintr_retry_call(wait.wait, self.pid, wait_flags) 340 | end 341 | 342 | function subprocess_posix.wait(self: Popen, timeout: number?, endtime: number?) 343 | if self.returncode then 344 | return self.returncode 345 | end 346 | -- endtime is preferred to timeout. timeout is only used for printing. 347 | if endtime or timeout then 348 | if not endtime then 349 | endtime = subprocess_posix.time() + timeout 350 | elseif not timeout then 351 | timeout = self.remaining_time(endtime) 352 | end 353 | end 354 | if endtime then 355 | -- Enter a busy loop if we have a timeout. This busy loop was 356 | -- cribbed from Lib/threading.py in Thread.wait() at r71065. 357 | local delay = 0.0005 -- 500 us -> initial delay of 1 ms 358 | while true do 359 | -- self.waitpid_lock:acquire(false) 360 | if self.returncode then 361 | break 362 | end 363 | local pid, res, sts = try_wait(self, wait.WNOHANG) 364 | assert(pid == self.pid or pid == 0) 365 | if pid == self.pid then 366 | handle_exitstatus(self, res, sts) 367 | break 368 | end 369 | -- self.waitpid_lock:release(false) 370 | local remaining = self.remaining_time(endtime) 371 | if remaining <= 0 then 372 | return nil, exceptions.TimeoutExpired(self.cmd, timeout) 373 | end 374 | delay = math.min(delay * 2, remaining, .05) 375 | posix_time.nanosleep({tv_sec = math.floor(delay), tv_nsec = (delay - math.floor(delay)) * 1000000000}) 376 | end 377 | else 378 | while not self.returncode do 379 | -- self.waitpid_lock:acquire() do 380 | local pid, res, sts = try_wait(self, 0) 381 | -- Check the pid and loop as waitpid has been known to 382 | -- return 0 even without WNOHANG in odd situations. 383 | -- http://bugs.python.org/issue14396. 384 | if pid == self.pid then 385 | handle_exitstatus(self, res, sts) 386 | end 387 | -- end self.waitpid_lock:release() 388 | end 389 | end 390 | return self.returncode 391 | end 392 | 393 | --[[ 394 | function subprocess_posix.communicate(self: Popen, input:string?, endtime: number?, orig_timeout: number?) 395 | local stdin = self.stdin 396 | if stdin then 397 | if not self.communication_started then 398 | -- Flush stdio buffer. This might block, if the user has 399 | -- been writing to .stdin in an uncontrolled fashion. 400 | stdin:flush() 401 | if not input then 402 | stdin:close() 403 | end 404 | end 405 | end 406 | 407 | --Only create this mapping if we haven't already. 408 | if not self.communication_started then 409 | if self.stdout and not self.fileobj2output[self.stdout] then 410 | self.fileobj2output[self.stdout] = {} 411 | end 412 | if self.stderr and not self.fileobj2output[self.stderr] then 413 | self.fileobj2output[self.stderr] = {} 414 | end 415 | end 416 | 417 | local stdout: {string}? = self.stdout and self.fileobj2output[self.stdout] 418 | local stderr: {string}? = self.stderr and self.fileobj2output[self.stderr] 419 | 420 | if stdin and not self.input then 421 | self.input_offset = 0 422 | self.input = input 423 | end 424 | 425 | -- TODO port PopenSelector... 426 | end 427 | ]] 428 | 429 | function subprocess_posix.kill(self: Popen) 430 | signal.kill(self.pid, signal.SIGKILL) 431 | end 432 | 433 | function subprocess_posix.terminate(self: Popen) 434 | signal.kill(self.pid, signal.SIGTERM) 435 | end 436 | 437 | function subprocess_posix.open(fd: integer, mode:string): (file)|(nil,string,integer) 438 | return stdio.fdopen(fd, mode) 439 | end 440 | 441 | function subprocess_posix.close(fd: integer): (integer)|(nil, string, integer) 442 | return unistd.close(fd) 443 | end 444 | 445 | return subprocess_posix 446 | 447 | -------------------------------------------------------------------------------- /subprocess/posix/close_fds.c: -------------------------------------------------------------------------------- 1 | 2 | #ifdef HAVE_SYS_SYSCALL_H 3 | #include 4 | #endif 5 | 6 | #if (_BSD_SOURCE || _SVID_SOURCE || (_POSIX_C_SOURCE >= 200809L || _XOPEN_SOURCE >= 700)) 7 | #define HAVE_DIRFD 8 | #endif 9 | 10 | #include 11 | #include 12 | #include 13 | 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | 20 | #if defined(__FreeBSD__) || (defined(__APPLE__) && defined(__MACH__)) 21 | # define FD_DIR "/dev/fd" 22 | #else 23 | # define FD_DIR "/proc/self/fd" 24 | #endif 25 | 26 | 27 | /* Get the maximum file descriptor that could be opened by this process. 28 | * This function is async signal safe for use between fork() and exec(). 29 | */ 30 | static long safe_get_max_fd(void) { 31 | long local_max_fd; 32 | #if defined(__NetBSD__) 33 | local_max_fd = fcntl(0, F_MAXFD); 34 | if (local_max_fd >= 0) { 35 | return local_max_fd; 36 | } 37 | #endif 38 | #ifdef _SC_OPEN_MAX 39 | local_max_fd = sysconf(_SC_OPEN_MAX); 40 | if (local_max_fd != -1) { 41 | return local_max_fd; 42 | } 43 | #endif 44 | return 256; /* Matches legacy Lib/subprocess.py behavior. */ 45 | } 46 | 47 | 48 | /* Convert ASCII to a positive int, no libc call. no overflow. -1 on error. */ 49 | static int pos_int_from_ascii(char *name) { 50 | int num = 0; 51 | while (*name >= '0' && *name <= '9') { 52 | num = num * 10 + (*name - '0'); 53 | ++name; 54 | } 55 | if (*name) { 56 | return -1; /* Non digit found, not a number. */ 57 | } 58 | return num; 59 | } 60 | 61 | 62 | #if defined(__FreeBSD__) 63 | /* When /dev/fd isn't mounted it is often a static directory populated 64 | * with 0 1 2 or entries for 0 .. 63 on FreeBSD, NetBSD and OpenBSD. 65 | * NetBSD and OpenBSD have a /proc fs available (though not necessarily 66 | * mounted) and do not have fdescfs for /dev/fd. MacOS X has a devfs 67 | * that properly supports /dev/fd. 68 | */ 69 | static int is_fdescfs_mounted_on_dev_fd(void) { 70 | struct stat dev_stat; 71 | struct stat dev_fd_stat; 72 | if (stat("/dev", &dev_stat) != 0) { 73 | return 0; 74 | } 75 | if (stat(FD_DIR, &dev_fd_stat) != 0) { 76 | return 0; 77 | } 78 | if (dev_stat.st_dev == dev_fd_stat.st_dev) { 79 | return 0; /* / == /dev == /dev/fd means it is static. #fail */ 80 | } 81 | return 1; 82 | } 83 | #endif 84 | 85 | 86 | /* Is fd found in the sorted Lua array? */ 87 | static int is_fd_in_sorted_fd_sequence(int fd, lua_State* L, int FDS_TO_KEEP) 88 | { 89 | /* Binary search. */ 90 | int search_min = 0; 91 | int search_max = luaL_len(L, FDS_TO_KEEP) - 1; 92 | if (search_max < 0) 93 | return 0; 94 | do { 95 | long middle = (search_min + search_max) / 2; 96 | long middle_fd; 97 | 98 | lua_geti(L, FDS_TO_KEEP, middle + 1); 99 | middle_fd = lua_tointeger(L, -1); 100 | lua_pop(L, 1); 101 | 102 | if (fd == middle_fd) 103 | return 1; 104 | if (fd > middle_fd) 105 | search_min = middle + 1; 106 | else 107 | search_max = middle - 1; 108 | } while (search_min <= search_max); 109 | return 0; 110 | } 111 | 112 | 113 | /****************************************************************************** 114 | * Implementation 0: portable, brute-force (used as a fallback below) 115 | *****************************************************************************/ 116 | 117 | 118 | /* Close all file descriptors in the range from start_fd and higher 119 | * except for those in the table at index FDS_TO_KEEP. If the range defined by 120 | * [start_fd, safe_get_max_fd()) is large this will take a long 121 | * time as it calls close() on EVERY possible fd. 122 | * 123 | * It isn't possible to know for sure what the max fd to go up to 124 | * is for processes with the capability of raising their maximum. 125 | */ 126 | void close_fds_by_brute_force(long start_fd, lua_State* L, int FDS_TO_KEEP) { 127 | long end_fd = safe_get_max_fd(); 128 | int num_fds_to_keep = luaL_len(L, FDS_TO_KEEP); 129 | int keep_seq_idx; 130 | int fd_num; 131 | /* As FDS_TO_KEEP is sorted we can loop through the list closing 132 | * fds inbetween any in the keep list falling within our range. */ 133 | for (keep_seq_idx = 0; keep_seq_idx < num_fds_to_keep; ++keep_seq_idx) { 134 | lua_Integer keep_fd; 135 | 136 | lua_geti(L, FDS_TO_KEEP, keep_seq_idx + 1); 137 | keep_fd = lua_tointeger(L, -1); 138 | lua_pop(L, 1); 139 | 140 | if (keep_fd < start_fd) 141 | continue; 142 | for (fd_num = start_fd; fd_num < keep_fd; ++fd_num) { 143 | while (close(fd_num) < 0 && errno == EINTR); 144 | } 145 | start_fd = keep_fd + 1; 146 | } 147 | if (start_fd <= end_fd) { 148 | for (fd_num = start_fd; fd_num < end_fd; ++fd_num) { 149 | while (close(fd_num) < 0 && errno == EINTR); 150 | } 151 | } 152 | } 153 | 154 | 155 | #if defined(__linux__) && defined(HAVE_SYS_SYSCALL_H) 156 | 157 | /****************************************************************************** 158 | * Implementation 1: Linux, syscall 159 | *****************************************************************************/ 160 | 161 | 162 | /* It doesn't matter if d_name has room for NAME_MAX chars; we're using this 163 | * only to read a directory of short file descriptor number names. The kernel 164 | * will return an error if we didn't give it enough space. Highly Unlikely. 165 | * This structure is very old and stable: It will not change unless the kernel 166 | * chooses to break compatibility with all existing binaries. Highly Unlikely. 167 | */ 168 | struct linux_dirent64 { 169 | unsigned long long d_ino; 170 | long long d_off; 171 | unsigned short d_reclen; /* Length of this linux_dirent */ 172 | unsigned char d_type; 173 | char d_name[256]; /* Filename (null-terminated) */ 174 | }; 175 | 176 | 177 | /* Originally called _close_open_fds_safe in Python sources. 178 | * 179 | * Original comments: 180 | * ------------------ 181 | * Close all open file descriptors in the range from start_fd and higher 182 | * Do not close any in the sorted FDS_TO_KEEP list. 183 | * 184 | * This version is async signal safe as it does not make any unsafe C library 185 | * calls, malloc calls or handle any locks. It is _unfortunate_ to be forced 186 | * to resort to making a kernel system call directly but this is the ONLY api 187 | * available that does no harm. opendir/readdir/closedir perform memory 188 | * allocation and locking so while they usually work they are not guaranteed 189 | * to (especially if you have replaced your malloc implementation). A version 190 | * of this function that uses those can be found in the _maybe_unsafe variant. 191 | * 192 | * This is Linux specific because that is all I am ready to test it on. It 193 | * should be easy to add OS specific dirent or dirent64 structures and modify 194 | * it with some cpp #define magic to work on other OSes as well if you want. 195 | */ 196 | void close_open_fds(int start_fd, lua_State* L, int FDS_TO_KEEP) { 197 | int fd_dir_fd; 198 | 199 | fd_dir_fd = open(FD_DIR, O_RDONLY); 200 | if (fd_dir_fd == -1) { 201 | /* No way to get a list of open fds. */ 202 | close_fds_by_brute_force(start_fd, L, FDS_TO_KEEP); 203 | return; 204 | } else { 205 | char buffer[sizeof(struct linux_dirent64)]; 206 | int bytes; 207 | while ((bytes = syscall(SYS_getdents64, fd_dir_fd, 208 | (struct linux_dirent64 *)buffer, 209 | sizeof(buffer))) > 0) { 210 | struct linux_dirent64 *entry; 211 | int offset; 212 | for (offset = 0; offset < bytes; offset += entry->d_reclen) { 213 | int fd; 214 | entry = (struct linux_dirent64 *)(buffer + offset); 215 | if ((fd = pos_int_from_ascii(entry->d_name)) < 0) 216 | continue; /* Not a number. */ 217 | if (fd != fd_dir_fd && fd >= start_fd && 218 | !is_fd_in_sorted_fd_sequence(fd, L, FDS_TO_KEEP)) { 219 | while (close(fd) < 0 && errno == EINTR); 220 | } 221 | } 222 | } 223 | while (close(fd_dir_fd) < 0 && errno == EINTR); 224 | } 225 | } 226 | 227 | 228 | #else /* NOT (defined(__linux__) && defined(HAVE_SYS_SYSCALL_H)) */ 229 | 230 | /****************************************************************************** 231 | * Implementation 2: portable fallback 232 | *****************************************************************************/ 233 | 234 | 235 | /* Originally called _close_open_fds_maybe_unsafe in Python sources. 236 | * 237 | * Original comments: 238 | * ------------------ 239 | * Close all open file descriptors from start_fd and higher. 240 | * Do not close any in the sorted FDS_TO_KEEP list. 241 | * 242 | * This function violates the strict use of async signal safe functions. :( 243 | * It calls opendir(), readdir() and closedir(). Of these, the one most 244 | * likely to ever cause a problem is opendir() as it performs an internal 245 | * malloc(). Practically this should not be a problem. The Java VM makes the 246 | * same calls between fork and exec in its own UNIXProcess_md.c implementation. 247 | * 248 | * readdir_r() is not used because it provides no benefit. It is typically 249 | * implemented as readdir() followed by memcpy(). See also: 250 | * http://womble.decadent.org.uk/readdir_r-advisory.html 251 | */ 252 | void close_open_fds(long start_fd, lua_State* L, int FDS_TO_KEEP) 253 | { 254 | DIR *proc_fd_dir; 255 | #ifndef HAVE_DIRFD 256 | while (is_fd_in_sorted_fd_sequence(start_fd, L, FDS_TO_KEEP)) { 257 | ++start_fd; 258 | } 259 | /* Close our lowest fd before we call opendir so that it is likely to 260 | * reuse that fd otherwise we might close opendir's file descriptor in 261 | * our loop. This trick assumes that fd's are allocated on a lowest 262 | * available basis. */ 263 | while (close(start_fd) < 0 && errno == EINTR); 264 | ++start_fd; 265 | #endif 266 | 267 | #if defined(__FreeBSD__) 268 | if (!is_fdescfs_mounted_on_dev_fd()) 269 | proc_fd_dir = NULL; 270 | else 271 | #endif 272 | proc_fd_dir = opendir(FD_DIR); 273 | if (!proc_fd_dir) { 274 | /* No way to get a list of open fds. */ 275 | close_fds_by_brute_force(start_fd, L, FDS_TO_KEEP); 276 | } else { 277 | struct dirent *dir_entry; 278 | #ifdef HAVE_DIRFD 279 | int fd_used_by_opendir = dirfd(proc_fd_dir); 280 | #else 281 | int fd_used_by_opendir = start_fd - 1; 282 | #endif 283 | errno = 0; 284 | while ((dir_entry = readdir(proc_fd_dir))) { 285 | int fd; 286 | if ((fd = pos_int_from_ascii(dir_entry->d_name)) < 0) 287 | continue; /* Not a number. */ 288 | if (fd != fd_used_by_opendir && fd >= start_fd && 289 | !is_fd_in_sorted_fd_sequence(fd, L, FDS_TO_KEEP)) { 290 | while (close(fd) < 0 && errno == EINTR); 291 | } 292 | errno = 0; 293 | } 294 | if (errno) { 295 | /* readdir error, revert behavior. Highly Unlikely. */ 296 | close_fds_by_brute_force(start_fd, L, FDS_TO_KEEP); 297 | } 298 | closedir(proc_fd_dir); 299 | } 300 | } 301 | 302 | 303 | #endif /* else NOT (defined(__linux__) && defined(HAVE_SYS_SYSCALL_H)) */ 304 | 305 | 306 | -------------------------------------------------------------------------------- /subprocess/posix/close_fds.h: -------------------------------------------------------------------------------- 1 | 2 | #ifndef SUBPROCESS_POSIX_CLOSE_FDS_H 3 | #define SUBPROCESS_POSIX_CLOSE_FDS_H 4 | 5 | #include "lua.h" 6 | 7 | void close_fds_by_brute_force(long start_fd, lua_State* L, int FDS_TO_KEEP); 8 | void close_open_fds(long start_fd, lua_State* L, int FDS_TO_KEEP); 9 | 10 | #endif 11 | -------------------------------------------------------------------------------- /subprocess/posix/compat-5.3.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include "compat-5.3.h" 9 | 10 | /* don't compile it again if it already is included via compat53.h */ 11 | #ifndef COMPAT53_C_ 12 | #define COMPAT53_C_ 13 | 14 | 15 | 16 | /* definitions for Lua 5.1 only */ 17 | #if defined( LUA_VERSION_NUM ) && LUA_VERSION_NUM == 501 18 | 19 | 20 | COMPAT53_API int lua_absindex (lua_State *L, int i) { 21 | if (i < 0 && i > LUA_REGISTRYINDEX) 22 | i += lua_gettop(L) + 1; 23 | return i; 24 | } 25 | 26 | 27 | static void compat53_call_lua (lua_State *L, char const code[], size_t len, 28 | int nargs, int nret) { 29 | lua_rawgetp(L, LUA_REGISTRYINDEX, (void*)code); 30 | if (lua_type(L, -1) != LUA_TFUNCTION) { 31 | lua_pop(L, 1); 32 | if (luaL_loadbuffer(L, code, len, "=none")) 33 | lua_error(L); 34 | lua_pushvalue(L, -1); 35 | lua_rawsetp(L, LUA_REGISTRYINDEX, (void*)code); 36 | } 37 | lua_insert(L, -nargs-1); 38 | lua_call(L, nargs, nret); 39 | } 40 | 41 | 42 | static const char compat53_arith_code[] = 43 | "local op,a,b=...\n" 44 | "if op==0 then return a+b\n" 45 | "elseif op==1 then return a-b\n" 46 | "elseif op==2 then return a*b\n" 47 | "elseif op==3 then return a/b\n" 48 | "elseif op==4 then return a%b\n" 49 | "elseif op==5 then return a^b\n" 50 | "elseif op==6 then return -a\n" 51 | "end\n"; 52 | 53 | COMPAT53_API void lua_arith (lua_State *L, int op) { 54 | if (op < LUA_OPADD || op > LUA_OPUNM) 55 | luaL_error(L, "invalid 'op' argument for lua_arith"); 56 | luaL_checkstack(L, 5, "not enough stack slots"); 57 | if (op == LUA_OPUNM) 58 | lua_pushvalue(L, -1); 59 | lua_pushnumber(L, op); 60 | lua_insert(L, -3); 61 | compat53_call_lua(L, compat53_arith_code, 62 | sizeof(compat53_arith_code)-1, 3, 1); 63 | } 64 | 65 | 66 | static const char compat53_compare_code[] = 67 | "local a,b=...\n" 68 | "return a<=b\n"; 69 | 70 | COMPAT53_API int lua_compare (lua_State *L, int idx1, int idx2, int op) { 71 | int result = 0; 72 | switch (op) { 73 | case LUA_OPEQ: 74 | return lua_equal(L, idx1, idx2); 75 | case LUA_OPLT: 76 | return lua_lessthan(L, idx1, idx2); 77 | case LUA_OPLE: 78 | luaL_checkstack(L, 5, "not enough stack slots"); 79 | idx1 = lua_absindex(L, idx1); 80 | idx2 = lua_absindex(L, idx2); 81 | lua_pushvalue(L, idx1); 82 | lua_pushvalue(L, idx2); 83 | compat53_call_lua(L, compat53_compare_code, 84 | sizeof(compat53_compare_code)-1, 2, 1); 85 | result = lua_toboolean(L, -1); 86 | lua_pop(L, 1); 87 | return result; 88 | default: 89 | luaL_error(L, "invalid 'op' argument for lua_compare"); 90 | } 91 | return 0; 92 | } 93 | 94 | 95 | COMPAT53_API void lua_copy (lua_State *L, int from, int to) { 96 | int abs_to = lua_absindex(L, to); 97 | luaL_checkstack(L, 1, "not enough stack slots"); 98 | lua_pushvalue(L, from); 99 | lua_replace(L, abs_to); 100 | } 101 | 102 | 103 | COMPAT53_API void lua_len (lua_State *L, int i) { 104 | switch (lua_type(L, i)) { 105 | case LUA_TSTRING: /* fall through */ 106 | case LUA_TTABLE: 107 | if (!luaL_callmeta(L, i, "__len")) 108 | lua_pushnumber(L, (int)lua_objlen(L, i)); 109 | break; 110 | case LUA_TUSERDATA: 111 | if (luaL_callmeta(L, i, "__len")) 112 | break; 113 | /* maybe fall through */ 114 | default: 115 | luaL_error(L, "attempt to get length of a %s value", 116 | lua_typename(L, lua_type(L, i))); 117 | } 118 | } 119 | 120 | 121 | COMPAT53_API int lua_rawgetp (lua_State *L, int i, const void *p) { 122 | int abs_i = lua_absindex(L, i); 123 | lua_pushlightuserdata(L, (void*)p); 124 | lua_rawget(L, abs_i); 125 | return lua_type(L, -1); 126 | } 127 | 128 | COMPAT53_API void lua_rawsetp (lua_State *L, int i, const void *p) { 129 | int abs_i = lua_absindex(L, i); 130 | luaL_checkstack(L, 1, "not enough stack slots"); 131 | lua_pushlightuserdata(L, (void*)p); 132 | lua_insert(L, -2); 133 | lua_rawset(L, abs_i); 134 | } 135 | 136 | 137 | COMPAT53_API lua_Integer lua_tointegerx (lua_State *L, int i, int *isnum) { 138 | lua_Integer n = lua_tointeger(L, i); 139 | if (isnum != NULL) { 140 | *isnum = (n != 0 || lua_isnumber(L, i)); 141 | } 142 | return n; 143 | } 144 | 145 | 146 | COMPAT53_API lua_Number lua_tonumberx (lua_State *L, int i, int *isnum) { 147 | lua_Number n = lua_tonumber(L, i); 148 | if (isnum != NULL) { 149 | *isnum = (n != 0 || lua_isnumber(L, i)); 150 | } 151 | return n; 152 | } 153 | 154 | 155 | COMPAT53_API void luaL_checkversion (lua_State *L) { 156 | (void)L; 157 | } 158 | 159 | 160 | COMPAT53_API int luaL_getsubtable (lua_State *L, int i, const char *name) { 161 | int abs_i = lua_absindex(L, i); 162 | luaL_checkstack(L, 3, "not enough stack slots"); 163 | lua_pushstring(L, name); 164 | lua_gettable(L, abs_i); 165 | if (lua_istable(L, -1)) 166 | return 1; 167 | lua_pop(L, 1); 168 | lua_newtable(L); 169 | lua_pushstring(L, name); 170 | lua_pushvalue(L, -2); 171 | lua_settable(L, abs_i); 172 | return 0; 173 | } 174 | 175 | 176 | COMPAT53_API int luaL_len (lua_State *L, int i) { 177 | int res = 0, isnum = 0; 178 | luaL_checkstack(L, 1, "not enough stack slots"); 179 | lua_len(L, i); 180 | res = (int)lua_tointegerx(L, -1, &isnum); 181 | lua_pop(L, 1); 182 | if (!isnum) 183 | luaL_error(L, "object length is not a number"); 184 | return res; 185 | } 186 | 187 | 188 | COMPAT53_API void luaL_setfuncs (lua_State *L, const luaL_Reg *l, int nup) { 189 | luaL_checkstack(L, nup+1, "too many upvalues"); 190 | for (; l->name != NULL; l++) { /* fill the table with given functions */ 191 | int i; 192 | lua_pushstring(L, l->name); 193 | for (i = 0; i < nup; i++) /* copy upvalues to the top */ 194 | lua_pushvalue(L, -(nup + 1)); 195 | lua_pushcclosure(L, l->func, nup); /* closure with those upvalues */ 196 | lua_settable(L, -(nup + 3)); /* table must be below the upvalues, the name and the closure */ 197 | } 198 | lua_pop(L, nup); /* remove upvalues */ 199 | } 200 | 201 | 202 | COMPAT53_API void luaL_setmetatable (lua_State *L, const char *tname) { 203 | luaL_checkstack(L, 1, "not enough stack slots"); 204 | luaL_getmetatable(L, tname); 205 | lua_setmetatable(L, -2); 206 | } 207 | 208 | 209 | COMPAT53_API void *luaL_testudata (lua_State *L, int i, const char *tname) { 210 | void *p = lua_touserdata(L, i); 211 | luaL_checkstack(L, 2, "not enough stack slots"); 212 | if (p == NULL || !lua_getmetatable(L, i)) 213 | return NULL; 214 | else { 215 | int res = 0; 216 | luaL_getmetatable(L, tname); 217 | res = lua_rawequal(L, -1, -2); 218 | lua_pop(L, 2); 219 | if (!res) 220 | p = NULL; 221 | } 222 | return p; 223 | } 224 | 225 | 226 | COMPAT53_API const char *luaL_tolstring (lua_State *L, int idx, size_t *len) { 227 | if (!luaL_callmeta(L, idx, "__tostring")) { 228 | int t = lua_type(L, idx); 229 | switch (t) { 230 | case LUA_TNIL: 231 | lua_pushliteral(L, "nil"); 232 | break; 233 | case LUA_TSTRING: 234 | case LUA_TNUMBER: 235 | lua_pushvalue(L, idx); 236 | break; 237 | case LUA_TBOOLEAN: 238 | if (lua_toboolean(L, idx)) 239 | lua_pushliteral(L, "true"); 240 | else 241 | lua_pushliteral(L, "false"); 242 | break; 243 | default: 244 | lua_pushfstring(L, "%s: %p", lua_typename(L, t), 245 | lua_topointer(L, idx)); 246 | break; 247 | } 248 | } 249 | return lua_tolstring(L, -1, len); 250 | } 251 | 252 | 253 | #if !defined(COMPAT53_IS_LUAJIT) 254 | static int compat53_countlevels (lua_State *L) { 255 | lua_Debug ar; 256 | int li = 1, le = 1; 257 | /* find an upper bound */ 258 | while (lua_getstack(L, le, &ar)) { li = le; le *= 2; } 259 | /* do a binary search */ 260 | while (li < le) { 261 | int m = (li + le)/2; 262 | if (lua_getstack(L, m, &ar)) li = m + 1; 263 | else le = m; 264 | } 265 | return le - 1; 266 | } 267 | 268 | static int compat53_findfield (lua_State *L, int objidx, int level) { 269 | if (level == 0 || !lua_istable(L, -1)) 270 | return 0; /* not found */ 271 | lua_pushnil(L); /* start 'next' loop */ 272 | while (lua_next(L, -2)) { /* for each pair in table */ 273 | if (lua_type(L, -2) == LUA_TSTRING) { /* ignore non-string keys */ 274 | if (lua_rawequal(L, objidx, -1)) { /* found object? */ 275 | lua_pop(L, 1); /* remove value (but keep name) */ 276 | return 1; 277 | } 278 | else if (compat53_findfield(L, objidx, level - 1)) { /* try recursively */ 279 | lua_remove(L, -2); /* remove table (but keep name) */ 280 | lua_pushliteral(L, "."); 281 | lua_insert(L, -2); /* place '.' between the two names */ 282 | lua_concat(L, 3); 283 | return 1; 284 | } 285 | } 286 | lua_pop(L, 1); /* remove value */ 287 | } 288 | return 0; /* not found */ 289 | } 290 | 291 | static int compat53_pushglobalfuncname (lua_State *L, lua_Debug *ar) { 292 | int top = lua_gettop(L); 293 | lua_getinfo(L, "f", ar); /* push function */ 294 | lua_pushvalue(L, LUA_GLOBALSINDEX); 295 | if (compat53_findfield(L, top + 1, 2)) { 296 | lua_copy(L, -1, top + 1); /* move name to proper place */ 297 | lua_pop(L, 2); /* remove pushed values */ 298 | return 1; 299 | } 300 | else { 301 | lua_settop(L, top); /* remove function and global table */ 302 | return 0; 303 | } 304 | } 305 | 306 | static void compat53_pushfuncname (lua_State *L, lua_Debug *ar) { 307 | if (*ar->namewhat != '\0') /* is there a name? */ 308 | lua_pushfstring(L, "function " LUA_QS, ar->name); 309 | else if (*ar->what == 'm') /* main? */ 310 | lua_pushliteral(L, "main chunk"); 311 | else if (*ar->what == 'C') { 312 | if (compat53_pushglobalfuncname(L, ar)) { 313 | lua_pushfstring(L, "function " LUA_QS, lua_tostring(L, -1)); 314 | lua_remove(L, -2); /* remove name */ 315 | } 316 | else 317 | lua_pushliteral(L, "?"); 318 | } 319 | else 320 | lua_pushfstring(L, "function <%s:%d>", ar->short_src, ar->linedefined); 321 | } 322 | 323 | #define COMPAT53_LEVELS1 12 /* size of the first part of the stack */ 324 | #define COMPAT53_LEVELS2 10 /* size of the second part of the stack */ 325 | 326 | COMPAT53_API void luaL_traceback (lua_State *L, lua_State *L1, 327 | const char *msg, int level) { 328 | lua_Debug ar; 329 | int top = lua_gettop(L); 330 | int numlevels = compat53_countlevels(L1); 331 | int mark = (numlevels > COMPAT53_LEVELS1 + COMPAT53_LEVELS2) ? COMPAT53_LEVELS1 : 0; 332 | if (msg) lua_pushfstring(L, "%s\n", msg); 333 | lua_pushliteral(L, "stack traceback:"); 334 | while (lua_getstack(L1, level++, &ar)) { 335 | if (level == mark) { /* too many levels? */ 336 | lua_pushliteral(L, "\n\t..."); /* add a '...' */ 337 | level = numlevels - COMPAT53_LEVELS2; /* and skip to last ones */ 338 | } 339 | else { 340 | lua_getinfo(L1, "Slnt", &ar); 341 | lua_pushfstring(L, "\n\t%s:", ar.short_src); 342 | if (ar.currentline > 0) 343 | lua_pushfstring(L, "%d:", ar.currentline); 344 | lua_pushliteral(L, " in "); 345 | compat53_pushfuncname(L, &ar); 346 | lua_concat(L, lua_gettop(L) - top); 347 | } 348 | } 349 | lua_concat(L, lua_gettop(L) - top); 350 | } 351 | 352 | 353 | COMPAT53_API int luaL_fileresult (lua_State *L, int stat, const char *fname) { 354 | int en = errno; /* calls to Lua API may change this value */ 355 | if (stat) { 356 | lua_pushboolean(L, 1); 357 | return 1; 358 | } 359 | else { 360 | lua_pushnil(L); 361 | if (fname) 362 | lua_pushfstring(L, "%s: %s", fname, strerror(en)); 363 | else 364 | lua_pushstring(L, strerror(en)); 365 | lua_pushnumber(L, (lua_Number)en); 366 | return 3; 367 | } 368 | } 369 | #endif /* not COMPAT53_IS_LUAJIT */ 370 | 371 | 372 | COMPAT53_API void luaL_buffinit (lua_State *L, luaL_Buffer_53 *B) { 373 | /* make it crash if used via pointer to a 5.1-style luaL_Buffer */ 374 | B->b.p = NULL; 375 | B->b.L = NULL; 376 | B->b.lvl = 0; 377 | /* reuse the buffer from the 5.1-style luaL_Buffer though! */ 378 | B->ptr = B->b.buffer; 379 | B->capacity = LUAL_BUFFERSIZE; 380 | B->nelems = 0; 381 | B->L2 = L; 382 | } 383 | 384 | 385 | COMPAT53_API char *luaL_prepbuffsize (luaL_Buffer_53 *B, size_t s) { 386 | if (B->capacity - B->nelems < s) { /* needs to grow */ 387 | char* newptr = NULL; 388 | size_t newcap = B->capacity * 2; 389 | if (newcap - B->nelems < s) 390 | newcap = B->nelems + s; 391 | if (newcap < B->capacity) /* overflow */ 392 | luaL_error(B->L2, "buffer too large"); 393 | newptr = lua_newuserdata(B->L2, newcap); 394 | memcpy(newptr, B->ptr, B->nelems); 395 | if (B->ptr != B->b.buffer) 396 | lua_replace(B->L2, -2); /* remove old buffer */ 397 | B->ptr = newptr; 398 | B->capacity = newcap; 399 | } 400 | return B->ptr+B->nelems; 401 | } 402 | 403 | 404 | COMPAT53_API void luaL_addlstring (luaL_Buffer_53 *B, const char *s, size_t l) { 405 | memcpy(luaL_prepbuffsize(B, l), s, l); 406 | luaL_addsize(B, l); 407 | } 408 | 409 | 410 | COMPAT53_API void luaL_addvalue (luaL_Buffer_53 *B) { 411 | size_t len = 0; 412 | const char *s = lua_tolstring(B->L2, -1, &len); 413 | if (!s) 414 | luaL_error(B->L2, "cannot convert value to string"); 415 | if (B->ptr != B->b.buffer) 416 | lua_insert(B->L2, -2); /* userdata buffer must be at stack top */ 417 | luaL_addlstring(B, s, len); 418 | lua_remove(B->L2, B->ptr != B->b.buffer ? -2 : -1); 419 | } 420 | 421 | 422 | void luaL_pushresult (luaL_Buffer_53 *B) { 423 | lua_pushlstring(B->L2, B->ptr, B->nelems); 424 | if (B->ptr != B->b.buffer) 425 | lua_replace(B->L2, -2); /* remove userdata buffer */ 426 | } 427 | 428 | 429 | #endif /* Lua 5.1 */ 430 | 431 | 432 | 433 | /* definitions for Lua 5.1 and Lua 5.2 */ 434 | #if defined( LUA_VERSION_NUM ) && LUA_VERSION_NUM <= 502 435 | 436 | 437 | COMPAT53_API int lua_geti (lua_State *L, int index, lua_Integer i) { 438 | index = lua_absindex(L, index); 439 | lua_pushinteger(L, i); 440 | lua_gettable(L, index); 441 | return lua_type(L, -1); 442 | } 443 | 444 | 445 | COMPAT53_API int lua_isinteger (lua_State *L, int index) { 446 | if (lua_type(L, index) == LUA_TNUMBER) { 447 | lua_Number n = lua_tonumber(L, index); 448 | lua_Integer i = lua_tointeger(L, index); 449 | if (i == n) 450 | return 1; 451 | } 452 | return 0; 453 | } 454 | 455 | 456 | static void compat53_reverse (lua_State *L, int a, int b) { 457 | for (; a < b; ++a, --b) { 458 | lua_pushvalue(L, a); 459 | lua_pushvalue(L, b); 460 | lua_replace(L, a); 461 | lua_replace(L, b); 462 | } 463 | } 464 | 465 | 466 | COMPAT53_API void lua_rotate (lua_State *L, int idx, int n) { 467 | int n_elems = 0; 468 | idx = lua_absindex(L, idx); 469 | n_elems = lua_gettop(L)-idx+1; 470 | if (n < 0) 471 | n += n_elems; 472 | if ( n > 0 && n < n_elems) { 473 | luaL_checkstack(L, 2, "not enough stack slots available"); 474 | n = n_elems - n; 475 | compat53_reverse(L, idx, idx+n-1); 476 | compat53_reverse(L, idx+n, idx+n_elems-1); 477 | compat53_reverse(L, idx, idx+n_elems-1); 478 | } 479 | } 480 | 481 | 482 | COMPAT53_API void lua_seti (lua_State *L, int index, lua_Integer i) { 483 | luaL_checkstack(L, 1, "not enough stack slots available"); 484 | index = lua_absindex(L, index); 485 | lua_pushinteger(L, i); 486 | lua_insert(L, -2); 487 | lua_settable(L, index); 488 | } 489 | 490 | 491 | #if !defined(lua_str2number) 492 | # define lua_str2number(s, p) strtod(s, p) 493 | #endif 494 | 495 | COMPAT53_API size_t lua_stringtonumber (lua_State *L, const char *s) { 496 | char* endptr; 497 | lua_Number n = lua_str2number(s, &endptr); 498 | if (endptr != s) { 499 | while (*endptr != '\0' && isspace((unsigned char)*endptr)) 500 | ++endptr; 501 | if (*endptr == '\0') { 502 | lua_pushnumber(L, n); 503 | return endptr - s + 1; 504 | } 505 | } 506 | return 0; 507 | } 508 | 509 | 510 | COMPAT53_API void luaL_requiref (lua_State *L, const char *modname, 511 | lua_CFunction openf, int glb) { 512 | luaL_checkstack(L, 3, "not enough stack slots available"); 513 | luaL_getsubtable(L, LUA_REGISTRYINDEX, "_LOADED"); 514 | if (lua_getfield(L, -1, modname) == LUA_TNIL) { 515 | lua_pop(L, 1); 516 | lua_pushcfunction(L, openf); 517 | lua_pushstring(L, modname); 518 | lua_call(L, 1, 1); 519 | lua_pushvalue(L, -1); 520 | lua_setfield(L, -3, modname); 521 | } 522 | if (glb) { 523 | lua_pushvalue(L, -1); 524 | lua_setglobal(L, modname); 525 | } 526 | lua_replace(L, -2); 527 | } 528 | 529 | 530 | #endif /* Lua 5.1 and 5.2 */ 531 | 532 | 533 | #endif /* COMPAT53_C_ */ 534 | 535 | 536 | /********************************************************************* 537 | * This file contains parts of Lua 5.2's and Lua 5.3's source code: 538 | * 539 | * Copyright (C) 1994-2014 Lua.org, PUC-Rio. 540 | * 541 | * Permission is hereby granted, free of charge, to any person obtaining 542 | * a copy of this software and associated documentation files (the 543 | * "Software"), to deal in the Software without restriction, including 544 | * without limitation the rights to use, copy, modify, merge, publish, 545 | * distribute, sublicense, and/or sell copies of the Software, and to 546 | * permit persons to whom the Software is furnished to do so, subject to 547 | * the following conditions: 548 | * 549 | * The above copyright notice and this permission notice shall be 550 | * included in all copies or substantial portions of the Software. 551 | * 552 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 553 | * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 554 | * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 555 | * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 556 | * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 557 | * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 558 | * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 559 | *********************************************************************/ 560 | 561 | -------------------------------------------------------------------------------- /subprocess/posix/compat-5.3.h: -------------------------------------------------------------------------------- 1 | #ifndef COMPAT53_H_ 2 | #define COMPAT53_H_ 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | 12 | #if defined(COMPAT53_PREFIX) 13 | /* - change the symbol names of functions to avoid linker conflicts 14 | * - compat-5.3.c needs to be compiled (and linked) separately 15 | */ 16 | # if !defined(COMPAT53_API) 17 | # define COMPAT53_API extern 18 | # endif 19 | # undef COMPAT53_INCLUDE_SOURCE 20 | #else /* COMPAT53_PREFIX */ 21 | /* - make all functions static and include the source. 22 | * - don't mess with the symbol names of functions 23 | * - compat-5.3.c doesn't need to be compiled (and linked) separately 24 | */ 25 | # define COMPAT53_PREFIX lua 26 | # undef COMPAT53_API 27 | # if defined(__GNUC__) || defined(__clang__) 28 | # define COMPAT53_API __attribute__((__unused__)) static 29 | # else 30 | # define COMPAT53_API static 31 | # endif 32 | # define COMPAT53_INCLUDE_SOURCE 33 | #endif /* COMPAT53_PREFIX */ 34 | 35 | #define COMPAT53_CONCAT_HELPER(a, b) a##b 36 | #define COMPAT53_CONCAT(a, b) COMPAT53_CONCAT_HELPER(a, b) 37 | 38 | 39 | 40 | /* declarations for Lua 5.1 */ 41 | #if defined(LUA_VERSION_NUM) && LUA_VERSION_NUM == 501 42 | 43 | /* XXX not implemented: 44 | * lua_arith (new operators) 45 | * lua_upvalueid 46 | * lua_upvaluejoin 47 | * lua_version 48 | * lua_yieldk 49 | * luaL_execresult 50 | * luaL_loadbufferx 51 | * luaL_loadfilex 52 | */ 53 | 54 | /* PUC-Rio Lua uses lconfig_h as include guard for luaconf.h, 55 | * LuaJIT uses luaconf_h. If you use PUC-Rio's include files 56 | * but LuaJIT's library, you will need to define the macro 57 | * COMPAT53_IS_LUAJIT yourself! */ 58 | #if !defined(COMPAT53_IS_LUAJIT) && defined(luaconf_h) 59 | # define COMPAT53_IS_LUAJIT 60 | #endif 61 | 62 | #define LUA_OK 0 63 | #define LUA_OPADD 0 64 | #define LUA_OPSUB 1 65 | #define LUA_OPMUL 2 66 | #define LUA_OPDIV 3 67 | #define LUA_OPMOD 4 68 | #define LUA_OPPOW 5 69 | #define LUA_OPUNM 6 70 | #define LUA_OPEQ 0 71 | #define LUA_OPLT 1 72 | #define LUA_OPLE 2 73 | 74 | typedef struct luaL_Stream { 75 | FILE *f; 76 | /* The following field is for LuaJIT which adds a uint32_t field 77 | * to file handles. */ 78 | #if INT_MAX > 2147483640L 79 | unsigned int type; 80 | #else 81 | unsigned long type; 82 | #endif 83 | lua_CFunction closef; 84 | } luaL_Stream; 85 | 86 | typedef size_t lua_Unsigned; 87 | 88 | typedef struct luaL_Buffer_53 { 89 | luaL_Buffer b; /* make incorrect code crash! */ 90 | char *ptr; 91 | size_t nelems; 92 | size_t capacity; 93 | lua_State *L2; 94 | } luaL_Buffer_53; 95 | #define luaL_Buffer luaL_Buffer_53 96 | 97 | #define lua_absindex COMPAT53_CONCAT(COMPAT53_PREFIX, _absindex) 98 | COMPAT53_API int lua_absindex (lua_State *L, int i); 99 | 100 | #define lua_arith COMPAT53_CONCAT(COMPAT53_PREFIX, _arith) 101 | COMPAT53_API void lua_arith (lua_State *L, int op); 102 | 103 | #define lua_compare COMPAT53_CONCAT(COMPAT53_PREFIX, _compare) 104 | COMPAT53_API int lua_compare (lua_State *L, int idx1, int idx2, int op); 105 | 106 | #define lua_copy COMPAT53_CONCAT(COMPAT53_PREFIX, _copy) 107 | COMPAT53_API void lua_copy (lua_State *L, int from, int to); 108 | 109 | #define lua_getuservalue(L, i) \ 110 | (lua_getfenv(L, i), lua_type(L, -1)) 111 | #define lua_setuservalue(L, i) \ 112 | (luaL_checktype(L, -1, LUA_TTABLE), lua_setfenv(L, i)) 113 | 114 | #define lua_len COMPAT53_CONCAT(COMPAT53_PREFIX, _len) 115 | COMPAT53_API void lua_len (lua_State *L, int i); 116 | 117 | #define luaL_newlibtable(L, l) \ 118 | (lua_createtable(L, 0, sizeof(l)/sizeof(*(l))-1)) 119 | #define luaL_newlib(L, l) \ 120 | (luaL_newlibtable(L, l), luaL_register(L, NULL, l)) 121 | 122 | #define lua_pushglobaltable(L) \ 123 | lua_pushvalue(L, LUA_GLOBALSINDEX) 124 | 125 | #define lua_rawgetp COMPAT53_CONCAT(COMPAT53_PREFIX, _rawgetp) 126 | COMPAT53_API int lua_rawgetp (lua_State *L, int i, const void *p); 127 | 128 | #define lua_rawsetp COMPAT53_CONCAT(COMPAT53_PREFIX, _rawsetp) 129 | COMPAT53_API void lua_rawsetp(lua_State *L, int i, const void *p); 130 | 131 | #define lua_rawlen(L, i) lua_objlen(L, i) 132 | 133 | #define lua_tointegerx COMPAT53_CONCAT(COMPAT53_PREFIX, _tointegerx) 134 | COMPAT53_API lua_Integer lua_tointegerx (lua_State *L, int i, int *isnum); 135 | 136 | #define lua_tonumberx COMPAT53_CONCAT(COMPAT53_PREFIX, _tonumberx) 137 | COMPAT53_API lua_Number lua_tonumberx (lua_State *L, int i, int *isnum); 138 | 139 | #define luaL_checkversion COMPAT53_CONCAT(COMPAT53_PREFIX, L_checkversion) 140 | COMPAT53_API void luaL_checkversion (lua_State *L); 141 | 142 | #define luaL_getsubtable COMPAT53_CONCAT(COMPAT53_PREFIX, L_getsubtable) 143 | COMPAT53_API int luaL_getsubtable (lua_State* L, int i, const char *name); 144 | 145 | #define luaL_len COMPAT53_CONCAT(COMPAT53_PREFIX, L_len) 146 | COMPAT53_API int luaL_len (lua_State *L, int i); 147 | 148 | #define luaL_setfuncs COMPAT53_CONCAT(COMPAT53_PREFIX, L_setfuncs) 149 | COMPAT53_API void luaL_setfuncs (lua_State *L, const luaL_Reg *l, int nup); 150 | 151 | #define luaL_setmetatable COMPAT53_CONCAT(COMPAT53_PREFIX, L_setmetatable) 152 | COMPAT53_API void luaL_setmetatable (lua_State *L, const char *tname); 153 | 154 | #define luaL_testudata COMPAT53_CONCAT(COMPAT53_PREFIX, L_testudata) 155 | COMPAT53_API void *luaL_testudata (lua_State *L, int i, const char *tname); 156 | 157 | #define luaL_tolstring COMPAT53_CONCAT(COMPAT53_PREFIX, L_tolstring) 158 | COMPAT53_API const char *luaL_tolstring (lua_State *L, int idx, size_t *len); 159 | 160 | #if !defined(COMPAT53_IS_LUAJIT) 161 | #define luaL_traceback COMPAT53_CONCAT(COMPAT53_PREFIX, L_traceback) 162 | COMPAT53_API void luaL_traceback (lua_State *L, lua_State *L1, const char *msg, int level); 163 | 164 | #define luaL_fileresult COMPAT53_CONCAT(COMPAT53_PREFIX, L_fileresult) 165 | COMPAT53_API int luaL_fileresult (lua_State *L, int stat, const char *fname); 166 | #endif /* COMPAT53_IS_LUAJIT */ 167 | 168 | #define lua_callk(L, na, nr, ctx, cont) \ 169 | ((void)(ctx), (void)(cont), lua_call(L, na, nr)) 170 | #define lua_pcallk(L, na, nr, err, ctx, cont) \ 171 | ((void)(ctx), (void)(cont), lua_pcall(L, na, nr, err)) 172 | 173 | #define luaL_buffinit COMPAT53_CONCAT(COMPAT53_PREFIX, _buffinit_53) 174 | COMPAT53_API void luaL_buffinit (lua_State *L, luaL_Buffer_53 *B); 175 | 176 | #define luaL_prepbuffsize COMPAT53_CONCAT(COMPAT53_PREFIX, _prepbufsize_53) 177 | COMPAT53_API char *luaL_prepbuffsize (luaL_Buffer_53 *B, size_t s); 178 | 179 | #define luaL_addlstring COMPAT53_CONCAT(COMPAT53_PREFIX, _addlstring_53) 180 | COMPAT53_API void luaL_addlstring (luaL_Buffer_53 *B, const char *s, size_t l); 181 | 182 | #define luaL_addvalue COMPAT53_CONCAT(COMPAT53_PREFIX, _addvalue_53) 183 | COMPAT53_API void luaL_addvalue (luaL_Buffer_53 *B); 184 | 185 | #define luaL_pushresult COMPAT53_CONCAT(COMPAT53_PREFIX, _pushresult_53) 186 | COMPAT53_API void luaL_pushresult (luaL_Buffer_53 *B); 187 | 188 | #undef luaL_buffinitsize 189 | #define luaL_buffinitsize(L, B, s) \ 190 | (luaL_buffinit(L, B), luaL_prepbuffsize(B, s)) 191 | 192 | #undef luaL_prepbuffer 193 | #define luaL_prepbuffer(B) \ 194 | luaL_prepbuffsize(B, LUAL_BUFFERSIZE) 195 | 196 | #undef luaL_addchar 197 | #define luaL_addchar(B, c) \ 198 | ((void)((B)->nelems < (B)->capacity || luaL_prepbuffsize(B, 1)), \ 199 | ((B)->ptr[(B)->nelems++] = (c))) 200 | 201 | #undef luaL_addsize 202 | #define luaL_addsize(B, s) \ 203 | ((B)->nelems += (s)) 204 | 205 | #undef luaL_addstring 206 | #define luaL_addstring(B, s) \ 207 | luaL_addlstring(B, s, strlen(s)) 208 | 209 | #undef luaL_pushresultsize 210 | #define luaL_pushresultsize(B, s) \ 211 | (luaL_addsize(B, s), luaL_pushresult(B)) 212 | 213 | #if defined(LUA_COMPAT_APIINTCASTS) 214 | #define lua_pushunsigned(L, n) \ 215 | lua_pushinteger(L, (lua_Integer)(n)) 216 | #define lua_tounsignedx(L, i, is) \ 217 | ((lua_Unsigned)lua_tointegerx(L, i, is)) 218 | #define lua_tounsigned(L, i) \ 219 | lua_tounsignedx(L, i, NULL) 220 | #define luaL_checkunsigned(L, a) \ 221 | ((lua_Unsigned)luaL_checkinteger(L, a)) 222 | #define luaL_optunsigned(L, a, d) \ 223 | ((lua_Unsigned)luaL_optinteger(L, a, (lua_Integer)(d))) 224 | #endif 225 | 226 | #endif /* Lua 5.1 only */ 227 | 228 | 229 | 230 | /* declarations for Lua 5.1 and 5.2 */ 231 | #if defined(LUA_VERSION_NUM) && LUA_VERSION_NUM <= 502 232 | 233 | typedef int lua_KContext; 234 | 235 | typedef int (*lua_KFunction)(lua_State *L, int status, lua_KContext ctx); 236 | 237 | #define lua_dump(L, w, d, s) \ 238 | ((void)(s), lua_dump(L, w, d)) 239 | 240 | #define lua_getfield(L, i, k) \ 241 | (lua_getfield(L, i, k), lua_type(L, -1)) 242 | 243 | #define lua_gettable(L, i) \ 244 | (lua_gettable(L, i), lua_type(L, -1)) 245 | 246 | #define lua_geti COMPAT53_CONCAT(COMPAT53_PREFIX, _geti) 247 | COMPAT53_API int lua_geti (lua_State *L, int index, lua_Integer i); 248 | 249 | #define lua_isinteger COMPAT53_CONCAT(COMPAT53_PREFIX, _isinteger) 250 | COMPAT53_API int lua_isinteger (lua_State *L, int index); 251 | 252 | #define lua_numbertointeger(n, p) \ 253 | ((*(p) = (lua_Integer)(n)), 1) 254 | 255 | #define lua_rawget(L, i) \ 256 | (lua_rawget(L, i), lua_type(L, -1)) 257 | 258 | #define lua_rawgeti(L, i, n) \ 259 | (lua_rawgeti(L, i, n), lua_type(L, -1)) 260 | 261 | #define lua_rotate COMPAT53_CONCAT(COMPAT53_PREFIX, _rotate) 262 | COMPAT53_API void lua_rotate (lua_State *L, int idx, int n); 263 | 264 | #define lua_seti COMPAT53_CONCAT(COMPAT53_PREFIX, _seti) 265 | COMPAT53_API void lua_seti (lua_State *L, int index, lua_Integer i); 266 | 267 | #define lua_strtonum COMPAT53_CONCAT(COMPAT53_PREFIX, _stringtonumber) 268 | COMPAT53_API size_t lua_stringtonumber (lua_State *L, const char *s); 269 | 270 | #define luaL_getmetafield(L, o, e) \ 271 | (luaL_getmetafield(L, o, e) ? lua_type(L, -1) : LUA_TNIL) 272 | 273 | #define luaL_requiref COMPAT53_CONCAT(COMPAT53_PREFIX, L_requiref_53) 274 | COMPAT53_API void luaL_requiref (lua_State *L, const char *modname, 275 | lua_CFunction openf, int glb ); 276 | 277 | #endif /* Lua 5.1 and Lua 5.2 */ 278 | 279 | 280 | 281 | /* declarations for Lua 5.2 */ 282 | #if defined(LUA_VERSION_NUM) && LUA_VERSION_NUM == 502 283 | 284 | /* XXX not implemented: 285 | * lua_isyieldable 286 | * lua_getextraspace 287 | * lua_arith (new operators) 288 | * lua_pushfstring (new formats) 289 | */ 290 | 291 | #define lua_getglobal(L, n) \ 292 | (lua_getglobal(L, n), lua_type(L, -1)) 293 | 294 | #define lua_getuservalue(L, i) \ 295 | (lua_getuservalue(L, i), lua_type(L, -1)) 296 | 297 | #define lua_rawgetp(L, i, p) \ 298 | (lua_rawgetp(L, i, p), lua_type(L, -1)) 299 | 300 | #define LUA_KFUNCTION(_name) \ 301 | static int (_name)(lua_State *L, int status, lua_KContext ctx); \ 302 | static int (_name ## _52)(lua_State *L) { \ 303 | lua_KContext ctx; \ 304 | int status = lua_getctx(L, &ctx); \ 305 | return (_name)(L, status, ctx); \ 306 | } \ 307 | static int (_name)(lua_State *L, int status, lua_KContext ctx) 308 | 309 | #define lua_pcallk(L, na, nr, err, ctx, cont) \ 310 | lua_pcallk(L, na, nr, err, ctx, cont ## _52) 311 | 312 | #define lua_callk(L, na, nr, ctx, cont) \ 313 | lua_callk(L, na, nr, ctx, cont ## _52) 314 | 315 | #define lua_yieldk(L, nr, ctx, cont) \ 316 | lua_yieldk(L, nr, ctx, cont ## _52) 317 | 318 | #ifdef lua_call 319 | # undef lua_call 320 | # define lua_call(L, na, nr) \ 321 | (lua_callk)(L, na, nr, 0, NULL) 322 | #endif 323 | 324 | #ifdef lua_pcall 325 | # undef lua_pcall 326 | # define lua_pcall(L, na, nr, err) \ 327 | (lua_pcallk)(L, na, nr, err, 0, NULL) 328 | #endif 329 | 330 | #ifdef lua_yield 331 | # undef lua_yield 332 | # define lua_yield(L, nr) \ 333 | (lua_yieldk)(L, nr, 0, NULL) 334 | #endif 335 | 336 | #endif /* Lua 5.2 only */ 337 | 338 | 339 | 340 | /* other Lua versions */ 341 | #if !defined(LUA_VERSION_NUM) || LUA_VERSION_NUM < 501 || LUA_VERSION_NUM > 503 342 | 343 | # error "unsupported Lua version (i.e. not Lua 5.1, 5.2, or 5.3)" 344 | 345 | #endif /* other Lua versions except 5.1, 5.2, and 5.3 */ 346 | 347 | 348 | 349 | /* helper macro for defining continuation functions (for every version 350 | * *except* Lua 5.2) */ 351 | #ifndef LUA_KFUNCTION 352 | #define LUA_KFUNCTION(_name) \ 353 | static int (_name)(lua_State *L, int status, lua_KContext ctx) 354 | #endif 355 | 356 | 357 | #if defined(COMPAT53_INCLUDE_SOURCE) 358 | # include "compat-5.3.c" 359 | #endif 360 | 361 | 362 | #endif /* COMPAT53_H_ */ 363 | 364 | -------------------------------------------------------------------------------- /subprocess/posix/core.c: -------------------------------------------------------------------------------- 1 | 2 | #include 3 | #include 4 | #include 5 | 6 | #include 7 | #include 8 | #include 9 | 10 | #include "compat-5.3.h" 11 | #include "close_fds.h" 12 | #include "../inheritable.h" 13 | 14 | /* Returns 1 if there is a problem with fd_sequence, 0 otherwise. */ 15 | static int fd_sequence_is_ok(lua_State* L, int idx) { 16 | lua_Integer len; 17 | int i; 18 | int prev_fd = -1; 19 | 20 | len = luaL_len(L, idx); 21 | 22 | for (i = 0; i < len; i++) { 23 | lua_Integer value; 24 | int valtype = lua_geti(L, idx, i + 1); 25 | if (valtype != LUA_TNUMBER) { 26 | return 0; 27 | } 28 | value = lua_tointeger(L, -1); 29 | lua_pop(L, 1); 30 | if (value < 0 || value < prev_fd || value > INT_MAX) { 31 | return 0; 32 | } 33 | prev_fd = value; 34 | } 35 | return 1; 36 | } 37 | 38 | void free_c_string_array(char** arr) { 39 | char** at = arr; 40 | while (*at) { 41 | free(*at); 42 | at++; 43 | } 44 | free(arr); 45 | } 46 | 47 | char** array_of_strings_to_c(lua_State* L, int idx) { 48 | lua_Integer len; 49 | int i = 0; 50 | int j = 0; 51 | char** ret; 52 | char* walk; 53 | 54 | len = luaL_len(L, idx); 55 | ret = calloc(len + 1, sizeof(char*)); 56 | if (!ret) { 57 | return NULL; 58 | } 59 | ret[len] = NULL; 60 | 61 | for (i = 0; i < len; i++) { 62 | int valtype = lua_geti(L, idx, i + 1); 63 | if (valtype != LUA_TSTRING) { 64 | goto failure; 65 | } 66 | ret[i] = strdup(lua_tostring(L, -1)); 67 | //fprintf(stderr, "[%d] %s\n", i, ret[i]); 68 | lua_pop(L, 1); 69 | } 70 | return ret; 71 | 72 | failure: 73 | free_c_string_array(ret); 74 | return NULL; 75 | } 76 | 77 | int make_inheritable(lua_State* L, int FDS_TO_KEEP, lua_Integer errpipe_write) { 78 | lua_Integer len; 79 | int i = 0; 80 | len = luaL_len(L, FDS_TO_KEEP); 81 | for (i = 0; i < len; ++i) { 82 | long fd; 83 | lua_geti(L, FDS_TO_KEEP, i + 1); 84 | fd = lua_tonumber(L, -1); 85 | lua_pop(L, 1); 86 | assert(0 <= fd && fd <= INT_MAX); 87 | if (fd == errpipe_write) { 88 | set_inheritable((int)fd, 0); 89 | continue; 90 | } 91 | if (set_inheritable((int)fd, 1) < 0) { 92 | return -1; 93 | } 94 | } 95 | return 0; 96 | } 97 | 98 | #define POSIX_CALL(call) do { if ((call) == -1) goto error; } while (0) 99 | 100 | int child_exec(char** exec_array, char** argv, char** envp, const char* cwd, 101 | lua_Integer p2cread, lua_Integer p2cwrite, 102 | lua_Integer c2pread, lua_Integer c2pwrite, 103 | lua_Integer errread, lua_Integer errwrite, 104 | lua_Integer errpipe_read, lua_Integer errpipe_write, 105 | int close_fds, int start_new_session, 106 | lua_State* L, int FDS_TO_KEEP) { 107 | 108 | int i, saved_errno, unused, reached_preexec = 0, n = 0; 109 | 110 | const char* err_msg = ""; 111 | /* Buffer large enough to hold a hex integer. We can't malloc. */ 112 | char hex_errno[sizeof(saved_errno)*2+1]; 113 | 114 | //fprintf(stderr, "errpipe_write %d\n", errpipe_write); 115 | 116 | if (make_inheritable(L, FDS_TO_KEEP, errpipe_write) < 0) { 117 | goto error; 118 | } 119 | 120 | /* Close parent's pipe ends. */ 121 | if (p2cwrite != -1) { 122 | POSIX_CALL(close(p2cwrite)); 123 | } 124 | if (c2pread != -1) { 125 | POSIX_CALL(close(c2pread)); 126 | } 127 | if (errread != -1) { 128 | POSIX_CALL(close(errread)); 129 | } 130 | POSIX_CALL(close(errpipe_read)); 131 | 132 | /* When duping fds, if there arises a situation where one of the fds is 133 | either 0, 1 or 2, it is possible that it is overwritten (#12607). */ 134 | if (c2pwrite == 0) { 135 | POSIX_CALL(c2pwrite = dup(c2pwrite)); 136 | } 137 | if (errwrite == 0 || errwrite == 1) { 138 | POSIX_CALL(errwrite = dup(errwrite)); 139 | } 140 | 141 | /* Dup fds for child. 142 | dup2() removes the CLOEXEC flag but we must do it ourselves if dup2() 143 | would be a no-op (issue #10806). */ 144 | if (p2cread == 0) { 145 | if (set_inheritable(p2cread, 1) < 0) { 146 | goto error; 147 | } 148 | } else if (p2cread != -1) { 149 | POSIX_CALL(dup2(p2cread, 0)); /* stdin */ 150 | } 151 | 152 | if (c2pwrite == 1) { 153 | if (set_inheritable(c2pwrite, 1) < 0) { 154 | goto error; 155 | } 156 | } else if (c2pwrite != -1) { 157 | POSIX_CALL(dup2(c2pwrite, 1)); /* stdout */ 158 | } 159 | 160 | if (errwrite == 2) { 161 | if (set_inheritable(errwrite, 1) < 0) { 162 | goto error; 163 | } 164 | } else if (errwrite != -1) { 165 | POSIX_CALL(dup2(errwrite, 2)); /* stderr */ 166 | } 167 | 168 | /* Close pipe fds. Make sure we don't close the same fd more than */ 169 | /* once, or standard fds. */ 170 | if (p2cread > 2) { 171 | POSIX_CALL(close(p2cread)); 172 | } 173 | if (c2pwrite > 2 && c2pwrite != p2cread) { 174 | POSIX_CALL(close(c2pwrite)); 175 | } 176 | if (errwrite != c2pwrite && errwrite != p2cread && errwrite > 2) { 177 | POSIX_CALL(close(errwrite)); 178 | } 179 | 180 | if (cwd) { 181 | POSIX_CALL(chdir(cwd)); 182 | } 183 | 184 | if (start_new_session) { 185 | POSIX_CALL(setsid()); 186 | } 187 | 188 | if (close_fds) { 189 | close_open_fds(3, L, FDS_TO_KEEP); 190 | } 191 | 192 | //FILE* tty2 = fopen("/dev/pts/2", "w"); 193 | //for (n = 0; argv[n]; n++) { 194 | //fprintf(tty2, "[%d] %s\n", n, argv[n]); 195 | //} 196 | reached_preexec = 1; 197 | /* This loop matches the Lib/os.py _execvpe()'s PATH search when */ 198 | /* given the executable_list generated by Lib/subprocess.py. */ 199 | saved_errno = 0; 200 | for (i = 0; exec_array[i] != NULL; ++i) { 201 | const char *executable = exec_array[i]; 202 | 203 | //fprintf(tty2, "trying %s %p %p!\n", executable, argv, envp); 204 | if (envp) { 205 | execve(executable, argv, envp); 206 | } else { 207 | execv(executable, argv); 208 | } 209 | //fprintf(tty2, "failed!\n"); 210 | if (errno != ENOENT && errno != ENOTDIR && saved_errno == 0) { 211 | saved_errno = errno; 212 | } 213 | } 214 | /* Report the first exec error, not the last. */ 215 | if (saved_errno) { 216 | errno = saved_errno; 217 | } 218 | 219 | error: 220 | saved_errno = errno; 221 | /* Report the posix error to our parent process. */ 222 | /* We ignore all write() return values as the total size of our writes is 223 | * less than PIPEBUF and we cannot do anything about an error anyways. */ 224 | if (saved_errno) { 225 | char *cur; 226 | unused = write(errpipe_write, "OSError:", 8); 227 | cur = hex_errno + sizeof(hex_errno); 228 | while (saved_errno != 0 && cur > hex_errno) { 229 | *--cur = "0123456789ABCDEF"[saved_errno % 16]; 230 | saved_errno /= 16; 231 | } 232 | unused = write(errpipe_write, cur, hex_errno + sizeof(hex_errno) - cur); 233 | unused = write(errpipe_write, ":", 1); 234 | if (!reached_preexec) { 235 | /* Indicate to the parent that the error happened before exec(). */ 236 | unused = write(errpipe_write, "noexec", 6); 237 | } 238 | /* We can't call strerror(saved_errno). It is not async signal safe. 239 | * The parent process will look the error message up. */ 240 | } else { 241 | unused = write(errpipe_write, "SubprocessError:0:", 18); 242 | unused = write(errpipe_write, err_msg, strlen(err_msg)); 243 | } 244 | if (unused) return; /* silly? yes! avoids gcc compiler warning. */ 245 | 246 | } 247 | 248 | int fork_exec(lua_State* L) { 249 | enum arg { 250 | ARGS = 1, EXECUTABLE_LIST, CLOSE_FDS, FDS_TO_KEEP, CWD, ENV_LIST, 251 | P2CREAD, P2CWRITE, C2PREAD, C2PWRITE, ERRREAD, ERRWRITE, 252 | ERRPIPE_READ, ERRPIPE_WRITE, START_NEW_SESSION 253 | }; 254 | const char* cwd = NULL; 255 | lua_Integer p2cread = -1, p2cwrite = -1, c2pread = -1, c2pwrite = -1; 256 | lua_Integer errread = -1, errwrite = -1, errpipe_read = -1, errpipe_write = -1; 257 | int close_fds = 0, start_new_session = 0; 258 | 259 | char** argv = NULL; 260 | char** exec_array = NULL; 261 | char** envp = NULL; 262 | pid_t pid = -1; 263 | int save_errno = 0; 264 | 265 | luaL_checktype(L, ARGS, LUA_TTABLE); 266 | luaL_checktype(L, EXECUTABLE_LIST, LUA_TTABLE); 267 | close_fds = lua_toboolean(L, CLOSE_FDS); 268 | luaL_checktype(L, FDS_TO_KEEP, LUA_TTABLE); 269 | cwd = lua_tostring(L, CWD); 270 | if (lua_type(L, ENV_LIST) != LUA_TTABLE && lua_type(L, ENV_LIST) != LUA_TNIL) { 271 | lua_pushstring(L, "env_list expects table or nil"); 272 | lua_error(L); 273 | } 274 | p2cread = luaL_checkinteger(L, P2CREAD); 275 | p2cwrite = luaL_checkinteger(L, P2CWRITE); 276 | c2pread = luaL_checkinteger(L, C2PREAD); 277 | c2pwrite = luaL_checkinteger(L, C2PWRITE); 278 | errread = luaL_checkinteger(L, ERRREAD); 279 | errwrite = luaL_checkinteger(L, ERRWRITE); 280 | errpipe_read = luaL_checkinteger(L, ERRPIPE_READ); 281 | errpipe_write = luaL_checkinteger(L, ERRPIPE_WRITE); 282 | start_new_session = lua_toboolean(L, START_NEW_SESSION); 283 | 284 | /* precondition */ 285 | if (close_fds && errpipe_write < 3) { 286 | lua_pushstring(L, "errpipe_write must be >= 3"); 287 | lua_error(L); 288 | } 289 | if (!fd_sequence_is_ok(L, FDS_TO_KEEP)) { 290 | lua_pushstring(L, "bad value(s) in fds_to_keep"); 291 | lua_error(L); 292 | } 293 | 294 | argv = array_of_strings_to_c(L, ARGS); 295 | if (!argv) { 296 | goto teardown; 297 | } 298 | 299 | exec_array = array_of_strings_to_c(L, EXECUTABLE_LIST); 300 | if (!exec_array) { 301 | goto teardown; 302 | } 303 | 304 | if (lua_type(L, ENV_LIST) == LUA_TTABLE) { 305 | //fprintf(stderr, "Making envp"); 306 | envp = array_of_strings_to_c(L, ENV_LIST); 307 | if (!envp) { 308 | goto teardown; 309 | } 310 | } 311 | 312 | pid = fork(); 313 | if (pid == 0) { 314 | /* Child process */ 315 | /* 316 | * Code from here to _exit() must only use async-signal-safe functions, 317 | * listed at `man 7 signal` or 318 | * http://www.opengroup.org/onlinepubs/009695399/functions/xsh_chap02_04.html. 319 | */ 320 | child_exec(exec_array, argv, envp, cwd, 321 | p2cread, p2cwrite, c2pread, c2pwrite, 322 | errread, errwrite, errpipe_read, errpipe_write, 323 | close_fds, start_new_session, L, FDS_TO_KEEP); 324 | _exit(255); 325 | return 0; /* Dead code to avoid a potential compiler warning. */ 326 | } 327 | 328 | if (pid == -1) { 329 | /* Capture the errno exception before errno can be clobbered. */ 330 | save_errno = errno; 331 | } 332 | 333 | teardown: 334 | 335 | /* Parent process */ 336 | if (envp) { 337 | free_c_string_array(envp); 338 | } 339 | if (argv) { 340 | free_c_string_array(argv); 341 | } 342 | if (exec_array) { 343 | free_c_string_array(exec_array); 344 | } 345 | 346 | if (pid <= 0) { 347 | char message[255]; 348 | snprintf(message, sizeof(message)-1, "Failed forking: (%d) %s", save_errno, strerror(save_errno)); 349 | lua_pushstring(L, message); 350 | lua_error(L); 351 | } 352 | lua_pushnumber(L, pid); 353 | return 1; 354 | } 355 | 356 | 357 | static luaL_Reg functions[] = { 358 | { "fork_exec", fork_exec }, 359 | NULL 360 | }; 361 | 362 | int luaopen_subprocess_posix_core(lua_State* L) { 363 | luaL_newlib(L, functions); 364 | return 1; 365 | } -------------------------------------------------------------------------------- /subprocess/types.lua: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | return {PIPE = -(1), STDOUT = -(2), DEVNULL = -(3)} 65 | 66 | 67 | -------------------------------------------------------------------------------- /subprocess/types.tl: -------------------------------------------------------------------------------- 1 | 2 | interface CalledProcessError 3 | type: string 4 | returncode: integer 5 | cmd: string|{string} 6 | output: string? 7 | end 8 | 9 | interface TimeoutExpired 10 | type: string 11 | timeout: number? 12 | cmd: string|{string} 13 | output: string? 14 | end 15 | 16 | interface Popen 17 | args: string|{string} 18 | stdin: file? 19 | stdout: file? 20 | stderr: file? 21 | stdin_buf: string 22 | stdout_buf: string 23 | stderr_buf: string 24 | input: string? 25 | input_offset: integer 26 | communication_started: boolean 27 | pid: integer? 28 | returncode: integer? 29 | closed_child_pipe_fds: boolean 30 | child_created: boolean 31 | devnull: integer? 32 | fileobj2output: {any:{string}} 33 | 34 | exit: () => () 35 | poll: (integer?) => (integer) 36 | remaining_time: (number) -> (number) 37 | check_timeout: (number?, number?) => (nil)|(TimeoutExpired) 38 | get_devnull: () -> (integer) 39 | wait: (number?, number?) => (integer) 40 | kill: () => () 41 | terminate: () => () 42 | end 43 | 44 | interface PopenArgs 45 | args: string|{string} 46 | bufsize: integer? 47 | executable: string? 48 | stdin: file? 49 | stdout: file? 50 | stderr: file? 51 | close_fds: boolean? 52 | shell: boolean? 53 | cwd: string? 54 | env: {string:string}? 55 | startupinfo: any -- XXX 56 | creationflags: integer? 57 | restore_signals: boolean? 58 | start_new_session: boolean? 59 | pass_fds: {any}? 60 | timeout: number? 61 | -- universal_newlines: boolean XXX not implemented 62 | end 63 | 64 | return { 65 | PIPE = -1, 66 | STDOUT = -2, 67 | DEVNULL = -3, 68 | } 69 | 70 | -------------------------------------------------------------------------------- /subprocess/windows.lua: -------------------------------------------------------------------------------- 1 | 2 | 3 | local subprocess_windows = {} 4 | 5 | require("subprocess.types") 6 | 7 | subprocess_windows.MAXFD = 256 8 | subprocess_windows.PLATFORM_DEFAULT_CLOSE_FDS = false 9 | 10 | local function extend (t1, t2) 11 | for _, e in ipairs(t2) do 12 | table.insert(t1,e) 13 | end 14 | end 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | local function list2cmdline (seq) 41 | 42 | 43 | 44 | 45 | 46 | local result = {} 47 | for _, arg in ipairs(seq) do 48 | local bs_buf = {} 49 | 50 | 51 | if 0 < #(result) then 52 | table.insert(result," ") 53 | end 54 | 55 | local needquote = arg:match(" ") or arg:match("\t") or arg == "" 56 | if needquote then 57 | table.insert(result,"\"") 58 | end 59 | 60 | for c in arg:gmatch(".") do 61 | if c == "\\" then 62 | 63 | table.insert(bs_buf,c) 64 | elseif c == "\"" then 65 | 66 | table.insert(result,("\\"):rep(#(bs_buf) * 2)) 67 | bs_buf = {} 68 | table.insert(result,"\\\"") 69 | else 70 | 71 | if bs_buf then 72 | extend(result,bs_buf) 73 | bs_buf = {} 74 | end 75 | table.insert(result,c) 76 | end 77 | end 78 | 79 | 80 | 81 | if 0 < #(bs_buf) then 82 | extend(result,bs_buf) 83 | end 84 | 85 | if needquote then 86 | for _, bs in ipairs(bs_buf) do 87 | table.insert(result,bs) 88 | end 89 | table.insert(result,"\"") 90 | end 91 | end 92 | return table.concat(result) 93 | end 94 | 95 | subprocess_windows.check_close_fds = function (close_fds, pass_fds, stdin, stdout, stderr) 96 | local any_stdio_set = stdin or stdout or stderr 97 | if close_fds == nil then 98 | return not (any_stdio_set) 99 | else 100 | if close_fds and any_stdio_set then 101 | error("close_fds is not supported on Windows platforms if you redirect stdin/stdout/stderr") 102 | end 103 | end 104 | return close_fds 105 | end 106 | 107 | subprocess_windows.check_creationflags = function (creationflags) 108 | return creationflags 109 | end 110 | 111 | subprocess_windows.wrap_handles = function (p2cwrite, c2pread, errread) 112 | if not (p2cwrite == -(1)) then 113 | p2cwrite = msvcrt.open_osfhandle(p2cwrite.Detach(),0) 114 | end 115 | if not (c2pread == -(1)) then 116 | c2pread = msvcrt.open_osfhandle(c2pread.Detach(),0) 117 | end 118 | if not (errread == -(1)) then 119 | errread = msvcrt.open_osfhandle(errread.Detach(),0) 120 | end 121 | return p2cwrite, c2pread, errread 122 | end 123 | 124 | return subprocess_windows 125 | 126 | 127 | -------------------------------------------------------------------------------- /subprocess/windows.tl: -------------------------------------------------------------------------------- 1 | 2 | -- Windows implementation of the subprocess module. 3 | local subprocess_windows = {} 4 | 5 | require("subprocess.types") 6 | 7 | subprocess_windows.MAXFD = 256 8 | subprocess_windows.PLATFORM_DEFAULT_CLOSE_FDS = false 9 | 10 | local function extend(t1, t2) 11 | for _, e in ipairs(t2) do 12 | table.insert(t1, e) 13 | end 14 | end 15 | 16 | --[[ 17 | Translate a sequence of arguments into a command line 18 | string, using the same rules as the MS C runtime: 19 | 20 | 1) Arguments are delimited by white space, which is either a 21 | space or a tab. 22 | 23 | 2) A string surrounded by double quotation marks is 24 | interpreted as a single argument, regardless of white space 25 | contained within. A quoted string can be embedded in an 26 | argument. 27 | 28 | 3) A double quotation mark preceded by a backslash is 29 | interpreted as a literal double quotation mark. 30 | 31 | 4) Backslashes are interpreted literally, unless they 32 | immediately precede a double quotation mark. 33 | 34 | 5) If backslashes immediately precede a double quotation mark, 35 | every pair of backslashes is interpreted as a literal 36 | backslash. If the number of backslashes is odd, the last 37 | backslash escapes the next double quotation mark as 38 | described in rule 3. 39 | ]] 40 | local function list2cmdline(seq) 41 | 42 | -- See 43 | -- http://msdn.microsoft.com/en-us/library/17w5ykft.aspx 44 | -- or search http://msdn.microsoft.com for 45 | -- "Parsing C++ Command-Line Arguments" 46 | local result: {string} = {} 47 | for _, arg in ipairs(seq) do 48 | local bs_buf: {string} = {} 49 | 50 | -- Add a space to separate this argument from the others 51 | if #result > 0 then 52 | table.insert(result, " ") 53 | end 54 | 55 | local needquote = arg:match(" ") or arg:match("\t") or arg == "" 56 | if needquote then 57 | table.insert(result, '"') 58 | end 59 | 60 | for c in arg:gmatch(".") do 61 | if c == "\\" then 62 | -- Don't know if we need to double yet. 63 | table.insert(bs_buf, c) 64 | elseif c == '"' then 65 | -- Double backslashes. 66 | table.insert(result, ("\\"):rep( #bs_buf * 2) ) 67 | bs_buf = {} 68 | table.insert(result, '\\"') 69 | else 70 | -- Normal char 71 | if bs_buf then 72 | extend(result, bs_buf) 73 | bs_buf = {} 74 | end 75 | table.insert(result, c) 76 | end 77 | end 78 | 79 | -- Add remaining backslashes, if any. 80 | 81 | if #bs_buf > 0 then 82 | extend(result, bs_buf) 83 | end 84 | 85 | if needquote then 86 | for _, bs in ipairs(bs_buf) do 87 | table.insert(result, bs) 88 | end 89 | table.insert(result, '"') 90 | end 91 | end 92 | return table.concat(result) 93 | end 94 | 95 | function subprocess_windows.check_close_fds(close_fds: boolean, pass_fds:{any}?, stdin: integer?, stdout: integer?, stderr: integer?) 96 | local any_stdio_set = stdin or stdout or stderr 97 | if close_fds == nil then 98 | return not any_stdio_set 99 | else 100 | if close_fds and any_stdio_set then 101 | error("close_fds is not supported on Windows platforms if you redirect stdin/stdout/stderr") 102 | end 103 | end 104 | return close_fds 105 | end 106 | 107 | function subprocess_windows.check_creationflags(creationflags: integer) 108 | return creationflags 109 | end 110 | 111 | function subprocess_windows.wrap_handles(p2cwrite, c2pread, errread) 112 | if p2cwrite ~= -1 then 113 | p2cwrite = msvcrt.open_osfhandle(p2cwrite.Detach(), 0) 114 | end 115 | if c2pread ~= -1 then 116 | c2pread = msvcrt.open_osfhandle(c2pread.Detach(), 0) 117 | end 118 | if errread ~= -1 then 119 | errread = msvcrt.open_osfhandle(errread.Detach(), 0) 120 | end 121 | return p2cwrite, c2pread, errread 122 | end 123 | 124 | return subprocess_windows 125 | -------------------------------------------------------------------------------- /test/subprocess_spec.lua: -------------------------------------------------------------------------------- 1 | 2 | local subprocess = require("subprocess") 3 | 4 | local executable = "lua" 5 | 6 | if not describe then 7 | function describe(d, f) local ok, err = pcall(f); if not ok then print("Fail: "..err) end end 8 | function it(d, f) local ok, err = pcall(f); if not ok then print("Fail: "..err) end end 9 | end 10 | 11 | describe("subprocess module", function() 12 | describe("process test case", function() 13 | it("gives back file descriptors", function () 14 | local p = subprocess.Popen({executable, '-e', 'os.exit(0)'}, {stdin = subprocess.PIPE, stdout = subprocess.PIPE, stderr = subprocess.PIPE}) 15 | assert(io.type(p.stdin) == "file") 16 | assert(io.type(p.stdout) == "file") 17 | assert(io.type(p.stderr) == "file") 18 | p.stdin:close() 19 | p.stdout:close() 20 | p.stderr:close() 21 | p:wait() 22 | end) 23 | it("works with unbuffered IO", function () 24 | local p = subprocess.Popen({executable, '-e', 'os.exit(0)'}, {stdin = subprocess.PIPE, stdout = subprocess.PIPE, stderr = subprocess.PIPE, bufsize=0}) 25 | assert(p.stdin_buf == "no") 26 | assert(p.stdout_buf == "no") 27 | assert(p.stderr_buf == "no") 28 | p.stdin:close() 29 | p.stdout:close() 30 | p.stderr:close() 31 | p:wait() 32 | end) 33 | it("calls given an array", function () 34 | local rc = subprocess.call({executable, '-e', 'os.exit(42)'}) 35 | assert(rc == 42) 36 | end) 37 | it("kills a process with a timeout", function () 38 | local rc, err = subprocess.call({executable, '-e', 'while true do end'}, {timeout = 0.1}) 39 | assert((not rc) and err.type == "TimeoutExpired") 40 | end) 41 | it("gets output from stdout", function () 42 | local p = subprocess.Popen({executable, '-e', 'print("one");print("two");print("three")'}, {stdout = subprocess.PIPE}) 43 | assert(p.stdout) 44 | local text = "" 45 | local count = 0 46 | for line in p.stdout:lines() do 47 | text = text .. line 48 | count = count + 1 49 | end 50 | assert(count == 3) 51 | assert(text == "onetwothree") 52 | p:wait() 53 | end) 54 | end) 55 | end) 56 | 57 | print("Done!") 58 | 59 | -------------------------------------------------------------------------------- /tlc.lua: -------------------------------------------------------------------------------- 1 | 2 | local subprocess = {} 3 | 4 | 5 | local mswindows = (package.cpath:lower()):match("%.dll") 6 | 7 | local types = require("subprocess.types") 8 | local exceptions = require("subprocess.exceptions") 9 | 10 | local plat 11 | if mswindows then 12 | plat = require("subprocess.windows") 13 | else 14 | plat = require("subprocess.posix") 15 | end 16 | 17 | local MAXFD = plat.MAXFD 18 | 19 | local PIPE = types.PIPE 20 | local STDOUT = types.PIPE 21 | local DEVNULL = types.PIPE 22 | 23 | subprocess.PIPE = types.PIPE 24 | subprocess.STDOUT = types.PIPE 25 | subprocess.DEVNULL = types.PIPE 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | local active = {} 34 | 35 | local function cleanup () 36 | local mark = {} 37 | for i, inst in ipairs(active) do 38 | local res = inst:poll(math.maxinteger) 39 | if res then 40 | table.insert(mark,i) 41 | end 42 | end 43 | for i = #(mark), 1 do 44 | table.remove(active,mark[i]) 45 | end 46 | end 47 | 48 | local Popen_metatable = {__gc = function (self) 49 | 50 | if not (self.child_created) then 51 | 52 | return 53 | end 54 | 55 | self:poll(math.maxinteger) 56 | if not (self.returncode) then 57 | 58 | table.insert(active,self) 59 | end 60 | end} 61 | 62 | 63 | local function exit (self) 64 | if self.stdin then self.stdin:close() end 65 | if self.stdout then self.stdout:close() end 66 | if self.stderr then self.stderr:close() end 67 | 68 | self:wait() 69 | end 70 | 71 | communicate = function (self, input, timeout) 72 | if self.communication_started and input then 73 | error("Cannot send input after starting communication") 74 | end 75 | 76 | local stdout, stderr 77 | 78 | 79 | 80 | 81 | 82 | local nils = (self.stdin and 1 or 0) + (self.stdout and 1 or 0) + (self.stderr and 1 or 0) 83 | 84 | 85 | if not (timeout) and not (self.communication_started) and 2 <= nils then 86 | stdout = nil 87 | stderr = nil 88 | local self_stdin, self_stdout, self_stderr = self.stdin, self.stdout, self.stderr 89 | if self_stdin then 90 | if input then 91 | local ok, err = pcall(self_stdin.write,self_stdin,input) 92 | if not (ok) then return nil, nil, err end 93 | end 94 | self_stdin:close() 95 | elseif self_stdout then 96 | stdout = self_stdout:read("*a") 97 | self_stdout:close() 98 | elseif self_stderr then 99 | stderr = self_stderr:read("*a") 100 | self_stderr:close() 101 | end 102 | self:wait() 103 | else 104 | local endtime = timeout and plat.time() + timeout or nil 105 | local ok 106 | ok, stdout, stderr = pcall(plat.communicate,input,endtime,timeout) 107 | self.communication_started = true 108 | self:wait(endtime and self.remaining_time(endtime) or nil,endtime) 109 | end 110 | return stdout, stderr 111 | end 112 | 113 | local function remaining_time (endtime) 114 | return (endtime - plat.time()) 115 | end 116 | 117 | local function check_timeout (self, endtime, orig_timeout) 118 | if not (endtime) then 119 | return nil 120 | end 121 | if endtime < plat.time() then 122 | return exceptions.TimeoutExpired(self.args,orig_timeout) 123 | end 124 | end 125 | 126 | local function open_and_set_buf (fobj, fd, mode, bufsize) 127 | local bufmode = "full" 128 | if not (fd == -(1)) then 129 | local err 130 | fobj, err = plat.open(fd,mode) 131 | if bufsize then 132 | bufmode = 0 < bufsize and "full" or "no" 133 | fobj:setvbuf(bufmode,bufsize) 134 | end 135 | end 136 | return fobj, bufmode 137 | end 138 | 139 | subprocess.Popen = function (args, kwargs, with_fn) 140 | if not (kwargs) then kwargs = {} end 141 | local pass_fds = kwargs.pass_fds or {} 142 | local close_fds = plat.check_close_fds(kwargs.close_fds,pass_fds,kwargs.stdin,kwargs.stdout,kwargs.stderr) 143 | local creationflags = plat.check_creationflags(kwargs.creationflags or 0) 144 | local shell = (not (kwargs.shell == nil)) or false 145 | local start_new_session = kwargs.start_new_session and true or false 146 | 147 | local self = {args = args, input = nil, input_offset = 0, communication_started = false, closed_child_pipe_fds = false, child_created = false, fileobj2output = {}, stdin_buf = "full", stdout_buf = "full", stderr_buf = "full", exit = exit, get_devnull = plat.get_devnull, communicate = communicate, poll = plat.poll, remaining_time = remaining_time, check_timeout = check_timeout, wait = plat.wait} 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | setmetatable(self,Popen_metatable) 169 | 170 | cleanup() 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | local p2cread, p2cwrite, c2pread, c2pwrite, errread, errwrite = plat.get_handles(self,kwargs.stdin,kwargs.stdout,kwargs.stderr) 191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | 199 | p2cwrite, c2pread, errread = plat.wrap_handles(p2cwrite,c2pread,errread) 200 | 201 | self.stdin, self.stdin_buf = open_and_set_buf(self.stdin,p2cwrite,"wb",kwargs.bufsize) 202 | self.stdout, self.stdout_buf = open_and_set_buf(self.stdout,c2pread,"rb",kwargs.bufsize) 203 | self.stderr, self.stderr_buf = open_and_set_buf(self.stderr,errread,"rb",kwargs.bufsize) 204 | 205 | local ok, err, errcode = plat.execute_child(self,args,kwargs.executable,close_fds,pass_fds,kwargs.cwd,kwargs.env,kwargs.startupinfo,creationflags,shell,p2cread,p2cwrite,c2pread,c2pwrite,errread,errwrite,start_new_session) 206 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | 214 | if not (ok) then 215 | if self.stdin then self.stdin:close() end 216 | if self.stdout then self.stdout:close() end 217 | if self.stderr then self.stderr:close() end 218 | if not (self.closed_child_pipe_fds) then 219 | if kwargs.stdin == PIPE then plat.close(p2cread) end 220 | if kwargs.stdout == PIPE then plat.close(c2pwrite) end 221 | if kwargs.stderr == PIPE then plat.close(errwrite) end 222 | end 223 | return nil, err, errcode 224 | end 225 | 226 | if with_fn then 227 | local ret = table.pack(with_fn(self)) 228 | self:exit() 229 | return table.unpack(ret,1,ret.n) 230 | end 231 | 232 | return self 233 | end 234 | 235 | subprocess.call = function (args, kwargs) 236 | return subprocess.Popen(args,kwargs,function (p) 237 | local exit, err = p:wait(kwargs and kwargs.timeout) 238 | if err then 239 | p:kill() 240 | p:wait() 241 | return nil, err 242 | end 243 | return exit 244 | end) 245 | end 246 | 247 | subprocess.check_call = function (args, kwargs) 248 | local exit, err = subprocess.call(args,kwargs) 249 | if not (exit == 0) then 250 | error("Error calling process: " .. tostring(exit) .. " " .. tostring(err)) 251 | end 252 | return 0 253 | end 254 | 255 | 256 | 257 | 258 | 259 | 260 | 261 | 262 | 263 | 264 | 265 | 266 | 267 | 268 | 269 | 270 | 271 | 272 | 273 | 274 | 275 | 276 | 277 | 278 | 279 | 280 | 281 | 282 | 283 | 284 | 285 | 286 | 287 | 288 | 289 | 290 | 291 | 292 | 293 | 294 | 295 | 296 | 297 | 298 | 299 | 300 | 301 | 302 | 303 | 304 | 305 | 306 | 307 | 308 | 309 | return subprocess 310 | 311 | 312 | --------------------------------------------------------------------------------