os.execute without inheriting parent's fds
I have a problem analogous to the one described here: Prevent fork() from copying sockets
Basically, inside my Lua script I'm spawning another script which:
- doesn't require communicating with my script either way
- continues running after my script had finished
- is a 3rd party program, code of which I have no control over
The problem is that my Lua script opens a TCP socket to listen on a specific port and after it's quit and despite an explicit server:close()
the child (or more specifically its children) holds onto the socket and keeps the port open (in LISTEN state) preventing my script from running again.
Here's example code that demonstrates the problem:
require('socket')
print('listening')
s = socket.bind("*", 9999)
s:settimeout(1)
while true do
print('accepting connection')
local c = s:accept()
if c then
c:settimeout(1)
local rec = c:receive()
print('re开发者_JAVA百科ceived ' .. rec)
c:close()
if rec == "quit" then break end
if rec == "exec" then
print('running ping in background')
os.execute('sleep 10s &')
break
end
end
end
print('closing server')
s:close()
If I run the above script and echo quit | nc localhost 9999
everything works well - program quits and port is closed.
However if I do echo exec | nc localhost 9999
the program quits but the port is blocked by the spawned sleep
(as confirmed by netstat -lpn
) until it exits.
How do I tackle this in the simplest possible manner, preferably without adding any extra dependencies.
I found a much simpler solution which utilizes the fact that os.execute(cmd)
runs cmd
in a shell
, which, as it turns out, is capable of closing file descriptors as seen here:
http://linux.die.net/man/1/ash (section Redirections )
http://www.gnu.org/software/bash/manual/bashref.html#Redirections
For example (tested in ash
):
exec 3<&- # closes fd3
exec 3<&- 4<&- # closes fd3 and fd4
eval exec `seq 1 255 | sed -e 's/.*/&<\&-/'` # closes all file descriptors
So in my luasocket
based example it's enough to replace:
os.execute('sleep 10s &')
with:
os.execute("eval exec `seq 1 255 | sed -e 's/.*/&<\\&-/'`; sleep 10s &")
This closes all file descriptors, including my server socket, before executing the actual command (here sleep 10s
) so that it doesn't hog the port after my script exits. It also has the bonus of taking care of stdout
and stderr
redirection.
This is much more compact and uncomplicated than working around Lua
's limitations and doesn't require any extra dependencies. Thanks go to #uclibc where I got some brilliant help with final shell syntax from the embedded linux crew.
I'm not sure if you're going to be able to do it like that if you want to keep the s:close
only at the end of the whole program. You might succeed by moving it up into before os.execute
, since you're break
ing anyway (but you're probably not doing this in your real program). Edit for clarity: The actual problem is that the only place you're spawning a subprocess in this case, is by using os.execute()
, and you have no control over the child environment of sleep, in which everything is inherited from the main program, including socket and file descriptors.
So, the canonical way to do this on POSIX is to use fork(); close(s); exec();
, instead of system()
(aka, os.execute
) as system()
/os.execute
will hang on to the current process state during execution, and you won't be able to close it while blocked in the sub-process.
So, a suggestion would be to grab luaposix, and use its posix.fork()
and posix.exec()
functionality, and calling s:close()
in the fork
ed child process. Shouldn't be so bad, since you're already using an external package by relying upon luasocket
.
EDIT: Here's heavily commented code to do it with luaposix:
require('socket')
require('posix')
print('listening')
s = socket.bind("*", 9999)
s:settimeout(1)
while true do
print('accepting connection')
local c = s:accept()
if c then
c:settimeout(1)
local rec = c:receive()
print('received ' .. rec)
c:close()
if rec == "quit" then break end
if rec == "exec" then
local pid = posix.fork()
if pid == 0 then
print('child: running ping in background')
s:close()
-- exec() replaces current process, doesn't return.
-- execp has PATH resolution
rc = posix.execp('sleep','60s');
-- exec has no PATH resolution, probably "more secure"
--rc = posix.exec('/usr/bin/sleep','60s');
print('exec failed with rc: ' .. rc);
else
-- if you want to catch the SIGCHLD:
--print('parent: waiting for ping to return')
--posix.wait( pid )
print('parent: exiting loop')
end
break;
end
end
end
print('closing server')
s:close()
This closes the socket in the child process before calling exec
, and the netstat -nlp
output shows the system is correctly no longer listening on port 9999 when the parent exits.
P.S. The line print('exec failed with rc: ' .. rc);
complained about a type problem once when exec failed. I don't actually know lua, so you'll have to fix that. :) Furthermore, fork()
can fail, returning -1. Probably should check for that in your main code, too, for completeness.
The POSIX approach is to set your file descriptors with the FD_CLOEXEC flag, using fcntl (2). When set, all sub-processes will not inherit the file descriptors marked with that flag.
Stock Lua doesn't have the fcntl feature but it's possible to add it with the lua posix module as introduced in previous answers. To take your example, you would have to change the starts as such:
require('socket')
require('posix')
s = socket.bind("*", 9999)
posix.setfl(s, posix.FD_CLOEXEC)
s:settimeout(1)
Note that I didn't find the FD_CLOEXEC constant in the luaposix source so you might have to add it by hand as well.
精彩评论