it-roy-ru.com

Что может привести к сбою exec? Что происходит дальше?

По каким причинам exec (execl, execlp и т.д.) Может потерпеть неудачу? Если вы делаете вызов exec, и он возвращается, есть ли какие-либо рекомендации, кроме как просто паниковать и вызывать exit?

23
pythonic metaphor

Со страницы руководства exec(3) :

Функции execl(), execle(), execlp(), execvp() и execvP() могут завершиться ошибкой и установить errno для любых ошибок, указанных для библиотечных функций execve(2) и malloc(3).

Функция execv() может завершиться ошибкой и установить errno для любой из ошибок, указанных для библиотечной функции execve(2).

А затем со страницы руководства execve(2) :

ОШИБКИ

Execve() потерпит неудачу и вернется к вызывающему процессу, если:

  • [E2BIG] - Количество байтов в списке аргументов нового процесса превышает установленный системой предел. Этот предел определяется переменной sysctl(3) MIB KERN_ARGMAX.
  • [EACCES] - Разрешение на поиск отказано для компонента префикса пути.
  • [EACCES] - Новый файл процесса не является обычным файлом.
  • [EACCES] - новый режим файла процесса запрещает выполнение.
  • [EACCES] - новый файл процесса находится в файловой системе, смонтированной с отключенным выполнением (MNT_NOEXEC в <sys/mount.h>).
  • [EFAULT] - Новый файл процесса не такой длинный, как указано значениями размера в его заголовке.
  • [EFAULT] - путь, argv или envp указывают на недопустимый адрес.
  • [EIO] - ошибка ввода-вывода при чтении из файловой системы.
  • [ELOOP] - При переводе пути было встречено слишком много символических ссылок. Это считается показателем зацикливания символической ссылки.
  • [ENAMETOOLONG] - Компонент имени пути превысил {NAME_MAX} символов, или полное имя пути превысило {PATH_MAX} символов.
  • [ENOENT] - новый файл процесса не существует.
  • [ENOEXEC] - Новый файл процесса имеет соответствующие права доступа, но имеет нераспознанный формат (например, недопустимый магический номер в заголовке).
  • [ENOMEM] - новому процессу требуется больше виртуальной памяти, чем разрешено наложенным максимумом (getrlimit(2)).
  • [ENOTDIR] - Компонент префикса пути не является каталогом.
  • [ETXTBSY] - Новый файл процесса представляет собой файл чистой процедуры (общий текст), который в настоящее время открыт для записи или чтения каким-либо процессом.

malloc() намного менее сложна и использует только ENOMEM. Из malloc(3) man page :

В случае успеха функции calloc(), malloc(), realloc(), reallocf() и valloc() возвращают указатель на выделенную память. Если есть ошибка, они возвращают указатель NULL и устанавливают errno в ENOMEM.

19
Carl Norum

Проблема с обработкой ошибки exec состоит в том, что обычно exec выполняется в дочернем процессе, и вы хотите выполнить обработку ошибок в родительском процессе. Но вы не можете просто exit(errno), потому что (1) вы не знаете, подходят ли коды ошибок в коде выхода, и (2) вы не можете отличить отказ от exec от кодов выхода из новой программы, которую вы exec.

Лучшее из известных мне решений - это использование каналов для сообщения об успехе или неудаче exec:

  1. Перед разветвлением откройте канал в родительском процессе.
  2. После разветвления родитель закрывает пишущий конец канала и читает с читающего конца.
  3. Потомок закрывает конец чтения и устанавливает флаг close-on-exec для конца записи.
  4. Ребенок вызывает exec.
  5. В случае сбоя exec дочерний процесс записывает код ошибки обратно в родительский с использованием канала, а затем завершает работу.
  6. Родитель читает eof (чтение нулевой длины), если дочерний объект успешно выполнил exec, так как close-on-exec сделал успешным exec, закрывая конец записи канала. Или, если exec не удалось, родительский файл считывает код ошибки и может действовать соответствующим образом. В любом случае родительский блок блокируется до тех пор, пока дочерний элемент не вызовет exec.
  7. Родитель закрывает конец чтения канала.
34
R..

То, что вы делаете после возврата вызова exec(), зависит от контекста - что должна делать программа, что такое ошибка и что вы можете сделать, чтобы обойти проблему.

Одним из источников проблем может быть то, что вы указали простое имя программы вместо пути; возможно, вы можете повторить попытку с помощью execvp() или преобразовать команду в вызов sh -c 'what you originally specified'. Является ли любой из них разумным, зависит от приложения. Если возникают серьезные проблемы с безопасностью, возможно, вы больше не пытаетесь.

