Propagacja sygnału rurociągu Bash - jak to działa?

5

Odpowiadając na to pytanie, nie byłem w stanie w pełni wyjaśnić, w jaki sposób sygnały rozprzestrzeniają się w rurociągu.

Rozważ następujące przykłady.

Używanie timeoutjako pierwszego elementu w potoku

Powoduje to, gpgaby wyskoczyć połów SIGTERM, który został dostarczony catprzez timeoutpozostawiając uszkodzonego pliku.

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

Używanie timeoutw środku rurociągu

Działa to zgodnie z oczekiwaniami - gpgkończy się czysto.

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

Używanie SIGUSR1zamiastSIGTERM

Ponownie działa to zgodnie z oczekiwaniami - gpgkończy się czysto. Spodziewam się, ponieważ catprzestaje działać SIGUSR1, a gpgignoruje to.

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

Korzystanie z podstawienia procesu

Znowu to działa - choć się nie spodziewałem.

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

Mogę tylko założyć, że sygnał pierwszego elementu w potoku jest propagowany do reszty elementów w potoku (nawet rozdzielenie ich timeout cat | cat | gpgniepowodzeniem).

Szukałem dokumentacji i bawiłem się set -e, set -o pipefailale nie działały tak, jak się spodziewałem.

  • Co się właściwie dzieje?
  • Jakie są semantyki?
  • Czy mamy nad tym kontrolę?
  • Czy istnieje lepszy sposób niż przeniesienie procesu generowania sygnału z przodu rurociągu?
Attie
źródło

Odpowiedzi:

8

Mogę jedynie założyć, że sygnał pierwszego elementu w potoku jest propagowany do pozostałych elementów w potoku.

O ile mi wiadomo, nie ma takiej propagacji. Odpowiem głównie na twoje pierwsze pytanie:

Co się właściwie dzieje?

Krótka odpowiedź

(Może to być nieco uproszczone).

  1. Podczas uruchamiania potoku interaktywnie bashumieszcza każdy proces w grupie procesów o PGID(ID grupy procesów) równej PID(ID procesu) pierwszego polecenia.
  2. timeoutzmienia własne PGIDna własne PID. To nic nie zmienia, jeśli timeoutjest to pierwsze polecenie w potoku.
  3. timeoutwysyła sygnał nie tylko do podstawowego polecenia, ale także do całej grupy procesów. Jeśli timeoutjest to pierwsze polecenie w potoku, wówczas jego grupa procesów będzie nadal zawierać gpg, dlatego gpgotrzyma sygnał.

Zjawisko to zostało zbadane i opracowane poniżej.


Opracowanie

1. bashzachowanie

Podczas uruchamiania potoku interaktywnie bashumieszcza każdy proces w grupie procesów PGIDrówny PIDpierwszemu poleceniu. Możesz wykonać własne testy (patrz Czy można uzyskać identyfikator grupy procesów/proc? ). Nie badałem bardziej złożonych możliwości (np. Co, jeśli pierwsze „polecenie” jest podpowłoką?), W twoim przypadku nie mają one znaczenia. Liczy się to, że gpgw tych poleceniach

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 )

dostaje PGIDrówny PIDod

  • timeout
  • (pierwszy) cat
  • timeout
  • gpg (tj. sam)

odpowiednio, kolejno.

2. timeoutzmienia własną PGID(lub nie)

Uruchom, strace timeout 1 cata zobaczysz między innymi:

setpgid(0, 0)

Fragment man 2 setpgid:

int setpgid(pid_t pid, pid_t pgid);

setpgid()ustawia PGIDprocesu określonego przez pidsię pgid. Jeśli pidwynosi zero, używany jest identyfikator procesu wywołującego. Jeśli pgidwynosi zero, wówczas PGIDproces określony przez pid jest taki sam jak jego identyfikator procesu.

Oznacza to, że timeoutustawia jego PGIDrówność PID. Istnieją dwie możliwości:

  • if timeoutjest pierwszym poleceniem, PGIDjest takie samo przed i po setpgid, więc gpgnadal ma to samo PGIDco timeout;
  • jeśli timeoutnie jest to pierwsze polecenie, PGIDjest ono zmieniane, a nawet jeśli gpgpoczątkowo było takie samo PGIDjak timeoutdwa, PGIDteraz są różne.

3. timeoutwysyła więcej sygnałów, niż się spodziewałeś

To samo strace timeout 1 catujawnia linie takie jak:

kill(19401, SIGTERM)

kill(0, SIGTERM)

W tym przykładzie 19401jest PIDod cat. Jeśli użyjesz, -s USR1będzie SIGUSR1zamiast tego SIGTERM. Ta sekunda killjest odpowiedzialna za to, co Twoim zdaniem było propagacją sygnału przez rurociąg. Zobacz man 2 kill(fragment):

int kill(pid_t pid, int sig);

Jeśli pidjest równy 0, to sigjest wysyłany do każdego procesu w grupie procesów procesu wywołującego.

Proces wywoływania jest timeout. Wysyła sygnały do ​​całej grupy procesów. Przyznaję, że nie wiem, jaki jest tego cel, nadal tak jest.

Więc jeśli timeoutjest to pierwsze polecenie w potoku, wybrany sygnał zostanie wysłany do każdej jego części (cóż, prawie; rozważ inne timeoutw tym samym potoku). Obejmuje to gpg. To zależy od gpgtego, jak zareaguje na sygnał.


Inne pytania

Czy mamy nad tym kontrolę? Czy istnieje lepszy sposób niż przeniesienie procesu generowania sygnału z przodu rurociągu?

Moje szybkie wyszukiwanie nie przyniosło wspólnego narzędzia do ustawiania / zmiany PGID. Myślę, że możesz napisać własny program, który zadzwoni setpgid(2); ale teraz, kiedy wiemy, co się dzieje, przejście timeoutz przodu rurociągu wydaje się dość rozsądnym podejściem.

Pamiętaj też, że timeoutdzieje się tak z powodu zachowania. Inne procesy generujące sygnały mogą nie wymagać takiego obejścia.

Kamil Maciorowski
źródło