erro de pipe quebrado com popen e JS ffi

3

Estou usando um ffi para o nodejs, que na maior parte não tem nada a ver com essa questão, que é realmente sobre entender melhor os canais, mas oferece algum contexto.

function exec(cmd) {
  var buffer = new Buffer(32);
  var result = '';
  var fp = libc.popen('( ' + cmd + ') 2>&1', 'r');
  var code;

  if (!fp) throw new Error('execSync error: '+cmd);

  while( !libc.feof(fp) ){
    libc.fgets(buffer, 32, fp)
    result += buffer.readCString();
  }
  code = libc.pclose(fp) >> 8;

  return {
    stdout: result,
    code: code
  };
}

o que me leva a este pedaço de código que, quando eu executo usando essa função exec

 tr -dc "[:alpha:]" < /dev/urandom | head -c ${1-8}

Eu recebo o erro:

write error: Broken pipe
tr: write error

mas eu recebo a saída que espero: 8 números aleatórios. Isso confundiu o inferno fora de mim, mas depois em algum googling selvagem eu encontrei esta resposta de pilha que se encaixam perfeitamente a minha situação.

Ainda tenho algumas perguntas.

Por que:

tr -dc "[:alpha:]" < /dev/urandom | head -c ${1-8}

lançar um erro de pipe quebrado quando chamado com o meu comando exec, mas não quando chamado do shell? Eu não entendo porque quando eu chamo:

tr -dc "[:alpha:]" < /dev/urandom

lê indefinidamente, mas quando eu canalizo para:

head -c ${1-8}

Funciona sem causar um erro de canal quebrado. Parece que head usaria o que precisa e tr iria ler para sempre. Pelo menos deveria jogar cano quebrado; head consumiria os primeiros 8 bytes e, em seguida, tr ainda estaria lançando a saída e o canal quebrado seria lançado por tr porque head parou de ser executado.

Ambas as situações fazem sentido para mim, mas parece que elas são algumas que são exclusivas uma da outra. Eu não entendo o que é diferente entre chamar:

exec(tr -dc "[:alpha:]" < /dev/urandom | head -c ${1-8})

e

tr -dc "[:alpha:]" < /dev/urandom | head -c ${1-8}

diretamente da linha de comando e, especificamente, por que < um arquivo sem fim em algo e, em seguida, | em algo faz com que ele não seja executado indefinidamente. Eu venho fazendo isso há anos e nunca questionei por que isso funciona dessa maneira.

Por fim, não há problema em ignorar esse erro de cano quebrado? Existe uma maneira de consertar isso? Estou fazendo algo errado no meu código JavaScript j ++ do C ++? Estou sentindo falta de algum tipo de básico popen?

------ EDITAR

mexendo um pouco mais no código

exec('head -10 /dev/urandom | tr -dc "[:alpha:]" | head -c 8')

não gera nenhum erro de pipe!

    
por Prospero 27.05.2013 / 11:39

1 resposta

5

Normalmente, tr não deve poder gravar essa mensagem de erro porque ela deveria ter sido eliminada por um sinal SIGPIPE ao tentar gravar algo após a outra extremidade do canal ter sido fechada após o término de head .

Você recebe essa mensagem de erro porque, de alguma forma, o processo que está executando tr foi configurado para ignorar SIGPIPEs. Eu suspeito que isso possa ser feito pela implementação popen() em sua linguagem lá.

Você pode reproduzi-lo fazendo:

sh -c 'trap "" PIPE; tr -dc "[:alpha:]" < /dev/urandom | head -c 8'

Você pode confirmar o que está acontecendo:

strace -fe signal sh your-program

(ou o equivalente no seu sistema, se não estiver usando o Linux). Você verá algo como:

rt_sigaction(SIGPIPE, {SIG_IGN, ~[RTMIN RT_1], SA_RESTORER, 0x37cfc324f0}, NULL, 8) = 0

ou

signal(SIGPIPE, SIG_IGN)

feito em um processo antes que o mesmo processo ou um de seus descendentes execute o /bin/sh que interpreta essa linha de comando e inicia tr e head .

Se você fizer um strace -fe write , verá algo como:

write(1, "AJiYTlFFjjVIzkhCAhccuZddwcydwIIw"..., 4096) = -1 EPIPE (Broken pipe)

A chamada do sistema write falha com um erro EPIPE em vez de acionar um SIGPIPE.

Em qualquer caso, tr sairá. Ao ignorar SIGPIPE, por causa desse erro (mas que também dispara uma mensagem de erro). Quando não, sai ao receber o SIGPIPE. Você quer que ele saia, pois você não quer que ele continue lendo /dev/urandom depois que esses 8 bytes tiverem sido read por head .

Para evitar essa mensagem de erro, você pode restaurar o manipulador padrão do SIGPIPE com:

trap - PIPE

Antes de chamar tr :

popen("trap - PIPE; { tr ... | head -c 8; } 2>&1", ...)
    
por 27.05.2013 / 11:55

Tags