web-dev-qa-db-ja.com

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)manページから:

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. シェル(私の場合、bash)はコマンド間でSTDIN(および/またはSTDOUT/STDERR)のフラグを自動的に復元する必要がありますか?別のプログラムによるSTDINの変更に依存するコマンドの使用例はありますか?
  3. プログラムの起動時にSTDINがブロッキングモードになるとプログラムが想定するのはバグですか?障害が発生する場合は、各プログラムが非ブロッキングモードを明確にオフにする必要がありますか(上記の例を参照)?

参考までに、私はUbuntu 17.10とGNU bash、バージョン4.4.12(1)-release(x86_64-pc-linux-gnu)を使用しています。

更新:この問題は、bashへのパッチでFedoraに対処されたようです:

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のフラグを復元する必要があるように思われるため、次のプログラムを使用して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に警告を出力します。

4
recvfrom

これが発生したら、コマンドラインからbashを実行してから終了します(最初のbashに戻ります)。再び動作するはずです。ここでやや興味深い詳細: https://stackoverflow.com/questions/19895185/bash-Shell-read-error-0-resource-temporarily-unavailable

2
Oliver

回避策をスクリプト化する必要がある場合は、

Perl -MFcntl -e 'fcntl STDIN, F_SETFL, fcntl(STDIN, F_GETFL, 0) & ~O_NONBLOCK'
1
niry