Процесс, запущенный из системной команды в C, наследует родительский fd
У меня есть пример приложения SIP-сервера, прослушивающего порты tcp и udp 5060. В какой-то момент кода я делаю систему ("pppd file /etc/ppp/myoptions &");
После этого, если я делаю netstat -apn, он показывает, что порты 5060 также открыты для pppd! Есть ли способ избежать этого? Это стандартное поведение системной функции в Linux?
Спасибо элисон
5 ответов
Да, по умолчанию всякий раз, когда вы запускаете процесс (который system
делает), ребенок наследует все файловые дескрипторы родителя. Если ребенку не нужны эти дескрипторы, он ДОЛЖЕН их закрыть. Способ сделать это с system
(или любой другой метод, который выполняет fork+exec) - установить флаг FD_CLOEXEC для всех файловых дескрипторов, которые не должны использоваться дочерними элементами вашего процесса. Это заставит их автоматически закрываться всякий раз, когда какой-либо дочерний элемент исполняет какую-либо другую программу.
Как правило, в ЛЮБОЕ ВРЕМЯ ваша программа открывает ЛЮБОЙ ВИД дескриптора файла, который будет существовать в течение длительного периода времени (например, сокета прослушивания в вашем примере), и который не должен передаваться дочерним элементам.
fcntl(fd, F_SETFD, fcntl(fd, F_GETFD) | FD_CLOEXEC);
на дескриптор файла.
По состоянию на 2016 год? версия POSIX.1, вы можете использовать SOCK_CLOEXEC
Отметьте or'd в типе сокета, чтобы получить это поведение автоматически при создании сокета:
listenfd = socket(AF_INET, SOCK_STREAM|SOCK_CLOEXEC, 0);
bind(listenfd, ...
listen(listemfd, ...
который гарантирует, что он будет закрыт должным образом, даже если какой-то другой одновременно работающий поток делает system
или же fork
+exec
вызов. К счастью, этот флаг некоторое время поддерживался в Linux и BSD Unixes (но, к сожалению, не в OSX).
Вам, вероятно, следует избегать system()
функционировать в целом. Это опасно по своей природе, так как вызывает оболочку, которая может быть подделана и довольно непереносима даже между Unicies.
Что вы должны сделать, это fork()/exec()
танец. Это выглядит примерно так
if(!fork()){
//close file descriptors
...
execlp("pppd", "pppd", "file", "/etc/ppp/myoptions", NULL);
perror("exec");
exit(-1);
}
Как уже говорили другие, это стандартное поведение, от которого зависят программы.
Когда дело доходит до предотвращения этого у вас есть несколько вариантов. Во-первых, закрывает все файловые дескрипторы после fork()
, как предполагает Дэйв. Во-вторых, есть поддержка POSIX для использования fcntl
с FD_CLOEXEC
установить бит 'close on exec' на индивидуальной основе.
Наконец, поскольку вы упоминаете, что работаете в Linux, существует ряд изменений, предназначенных для того, чтобы вы могли установить этот бит прямо в момент открытия. Естественно, это зависит от платформы. Обзор можно найти по адресу http://udrepper.livejournal.com/20407.html
Это означает, что вы можете использовать побитовый или с типом в вашем вызове создания сокета, чтобы установить SOCK_CLOEXEC
флаг. Если вы используете ядро 2.6.27 или новее, то есть.
Да, это стандартное поведение fork()
в линуксе, из которого system()
реализовано.
Идентификатор, возвращенный из socket()
call является допустимым файловым дескриптором. Это значение можно использовать с файлово-ориентированными функциями, такими как read()
, write()
, ioctl()
, а также close()
,
Обратное утверждение о том, что каждый файловый дескриптор является сокетом, не соответствует действительности. Нельзя открыть обычный файл с open()
и передать этот дескриптор, например, bind()
или же listen()
,
Когда вы звоните system()
дочерний процесс наследует те же файловые дескрипторы, что и родительский. Вот как stdout
(0), stdin
(1) и stderr
(2) наследуются дочерними процессами. Если вы решите открыть сокет с дескриптором файла 0, 1 или 2, дочерний процесс унаследует этот сокет как один из стандартных файловых дескрипторов ввода / вывода.
Ваш дочерний процесс наследует каждый открытый дескриптор файла от родительского, включая сокет, который вы открыли.
system()
копирует текущий процесс, а затем запускает дочерний процесс поверх него. (текущего процесса больше нет. Вероятно, поэтому pppd использует 5060. Вы можете попробовать fork()/exec()
создать дочерний процесс и сохранить родительский процесс.