Конвейер как параллельная команда

Новые производные OpenBSD netcat, включая FreeBSD[1] и Debian[2], поддерживают флаг -d, который предотвращает чтение из stdin и исправляет описанную проблему.

Проблема в том, что netcat опрашивает stdin, а также его "сетевой" fd, и stdin снова открывается из /dev/null во втором случае выше, когда функция оболочки запускается в фоновом режиме перед созданием конвейера. Это означает, что при первом чтении из stdin (fd 0) сразу же открывается EOF, но netcat продолжит опрос(2) на уже закрытом stdin, создавая бесконечный цикл.

Вот перенаправление стдина перед созданием трубопровода:

249 [pid 23186] open("/dev/null", O_RDONLY 
251 [pid 23186] <... open resumed> )        = 3
253 [pid 23186] dup2(3, 0)                  = 0
254 [pid 23186] close(3)                    = 0

Теперь, когда netcat (пид 23187) вызывает свой первый опрос(2), он считывает EOF из stdin и закрывает fd 0:

444 [pid 23187] poll([{fd=4, events=POLLIN}, {fd=0, events=POLLIN}], 2, 4294967295) = 2 ([{fd=4, revents=POLLIN|POLLHUP}, {fd=0, revents=POLLIN}])
448 [pid 23187] read(0,  
450 [pid 23187] <... read resumed> "", 2048) = 0
456 [pid 23187] close(0 
458 [pid 23187] <... close resumed> )       = 0

При следующем вызове accept(2) клиент получает fd 0, который теперь является самым низким свободным fd:

476 [pid 23187] accept(3,  
929 [pid 23187] <... accept resumed> {sa_family=AF_LOCAL, NULL}, [2]) = 0

Обратите внимание, что netcat теперь дважды включает fd 0 в аргументы к опросу(2): один раз для STDIN_FILENO, который всегда включается в отсутствие параметра командной строки -d, и один раз для вновь подключенного клиента:

930 [pid 23187] poll([{fd=0, events=POLLIN}, {fd=0, events=POLLIN}], 2, 4294967295) = 2 ([{fd=0, revents=POLLIN|POLLHUP}, {fd=0, revents=POLLIN|POLLHUP}])

Клиент посылает EOF, а netcat отключается:

936 [pid 23187] read(0,  
938 [pid 23187] <... read resumed> "", 2048) = 0
940 [pid 23187] shutdown(0, SHUT_WR 
942 [pid 23187] <... shutdown resumed> )    = 0
944 [pid 23187] close(0 
947 [pid 23187] <... close resumed> )       = 0

Но теперь у него возникли проблемы, потому что он продолжит опрос по fd 0, который теперь закрыт. Код netcat не обрабатывает случай установки POLLNVAL в . revents член struct pollfd, поэтому он попадает в бесконечный цикл, никогда больше не вызывать accept(2):

949 [pid 23187] poll([{fd=0, events=POLLIN}, {fd=-1}], 2, 4294967295 
951 [pid 23187] <... poll resumed> )        = 1 ([{fd=0, revents=POLLNVAL}])
953 [pid 23187] poll([{fd=0, events=POLLIN}, {fd=-1}], 2, 4294967295 
955 [pid 23187] <... poll resumed> )        = 1 ([{fd=0, revents=POLLNVAL}])
...

В первой команде, где конвейер фоновый, но не запущен в оболочковой функции, stdin остается открытым, так что этот случай не возникает.

Ссылки на код (см. readwrite function):

  1. http://svnweb.freebsd.org/base/head/contrib/netcat/
  2. https://sources.debian.net/src/netcat-openbsd/1.105-7/

2
08.12.2017, 23:31
0 ответов

Теги

Похожие вопросы