3

Отвечая на этот вопрос, я не смог полностью объяснить, как сигналы распространяются по конвейеру.

Рассмотрим следующие примеры.

Использование timeout в качестве первого элемента в конвейере

Это заставляет gpg выручить, поймав SIGTERM который был доставлен cat , по timeout , оставив поврежденный файл.

$ timeout 1 cat /dev/urandom | gpg -er attie@attie.co.uk > ./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 <attie@attie.co.uk>"
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 <attie@attie.co.uk>"
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!

Использование timeout в середине конвейера

Это работает, как и ожидалось - gpg выходит чисто.

$ cat /dev/urandom | timeout 1 cat | gpg -er attie@attie.co.uk > ./myfile.gpg
$ gpg -qd < ./myfile.gpg > /dev/null

You need a passphrase to unlock the secret key for
user: "Attie Grande <attie@attie.co.uk>"
4096-bit RSA key, ID C9AEA6AE, created 2016-12-13 (main key ID 7826F053)

Использование SIGUSR1 вместо SIGTERM

Опять же, это работает, как и ожидалось - gpg выходит чисто. Я ожидаю, потому что cat выходит на SIGUSR1 , в то время как gpg игнорирует это.

$ timeout -sUSR1 1 cat /dev/urandom | gpg -er attie@attie.co.uk > ./myfile.gpg
$ gpg -qd < ./myfile.gpg > /dev/null

You need a passphrase to unlock the secret key for
user: "Attie Grande <attie@attie.co.uk>"
4096-bit RSA key, ID C9AEA6AE, created 2016-12-13 (main key ID 7826F053)

Использование процесса замены

Опять же, это работает - хотя я не ожидал этого.

$ gpg -er attie@attie.co.uk > ./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 <attie@attie.co.uk>"
4096-bit RSA key, ID C9AEA6AE, created 2016-12-13 (main key ID 7826F053)

Я могу только предположить, что сигнал первого элемента в конвейере распространяется на остальные элементы в конвейере (даже разделение их timeout cat | cat | gpg не удается).

Я посмотрел документацию и поиграл с set -e , set -o pipefail но они не действовали так, как я ожидал.

  • Что на самом деле происходит?
  • Какова семантика?
  • Есть ли у нас контроль над этим?
  • Есть ли лучший способ, чем переместить процесс генерации сигнала из передней части конвейера?

1 ответ1

6

Я могу только предположить, что сигнал первого элемента в конвейере распространяется на остальные элементы в конвейере.

Насколько я знаю, такого распространения нет. Я собираюсь ответить в основном на ваш первый вопрос:

Что на самом деле происходит?

Короткий ответ

(Это может быть несколько упрощено.)

  1. При запуске канала bash помещает каждый процесс в группу процессов с PGID (ID группы процессов), равным PID (ID процесса) первой команды.
  2. Тайм- timeout меняет свой собственный PGID на свой собственный PID . Это ничего не меняет, если timeout является первой командой в конвейере.
  3. timeout отправляет сигнал не только основной команде, но и всей группе процессов. Если timeout является первой командой в конвейере, его группа процессов все равно будет включать gpg , поэтому gpg получит сигнал.

Феномен исследован и разработан ниже.


разработка

1. поведение bash

При запуске канала bash помещает каждый процесс в группу процессов с PGID равным PID первой команды. Вы можете создавать свои собственные тесты (см. Возможно ли получить идентификатор группы процессов из /proc?). Я не исследовал более сложные возможности (например, что, если первая "команда" является подоболочкой?), В вашем случае они не имеют значения. Важно то, что gpg в этих командах

timeout 1 cat /dev/urandom | gpg -er attie@attie.co.uk > ./myfile.gpg
cat /dev/urandom | timeout 1 cat | gpg -er attie@attie.co.uk > ./myfile.gpg
timeout -sUSR1 1 cat /dev/urandom | gpg -er attie@attie.co.uk > ./myfile.gpg
gpg -er attie@attie.co.uk > ./myfile.gpg < <( timeout 1 cat /dev/urandom )

получает PGID равный PID

  • timeout
  • (первая) cat
  • timeout
  • gpg (то есть сам)

соответственно.

2. timeout меняет свой собственный PGID (или нет)

Запустите strace timeout 1 cat и вы увидите среди прочего:

setpgid(0, 0)

Отрывок из man 2 setpgid:

int setpgid(pid_t pid, pid_t pgid);

setpgid() устанавливает PGID процесса, указанного в pid в pgid . Если pid равен нулю, то используется идентификатор процесса вызывающего процесса. Если pgid равен нулю, то PGID процесса, указанного в pid становится таким же, как его идентификатор процесса.

Это означает, что timeout устанавливает свой PGID равным своему PID . Есть две возможности:

  • если timeout - первая команда, ее PGID одинаков до и после setpgid , поэтому у gpg все тот же PGID что и у timeout ;
  • если timeout не первая команда, его PGID изменяется, и даже если у gpg изначально был тот же самый PGID что и у timeout два PGID теперь разные.

3. timeout посылает больше сигналов, чем вы ожидали

Тот же самый strace timeout 1 cat показывает такие строки:

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

В этом примере 19401 является PID cat . Если вы использовали -s USR1 то вместо SIGUSR1 и т.д. Будет SIGTERM и т.д. Это второе kill ответственно за то, что, по вашему мнению, было распространением сигнала по конвейеру. Смотрите, как man 2 kill (отрывок):

int kill(pid_t pid, int sig);

Если pid равен 0 , то sig отправляется каждому процессу в группе процессов вызывающего процесса.

Процесс вызова timeout . Он посылает сигналы всей своей группе процессов. Я признаю, что не знаю, какова цель этого, все же это делает.

Таким образом, если timeout является первой командой в конвейере, то выбранный сигнал будет отправлен в каждую его часть (ну, почти; рассмотрим другой timeout в том же конвейере). Это включает в себя gpg . Тогда это до gpg как это реагирует на сигнал.


Другие вопросы

Есть ли у нас контроль над этим? Есть ли лучший способ, чем перенести процесс генерации сигнала с передней части конвейера?

Мой быстрый поиск не дал общего инструмента для установки / изменения PGID . Я думаю, что вы можете написать свою собственную программу, которая будет вызывать setpgid(2) или около того; но теперь, когда мы знаем, что происходит, перемещение по timeout с фронта трубопровода кажется довольно вменяемым подходом.

Также обратите внимание, что это из-за того, как ведет себя timeout . Другие процессы генерации сигналов могут не нуждаться в таком обходном пути.

Всё ещё ищете ответ? Посмотрите другие вопросы с метками .