Tôi đã đấu tranh để làm cho IRC-Bouncer / Thông báo Script của tôi hoạt động.
Đây có nghĩa là một tập lệnh sẽ tự động đăng nhập vào một máy từ xa, đính kèm vào một phiên màn hình (hoặc bắt đầu một phiên bản nếu không tồn tại) đang chạy weechat, đồng thời mở một kết nối ssh khác sử dụng netcat để đọc thông báo từ tệp socket mà phần bổ sung weechat xuất các tin nhắn thông báo của tôi. Các thông báo này sau đó được đưa vào lib-notify (thông qua gửi thông báo) để tôi có thể được cảnh báo về hoạt động trong weechat.
Đây là kịch bản:
#!/bin/bash
BOUNCER="MYUSER@MYBOUNCER.NET"
function irc_notify() {
ssh $BOUNCER "nc -k -l -U /tmp/weechat.notify.sock" | \
while read type message; do
notify-send -i weechat -u critical "$(echo -n $type | base64 -di -)" "$(echo -n $message | base64 -di -)"
done
}
# Start listening for notifications
irc_notify &
# Attach to remote IRC Bouncer Screen
ssh $BOUNCER -t 'screen -d -R -S irc weechat'
# Cleanup Socket Listener
echo "cleaning up notification socket listener…"
ssh $BOUNCER 'pkill -f -x "nc -k -l -U /tmp/weechat.notify.sock"'
Các thiết lập thực sự hoạt động thực sự tốt, ngoại trừ một trục trặc lớn. Chỉ có hai thông báo được gửi đến trình quản lý thông báo của tôi, mỗi lần gọi tập lệnh. Sau đó: không có gì.
Vì vậy, để loại bỏ các vấn đề với tập lệnh thông báo trong weechat, tôi đã loại bỏ lệnh gọi ssh thứ hai (cái được gắn vào phiên màn hình và bắt đầu weechat) và thay thế nó bằng một read
lệnh để chặn thực thi trong khi tôi kiểm tra. Sau đó, sử dụng irb
trên máy từ xa, tôi đã gửi tin nhắn đến ổ cắm bằng ruby.
Tuy nhiên, ngay cả khi tôi đang gửi tin nhắn theo cách thủ công, vẫn chỉ có hai tin nhắn xuất hiện trước khi nó ngừng hoạt động .
strace
cho tôi thấy một số hành vi thú vị (khi tôi gắn liền với quá trình rẽ nhánh) trong đó dường như các tin nhắn đã ngừng bị chấm dứt bởi các ký tự dòng mới sau tin nhắn đầu tiên hoặc thứ hai. Nhưng sau một vài lần nữa, họ ngừng xuất hiện strace
cùng nhau.
Lúc này tôi quyết định xem có gì trong kịch bản của mình gây ra hành vi lạ không. Vì vậy, trên dòng lệnh tôi chỉ cần gọi ssh $BOUNCER "nc -k -l -U /tmp/weechat.notify.sock"
trực tiếp kết nối ssh ( ). Và lo lắng, tất cả các tin nhắn tôi đang gửi bằng tay đều xuất hiện (tất nhiên vẫn được mã hóa base64).
Vì vậy, sau đó tôi đã thêm vào logic để giải mã mọi tin nhắn khi nó đến, giống như tôi có trong kịch bản của mình, và nó cũng hoạt động hoàn hảo cho mọi tin nhắn. Bao gồm cả khi tôi cho những tin nhắn này vào thông báo-gửi.
Vì vậy, tại thời điểm này tôi đã quyết định rằng một cái gì đó kỳ lạ phải xảy ra khi tôi rẽ nhánh . Nhưng tôi quan sát thấy không có sự khác biệt về hiệu quả khi tôi căn cứ lại các lệnh trong thiết bị đầu cuối. Vì vậy, tôi tự hỏi liệu có lẽ điều gì đó kỳ lạ đã xảy ra bởi vì nó đang được chạy từ trong một kịch bản.
Đó là khi mọi thứ trở nên kỳ lạ
Tôi đã bắt đầu bằng cách phá vỡ logic ra khỏi hàm và chỉ cần gọi nó trực tiếp, với một dấu và ở cuối các lệnh được đặt. Thích như vậy:
ssh $BOUNCER "nc -k -l -U /tmp/weechat.notify.sock" | \
while read type message; do
notify-send -i weechat -u critical "$(echo -n $type | base64 -di -)" "$(echo -n $message | base64 -di -)"
done &
Ngay khi tôi làm điều đó, các tin nhắn đột nhiên bắt đầu hoạt động. Và ngay sau khi tôi hoàn nguyên thay đổi, tôi đã quay lại quảng trường với hành vi chỉ có hai tin nhắn kỳ lạ.
Nhưng sửa chữa này, giới thiệu một số hành vi kỳ lạ khác. Khi tôi ở trong phiên màn hình, tôi sẽ phải nhấn từng phím nhiều lần trước khi chương trình được đăng ký. Như thể có một điều kiện cuộc đua chiến đấu với STDIN.
Có lẽ hai phiên SSH đã chiến đấu với nó (mặc dù tôi không chắc tại sao) Tôi đã cố gắng đóng và / hoặc chiếm STDIN trên lệnh ssh đầu tiên thông qua nhiều phương tiện khác nhau. Chẳng hạn như bằng cách đặt ống : |
trước nó, hoặc nối <&-
hoặc </dev/null
sau phần SSH của đường ống. Và trong khi điều đó dường như đã giải quyết được tình trạng chủng tộc, điều này lại giới thiệu hành vi chỉ có hai tin nhắn.
Nghĩ rằng nó có thể có liên quan đến việc xử lý nhiều lớp phụ , sau đó tôi đã cố gắng tái tạo điều này bằng cách gói cuộc gọi SSH bằng bash -c
như vậy : bash -c 'ssh $BOUNCER "nc -k -l -U /tmp/weechat.notify.sock" &'
. Và điều này cũng thể hiện hành vi chỉ có hai tin nhắn.
Tôi cũng đã tiếp tục và thử nghiệm điều này trực tiếp trên máy từ xa (SSH đến localhost và gói trong hai lệnh bash -c
) và chứng kiến hành vi bị hỏng tương tự. Nó cũng dường như không liên quan đến việc gấp đôi gây ra các quá trình mồ côi. Vì nó dường như không quan trọng nếu quá trình kết thúc mồ côi hay không.
Tôi cũng xác minh điều này cũng xảy ra theozsh
.
Có vẻ như điều này bằng cách nào đó liên quan đến cách xử lý STDIN và STDOUT khi một quy trình được chạy dưới các lớp xử lý phụ.
Repro. Hướng dẫn & strace
đầu ra:
Để đơn giản hóa việc gỡ lỗi, tôi đã xóa SSH khỏi ảnh và viết hai tập lệnh kiểm tra đơn giản hóa để tái tạo thành công hành vi hoàn toàn cục bộ.
Sử dụng socket
lệnh của Juergen Nickelsen, tôi đã tạo một Ổ cắm tên miền UNIX ( socket -l -s ./test.sock
) cục bộ và một lần nữa có thể gửi tin nhắn thử nghiệm tới nó bằng irb
cách sử dụng đoạn mã Ruby sau:
require 'socket'
require 'base64'
SOCKET = './test.sock'
def send(subtitle, message)
UNIXSocket.open(SOCKET) do |socket|
socket.puts "#{Base64.strict_encode64(subtitle)} #{Base64.strict_encode64(message)}"
end
end
send('test', 'hi')
send('test', 'hi')
send('test', 'hi')
send('test', 'hi')
send('test', 'hi')
send('test', 'hi')
Tập lệnh đầu tiên chỉ làm nền cho biểu thức đường ống (như đã nêu trước đây, đã xử lý số lượng tin nhắn không giới hạn):
#!/bin/bash
# to aid in cleanup when using Ctrl-C to exit strace
trap "pkill -f -x 'nc -k -l -U $HOME/test.sock'; exit" SIGINT
# Start listening for notifications
nc -k -l -U $HOME/test.sock | \
while read type message; do
# write messages to a local file instead of sending to notification daemon for simplicity.
echo "$(echo -n $type | base64 -di -)" "$(echo -n $message | base64 -di -)" >> /tmp/msg
done &
read
Và tạo ra đầu ra sau khi chạy với strace -f
: http://pastebin.com/SMjti3qW
Tập lệnh thứ hai làm nền cho chức năng gói (kích hoạt hành vi 2 và thực hiện):
#!/bin/bash
# to aid in cleanup when using Ctrl-C to exit strace
trap "pkill -f -x 'nc -k -l -U $HOME/test.sock'; exit" SIGINT
# Start listening for notifications
function irc_notify() {
nc -k -l -U $HOME/test.sock | \
while read type message; do
# write messages to a local file instead of sending to notification daemon for simplicity.
echo "$(echo -n $type | base64 -di -)" "$(echo -n $message | base64 -di -)" >> /tmp/msg
done
}
irc_notify &
read
Và lần lượt, đã tạo ra đầu ra sau đây khi chạy với strace -f
: http://pastebin.com/WsrXX0EJ
Một điều nổi bật với tôi khi nhìn vào strace
đầu ra từ các tập lệnh trên là đầu ra cụ thể cho nc
lệnh . Dường như cho thấy một trong những khác biệt chính giữa việc thực hiện hai tập lệnh này.
Đầu ra "làm việc" của tập lệnh đầu tiênnc
strace
:
accept(3, {sa_family=AF_FILE, NULL}, [2]) = 4
poll([{fd=4, events=POLLIN}, {fd=0, events=POLLIN}], 2, -1) = 1 ([{fd=4, revents=POLLIN|POLLHUP}])
read(4, "dGVzdA== aGk=\n", 2048) = 14
write(1, "dGVzdA== aGk=\n", 14) = 14
poll([{fd=4, events=POLLIN}, {fd=0, events=POLLIN}], 2, -1) = 1 ([{fd=4, revents=POLLIN|POLLHUP}])
read(4, "", 2048) = 0
shutdown(4, 0 /* receive */) = 0
close(4) = 0
accept(3, {sa_family=AF_FILE, NULL}, [2]) = 4
poll([{fd=4, events=POLLIN}, {fd=0, events=POLLIN}], 2, -1) = 1 ([{fd=4, revents=POLLIN|POLLHUP}])
read(4, "dGVzdA== aGk=\n", 2048) = 14
write(1, "dGVzdA== aGk=\n", 14) = 14
poll([{fd=4, events=POLLIN}, {fd=0, events=POLLIN}], 2, -1) = 1 ([{fd=4, revents=POLLIN|POLLHUP}])
read(4, "", 2048) = 0
shutdown(4, 0 /* receive */) = 0
close(4) = 0
accept(3, {sa_family=AF_FILE, NULL}, [2]) = 4
poll([{fd=4, events=POLLIN}, {fd=0, events=POLLIN}], 2, -1) = 1 ([{fd=4, revents=POLLIN|POLLHUP}])
read(4, "dGVzdA== aGk=\n", 2048) = 14
write(1, "dGVzdA== aGk=\n", 14) = 14
poll([{fd=4, events=POLLIN}, {fd=0, events=POLLIN}], 2, -1) = 1 ([{fd=4, revents=POLLIN|POLLHUP}])
read(4, "", 2048) = 0
shutdown(4, 0 /* receive */) = 0
close(4) = 0
accept(3, {sa_family=AF_FILE, NULL}, [2]) = 4
poll([{fd=4, events=POLLIN}, {fd=0, events=POLLIN}], 2, -1) = 1 ([{fd=4, revents=POLLIN|POLLHUP}])
read(4, "dGVzdA== aGk=\n", 2048) = 14
write(1, "dGVzdA== aGk=\n", 14) = 14
poll([{fd=4, events=POLLIN}, {fd=0, events=POLLIN}], 2, -1) = 1 ([{fd=4, revents=POLLIN|POLLHUP}])
read(4, "", 2048) = 0
shutdown(4, 0 /* receive */) = 0
close(4) = 0
accept(3, {sa_family=AF_FILE, NULL}, [2]) = 4
poll([{fd=4, events=POLLIN}, {fd=0, events=POLLIN}], 2, -1) = 1 ([{fd=4, revents=POLLIN|POLLHUP}])
read(4, "dGVzdA== aGk=\n", 2048) = 14
write(1, "dGVzdA== aGk=\n", 14) = 14
poll([{fd=4, events=POLLIN}, {fd=0, events=POLLIN}], 2, -1) = 1 ([{fd=4, revents=POLLIN|POLLHUP}])
read(4, "", 2048) = 0
shutdown(4, 0 /* receive */) = 0
close(4) = 0
accept(3, {sa_family=AF_FILE, NULL}, [2]) = 4
poll([{fd=4, events=POLLIN}, {fd=0, events=POLLIN}], 2, -1) = 1 ([{fd=4, revents=POLLIN|POLLHUP}])
read(4, "dGVzdA== aGk=\n", 2048) = 14
write(1, "dGVzdA== aGk=\n", 14) = 14
poll([{fd=4, events=POLLIN}, {fd=0, events=POLLIN}], 2, -1) = 1 ([{fd=4, revents=POLLIN|POLLHUP}])
read(4, "", 2048) = 0
shutdown(4, 0 /* receive */) = 0
close(4) = 0
accept(3,
Hành vi "2 và xong" của Script thứ hai được thấy ở nc
strace
đầu ra:
accept(3, {sa_family=AF_FILE, NULL}, [2]) = 4
poll([{fd=4, events=POLLIN}, {fd=0, events=POLLIN}], 2, -1) = 2 ([{fd=4, revents=POLLIN|POLLHUP}, {fd=0, revents=POLLHUP}])
read(4, "dGVzdA== aGk=\n", 2048) = 14
write(1, "dGVzdA== aGk=\n", 14) = 14
shutdown(4, 1 /* send */) = 0
close(0) = 0
poll([{fd=4, events=POLLIN}, {fd=-1}], 2, -1) = 1 ([{fd=4, revents=POLLIN|POLLHUP}])
read(4, "", 2048) = 0
shutdown(4, 0 /* receive */) = 0
close(4) = 0
accept(3, {sa_family=AF_FILE, NULL}, [2]) = 0
poll([{fd=0, events=POLLIN}, {fd=0, events=POLLIN}], 2, -1) = 2 ([{fd=0, revents=POLLIN|POLLHUP}, {fd=0, revents=POLLIN|POLLHUP}])
read(0, "dGVzdA== aGk=\n", 2048) = 14
write(1, "dGVzdA== aGk=\n", 14) = 14
read(0, "", 2048) = 0
shutdown(0, 1 /* send */) = 0
close(0) = 0
poll([{fd=0, events=POLLIN}, {fd=-1}], 2, -1) = 1 ([{fd=0, revents=POLLNVAL}])
poll([{fd=0, events=POLLIN}, {fd=-1}], 2, -1) = 1 ([{fd=0, revents=POLLNVAL}])
poll([{fd=0, events=POLLIN}, {fd=-1}], 2, -1) = 1 ([{fd=0, revents=POLLNVAL}])
poll([{fd=0, events=POLLIN}, {fd=-1}], 2, -1) = 1 ([{fd=0, revents=POLLNVAL}])
poll([{fd=0, events=POLLIN}, {fd=-1}], 2, -1) = 1 ([{fd=0, revents=POLLNVAL}])
poll([{fd=0, events=POLLIN}, {fd=-1}], 2, -1) = 1 ([{fd=0, revents=POLLNVAL}])
poll([{fd=0, events=POLLIN}, {fd=-1}], 2, -1) = 1 ([{fd=0, revents=POLLNVAL}])
poll([{fd=0, events=POLLIN}, {fd=-1}], 2, -1) = 1 ([{fd=0, revents=POLLNVAL}])
poll([{fd=0, events=POLLIN}, {fd=-1}], 2, -1) = 1 ([{fd=0, revents=POLLNVAL}])
poll([{fd=0, events=POLLIN}, {fd=-1}], 2, -1) = 1 ([{fd=0, revents=POLLNVAL}])
.......[truncated].......
Tôi không phải là nơi tôi muốn liên quan đến strace
khả năng đọc đầu ra của mình , vì vậy tôi không chắc chắn chính xác những gì các đầu ra khác nhau này có nghĩa là vượt qua sự thật rằng một cái rõ ràng là hoạt động trong khi cái kia thì không.
Khi tôi đi qua strace
đầu ra lớn hơn , có vẻ như các tin nhắn sau hai lần đầu tiên không còn bị chấm dứt bởi một dòng mới? Nhưng một lần nữa, tôi không chắc điều đó có nghĩa gì, hoặc nếu tôi đọc đúng.
Và tôi chắc chắn không hiểu làm thế nào các kỹ thuật xử lý phụ khác nhau, hoặc thậm chí đóng STDIN, có thể ảnh hưởng đến hành vi này.
Có ai biết tôi đang gặp phải điều gì ở đây không?
-
tl; dr
Tôi đang cố gắng tìm hiểu tại sao chạy trình nghe thông báo của mình dưới nhiều lớp kết quả xử lý phụ chỉ trong hai tin nhắn được xử lý; và không làm như vậy dẫn đến tình trạng đua trên STDIN.
-n
vào ssh dường như có tác dụng tương tự như bất kỳ nỗ lực nào khác để đóng STDIN (hai và thực hiện hành vi). Cũng như đóng STDIN trên quy trình con. Tôi chạy strace
vào nc
mặc dù, và nó cho thấy một số khác biệt về hành vi thú vị giữa kịch bản mà tất cả các thư nhận được thông qua ( pastebin.com/tCSwtA99 ), và khi chỉ có hai get qua ( pastebin.com/gZueVpb0 ). Tôi không phải là người giỏi nhất trong việc đọc strace
đầu ra. Tôi có thể phải đào qua một số tài liệu gọi hệ thống để hiểu điều này.
strace -f
? Điều đó sẽ theo dĩa để bao gồm dấu vết từ tất cả các quy trình con sinh ra.
-f
vào nc
strace
không có bất kỳ ảnh hưởng nào, vì nc
không ngã ba chút nào. Tôi sẽ cung cấp cho bạn toàn bộ đầu ra để chạy trên tập lệnh phía máy khách, nhưng những gì nó cung cấp quá dài và chứa quá nhiều thông tin nhạy cảm (từ LDAP, v.v.) mà tôi không thể vệ sinh đúng cách để dán ở đây. Nhưng tôi có thể nói với bạn rằng, những gì tôi luôn quan sát khi tôi thử rằng hai tin nhắn luôn được chuyển qua, nhưng tin nhắn cuối cùng dường như không bị chấm dứt chính xác (nó thiếu một ký tự dòng mới) hoặc một cái gì đó, và dường như trùng với khi nc
bắt đầu phun poll()
tin nhắn.
strace
đầu ra sạch hơn . Tôi đã chạy hai phiên bản của kịch bản thử nghiệm này với strace -f
; một trong đó làm nền cho biểu thức đường ống và sẽ xử lý các thông báo kiểm tra không giới hạn ( pastebin.com/GDjxTJuJ ) và một trong đó làm nền cho chức năng gói và chỉ xử lý hai thông báo trước khi ngắt ( pastebin.com/GMtkKefP ). Đầu strace -f
ra của các tập lệnh tương ứng này là 1) pastebin.com/SMjti3qW và 2) pastebin.com/WsrXX0EJ
ssh -n
sẽ ngăn đọc từ stdin, mặc dù điều này dường như không phải là nguyên nhân của vấn đề chính. Những gì hiệnstrace
củanc
chương trình khi so sánh với các tiến trình con? Lànc
nhận và phát dữ liệu? Có phải nó đang chặn? Đóng stdout cho quy trình con có ảnh hưởng gì không?