default: /* Родитель ждет завершения нашего потомка */
if (errno!= EINTR) { /* Ошибка, отличная от EINTR */
status = –1;
break; /* Выходим из цикла */
}
}
break;
}
/* Разблокируем SIGCHLD, восстанавливаем диспозиции для SIGINT и SIGQUIT */
sigaction(SIGINT, &saOrigInt, NULL);
sigaction(SIGQUIT, &saOrigQuit, NULL);
return status;
}
procexec/system.c
Переносимые приложения должны следить за тем, чтобы функция system() не вызывалась, когда диспозицией сигнала SIGCHLD является SIG_IGN, поскольку вызов waitpid() в этом случае не может получить статус потомка (если игнорировать сигнал SIGCHLD, статус потомка сразу же сбрасывается, как было описано в подразделе 26.3.3).
В некоторых реализациях UNIX system(), обнаружив установленную диспозицию SIG_IGN сигнала SIGCHLD, временно меняет ее на SIG_DFL. Такой подход работает в системах, которые (в отличие от Linux) снимают дочерние процессы-«зомби», если диспозицией SIGCHLD является SIG_IGN (в остальных системах такая реализация system() имела бы отрицательные последствия: любой другой потомок вызывающего процесса, завершающийся во время ее работы, превращался бы в «зомби», которого нельзя уничтожить).
В некоторых реализациях UNIX (в частности, в Solaris) /bin/sh не является стандартной командной оболочкой POSIX. Если мы хотим вызывать именно стандартную оболочку, нам следует использовать библиотечную функцию confstr(), которая позволяет получить значение конфигурационной переменной _CS_PATH. Это значение представляет собой список каталогов (того же формата, что и в PATH) со стандартными системными утилитами. Мы можем присвоить этот список переменной PATH и затем вызвать стандартную команду оболочку с помощью execlp():
char path[PATH_MAX];
if (confstr(_CS_PATH, path, PATH_MAX) == 0)
_exit(127);
if (setenv("PATH", path, 1) == –1)
_exit(127);
execlp("sh", "sh", "-c", command, (char *) NULL);
_exit(127);
С помощью вызова execve() процесс может заменить программу, которая в нем выполняется, на новую. Этот вызов принимает списки аргументов (argv) и переменных среды для новой программы. Вокруг него существуют различные библиотечные функции-обертки, которые предоставляют разные интерфейсы к одним и тем же возможностям.
Все функции семейства exec() позволяют загружать бинарные исполняемые файлы или выполнять скрипты интерпретатора. В последнем случае его интерпретатор заменяет собой текущую программу процесса. Интерпретатор, а точнее, путь к нему обычно определяется первой строчкой скрипта (начинающейся с символов #!). Если такой строчки нет, скрипт может быть выполнен только с помощью функций execlp() или execvp(), которые используют в качестве интерпретатора командную оболочку.
Мы продемонстрировали, как с помощью сочетания вызовов fork(), exec(), exit() и wait() можно реализовать функцию system(), которая способна запускать произвольные консольные команды.
Ознакомьтесь с источниками, приведенными в разделе 24.6
27.1. Последняя команда в следующей сессии командной строки использует программу из листинга 27.3 для запуска xyz. К чему это приведет?
$ echo $PATH
/usr/local/bin:/usr/bin:/bin:./dir1:./dir2
$ ls — l dir1
total 8
— rw-r-r- 1 mtk users 7860 Jun 13 11:55 xyz
$ ls — l dir2
total 28
— rwxr-xr-x 1 mtk users 27452 Jun 13 11:55 xyz
$ ./t_execlp xyz
27.2. Используйте вызов execve(), чтобы реализовать функцию execlp(). Вам нужно применить программный интерфейс stdarg(3) для обработки списка аргументов переменной длины, передаваемого в execlp(). Вам также понадобятся функции из пакета malloc, чтобы выделить память для списка аргументов и списка с переменными среды. Также стоит отметить, что для проверки существования файла в определенном каталоге и определения того, является ли он исполняемым, вы можете просто попытаться его запустить.
27.3. Какой бы вывод мы получили, если бы следующий скрипт был исполняемым и запущенным с помощью вызова exec()?
#!/bin/cat — n
Hello world
27.4. Какой результат будет при выполнении следующего кода? В каких обстоятельствах он может пригодиться?
childPid = fork();
if (childPid == –1)
errExit("fork1");
if (childPid == 0) { /* Потомок */
switch (fork()) {
case –1: errExit("fork2");
case 0: /* Потомок потомка */
/* — Здесь выполняется реальная работа — */
exit(EXIT_SUCCESS); /* После выполнения реальной работы */
default:
exit(EXIT_SUCCESS); /* Делаем дочерний процесс потомка «сиротой» */
}
}
/* Родитель перескакивает сюда */
if (waitpid(childPid, &status, 0) == –1)
errExit("waitpid");
/* Родитель приступает к выполнению других задач */
27.5. Запустив эту программу, мы обнаружим, что она не генерирует никакого вывода. Почему?
#include "tlpi_hdr.h"
int
main(int argc, char *argv[])
{