sistema C ("bash") ignora stdin

7

Eu tenho uma entrada de arquivo:

$ cat input
1echo 12345

e eu tenho o seguinte programa

1ª versão

#include <stdio.h>
#include <stdlib.h>

int main() {
  system("/bin/bash -i");
  return 0;
}

Agora, se eu executar,

$ gcc -o program program.c
$ ./program < input
bash: line 1: 1echo: command not found
$ exit

Tudo funciona como esperado.

Agora, quero ignorar o primeiro caractere da entrada do arquivo, por isso, faço uma chamada para getchar() antes de invocar system() .

2ª versão:

#include <stdio.h>
#include <stdlib.h>

int main() {
  getchar();
  system("/bin/bash -i");
  return 0;
}

Surpreendentemente, o bash sai instantaneamente como se não houvesse entrada.

$ gcc -o program program.c
$ ./program < input
$ exit

Pergunta por que o bash não está recebendo a entrada?

NOTA Eu tentei algumas coisas e descobri que forjar uma nova criança para o processo principal resolve o problema:

3a versão

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>

int main() {
  getchar();
  if (fork() > 0) {
    system("/bin/bash -i");
    wait(NULL);
  }
  return 0;
}

$ gcc -o program program.c
$ ./program < input
$ 12345
$ exit

SO Ubuntu 16.04 64bit, gcc 5.4

    
por Hedi Ghediri 16.12.2017 / 02:06

3 respostas

14

Um fluxo de arquivo é definido como :

fully buffered if and only if it can be determined not to refer to an interactive device

Como você está redirecionando para a entrada padrão, o stdin não é interativo e, portanto, é armazenado em buffer.

getchar é uma função de fluxo e fará com que o buffer seja preenchido de o fluxo, consumindo esses bytes e, em seguida, retornar um único byte para você. system apenas executa fork-exec , então o subprocesso herda todos os seus descritores de arquivos abertos. é. Quando bash tentar ler a partir de sua entrada padrão, descobrirá que ela já está no final do arquivo porque todo o conteúdo já foi lido pelo seu processo pai.

No seu caso, você quer consumir apenas esse byte antes de passar para o processo filho, então:

The setvbuf() function may be used after the stream pointed to by stream is associated with an open file but before any other operation [...] is performed on the stream.

Assim, adicionar uma chamada adequada antes do getchar() :

#include <stdio.h>
#include <stdlib.h>

int main() {
  setvbuf(stdin, NULL, _IONBF, 0 );
  getchar();
  system("/bin/bash -i");
  return 0;
}

fará o que você quiser, configurando stdin para não ser buffer ( _IONBF ). getchar fará com que apenas um único byte seja lido e o restante da entrada estará disponível para o sub-processo. Talvez seja melhor usar read em vez disso, evitando toda a interface de fluxos, nesse caso .

POSIX exige determinados comportamentos quando o identificador pode ser acessado de ambos os processos após uma bifurcação , mas observa explicitamente que

If the only action performed by one of the processes is one of the exec functions [...], the handle is never accessed in that process.

o que significa que system() não (tem que) fazer nada de especial com ele, pois é apenas fork-exec .

Provavelmente, isso é o que sua solução fork está atingindo. Se o identificador for acessado nos dois lados, , em seguida, o primeiro :

If the stream is open with a mode that allows reading and the underlying open file description refers to a device that is capable of seeking, the application shall either perform an fflush(), or the stream shall be closed.

Chamar fflush() em um fluxo de leitura significa que:

the file offset of the underlying open file description shall be set to the file position of the stream

para que a posição do descritor seja redefinida de volta para 1 byte, o mesmo que o do fluxo, e o subprocesso subsequente receberá sua entrada padrão a partir desse ponto.

Além disso, para a segunda alça (filha) :

If any previous active handle has been used by a function that explicitly changed the file offset, except as required above for the first handle, the application shall perform an lseek() or fseek() (as appropriate to the type of handle) to an appropriate location.

e suponho que "um local apropriado" possa ser o mesmo (embora não seja especificado). A chamada getchar() "alterou explicitamente o deslocamento do arquivo", portanto, este caso deve ser aplicado. A intenção da passagem é que o trabalho em qualquer ramificação do fork tenha o mesmo efeito, portanto, fork() > 0 e fork() == 0 devem funcionar da mesma forma. Entretanto, como nada realmente acontece neste ramo, é discutível que nenhuma dessas regras deve ser usada para pais ou filhos.

O resultado exato é provavelmente dependente da plataforma - pelo menos, exatamente o que conta como "já pode ser acessado" não é especificado diretamente, nem qual identificador é primeiro e segundo. Há também um caso anterior e prioritário para o processo pai:

