STDIN设置为非阻塞模式引起的问题

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)

在寻找原因的过程中,斯特雷斯显示程序尝试从 STDIN 读取但收到错误:

read(0, 0x7fffe1205cc7, 1) = -1 EAGAIN (Resource temporarily unavailable)

来自阅读(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 被标记为非阻塞(由 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 打印警告。

答案1

发生这种情况时,从命令行运行 bash,然后退出(返回第一个 bash)。应该再工作。这里有一些有趣的细节:https://stackoverflow.com/questions/19895185/bash-shell-read-error-0-resource-temporarily-unavailable

答案2

如果您需要编写解决方法的脚本,您可以使用

perl -MFcntl -e 'fcntl STDIN, F_SETFL, fcntl(STDIN, F_GETFL, 0) & ~O_NONBLOCK'

相关内容