STDIN 設置為非阻塞模式導致的問題
某些命令在給定的終端視窗中開始始終失敗:
$ sudo apt-get install ipython ... After this operation, 3,826 kB of additional disk space will be used. Do you want to continue? [Y/n] Abort. $ $ kinit -f <username> Password for <username>@<domain>: kinit: Pre-authentication failed: Cannot read password while getting initial credentials $ $ passwd Changing password for <username>. (current) UNIX password: passwd: Authentication token manipulation error passwd: password unchanged $ $ crontab -e Too many errors from stdincrontab: "/usr/bin/sensible-editor" exited with status 1 $ $ sudo docker run -it ubuntu bash (hangs forever)
在尋找原因時,strace發現程序嘗試從 STDIN 讀取但收到錯誤:
read(0, 0x7fffe1205cc7, 1) = -1 EAGAIN (Resource temporarily unavailable)
從*read(2)*手冊頁:
ERRORS EAGAIN The file descriptor fd refers to a file other than a socket and has been marked nonblocking (O_NONBLOCK), and the read would block.
果然,該終端視窗的 STDIN 被標記為非阻塞(由flags中的 4 表示):
$ cat /proc/self/fdinfo/0 pos: 0 flags: 0104002 mnt_id: 25
我假設我正在使用的某些程序將 STDIN 設置為非阻塞模式,然後在退出時沒有將其設置回來(或者它在它可能之前就被殺死了。)
我不知道如何從命令行解決此問題,因此我編寫了以下程序來解決此問題(它還允許您將 STDIN 更改為非阻塞模式以查看中斷的原因。)
#include <errno.h> #include <fcntl.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> int makeStdinNonblocking(int flags) { if (fcntl(STDIN_FILENO, F_SETFL, flags | O_NONBLOCK) < 0) { printf("Error calling fcntl in %s: %s\n", __FUNCTION__, strerror(errno)); return EXIT_FAILURE; } return EXIT_SUCCESS; } int makeStdinBlocking(int flags) { if (fcntl(STDIN_FILENO, F_SETFL, flags & ~(O_NONBLOCK)) < 0) { printf("Error calling fcntl in %s: %s\n", __FUNCTION__, strerror(errno)); return EXIT_FAILURE; } return EXIT_SUCCESS; } int main(int argc, char *argv[]) { int flags; if (argc != 2) { goto usage; } if ((flags = fcntl(STDIN_FILENO, F_GETFL, 0)) < 0) { printf("Error calling fcntl in %s: %s\n", __FUNCTION__, strerror(errno)); return EXIT_FAILURE; } if (0 == strncmp(argv[1], "nonblock", 9)) { return makeStdinNonblocking(flags); } else if ( 0 == strncmp(argv[1], "block", 6)) { return makeStdinBlocking(flags); } usage: printf("Usage: %s <nonblock|block>\n", argv[0]); return EXIT_FAILURE; }
無論如何,我想知道:
- 有沒有辦法使用標準命令行實用程序使 STDIN 不阻塞?
- shell(在我的例子中是 bash)是否應該在命令之間自動恢復 STDIN(和/或 STDOUT/STDERR)上的標誌?是否有一個命令依賴於另一個程序所做的 STDIN 更改的案例?
- 程序啟動時假設 STDIN 將處於阻塞模式是否是一個錯誤,如果它會導致事情中斷,每個程序是否應該專門關閉非阻塞模式(參見上面的範例)?
作為參考,我使用的是 Ubuntu 17.10 和 GNU bash,版本 4.4.12(1)-release (x86_64-pc-linux-gnu)
更新: Fedora 似乎通過 bash 更新檔解決了這個問題:
https://bugzilla.redhat.com/show_bug.cgi?id=1068697
不過,該修復似乎並未在上游應用,至少在 4.4.18(1)-release 版本(從 2018 年 1 月開始)。此外,bash 維護者提到 bash 不應該對此真正負責:
https://lists.gnu.org/archive/html/bug-bash/2017-01/msg00043.html
聽起來應用程序應該負責恢復 STDIN 的原始標誌,如果它改變了它們,所以我使用以下程序來檢查 STDIN:
#include <errno.h> #include <fcntl.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> int main() { int flags; if ((flags = fcntl(STDIN_FILENO, F_GETFL, 0)) < 0) { printf("Error calling fcntl in %s: %s\n", __FUNCTION__, strerror(errno)); } if (0 != (flags & (O_NONBLOCK))) { printf("Warning, STDIN in nonblock mode\n"); } return EXIT_SUCCESS; }
我編譯了程序 (
gcc -o checkstdin checkstdin.c
),然後將以下內容放入我的 .bashrc 以使其在每個命令之後執行:PROMPT_COMMAND+="/path/to/checkstdin"
如果它檢測到 STDIN 現在處於非阻塞模式,它將向 STDOUT 列印一個警告。
發生這種情況時,從命令行執行 bash,然後退出(返回第一個 bash)。應該再次工作。這裡有些有趣的細節:https ://stackoverflow.com/questions/19895185/bash-shell-read-error-0-resource-temporarily-unavailable 。
如果您需要編寫解決方法腳本,您可以使用
perl -MFcntl -e 'fcntl STDIN, F_SETFL, fcntl(STDIN, F_GETFL, 0) & ~O_NONBLOCK'