Command-Line

STDIN 設置為非阻塞模式導致的問題

  • December 19, 2019

某些命令在給定的終端視窗中開始始終失敗:

$ 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;
}

無論如何,我想知道:

  1. 有沒有辦法使用標準命令行實用程序使 STDIN 不阻塞?
  2. shell(在我的例子中是 bash)是否應該在命令之間自動恢復 STDIN(和/或 STDOUT/STDERR)上的標誌?是否有一個命令依賴於另一個程序所做的 STDIN 更改的案例?
  3. 程序啟動時假設 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'

引用自:https://unix.stackexchange.com/questions/437409