Консольную команду делают встроенной по одной из двух причин: повышенная эффективность или доступ к ресурсам оболочки. Некоторые часто используемые команды, такие как pwd, echo и test, достаточно простые для того, чтобы их имело смысл реализовывать как встроенные. Другие команды встраиваются в оболочку для того, чтобы иметь возможность изменять ее внутреннюю информацию, устанавливать атрибуты ее процесса или как-то влиять на ее работу. Например, команда cd должна изменять текущий каталог самой командной оболочки, поэтому ее нельзя выполнять в рамках отдельного процесса. Среди прочих команд, встроенных для того, чтобы пользоваться внутренними ресурсами оболочки, можно выделить exec, exit, read, set, source, ulimit, umask, wait и команды управления заданиями оболочки — jobs, fg и bg. Полный перечень встроенных команд, поддерживаемых вашей командной оболочкой, ищите на соответствующей справочной странице.
Иногда имеет смысл сделать так, чтобы файловые дескрипторы закрывались до вызова exec(). В частности, если мы запускаем неизвестную программу (то есть написанную не нами) из привилегированного процесса или программу, которой не нужны дескрипторы для открытых нами файлов, правила безопасного программирования требуют от нас убедиться в том, чтобы все ненужные файловые дескрипторы были закрыты до загрузки новой программы. Этого можно было бы достичь, вызывая close() для всех таких дескрипторов, но такой подход имеет следующие ограничения.
• Дескриптор файла может быть открыт библиотечной функцией. Эта функция не может принудить главную программу к закрытию дескриптора до выполнения вызова exec() (следует взять за правило, что библиотечные функции всегда должны устанавливать флаг FD_CLOEXEC для любых открываемых ими файлов, используя описанный ниже метод).
• Если вызов exec() по какой-то причине завершается неудачно, может иметь смысл оставить файловые дескрипторы открытыми. Если они уже закрылись, их восстановление со ссылками на те же файлы может оказаться затруднительным или вообще невозможным.
В связи с этим ядро предоставляет для каждого файлового дескриптора флаг FD_CLOEXEC. Если он установлен, дескриптор автоматически закрывается после успешного выполнения exec(), но остается открытым, если вызов завершается неудачей. Доступ к данному флагу можно получить с помощью системного вызова fcntl() (см. раздел 5.2). fcntl() поддерживает операцию F_GETFD, которая извлекает копию флагов файлового дескриптора:
int flags;
flags = fcntl(fd, F_GETFD);
if (flags == –1)
errExit("fcntl");
После извлечения этих флагов мы можем их обновить с помощью второго вызова fcntl() с аргументом F_SETFD, предварительно изменив бит FD_CLOEXEC:
flags |= FD_CLOEXEC;
if (fcntl(fd, F_SETFD, flags) == –1)
errExit("fcntl");
FD_CLOEXEC на самом деле является единственным битом, который используется в флагах файлового дескриптора. Он соответствует значению 1. В старых программах иногда можно встретить установку флага FD_CLOEXEC с помощью кода fcntl(fd, F_SETFD, 1), который полагается на тот факт, что при этом не будут затронуты никакие другие биты. Теоретически это может быть не так (в будущем какая-нибудь UNIX-система может обзавестись дополнительными битами в списке флагов), поэтому вам следует использовать методику, приведенную в основном тексте.
Многие реализации UNIX, включая Linux, позволяют изменять флаг FD_CLOEXEC с помощью нестандартных вызовов ioctl(). Установить флаг FD_CLOEXEC для дескриптора fd можно посредством кода ioctl(fd, FIOCLEX); для его сброса достаточно вызвать ioctl(fd, FIONCLEX).
Когда для создания дубликата файлового дескриптора используются вызовы dup(), dup2() или fcntl(), в полученной копии флаг FD_CLOEXEC всегда сбрасывается (это поведение сложилось исторически и закреплено в SUSv3).
Изменение флага FD_CLOEXEC продемонстрировано в листинге 27.6. В зависимости от наличия аргумента командной строки (любого набора символов) данная программа сначала устанавливает флаг FD_CLOEXEC для стандартного вывода, а затем выполняет команду ls. Вот что мы увидим после ее запуска:
$ ./closeonexec
— rwxr-xr-x 1 mtk users 28098 Jun 15 13:59 closeonexec
$ ./closeonexec n
ls: write error: Bad file descriptor
Мы можем видеть, что при втором запуске команда ls обнаруживает факт закрытия стандартного вывода и возвращает сообщение в стандартный вывод ошибок.
Листинг 27.6. Установка флага FD_CLOEXEC для файлового дескриптора
procexec/closeonexec.c
#include
#include "tlpi_hdr.h"
int
main(int argc, char *argv[])
{
int flags;
if (argc > 1) {
flags = fcntl(STDOUT_FILENO, F_GETFD); /* Извлекаем флаги */
if (flags == –1)
errExit("fcntl — F_GETFD");