2011-01-29 26 views
5

Tôi có một vấn đề tương tự như một trong những mô tả ở đây: Prevent fork() from copying socketsos.execute mà không kế thừa FDS mẹ của

Về cơ bản, bên trong kịch bản Lua tôi đang đẻ trứng khác kịch bản đó:

  • không yêu cầu liên lạc với tập lệnh của tôi theo cách tương tự
  • tiếp tục chạy sau khi tập lệnh của tôi hoàn thành
  • là chương trình của bên thứ ba, mã mà tôi không kiểm soát được

Vấn đề là kịch bản Lua của tôi mở một cổng TCP để nghe trên một cổng cụ thể và sau khi thoát và mặc dù rõ ràng server:close() đứa trẻ (hoặc cụ thể hơn là con của nó) giữ trên ổ cắm và giữ cổng mở (trong trạng thái LISTEN) ngăn không cho tập lệnh của tôi chạy lại.

Đây là mã ví dụ chứng tỏ vấn đề:

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() 

Nếu tôi chạy kịch bản trên và echo quit | nc localhost 9999 tất cả mọi thứ hoạt động tốt - chương trình chia tay và cổng được đóng lại.

Tuy nhiên nếu tôi làm echo exec | nc localhost 9999 thì thoát chương trình nhưng cổng bị chặn bởi số sleep được sinh ra (như được xác nhận bởi netstat -lpn) cho đến khi thoát.

Làm cách nào để giải quyết điều này theo cách đơn giản nhất có thể, tốt nhất là không thêm bất kỳ phụ thuộc bổ sung nào.

Trả lời

3

Tôi tìm thấy một giải pháp đơn giản hơn nhiều trong đó sử dụng thực tế là os.execute(cmd) chạy cmd trong một shell, trong đó, khi nó quay ra, có khả năng mô tả tập tin đóng cửa như đã thấy ở đây:


Ví dụ (thử nghiệm trong ash):

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

Vì vậy, trong luasocket dụ dựa của tôi nó là đủ để thay thế:

os.execute('sleep 10s &') 

với:

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

Thao tác này sẽ đóng tất cả các bộ mô tả tập tin, bao gồm cả socket máy chủ của tôi, trước khi thực hiện lệnh thực tế (ở đây sleep 10s) để nó không hog cổng sau khi tập lệnh của tôi thoát. Nó cũng có tiền thưởng của việc chăm sóc của stdoutstderr chuyển hướng.

Điều này nhỏ gọn hơn và không phức tạp hơn so với giới hạn của các giới hạn của Lua và không yêu cầu thêm bất kỳ phụ thuộc nào. Cảm ơn bạn đã đến #uclibc nơi tôi nhận được một số trợ giúp tuyệt vời với cú pháp shell cuối cùng từ nhóm Linux được nhúng.

+0

Làm tốt lắm! Trường hợp có một ý chí, có một cách :-) Vui mừng bạn đã trở lại với một bản cập nhật, quá. –

2

Tôi không chắc liệu bạn có thể thực hiện điều đó nếu bạn chỉ muốn giữ s:close ở cuối toàn bộ chương trình. Bạn có thể thành công bằng cách di chuyển nó lên trước os.execute, vì bạn đang break vẫn nhập (nhưng có thể bạn không làm điều này trong chương trình thực của mình). Chỉnh sửa cho rõ ràng: Vấn đề thực tế là nơi duy nhất bạn sinh ra một quy trình con trong trường hợp này, bằng cách sử dụng os.execute() và bạn không kiểm soát môi trường ngủ của trẻ, trong đó mọi thứ được kế thừa từ chương trình chính , bao gồm cả bộ mô tả ổ cắm và tệp.

Vì vậy, một cách kinh điển để làm điều này trên POSIX là sử dụng fork(); close(s); exec();, thay vì system() (aka, os.execute) như system()/os.execute sẽ treo vào trạng thái quá trình hiện tại trong khi thực hiện, và bạn sẽ không có khả năng đóng nó trong khi bị chặn trong tiến trình con.

