Propagação do sinal do pipeline Bash - como funciona?

3

Respondendo esta pergunta, não consegui explicar como os sinais se propagam por meio de um pipeline.

Considere os seguintes exemplos.

Usando timeout como o primeiro elemento no pipeline

Isso faz com que gpg resgate o SIGTERM que foi entregue para cat , por timeout , deixando um arquivo quebrado.

$ timeout 1 cat /dev/urandom | gpg -er [email protected] > ./myfile.gpg

gpg: Terminated caught ... exiting
Terminated
$ gpg -d < ./myfile.gpg > /dev/null

You need a passphrase to unlock the secret key for
user: "Attie Grande <[email protected]>"
4096-bit RSA key, ID C9AEA6AE, created 2016-12-13 (main key ID 7826F053)

gpg: encrypted with 4096-bit RSA key, ID C9AEA6AE, created 2016-12-13
      "Attie Grande <[email protected]>"
gpg: block_filter 0x145e790: read error (size=14775,a->size=14775)
gpg: block_filter 0x145f110: read error (size=10710,a->size=10710)
gpg: WARNING: encrypted message has been manipulated!
gpg: block_filter: pending bytes!
gpg: block_filter: pending bytes!

Usando timeout no meio do pipeline

Isso funciona como esperado - gpg sai corretamente.

$ cat /dev/urandom | timeout 1 cat | gpg -er [email protected] > ./myfile.gpg
$ gpg -qd < ./myfile.gpg > /dev/null

You need a passphrase to unlock the secret key for
user: "Attie Grande <[email protected]>"
4096-bit RSA key, ID C9AEA6AE, created 2016-12-13 (main key ID 7826F053)

Usando SIGUSR1 em vez de SIGTERM

Novamente, isso funciona como esperado - gpg sai limpo. Espero que cat seja encerrado em SIGUSR1 , enquanto gpg o ignore.

$ timeout -sUSR1 1 cat /dev/urandom | gpg -er [email protected] > ./myfile.gpg
$ gpg -qd < ./myfile.gpg > /dev/null

You need a passphrase to unlock the secret key for
user: "Attie Grande <[email protected]>"
4096-bit RSA key, ID C9AEA6AE, created 2016-12-13 (main key ID 7826F053)

Usando a substituição de processos

Novamente, isso funciona - embora eu não tenha esperado.

$ gpg -er [email protected] > ./myfile.gpg < <( timeout 1 cat /dev/urandom )
$ gpg -qd < ./myfile.gpg > /dev/null

You need a passphrase to unlock the secret key for
user: "Attie Grande <[email protected]>"
4096-bit RSA key, ID C9AEA6AE, created 2016-12-13 (main key ID 7826F053)

Eu só posso presumir que o sinal do primeiro elemento no pipeline é propagado até o restante dos elementos no pipeline (mesmo separando-os com timeout cat | cat | gpg falha).

Eu procurei por documentação e tive uma peça com set -e , set -o pipefail , mas eles não agiram como eu esperava.

  • O que está realmente acontecendo?
  • Quais são as semânticas?
  • Temos algum controle sobre isso?
  • Existe uma maneira melhor do que mover o formulário de geração de sinal na frente do pipeline?
por Attie 20.10.2017 / 18:21

1 resposta

6

I can only presume that the signal of the first element in the pipeline is propagated through to the rest of the elements in the pipeline.

Até onde eu sei, não existe tal propagação. Eu vou responder principalmente a sua primeira pergunta:

What is actually going on?

Resposta curta

(Isso pode ser um pouco simplificado.)

  1. Ao executar um canal, bash coloca cada processo em um grupo de processos com PGID (ID do grupo de processos) igual ao PID (ID do processo) do primeiro comando.
  2. timeout altera seu próprio PGID para seu próprio PID . Isso não muda nada se timeout for o primeiro comando no canal.
  3. timeout envia o sinal não apenas para o comando subjacente, mas também para todo o seu grupo de processos. Se timeout for o primeiro comando no pipeline, o grupo de processos ainda incluirá gpg , portanto, gpg obterá o sinal.

O fenômeno é pesquisado e elaborado abaixo.

Elaboração

1. bash behavior

Ao executar um canal, bash coloca cada processo em um grupo de processos com PGID igual ao PID do primeiro comando. Você pode fazer seus próprios testes (consulte É possível obter o ID do grupo de processos de /proc? ). Eu não pesquisei possibilidades mais complexas (por exemplo, se o primeiro "comando" é um subnível?), No seu caso eles não importam. O que importa é que gpg nesses comandos

timeout 1 cat /dev/urandom | gpg -er [email protected] > ./myfile.gpg
cat /dev/urandom | timeout 1 cat | gpg -er [email protected] > ./myfile.gpg
timeout -sUSR1 1 cat /dev/urandom | gpg -er [email protected] > ./myfile.gpg
gpg -er [email protected] > ./myfile.gpg < <( timeout 1 cat /dev/urandom )

obtém PGID igual ao PID de

  • timeout
  • (o primeiro) cat
  • timeout
  • gpg (ou seja, ela própria)

respectivamente.

2. timeout altera seu próprio PGID (ou não)

Execute strace timeout 1 cat e você verá, entre outras coisas:

setpgid(0, 0)

Um trecho de man 2 setpgid :

int setpgid(pid_t pid, pid_t pgid);

setpgid() sets the PGID of the process specified by pid to pgid. If pid is zero, then the process ID of the calling process is used. If pgid is zero, then the PGID of the process specified by pid is made the same as its process ID.

Isso significa que timeout define seu PGID igual a seu PID . Existem duas possibilidades:

  • se timeout for o primeiro comando, seu PGID será o mesmo antes e depois de setpgid , então gpg ainda terá o mesmo PGID de timeout ;
  • se timeout não for o primeiro comando, seu PGID será alterado e mesmo que gpg tenha inicialmente o mesmo PGID de timeout , os dois PGID s serão diferentes agora.

3. timeout envia mais sinais do que você esperava

O mesmo strace timeout 1 cat revela linhas como:

kill(19401, SIGTERM)
…
kill(0, SIGTERM)

Neste exemplo, 19401 é o PID de cat . Se você usou -s USR1 , então haverá SIGUSR1 em vez de SIGTERM etc. Esse segundo kill é responsável pelo que você pensou ser uma propagação de sinal através do pipeline. Veja man 2 kill (excerto):

int kill(pid_t pid, int sig);

If pid equals 0, then sig is sent to every process in the process group of the calling process.

O processo de chamada é timeout . Ele envia sinais para todo o seu grupo de processos. Eu admito que não sei qual é o propósito por trás disso, ainda acontece.

Portanto, se timeout for o primeiro comando no pipeline, o sinal escolhido será enviado para cada parte dele (bem, quase; considere outro timeout no mesmo pipeline). Isso inclui gpg . Então cabe a gpg como ele reage ao sinal.

Outras perguntas

Do we have any control over this? Is there a better way than moving the signal-generating-process from the front of the pipeline?

Minha pesquisa rápida não gerou nenhuma ferramenta comum para definir / alterar PGID . Eu acho que você pode escrever seu próprio programa que irá chamar setpgid(2) ou mais; mas agora, quando sabemos o que está acontecendo, mover timeout da frente do pipeline parece ser uma abordagem bastante sensata.

Observe também que isso ocorre devido a como timeout se comporta. Outros processos geradores de sinal podem não precisar dessa solução alternativa.

    
por 21.10.2017 / 23:56

Tags