os.execute без наследования fds родителей - PullRequest
5 голосов
/ 29 января 2011

У меня проблема, аналогичная описанной здесь: Предотвращение копирования () вилкой () сокетов

По сути, внутри моего скрипта Lua я создаю другой скрипт, который:

  • не требует связи с моим скриптом в любом случае
  • продолжает работать после того, как мой скрипт завершил
  • - сторонняя программа, код которой я не могу контролировать

Проблема в том, что мой Lua-скрипт открывает TCP-сокет для прослушивания определенного порта и после его выхода и несмотря на явный server:close() дочерний элемент (или, более конкретно, его дочерние элементы) удерживает сокет и держит порт открытым (в состоянии LISTEN), предотвращая повторный запуск моего скрипта.

Вот пример кода, который демонстрирует проблему:

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('received ' .. 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()

Если я запускаю вышеуказанный скрипт и echo quit | nc localhost 9999 все работает хорошо - программа закрывается и порт закрывается.

Однако, если я сделаю echo exec | nc localhost 9999, программа закроется, но порт будет заблокирован порожденным sleep (что подтверждается netstat -lpn), пока он не выйдет.

Как мне решить эту проблему самым простым способом, желательно без добавления каких-либо дополнительных зависимостей.

Ответы [ 3 ]

4 голосов
/ 30 января 2011

Я нашел гораздо более простое решение, которое использует тот факт, что os.execute(cmd) запускает cmd в shell, который, как оказывается, способен закрывать файловые дескрипторы, как показано здесь:


Например (проверено в ash):

    exec 3<&-                                      # closes fd3
    exec 3<&- 4<&-                                 # closes fd3 and fd4
    eval exec `seq 1 255 | sed -e 's/.*/&<\&-/'`   # closes all file descriptors 

Так что в моем примере luasocket достаточно заменить:

    os.execute('sleep 10s &')

с:

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

Это закрывает все файловые дескрипторы, включая сокет моего сервера, перед выполнением фактической команды (здесь sleep 10s), чтобы не перегружать порт после выхода из моего скрипта. Он также имеет бонус за заботу о перенаправлении stdout и stderr.

Это гораздо более компактно и несложно, чем обход ограничений Lua и не требует каких-либо дополнительных зависимостей. Благодарим вас за # uclibc , где я получил блестящую помощь по окончательному синтаксису оболочки от команды встроенного Linux.

2 голосов
/ 29 января 2011

Я не уверен, что вы сможете сделать это так, если вы хотите сохранить s:close только в конце всей программы. Вы можете преуспеть, переместив его до os.execute, так как вы все равно break (но вы, вероятно, не делаете этого в своей реальной программе). Редактировать для ясности : Реальная проблема в том, что единственное место, где вы порождаете подпроцесс в этом случае, это использование os.execute(), и вы не можете контролировать дочернюю среду сна, в которой все наследуется от основной программы, включая дескрипторы сокетов и файлов.

Итак, канонический способ сделать это в POSIX - использовать fork(); close(s); exec(); вместо system() (он же os.execute), поскольку system() / os.execute будет зависать в текущем состоянии процесса во время выполнения. и вы не сможете закрыть его, пока он заблокирован в подпроцессе.

Таким образом, было бы предложено захватить luaposix , использовать его функциональные возможности posix.fork() и posix.exec() и вызвать s:close() в дочернем процессе fork ed. Не должно быть так плохо, так как вы уже используете внешний пакет, полагаясь на 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. Строка print('exec failed with rc: ' .. rc); однажды пожаловалась на проблему с типом при сбое exec. Я на самом деле не знаю Луа, так что тебе придется это исправить. :) Кроме того, fork() может завершиться ошибкой, возвращая -1. Вероятно, следует проверить это в вашем основном коде на предмет полноты.

1 голос
/ 08 октября 2011

Подход POSIX заключается в установке дескрипторов файлов с флагом FD_CLOEXEC, используя fcntl (2) .Если установлено, все подпроцессы не будут наследовать файловые дескрипторы, помеченные этим флагом.

В Stock Lua нет функции fcntl, но ее можно добавить с помощью модуля lua posix как представлено в предыдущих ответах.В качестве примера вы должны изменить старты следующим образом:

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

Обратите внимание, что я не нашел константу FD_CLOEXEC в источнике luaposix, поэтому вам, возможно, придется добавить ее также вручную.

...