Problemas causados pelo STDIN configurado no modo sem bloqueio

3

Certos comandos começam a falhar consistentemente em uma determinada janela do terminal:

$ 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)

Ao procurar a causa, strace revelou que os programas tentam ler a partir de STDIN, mas recebem um erro:

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

Na página do manual 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.

Com certeza, STDIN é marcado como não-bloqueador para aquela janela de terminal (indicada por 4 em flags ):

$ cat /proc/self/fdinfo/0 
pos:    0
flags:  0104002
mnt_id: 25

Eu estou supondo que algum programa que eu estava usando set STDIN para o modo não-bloqueante e depois não o tenha configurado ao sair (ou foi morto antes que pudesse.)

Eu não consegui descobrir como consertar esse problema na linha de comando, então escrevi o seguinte programa para fazer isso (e também permite que você mude STDIN para o modo não-bloqueador para ver o que quebra).

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

Enfim, eu queria saber:

  1. Existe uma maneira de tornar o STDIN não não-bloqueador com utilitários de linha de comando padrão?
  2. O shell (no meu caso, bash) deve restaurar automaticamente os flags no STDIN (e / ou STDOUT / STDERR) entre os comandos? Existe um caso de uso para um comando que depende de alterações STDIN feitas por outro programa?
  3. É um erro para um programa supor que STDIN estará no modo de bloqueio quando o programa é iniciado, e cada programa deve desligar especificamente o modo sem bloqueio se isso fizer com que as coisas quebrem (veja os exemplos acima) )?

Para referência, estou usando o Ubuntu 17.10 e o GNU bash, versão 4.4.12 (1) -release (x86_64-pc-linux-gnu)

Atualização: Parece que este problema foi resolvido para o Fedora com um patch para o bash:

link

No entanto, a correção não parece ter sido aplicada a upstream, pelo menos com a versão 4.4.18 (1) -release (de janeiro de 2018). Além disso, o mantenedor do bash menciona que o bash não deveria ser realmente responsável por isso:

link

Parece que os aplicativos devem ser responsáveis por restaurar os sinalizadores originais de STDIN, se forem alterados, por isso estou usando o seguinte programa para verificar 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;
}

Eu compilei o programa ( gcc -o checkstdin checkstdin.c ) e coloquei o seguinte no meu .bashrc para executá-lo após cada comando:

PROMPT_COMMAND+="/path/to/checkstdin"

Ele imprimirá um aviso para o STDOUT se detectar que o STDIN está no modo sem bloqueio.

    
por recvfrom 13.04.2018 / 02:51

1 resposta

2

Quando isso acontecer, execute o bash a partir da linha de comando e saia (para retornar o primeiro bash). Deve funcionar novamente. Detalhes um pouco interessantes aqui: link .

    
por 15.06.2018 / 20:48