Если вы указали имя пути и с ним возникла проблема (ENOTDIR, ENOENT, EPERM), то у вас может не быть разумного запасного варианта, но вы можете осмысленно сообщить об ошибке.

В старые времена (более 10 лет назад) некоторые системы не поддерживали «#!» Обозначения Шебанга, и если вы не были уверены, выполняете ли вы исполняемый файл или сценарий оболочки, вы пробовали его как исполняемый файл, а затем повторяли его как сценарий оболочки. Это может сработать или не сработать, если вы запускаете сценарий Perl, но в те дни вы писали свои сценарии Perl, чтобы обнаружить, что они выполняются оболочкой, и выполнить их заново с помощью Perl. К счастью, те дни в основном прошли.

Насколько это возможно, важно убедиться, что процесс сообщает о проблеме, чтобы ее можно было отследить - записать свое сообщение в файл журнала или просто в stderr (или, возможно, даже syslog() ), чтобы те, кто имеет чтобы выяснить, что пошло не так, нужно больше информации, чтобы помочь им, кроме отчета несчастного конечного пользователя «Я попробовал X, и это не сработало». Крайне важно, что если ничего не работает, то состояние выхода не равно 0, поскольку это указывает на успех. Даже это может быть проигнорировано - но вы сделали то, что могли.

8
Jonathan Leffler

Независимо от того, паникуете ли вы, вы можете принять решение, основанное на значении ошибки.

3
user446568

Exec всегда должен успешно выполняться . (За исключением оболочек, т. Е. Если пользователь ввел поддельную команду)

Если exec не работает, это означает:

  • «ошибка» с программой (отсутствующий или неверный компонент, неверный путь, плохая память, ...), или
  • серьезная системная ошибка (нехватка памяти, слишком много процессов, ошибка диска, ...)

Для любой серьезной ошибки нормальный подход - написать сообщение об ошибке на stderr, а затем выйти с кодом ошибки. Почти все стандартные инструменты делают это. Для exec:

execl("bork", "bork", NULL);
perror("failed: exec");
exit(127);

Shell тоже это делает (более или менее).

Обычно, если дочерний процесс завершается с ошибкой, родительский процесс тоже терпит неудачу и должен завершиться. Неважно, был ли у ребенка сбой в exec или во время работы программы. Если exec не удалось, не имеет значения, почему exec не удалось. Если дочерний процесс завершился неудачей по какой-либо причине, вызывающий процесс столкнулся с проблемой и должен быть остановлен.

Не тратьте много времени на попытки предвидеть все возможные ошибки. Не пишите код, который пытается обработать каждый код ошибки наилучшим образом. Вы просто раздуваете код и вводите много новых ошибок. Если ваша программа испорчена или подвергается насилию, она должна просто перестать работать. Если вы заставите это продолжаться, из-за этого возникнут худшие проблемы.

Например, если в системе недостаточно памяти и происходит перестановка, мы не хотим повторять цикл снова и снова, пытаясь запустить процесс; это только ухудшит ситуацию. Если мы получим ошибку файловой системы, мы не хотим продолжать работать в этой файловой системе; это может ухудшить коррупцию. Если программа была установлена ​​неправильно, или имеет ошибку, или имеет повреждение памяти, мы хотим остановить как можно скорее, прежде чем эта поврежденная программа нанесет какой-либо реальный ущерб (например, отправка поврежденного отчета клиенту, разрушение базы данных,. ..).

Одна возможная альтернатива: сбойный процесс может вызвать помощь, приостановить саму себя (SIGSTOP), а затем повторить операцию, если будет предложено продолжить. Это может помочь, когда в системе недостаточно памяти или диски переполнены, или, возможно, даже если в программе возникла ошибка. Немногие операции настолько дороги и важны, что это того стоит.

Если вы создаете интерактивную программу с графическим интерфейсом, попробуйте сделать это в качестве тонкой оболочки над повторно используемыми инструментами командной строки (которые завершаются, если что-то идет не так). Каждая функция в вашей программе должна быть доступна через графический интерфейс, через командную строку и как вызов функции. Напишите свои функции. Напишите несколько инструментов для создания командных строк и оболочек графического интерфейса для любой функции. Используйте подпроцессы тоже.

Если вы создаете действительно критическую систему, такую ​​как контроллер для атомной электростанции или программа для прогнозирования цунами, то что вы делаете, читая мой тупой совет? Критические системы не должны полностью зависеть от компьютеров или программного обеспечения. Должно быть «ручное управление», чтобы кто-то управлял им. В частности, не пытайтесь построить критическую систему на MS Windows, которая похожа на строительство замков из песка под водой.

0
Sam Watkins