os.execute执行命令时不继承父进程的文件描述符。

我有一个类似于这里描述的问题:

防止fork()复制套接字

基本上,在我的Lua脚本中,我正在生成另一个脚本,它:

  • 无需沟通双向与我的脚本
  • 在我的脚本完成后继续运行
  • 是第三方程序,其代码我无法控制

问题在于,我的Lua脚本打开了一个TCP套接字以侦听特定端口,并且即使在明确的 server:close()之后,子进程(或更具体地说,其子代)仍持有套接字,并保持端口处于打开状态 (处于LISTEN状态),从而防止我的脚本再次运行。

以下是演示问题的示例代码:

需要('socket')

打印( '听')
s = socket.bind(“*”,9999)
s:settimeout(1)

当真时
    打印( '接受连接'local c = s:accept()
    如果c then
            c:settimeout(1local rec = c:receive()
            打印( '收到'  ..回收)
            c:close()
            如果REC ==“quit”则break end
            如果REC ==“exec”则
                    打印( '在后台运行ping'os执行('sleep 10s&'break
            end
    end
end
打印( '关闭服务器')
s:close()

如果我运行上面的脚本并且 echo quit | nc localhost 9999一切都很好 - 程序退出并关闭了端口。

但是,如果我执行 echo exec | nc localhost 9999 该程序退出,但端口被生成的 sleep 阻塞(通过 netstat -lpn确认)直到其退出。

我如何在尽可能简单的情况下解决这个问题,最好不要添加任何额外的依赖项。

原文链接 https://stackoverflow.com/questions/4835608

点赞
stackoverflow用户401390
stackoverflow用户401390

我不确定如果你想保持s:close仅在整个程序的末尾,你是否能够这样做。如果你将它移到os.execute之前,可能会成功,因为你无论如何都在使用break(但你可能在你的真实程序中没有这样做)。编辑以获得清晰度:实际问题在于,在这种情况下,你生成子进程的唯一位置是通过使用os.execute(),并且你无法控制sleep的子环境,其中一切都从主程序继承,包括套接字和文件描述符。

因此,在POSIX上完成此操作的规范方式是使用fork(); close(s); exec(),而不是使用system()(又名 os.execute),因为system()/ os.execute会在执行期间保持当前进程状态,并且您不能在子进程中改变它时关闭它。

因此,一个建议是获取luaposix,使用其posix.fork()posix.exec()功能,并在fork的子进程中调用s:close()。因为你已经使用了一个外部包,因此不应该那么糟糕,它依赖于luasocket


编辑:这是使用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()

这将在调用exec之前关闭子进程中的套接字,当父进程退出时,netstat -nlp输出显示系统正确地不再侦听端口9999。

P.S. 当exec失败时,该行print('exec failed with rc:'..rc);会抱怨类型问题。我实际上不知道lua,所以你必须解决这个问题。 :)此外,fork()可能会失败,返回-1。出于完整性,您在主代码中也应该检查这一点。

2011-01-29 08:27:49
stackoverflow用户594745
stackoverflow用户594745

我找到了一个更简单的解决方案,它利用了 os.execute(cmd) 运行 cmd 在一个 shell 中的特性,正如在这里所看到的那样,shell 能够关闭文件描述符:


例如(在 ash 中测试):

    exec 3<&-                                      # 关闭 fd3
    exec 3<&- 4<&-                                 # 关闭 fd3 和 fd4
    eval exec `seq 1 255 | sed -e 's/.*/&<\&-/'`   # 关闭所有文件描述符

因此,在我基于 luasocket 的示例中,只需将:

    os.execute('sleep 10s &')

替换为:

    os.execute("eval exec `seq 1 255 | sed -e 's/.*/&<\\&-/'`; sleep 10s &")

这将在执行实际命令之前关闭所有文件描述符,包括我的服务器套接字,以避免在我的脚本退出后占用该端口。它还有额外的好处,完成了 stdoutstderr 重定向。

这比绕过 Lua 的限制要更紧凑和简单,不需要任何额外的依赖项。感谢嵌入式 Linux 团队在 #uclibc 中提供的最终 shell 语法方面的卓越帮助。

2011-01-29 22:49:53
stackoverflow用户800380
stackoverflow用户800380

POSIX的做法是使用 FD_CLOEXEC 标志,使用 fcntl(2) 来设置文件描述符。设置后,所有子进程都不会继承标记有该标志的文件描述符。

原始的Lua没有 fcntl 功能,但可以使用之前介绍的 [lua posix] (https://github.com/rrthomas/luaposix) 模块添加它。以你的示例为例,你必须将开始更改为以下内容:

require('socket')
require('posix')
s = socket.bind("*", 9999)
posix.setfl(s, posix.FD_CLOEXEC)
s:settimeout(1)

请注意,我在 luaposix 源中找不到 FD_CLOEXEC 常量,所以您可能也需要手动添加它。

2011-10-08 12:39:33