If the only further action to be performed on any handle to this open file descriptor is to close it, no action need be taken.

que indiscutivelmente se aplica para o seu programa, uma vez que apenas termina depois. Se isso acontecer, todos os casos restantes, incluindo o fflush() , devem ser ignorados e o comportamento que você está vendo seria um desvio da especificação. É discutível que chamar fork() constitua uma ação no identificador, mas não explícita ou óbvia, então eu não confiaria nisso. Há também o suficiente "ou" e "ou" nos requisitos que muita variação parece aceitável.

Por vários motivos , acho que o comportamento que você está vendo pode ser um bug , ou pelo menos uma interpretação generosa da especificação. Minha leitura geral é que, como em todos os casos, uma ramificação do fork não faz nada, nenhuma dessas regras deveria ter sido aplicada e a posição do descritor deveria ter sido ignorada onde estava. Eu não posso ser definitivo sobre isso, mas parece a leitura mais direta.

Eu não confiaria na técnica fork funcionando. Sua terceira versão não funciona para mim aqui. Use setbuf / setvbuf . Se possível, eu até usaria popen ou similar para configurar o processo com o filtragem necessária explicitamente, em vez de depender dos caprichos das interações do fluxo e do descritor de arquivo.

    
por 16.12.2017 / 02:46
2

Aplaudo Michael Homer pela sua resposta , e para encontrar essa referência POSIX. Mas eu acredito que eu entendo pelo menos 70% dessas coisas, e eu não entendo completamente sua resposta - então eu preparei esta versão TL; DR do que eu acredito que ele está dizendo.

  • getchar (a.k.a. getc ), ao ler de um arquivo, vai realmente ler um bloco do arquivo (ou o arquivo inteiro, o que for menor). Ele lê dados em um buffer. Ele então lhe dará o primeiro caractere do buffer.

    • Chamadas subseqüentes retornarão caracteres subseqüentes do buffer, até que o buffer esteja esgotado. Ele tentará (então) ler outro bloco do arquivo. Essa “E / S armazenada em buffer” a torna muito mais eficiente para um programa ler um arquivo e lidar com ele um pouco de cada vez.


    Mesmo que o fluxo de arquivo em buffer tenha um ponteiro de E / S lógica esse é um caractere no arquivo, isso faz com que o ponteiro de E / S da descrição do arquivo seja movido um bloco no arquivo (ou, no caso do seu arquivo pequeno, ao final do arquivo). Quando system forks, o processo filho herda a descrição do arquivo, mas não o fluxo de arquivos, então o bash herda um ponteiro de E / S de arquivo que está no final do arquivo. Portanto, quando ele lê o arquivo, ele recebe apenas um EOF.

  • Na terceira versão do seu programa, main está chamando fork diretamente (em vez de apenas chamar system , que chama fork ). E, claro, como na segunda versão do seu programa, main chamadas getchar . O documento POSIX que Michael encontrou diz explicitamente que está descrevendo uma extensão para o padrão ISO C. Tanto quanto eu posso dizer a partir de sua linguagem críptica, está dizendo que main (ou, suponho, o compilador C) é responsável por perceber o problema acima quando fork é chamado diretamente pela rotina que chama a função stdio , e para resolvê-lo. Portanto, aparentemente, fork (ou algum outro mecanismo?) interage com a família stdio para obter o ponteiro físico de E / S da descrição do arquivo movido de volta para sincronizar com o ponteiro de E / S lógico do ponteiro do arquivo; isto é, um caractere no arquivo. O segundo fork - o oculto, chamado por system - por acaso se beneficia disso, como isso resulta no shell sendo capaz para ler o arquivo a partir do segundo byte.

Eu fiz uma pesquisa superficial em páginas de manual relevantes e não consegui encontrar esse comportamento descrito. E eu não consegui reproduzir seu resultado também. Então, isso pode ser o comportamento especificado POSIX, mas claramente ainda não foi implementado universalmente.

    
por 22.12.2017 / 05:28
1

Demônios nasais

man 3 getchar diz:

The getchar() function shall be equivalent to getc(stdin).

E man 3 getc diz:

It is not advisable to mix calls to  input  functions  from  the  stdio
library  with  low-level  calls  to  read(2)  for  the  file descriptor
associated with the input stream; the results  will  be  undefined  and
very probably not what you want.

Bash no modo interativo provavelmente está usando read , etc. (via readline) para acessar diretamente a entrada. Então, temos demônios nasais .

    
por 16.12.2017 / 02:36

Tags