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. シェル(私の場合は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)リリース(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'

関連情報