特定のターミナルウィンドウで、特定のコマンドが一貫して失敗し始めます。
$ 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;
}
とにかく、私は思っていました:
参考までに、私は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に警告を出力します。
これが発生したら、コマンドラインから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'