Вопрос или проблема
Может кто-нибудь, пожалуйста, объяснить, как должны использоваться параметры конфигурации Program
и ProgramArguments
в launchd? Я пытался зарегистрировать службу, которую я бы запускал в командной строке следующим образом:
$ /foo/bar/baz/python /foo/bar/baz/service start
Я пробовал разделить это различными способами для launchd:
<key>Program</key>
<string>/foo/bar/baz/python</string>
<key>ProgramArguments</key>
<array>
<string>/foo/bar/baz/service</string>
<string>start</string>
</array>
или
<key>Program</key>
<string>/foo/bar/baz/python</string>
<key>ProgramArguments</key>
<array>
<string>/foo/bar/baz/service start</string>
</array>
или
<key>ProgramArguments</key>
<array>
<string>/foo/bar/baz/python</string>
<string>/foo/bar/baz/service</string>
<string>start</string>
</array>
или
<key>Program</key>
<string>/bin/bash</string>
<key>ProgramArguments</key>
<array>
<string>-c</string>
<string>/foo/bar/baz/python /foo/bar/baz/service start</string>
</array>
и почти любая другая вариация, которая казалась бы логичной. Однако служба всегда завершалась неудачей с различными ошибками. Единственное, что сработало — это создать .sh скрипт с точной строкой и запустить его через launchd.
Итак, чтобы раз и навсегда понять службы launchd: как launchd использует эти два параметра конфигурации, как я мог бы повторить свою команду bash с их помощью и в чем разница между ними?
Или, возможно, я просто наткнулся на проблему выполнения этой конкретной службы с некоторыми переменными окружения, которые существуют при выполнении через bash? Сама служба не предоставляла никакой полезной информации.
Я проконсультировался с execvp(3)
документацией, как это рекомендовано в launchd.plist(5)
, но это не особо помогло в понимании.
Если указано Program
, оно всегда будет выполняемым программой, даже если ProgramArguments
также указаны.
<key>Program</key>
<string>/foo/bar/baz/python</string>
<key>ProgramArguments</key>
<array>
<string>/foo/bar/baz/python</string>
<string>/foo/bar/baz/service</string>
<string>start</string>
</array>
В этом случае первый элемент в ProgramArguments
фактически не оценивается, но передается как argv[0]
программе, которая выполняется. Обычно это не нужно, но может быть полезно. Например, программа может проверить argv[0]
и работать в другом режиме в зависимости от этого значения.
Для всего остального достаточно использовать только ProgramArguments
. Это определение задания работает так же, как и выше:
<key>ProgramArguments</key>
<array>
<string>/foo/bar/baz/python</string>
<string>/foo/bar/baz/service</string>
<string>start</string>
</array>
POSIX делает различие между фактической программой, которую вызывают, и именем программы. Например, если у вас есть бинарный /bin/abc
и вы создаете символическую ссылку ln -s /bin/abc xyz
, и теперь вы вызываете программу как ./xyz
, то фактически вызывается программа /bin/abc
, но имя программы ./xyz
.
Функция main()
бинарного файла получает массив аргументов, обычно называемый argv
, и argv[0]
содержит имя бинарного файла. Так что в приведенном выше примере это будет ./xyz
. Бинарный файл не имеет возможности узнать, что он фактически находится в /bin
и называется abc
.
Некоторые бинарные файлы действительно ведут себя по-разному в зависимости от того, под каким именем они вызываются. Например, gmake
(GNU Make) поддерживает многие специфические для GNU расширения, но когда он вызывается как make
, он ведет себя как стандартный make без этих расширений (на многих системах make
это просто символическая ссылка на gmake
).
Так что у вас есть бинарный файл для выполнения, имя бинарного файла и дополнительные аргументы (опционально).
Если вы указываете только ProgramArguments
, то первый элемент массива используется и для бинарного файла, и для имени программы. Если вы указываете и Program
, и ProgramArguments
, то Program
— это бинарный файл для выполнения, а первый элемент ProgramArguments
— это имя бинарного файла.
<key>Program</key>
<string>/usr/bin/gmake</string>
<key>ProgramArguments</key>
<array>
<string>make</string>
<string>...</string>
<string>...</string>
</array>
Это гарантирует, что будет вызываться gmake
, но как имя программы будет передано make
.
Синтаксис execvp
таков:
execvp( const char *file, char *const argv[] );
Где file
— это бинарный файл для выполнения, а argv
— массив, передаваемый основной функции этого бинарного файла. Однако argv[0]
не является первым аргументом, а именем программы.
Таким образом, если вы просто указываете ProgramArgument
, то launchd
вызовет функцию как
execvp(ProgramArguments[0], ProgramArguments);
но если вы указываете Program
и ProgramArgument
, то launchd
вызовет функцию как
execvp(Program, ProgramArguments);
позволяя вам задать программе любое имя, которое вы хотите. Однако, если вам вообще не нужны аргументы, вы можете просто указать Program
, и launchd
вызовет функцию как
execvp(Program, [Program]);
превращая Program
в массив с единственным элементом — именем программы, который просто совпадает с путем к бинарному файлу.
Ответ или решение
Понимание использования параметров Program
и ProgramArguments
в launchd
В мире macOS launchd является ключевым механизмом управления службами и процессами. Однако, использование launchd для запуска приложений может быть не совсем очевидным, особенно в контексте различий между параметрами Program
и ProgramArguments
. Рассмотрим это подробнее.
Параметр Program
Параметр Program
используется для указания полного пути к исполняемой программе. Это значит, что именно данный файл будет запускаться launchd. Задание Program
необязательно, если вы используете только один параметр ProgramArguments
— в таком случае первым элементом массива ProgramArguments
будет считаться как исполняемый файл.
Параметр ProgramArguments
ProgramArguments
— это массив, где:
- Первый элемент представляет либо полный путь к программе, либо имя программы, если также указан
Program
. - Последующие элементы массива
ProgramArguments
представляют собой аргументы, которые передаются в основную функцию программы.
Как работают Program и ProgramArguments
Когда вы указываете только ProgramArguments
, launchd реализует вызов:
execvp(ProgramArguments[0], ProgramArguments);
Здесь ProgramArguments[0]
служит как путь/имя программы, аргументами которой являются все последующие элементы массива.
Если вы определяете оба параметра, Program
получает путь к исполняемому файлу, а ProgramArguments[0]
используется как имя программы:
execvp(Program, ProgramArguments);
Это позволяет запускать программу под специфичным именем, что может повлиять на поведение программы, если оно зависит от argv[0]
.
Пример использования
Предположим, вы хотите запустить скрипт Python с помощью launchd, как на командной строке:
/foo/bar/baz/python /foo/bar/baz/service start
Для правильного исполнения через launchd ваш .plist файл может выглядеть следующим образом, используя только ProgramArguments
:
<key>ProgramArguments</key>
<array>
<string>/foo/bar/baz/python</string>
<string>/foo/bar/baz/service</string>
<string>start</string>
</array>
Заключение
В правильной конфигурации launchd важно точно определять роли Program
и ProgramArguments
, чтобы обеспечить корректную работу вашей службы. Если программа подбирает своё поведение на основе argv[0]
, рассматривайте возможность использования обоих параметров. В остальных случаях настройка исключительно ProgramArguments
может быть достаточной. Внимание к деталям и четкое понимание этих параметров поможет избежать ошибок и обеспечить надежную работу ваших сервисов на платформе macOS.