Vì vậy, một gợi ý sẽ được lấy luaposix, và sử dụng chức năng posix.fork()posix.exec() của nó, và kêu gọi s:close() trong quá trình con fork ed. Không nên quá tệ vì bạn đã sử dụng gói bên ngoài bằng cách dựa vào luasocket.


EDIT: Mã Dưới đây là rất nhiều nhận xét để làm điều đó với 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() 

này đóng socket trong quá trình đứa trẻ trước khi gọi exec, và netstat -nlp đầu ra cho thấy hệ thống là chính xác không nghe lâu hơn trên cổng 9999 khi cha mẹ thoát.

P.S. Dòng print('exec failed with rc: ' .. rc); đã phàn nàn về vấn đề loại một khi exec không thành công. Tôi không thực sự biết lua, vì vậy bạn sẽ phải sửa lỗi đó. :) Hơn nữa, fork() có thể không thành công, trả về -1. Có lẽ nên kiểm tra điều đó trong mã chính của bạn, quá, để hoàn thành.

+0

OK, đó là tiến bộ tho tôi đã bí mật hy vọng nó sẽ không đi đến 'fork() ing'. 'luaposix' tốt như dep vì nó được tích hợp vào trình thông dịch trên nền tảng đích - OpenWrt. Vấn đề với ví dụ của chúng tôi 'ngủ' hogging cổng đã biến mất nhưng đã được thay thế bằng 2 khác: - nếu bạn lấy đi' ngắt' sau 'fork' và gửi' exec' tới cổng 's: accept () 'không còn lần nữa và nó có nghĩa là không bị chặn. - nếu bạn thay thế 'sleep' bằng' ping', đầu ra của nó sẽ chuyển thành pty giống như đầu ra tập lệnh không mong muốn. Đây có thể vượt quá phạm vi của câu hỏi ban đầu nhưng có liên quan. – koniu

+0

Xin lỗi, myopenid đã đi xuống một chút ở đó. Tôi đã chơi nhiều hơn với mã cho mỗi câu hỏi khác của bạn, và tôi nghĩ rằng điều này vượt ra ngoài kiến ​​thức lua của tôi. Tôi đã thử 'io.output ('/ dev/null')' và 'io.stdout: close()', trước 'execp()', không có may mắn. Dưới đây là một số thông tin liên quan: [stackoverflow] (http://stackoverflow.com/questions/1466064) và [lua manual] (http://www.lua.org/pil/21.1.html). Trên 'wait()', tôi đã kiểm tra nguồn của 'luaposix', và nó không thực hiện' WNOHANG', do đó có thể là một vấn đề đối với việc không chặn. Nếu không ai khác trả lời câu hỏi này, bạn có thể đăng một câu hỏi khác về những vấn đề này. –

+0

Tôi tìm thấy một giải pháp cho vấn đề stdout ở đây: http://lua-users.org/wiki/HiddenFeatures. Điều này đóng tập tin tiêu chuẩn: 'local f = assert (io.open '/ dev/null'); debug.setfenv (io.stdout, debug.getfenv (f)); f: close(); khẳng định (io.stdout: close()) ' – koniu

1

Cách tiếp cận POSIX là đặt các bộ mô tả tệp của bạn bằng cờ FD_CLOEXEC, sử dụng fcntl (2). Khi được đặt, tất cả các quy trình phụ sẽ không kế thừa các bộ mô tả tệp được đánh dấu bằng cờ đó.

Cổ Lua không có tính năng fcntl nhưng có thể thêm nó với mô-đun lua posix như được giới thiệu trong các câu trả lời trước. Để lấy một ví dụ của bạn, bạn sẽ phải thay đổi bắt đầu như vậy:

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

Lưu ý rằng tôi không tìm thấy hằng FD_CLOEXEC trong nguồn luaposix vì vậy bạn có thể phải bổ sung nó bằng tay là tốt.

Các vấn đề liên quan